From 161fce9d509f6bc66781c68957af74ee98cf275f Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 8 Jun 2018 16:46:51 -0700 Subject: [PATCH] Expose objects and functions from the JavaScript global scope These are bindings to JavaScript's standard, built-in objects and 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 --- Cargo.toml | 3 +- src/js.rs | 88 +++++++++++++++++++++++ src/lib.rs | 8 +++ tests/all/js_globals.rs | 150 ++++++++++++++++++++++++++++++++++++++++ tests/all/main.rs | 1 + 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 src/js.rs create mode 100644 tests/all/js_globals.rs diff --git a/Cargo.toml b/Cargo.toml index 7cd2b5e87..40ff56ac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,8 @@ test = false doctest = false [features] -default = ["spans", "std"] +default = ["spans", "std", "js_globals"] +js_globals = [] spans = ["wasm-bindgen-macro/spans"] std = [] serde-serialize = ["serde", "serde_json", "std"] diff --git a/src/js.rs b/src/js.rs new file mode 100644 index 000000000..98e57928e --- /dev/null +++ b/src/js.rs @@ -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; + + /// 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; +} + +// 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; +} diff --git a/src/lib.rs b/src/lib.rs index 5905adc90..c1f3b266f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ //! interface. #![feature(use_extern_macros, wasm_import_module, try_reserve, unsize)] +#![cfg_attr(feature = "js_globals", feature(proc_macro, wasm_custom_section))] #![no_std] #[cfg(feature = "serde-serialize")] @@ -43,6 +44,13 @@ pub mod prelude { pub mod convert; pub mod describe; +#[cfg(feature = "js_globals")] +pub mod js; + +#[cfg(feature = "js_globals")] +mod wasm_bindgen { + pub use super::*; +} if_std! { extern crate std; diff --git a/tests/all/js_globals.rs b/tests/all/js_globals.rs new file mode 100644 index 000000000..1fc65992e --- /dev/null +++ b/tests/all/js_globals.rs @@ -0,0 +1,150 @@ +// Keep these tests in alphabetical order, just like the imports in `src/js.rs`. + +use super::project; + +#[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()); + } + "#) + .file("test.ts", r#" + import * as wasm from "./out"; + + export function test() { + wasm.test(); + } + "#) + .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"); + } + "#) + .file("test.ts", r#" + import * as wasm from "./out"; + + export function test() { + wasm.test(); + } + "#) + .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); + } + "#) + .file("test.ts", r#" + import * as wasm from "./out"; + + export function test() { + wasm.test(); + } + "#) + .test(); +} + +#[allow(non_snake_case)] +mod Object { + 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() + } +} diff --git a/tests/all/main.rs b/tests/all/main.rs index b909788a9..4f88bad8a 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -528,6 +528,7 @@ mod enums; mod import_class; mod imports; mod jsobjects; +#[cfg(feature = "js_globals")] mod js_globals; mod math; mod node; mod non_debug;