mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-31 17:31:06 +00:00
Merge pull request #274 from fitzgen/js-sys
Expose objects and functions from the JavaScript global scope
This commit is contained in:
commit
224d20337f
@ -17,7 +17,8 @@ test = false
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["spans", "std"]
|
default = ["spans", "std", "js_globals"]
|
||||||
|
js_globals = []
|
||||||
spans = ["wasm-bindgen-macro/spans"]
|
spans = ["wasm-bindgen-macro/spans"]
|
||||||
std = []
|
std = []
|
||||||
serde-serialize = ["serde", "serde_json", "std"]
|
serde-serialize = ["serde", "serde_json", "std"]
|
||||||
|
@ -266,6 +266,7 @@ impl ToTokens for ast::StructField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
#[doc(hidden)]
|
||||||
pub extern fn #desc() {
|
pub extern fn #desc() {
|
||||||
use wasm_bindgen::describe::*;
|
use wasm_bindgen::describe::*;
|
||||||
<#ty as WasmDescribe>::describe();
|
<#ty as WasmDescribe>::describe();
|
||||||
@ -445,6 +446,7 @@ impl ToTokens for ast::Export {
|
|||||||
// binary along with anything it references.
|
// binary along with anything it references.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
|
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
|
||||||
|
#[doc(hidden)]
|
||||||
pub extern fn #descriptor_name() {
|
pub extern fn #descriptor_name() {
|
||||||
use wasm_bindgen::describe::*;
|
use wasm_bindgen::describe::*;
|
||||||
inform(FUNCTION);
|
inform(FUNCTION);
|
||||||
@ -727,6 +729,7 @@ impl<'a> ToTokens for DescribeImport<'a> {
|
|||||||
(quote! {
|
(quote! {
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
#[doc(hidden)]
|
||||||
pub extern fn #describe_name() {
|
pub extern fn #describe_name() {
|
||||||
use wasm_bindgen::describe::*;
|
use wasm_bindgen::describe::*;
|
||||||
inform(FUNCTION);
|
inform(FUNCTION);
|
||||||
|
@ -29,7 +29,7 @@ pub struct Context<'a> {
|
|||||||
pub imported_names: HashSet<String>,
|
pub imported_names: HashSet<String>,
|
||||||
pub exported_classes: HashMap<String, ExportedClass>,
|
pub exported_classes: HashMap<String, ExportedClass>,
|
||||||
pub function_table_needed: bool,
|
pub function_table_needed: bool,
|
||||||
pub run_descriptor: &'a Fn(&str) -> Vec<u32>,
|
pub run_descriptor: &'a Fn(&str) -> Option<Vec<u32>>,
|
||||||
pub module_versions: Vec<(String, String)>,
|
pub module_versions: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,7 +474,10 @@ impl<'a> Context<'a> {
|
|||||||
for field in class.fields.iter() {
|
for field in class.fields.iter() {
|
||||||
let wasm_getter = shared::struct_field_get(name, &field.name);
|
let wasm_getter = shared::struct_field_get(name, &field.name);
|
||||||
let wasm_setter = shared::struct_field_set(name, &field.name);
|
let wasm_setter = shared::struct_field_set(name, &field.name);
|
||||||
let descriptor = self.describe(&wasm_getter);
|
let descriptor = match self.describe(&wasm_getter) {
|
||||||
|
None => continue,
|
||||||
|
Some(d) => d,
|
||||||
|
};
|
||||||
|
|
||||||
let set = {
|
let set = {
|
||||||
let mut cx = Js2Rust::new(&field.name, self);
|
let mut cx = Js2Rust::new(&field.name, self);
|
||||||
@ -1393,10 +1396,9 @@ impl<'a> Context<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn describe(&self, name: &str) -> Descriptor {
|
fn describe(&self, name: &str) -> Option<Descriptor> {
|
||||||
let name = format!("__wbindgen_describe_{}", name);
|
let name = format!("__wbindgen_describe_{}", name);
|
||||||
let ret = (self.run_descriptor)(&name);
|
(self.run_descriptor)(&name).map(|d| Descriptor::decode(&d))
|
||||||
Descriptor::decode(&ret)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn global(&mut self, s: &str) {
|
fn global(&mut self, s: &str) {
|
||||||
@ -1474,7 +1476,12 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
|||||||
if let Some(ref class) = export.class {
|
if let Some(ref class) = export.class {
|
||||||
return self.generate_export_for_class(class, export);
|
return self.generate_export_for_class(class, export);
|
||||||
}
|
}
|
||||||
let descriptor = self.cx.describe(&export.function.name);
|
|
||||||
|
let descriptor = match self.cx.describe(&export.function.name) {
|
||||||
|
None => return Ok(()),
|
||||||
|
Some(d) => d,
|
||||||
|
};
|
||||||
|
|
||||||
let (js, ts) = Js2Rust::new(&export.function.name, self.cx)
|
let (js, ts) = Js2Rust::new(&export.function.name, self.cx)
|
||||||
.process(descriptor.unwrap_function())?
|
.process(descriptor.unwrap_function())?
|
||||||
.finish("function", &format!("wasm.{}", export.function.name));
|
.finish("function", &format!("wasm.{}", export.function.name));
|
||||||
@ -1492,7 +1499,12 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
|||||||
export: &shared::Export,
|
export: &shared::Export,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let wasm_name = shared::struct_function_export_name(class_name, &export.function.name);
|
let wasm_name = shared::struct_function_export_name(class_name, &export.function.name);
|
||||||
let descriptor = self.cx.describe(&wasm_name);
|
|
||||||
|
let descriptor = match self.cx.describe(&wasm_name) {
|
||||||
|
None => return Ok(()),
|
||||||
|
Some(d) => d,
|
||||||
|
};
|
||||||
|
|
||||||
let (js, ts) = Js2Rust::new(&export.function.name, self.cx)
|
let (js, ts) = Js2Rust::new(&export.function.name, self.cx)
|
||||||
.method(export.method)
|
.method(export.method)
|
||||||
.process(descriptor.unwrap_function())?
|
.process(descriptor.unwrap_function())?
|
||||||
@ -1603,7 +1615,10 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
|||||||
import: &shared::ImportFunction)
|
import: &shared::ImportFunction)
|
||||||
-> Result<(), Error>
|
-> Result<(), Error>
|
||||||
{
|
{
|
||||||
let descriptor = self.cx.describe(&import.shim);
|
let descriptor = match self.cx.describe(&import.shim) {
|
||||||
|
None => return Ok(()),
|
||||||
|
Some(d) => d,
|
||||||
|
};
|
||||||
|
|
||||||
let target = match &import.method {
|
let target = match &import.method {
|
||||||
Some(shared::MethodData { class, kind, getter, setter }) => {
|
Some(shared::MethodData { class, kind, getter, setter }) => {
|
||||||
|
@ -140,11 +140,19 @@ impl Bindgen {
|
|||||||
module_versions: Default::default(),
|
module_versions: Default::default(),
|
||||||
run_descriptor: &|name| {
|
run_descriptor: &|name| {
|
||||||
let mut v = MyExternals(Vec::new());
|
let mut v = MyExternals(Vec::new());
|
||||||
let ret = instance
|
match instance.invoke_export(name, &[], &mut v) {
|
||||||
.invoke_export(name, &[], &mut v)
|
Ok(None) => Some(v.0),
|
||||||
.expect("failed to run export");
|
Ok(Some(_)) => {
|
||||||
assert!(ret.is_none());
|
unreachable!(
|
||||||
v.0
|
"there is only one export, and we only return None from it"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// Allow missing exported describe functions. This can
|
||||||
|
// happen when a nested dependency crate exports things
|
||||||
|
// but the root crate doesn't use them.
|
||||||
|
Err(wasmi::Error::Function(_)) => None,
|
||||||
|
Err(e) => panic!("unexpected error running descriptor: {}", e),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
for program in programs.iter() {
|
for program in programs.iter() {
|
||||||
@ -342,6 +350,7 @@ impl wasmi::ImportResolver for MyResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct MyExternals(Vec<u32>);
|
struct MyExternals(Vec<u32>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MyError(String);
|
struct MyError(String);
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
88
src/js.rs
Normal file
88
src/js.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
//! Bindings to JavaScript's standard, built-in objects, including their methods
|
||||||
|
//! and properties.
|
||||||
|
//!
|
||||||
|
//! This does *not* include any Web, Node, or any other JS environment
|
||||||
|
//! APIs. Only the things that are guaranteed to exist in the global scope by
|
||||||
|
//! the ECMAScript standard.
|
||||||
|
//!
|
||||||
|
//! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
|
||||||
|
//!
|
||||||
|
//! ## A Note About `camelCase`, `snake_case`, and Naming Conventions
|
||||||
|
//!
|
||||||
|
//! JavaScript's global objects use `camelCase` naming conventions for functions
|
||||||
|
//! and methods, but Rust style is to use `snake_case`. These bindings expose
|
||||||
|
//! the Rust style `snake_case` name. Additionally, acronyms within a method
|
||||||
|
//! name are all lower case, where as in JavaScript they are all upper case. For
|
||||||
|
//! example, `decodeURI` in JavaScript is exposed as `decode_uri` in these
|
||||||
|
//! bindings.
|
||||||
|
|
||||||
|
use wasm_bindgen_macro::*;
|
||||||
|
use JsValue;
|
||||||
|
if_std! {
|
||||||
|
use std::prelude::v1::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When adding new imports:
|
||||||
|
//
|
||||||
|
// * Keep imports in alphabetical order.
|
||||||
|
//
|
||||||
|
// * Rename imports with `js_name = ...` according to the note about `camelCase`
|
||||||
|
// and `snake_case` in the module's documentation above.
|
||||||
|
//
|
||||||
|
// * Include the one sentence summary of the import from the MDN link in the
|
||||||
|
// module's documentation above, and the MDN link itself.
|
||||||
|
//
|
||||||
|
// * If a function or method can throw an exception, make it catchable by adding
|
||||||
|
// `#[wasm_bindgen(catch)]`.
|
||||||
|
//
|
||||||
|
// * Add a new `#[test]` to the `tests/all/js_globals.rs` file. If the imported
|
||||||
|
// function or method can throw an exception, make sure to also add test
|
||||||
|
// coverage for that case.
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
/// The `decodeURI()` function decodes a Uniform Resource Identifier (URI)
|
||||||
|
/// previously created by `encodeURI` or by a similar routine.
|
||||||
|
///
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[wasm_bindgen(catch, js_name = decodeURI)]
|
||||||
|
pub fn decode_uri(encoded: &str) -> Result<String, JsValue>;
|
||||||
|
|
||||||
|
/// The `encodeURI()` function encodes a Uniform Resource Identifier (URI)
|
||||||
|
/// by replacing each instance of certain characters by one, two, three, or
|
||||||
|
/// four escape sequences representing the UTF-8 encoding of the character
|
||||||
|
/// (will only be four escape sequences for characters composed of two
|
||||||
|
/// "surrogate" characters).
|
||||||
|
///
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[wasm_bindgen(js_name = encodeURI)]
|
||||||
|
pub fn encode_uri(decoded: &str) -> String;
|
||||||
|
|
||||||
|
/// The `eval()` function evaluates JavaScript code represented as a string.
|
||||||
|
///
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
|
||||||
|
#[wasm_bindgen(catch)]
|
||||||
|
pub fn eval(js_source_text: &str) -> Result<JsValue, JsValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
pub type Object;
|
||||||
|
|
||||||
|
/// The Object constructor creates an object wrapper.
|
||||||
|
///
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> Object;
|
||||||
|
|
||||||
|
/// The `hasOwnProperty()` method returns a boolean indicating whether the
|
||||||
|
/// object has the specified property as its own property (as opposed to
|
||||||
|
/// inheriting it).
|
||||||
|
///
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
|
||||||
|
#[wasm_bindgen(method, js_name = hasOwnProperty)]
|
||||||
|
pub fn has_own_property(this: &Object, property: &str) -> bool;
|
||||||
|
}
|
20
src/lib.rs
20
src/lib.rs
@ -6,6 +6,7 @@
|
|||||||
//! interface.
|
//! interface.
|
||||||
|
|
||||||
#![feature(use_extern_macros, wasm_import_module, try_reserve, unsize)]
|
#![feature(use_extern_macros, wasm_import_module, try_reserve, unsize)]
|
||||||
|
#![cfg_attr(feature = "js_globals", feature(proc_macro, wasm_custom_section))]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
#[cfg(feature = "serde-serialize")]
|
#[cfg(feature = "serde-serialize")]
|
||||||
@ -43,6 +44,13 @@ pub mod prelude {
|
|||||||
|
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod describe;
|
pub mod describe;
|
||||||
|
#[cfg(feature = "js_globals")]
|
||||||
|
pub mod js;
|
||||||
|
|
||||||
|
#[cfg(feature = "js_globals")]
|
||||||
|
mod wasm_bindgen {
|
||||||
|
pub use super::*;
|
||||||
|
}
|
||||||
|
|
||||||
if_std! {
|
if_std! {
|
||||||
extern crate std;
|
extern crate std;
|
||||||
@ -67,6 +75,18 @@ const JSIDX_FALSE: u32 = 6;
|
|||||||
const JSIDX_RESERVED: u32 = 8;
|
const JSIDX_RESERVED: u32 = 8;
|
||||||
|
|
||||||
impl JsValue {
|
impl JsValue {
|
||||||
|
/// The `null` JS value constant.
|
||||||
|
pub const NULL: JsValue = JsValue { idx: JSIDX_NULL };
|
||||||
|
|
||||||
|
/// The `undefined` JS value constant.
|
||||||
|
pub const UNDEFINED: JsValue = JsValue { idx: JSIDX_UNDEFINED };
|
||||||
|
|
||||||
|
/// The `true` JS value constant.
|
||||||
|
pub const TRUE: JsValue = JsValue { idx: JSIDX_TRUE };
|
||||||
|
|
||||||
|
/// The `false` JS value constant.
|
||||||
|
pub const FALSE: JsValue = JsValue { idx: JSIDX_FALSE };
|
||||||
|
|
||||||
/// Creates a new JS value which is a string.
|
/// Creates a new JS value which is a string.
|
||||||
///
|
///
|
||||||
/// The utf-8 string provided is copied to the JS heap and the string will
|
/// The utf-8 string provided is copied to the JS heap and the string will
|
||||||
|
56
tests/all/js_globals/Object.rs
Executable file
56
tests/all/js_globals/Object.rs
Executable file
@ -0,0 +1,56 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use project;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
project()
|
||||||
|
.file("src/lib.rs", r#"
|
||||||
|
#![feature(proc_macro, wasm_custom_section)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::js;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn new_object() -> js::Object {
|
||||||
|
js::Object::new()
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.file("test.ts", r#"
|
||||||
|
import * as assert from "assert";
|
||||||
|
import * as wasm from "./out";
|
||||||
|
|
||||||
|
export function test() {
|
||||||
|
assert.equal(typeof wasm.new_object(), "object");
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.test()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn has_own_property() {
|
||||||
|
project()
|
||||||
|
.file("src/lib.rs", r#"
|
||||||
|
#![feature(proc_macro, wasm_custom_section)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::js;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn has_own_foo_property(obj: &js::Object) -> bool {
|
||||||
|
obj.has_own_property("foo")
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.file("test.ts", r#"
|
||||||
|
import * as assert from "assert";
|
||||||
|
import * as wasm from "./out";
|
||||||
|
|
||||||
|
export function test() {
|
||||||
|
assert.ok(wasm.has_own_foo_property({ foo: 42 }));
|
||||||
|
assert.ok(!wasm.has_own_foo_property({}));
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.test()
|
||||||
|
}
|
73
tests/all/js_globals/mod.rs
Normal file
73
tests/all/js_globals/mod.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Keep these tests in alphabetical order, just like the imports in `src/js.rs`.
|
||||||
|
|
||||||
|
use super::project;
|
||||||
|
|
||||||
|
mod Object;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
fn decode_uri() {
|
||||||
|
project()
|
||||||
|
.file("src/lib.rs", r#"
|
||||||
|
#![feature(proc_macro, wasm_custom_section)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::js;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn test() {
|
||||||
|
let x = js::decode_uri("https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B")
|
||||||
|
.ok()
|
||||||
|
.expect("should decode URI OK");
|
||||||
|
assert_eq!(x, "https://mozilla.org/?x=шеллы");
|
||||||
|
|
||||||
|
assert!(js::decode_uri("%E0%A4%A").is_err());
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
fn encode_uri() {
|
||||||
|
project()
|
||||||
|
.file("src/lib.rs", r#"
|
||||||
|
#![feature(proc_macro, wasm_custom_section)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::js;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn test() {
|
||||||
|
let x = js::encode_uri("ABC abc 123");
|
||||||
|
assert_eq!(x, "ABC%20abc%20123");
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval() {
|
||||||
|
project()
|
||||||
|
.file("src/lib.rs", r#"
|
||||||
|
#![feature(proc_macro, wasm_custom_section)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::js;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn test() {
|
||||||
|
let x = js::eval("42").ok().expect("should eval OK");
|
||||||
|
assert_eq!(x.as_f64().unwrap(), 42.0);
|
||||||
|
|
||||||
|
let err = js::eval("(function () { throw 42; }())")
|
||||||
|
.err()
|
||||||
|
.expect("eval should throw");
|
||||||
|
assert_eq!(err.as_f64().unwrap(), 42.0);
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.test();
|
||||||
|
}
|
@ -528,6 +528,7 @@ mod enums;
|
|||||||
mod import_class;
|
mod import_class;
|
||||||
mod imports;
|
mod imports;
|
||||||
mod jsobjects;
|
mod jsobjects;
|
||||||
|
#[cfg(feature = "js_globals")] mod js_globals;
|
||||||
mod math;
|
mod math;
|
||||||
mod node;
|
mod node;
|
||||||
mod non_debug;
|
mod non_debug;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user