Merge pull request #847 from alexcrichton/fix-window

Move all methods on `Window` back to methods
This commit is contained in:
Alex Crichton 2018-09-18 16:59:46 -07:00 committed by GitHub
commit 5832ff3ca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 157 additions and 148 deletions

View File

@ -98,10 +98,6 @@ pub enum ImportFunctionKind {
ty: syn::Type,
kind: MethodKind,
},
ScopedMethod {
ty: syn::Type,
operation: Operation,
},
Normal,
}
@ -432,16 +428,10 @@ impl ImportFunction {
}
};
Some(shared::MethodData {
class: Some(class.clone()),
class: class.clone(),
kind,
})
}
ImportFunctionKind::ScopedMethod { ref operation, .. } => {
Some(shared::MethodData {
class: None,
kind: shared::MethodKind::Operation(shared_operation(operation)),
})
}
ImportFunctionKind::Normal => None,
};

View File

@ -772,9 +772,6 @@ impl TryToTokens for ast::ImportFunction {
}
class_ty = Some(ty);
}
ast::ImportFunctionKind::ScopedMethod { ref ty, .. } => {
class_ty = Some(ty);
}
ast::ImportFunctionKind::Normal => {}
}
let vis = &self.function.rust_vis;

View File

@ -256,7 +256,6 @@ impl ImportedTypes for ast::ImportFunctionKind {
{
match self {
ast::ImportFunctionKind::Method { ty, .. } => ty.imported_types(f),
ast::ImportFunctionKind::ScopedMethod { ty, .. } => ty.imported_types(f),
ast::ImportFunctionKind::Normal => {}
}
}

View File

@ -2008,30 +2008,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
}
};
let class = match &method_data.class {
Some(class) => self.import_name(info, class)?,
None => {
let op = match &method_data.kind {
shared::MethodKind::Operation(op) => op,
shared::MethodKind::Constructor => {
bail!("\"no class\" methods cannot be constructors")
}
};
match &op.kind {
shared::OperationKind::Regular => {
return Ok(import.function.name.to_string())
}
shared::OperationKind::Getter(g) => {
return Ok(format!("(() => {})", g));
}
shared::OperationKind::Setter(g) => {
return Ok(format!("(v => {} = v)", g));
}
_ => bail!("\"no class\" methods must be regular/getter/setter"),
}
}
};
let class = self.import_name(info, &method_data.class)?;
let op = match &method_data.kind {
shared::MethodKind::Constructor => return Ok(format!("new {}", class)),
shared::MethodKind::Operation(op) => op,

View File

@ -23,6 +23,8 @@ extern crate wasm_bindgen;
use std::mem;
use std::fmt;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering::SeqCst};
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
// When adding new imports:
@ -4100,3 +4102,54 @@ extern {
#[wasm_bindgen(method)]
pub fn finally(this: &Promise, cb: &Closure<FnMut()>) -> Promise;
}
/// Returns a handle to the global scope object.
///
/// This allows access to the global properties and global names by accessing
/// the `Object` returned.
pub fn global() -> Object {
// Cached `Box<JsValue>`, if we've already executed this.
//
// 0 = not calculated
// n = Some(n) == Some(Box<JsValue>)
static GLOBAL: AtomicUsize = ATOMIC_USIZE_INIT;
match GLOBAL.load(SeqCst) {
0 => {}
n => return unsafe { (*(n as *const JsValue)).clone().unchecked_into() },
}
// Ok we don't have a cached value, let's load one!
//
// According to StackOverflow you can access the global object via:
//
// const global = Function('return this')();
//
// I think that's because the manufactured function isn't in "strict" mode.
// It also turns out that non-strict functions will ignore `undefined`
// values for `this` when using the `apply` function.
//
// As a result we use the equivalent of this snippet to get a handle to the
// global object in a sort of roundabout way that should hopefully work in
// all contexts like ESM, node, browsers, etc.
let this = Function::new_no_args("return this")
.call0(&JsValue::undefined())
.ok();
// Note that we avoid `unwrap()` on `call0` to avoid code size bloat, we
// just handle the `Err` case as returning a different object.
debug_assert!(this.is_some());
let this = match this {
Some(this) => this,
None => return JsValue::undefined().unchecked_into(),
};
let ptr: *mut JsValue = Box::into_raw(Box::new(this.clone()));
match GLOBAL.compare_exchange(0, ptr as usize, SeqCst, SeqCst) {
// We stored out value, relinquishing ownership of `ptr`
Ok(_) => {}
// Another thread one, drop our value
Err(_) => unsafe { drop(Box::from_raw(ptr)) },
}
this.unchecked_into()
}

View File

@ -510,7 +510,6 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let shim = {
let ns = match kind {
ast::ImportFunctionKind::ScopedMethod { .. } |
ast::ImportFunctionKind::Normal => (0, "n"),
ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
};

View File

@ -51,7 +51,7 @@ pub struct ImportFunction {
#[derive(Deserialize, Serialize)]
pub struct MethodData {
pub class: Option<String>,
pub class: String,
pub kind: MethodKind,
}

View File

@ -1,7 +1,8 @@
//! Runtime detection of whether we're in node.js or a browser.
use wasm_bindgen::prelude::*;
use js_sys::Function;
use wasm_bindgen::JsCast;
use js_sys;
#[wasm_bindgen]
extern {
@ -13,49 +14,8 @@ extern {
/// Returns whether it's likely we're executing in a browser environment, as
/// opposed to node.js.
pub fn is_browser() -> bool {
// This is a bit tricky to define. The basic crux of this is that we want to
// test if the `self` identifier is defined. That is defined in browsers
// (and web workers!) but not in Node. To that end you might expect:
//
// #[wasm_bindgen]
// extern {
// #[wasm_bindgen(js_name = self)]
// static SELF: JsValue;
// }
//
// *SELF != JsValue::undefined()
//
// this currently, however, throws a "not defined" error in JS because the
// generated function looks like `function() { return self; }` which throws
// an error in Node because `self` isn't defined.
//
// To work around this limitation we instead lookup the value of `self`
// through the `this` object, basically generating `this.self`.
//
// Unfortunately that's also hard to do! In ESM modes the top-level `this`
// object is undefined, meaning that we can't just generate a function that
// returns `this.self` as it'll throw "can't access field `self` of
// `undefined`" whenever ESMs are being used.
//
// So finally we reach the current implementation. According to
// StackOverflow you can access the global object via:
//
// const global = Function('return this')();
//
// I think that's because the manufactured function isn't in "strict" mode.
// It also turns out that non-strict functions will ignore `undefined`
// values for `this` when using the `apply` function. Add it all up, and you
// get the below code:
//
// * Manufacture a function
// * Call `apply` where we specify `this` but the function ignores it
// * Once we have `this`, use a structural getter to get the value of `self`
// * Last but not least, test whether `self` is defined or not.
//
// Whew!
let this = Function::new_no_args("return this")
.call0(&JsValue::undefined())
.unwrap();
assert!(this != JsValue::undefined());
This::from(this).self_() != JsValue::undefined()
// Test whether we're in a browser by seeing if the `self` property is
// defined on the global object, which should in turn only be true in
// browsers.
js_sys::global().unchecked_into::<This>().self_() != JsValue::undefined()
}

View File

@ -15,6 +15,14 @@
extern crate wasm_bindgen;
extern crate js_sys;
use js_sys::Object;
#[cfg(feature = "Window")]
pub fn window() -> Option<Window> {
use wasm_bindgen::JsCast;
js_sys::global().dyn_into::<Window>().ok()
}
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View File

@ -1,56 +1,60 @@
use wasm_bindgen_test::*;
use web_sys::Window;
use web_sys::{self, Location};
fn location() -> Location {
web_sys::window().unwrap().location()
}
#[wasm_bindgen_test]
fn href() {
let loc = Window::location();
let loc = location();
loc.href().unwrap();
}
#[wasm_bindgen_test]
fn origin() {
let loc = Window::location();
let loc = location();
loc.origin().unwrap();
}
#[wasm_bindgen_test]
fn protocol() {
let loc = Window::location();
let loc = location();
loc.protocol().unwrap();
}
#[wasm_bindgen_test]
fn host() {
let loc = Window::location();
let loc = location();
loc.host().unwrap();
}
#[wasm_bindgen_test]
fn hostname() {
let loc = Window::location();
let loc = location();
loc.hostname().unwrap();
}
#[wasm_bindgen_test]
fn port() {
let loc = Window::location();
let loc = location();
loc.port().unwrap();
}
#[wasm_bindgen_test]
fn pathname() {
let loc = Window::location();
let loc = location();
loc.pathname().unwrap();
}
#[wasm_bindgen_test]
fn search() {
let loc = Window::location();
let loc = location();
loc.search().unwrap();
}
#[wasm_bindgen_test]
fn hash() {
let loc = Window::location();
let loc = location();
loc.hash().unwrap();
}

View File

@ -1,4 +1,8 @@
global.global_no_args = () => 3;
global.global_with_args = (a, b) => a + b;
global.global_attribute = 'x';
const map = {
global_no_args: () => 3,
global_with_args: (a, b) => a + b,
global_attribute: 'x',
};
global.get_global = () => map;

View File

@ -1,13 +1,20 @@
use js_sys::Object;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
include!(concat!(env!("OUT_DIR"), "/global.rs"));
#[wasm_bindgen]
extern {
fn get_global() -> Global;
}
#[wasm_bindgen_test]
fn works() {
assert_eq!(Global::global_no_args(), 3);
assert_eq!(Global::global_with_args("a", "b"), "ab");
assert_eq!(Global::global_attribute(), "x");
Global::set_global_attribute("y");
assert_eq!(Global::global_attribute(), "y");
let x = get_global();
assert_eq!(x.global_no_args(), 3);
assert_eq!(x.global_with_args("a", "b"), "ab");
assert_eq!(x.global_attribute(), "x");
x.set_global_attribute("y");
assert_eq!(x.global_attribute(), "y");
}

View File

@ -87,7 +87,11 @@ global.Unforgeable = class Unforgeable {
}
};
global.m = () => 123;
global.GlobalMethod = class {
m() { return 123; }
};
global.get_global_method = () => new GlobalMethod();
global.Indexing = function () {
return new Proxy({}, {

View File

@ -72,9 +72,16 @@ fn nullable_method() {
assert!(f.opt(None) == None);
}
#[wasm_bindgen]
extern {
fn get_global_method() -> GlobalMethod;
}
#[wasm_bindgen_test]
fn global_method() {
assert_eq!(GlobalMethod::m(), 123);
let x = get_global_method();
assert_eq!(x.m(), 123);
}
#[wasm_bindgen_test]

View File

@ -44,7 +44,6 @@ pub(crate) struct FirstPassRecord<'src> {
pub(crate) struct InterfaceData<'src> {
/// Whether only partial interfaces were encountered
pub(crate) partial: bool,
pub(crate) global: bool,
pub(crate) attributes: Vec<&'src AttributeInterfaceMember<'src>>,
pub(crate) consts: Vec<&'src ConstMember<'src>>,
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
@ -373,12 +372,6 @@ fn process_interface_attribute<'src>(
false,
);
}
ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "Global" => {
record.interfaces.get_mut(self_name).unwrap().global = true;
}
ExtendedAttribute::IdentList(id) if id.identifier.0 == "Global" => {
record.interfaces.get_mut(self_name).unwrap().global = true;
}
_ => {}
}
}

View File

@ -305,6 +305,14 @@ impl<'a> ToIdlType<'a> for Identifier<'a> {
name: self.0,
single_function: data.single_function,
})
} else if self.0 == "WindowProxy" {
// See this for more info:
//
// https://html.spec.whatwg.org/multipage/window-object.html#windowproxy
//
// namely this seems to be "legalese" for "this is a `Window`", so
// let's translate it as such.
Some(IdlType::Interface("Window"))
} else {
warn!("Unrecognized type: {}", self.0);
None

View File

@ -554,18 +554,11 @@ impl<'src> FirstPassRecord<'src> {
None => false,
};
let global = self
.interfaces
.get(self_name)
.map(|interface_data| interface_data.global)
.unwrap_or(false);
for mut import_function in self.create_getter(
identifier,
&type_.type_,
self_name,
is_static,
global,
attrs,
container_attrs,
) {
@ -581,7 +574,6 @@ impl<'src> FirstPassRecord<'src> {
&type_.type_,
self_name,
is_static,
global,
attrs,
container_attrs,
) {
@ -602,7 +594,7 @@ impl<'src> FirstPassRecord<'src> {
op_data: &OperationData<'src>,
) {
let import_function_kind = |opkind| {
self.import_function_kind(self_name, data.global, op_data.is_static, opkind)
self.import_function_kind(self_name, op_data.is_static, opkind)
};
let kind = match id {
OperationId::Constructor(ctor_name) => {

View File

@ -316,7 +316,6 @@ impl<'src> FirstPassRecord<'src> {
structural,
shim: {
let ns = match kind {
backend::ast::ImportFunctionKind::ScopedMethod { .. } |
backend::ast::ImportFunctionKind::Normal => "",
backend::ast::ImportFunctionKind::Method { ref class, .. } => class,
};
@ -334,12 +333,11 @@ impl<'src> FirstPassRecord<'src> {
ty: &weedle::types::Type<'src>,
self_name: &str,
is_static: bool,
global: bool,
attrs: &Option<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
) -> Option<backend::ast::ImportFunction> {
let kind = backend::ast::OperationKind::Getter(Some(raw_ident(name)));
let kind = self.import_function_kind(self_name, global, is_static, kind);
let kind = self.import_function_kind(self_name, is_static, kind);
let ret = ty.to_idl_type(self)?;
self.create_one_function(
&name,
@ -361,12 +359,11 @@ impl<'src> FirstPassRecord<'src> {
field_ty: &weedle::types::Type<'src>,
self_name: &str,
is_static: bool,
global: bool,
attrs: &Option<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
) -> Option<backend::ast::ImportFunction> {
let kind = backend::ast::OperationKind::Setter(Some(raw_ident(name)));
let kind = self.import_function_kind(self_name, global, is_static, kind);
let kind = self.import_function_kind(self_name, is_static, kind);
let field_ty = field_ty.to_idl_type(self)?;
self.create_one_function(
&name,
@ -384,7 +381,6 @@ impl<'src> FirstPassRecord<'src> {
pub fn import_function_kind(
&self,
self_name: &str,
global: bool,
is_static: bool,
operation_kind: backend::ast::OperationKind,
) -> backend::ast::ImportFunctionKind {
@ -393,17 +389,10 @@ impl<'src> FirstPassRecord<'src> {
kind: operation_kind,
};
let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str()));
if global {
backend::ast::ImportFunctionKind::ScopedMethod {
ty,
operation,
}
} else {
backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: backend::ast::MethodKind::Operation(operation),
}
backend::ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: backend::ast::MethodKind::Operation(operation),
}
}
@ -641,6 +630,18 @@ fn has_named_attribute(list: Option<&ExtendedAttributeList>, attribute: &str) ->
})
}
fn has_ident_attribute(list: Option<&ExtendedAttributeList>, ident: &str) -> bool {
let list = match list {
Some(list) => list,
None => return false,
};
list.body.list.iter().any(|attr| match attr {
ExtendedAttribute::Ident(id) => id.lhs_identifier.0 == ident,
ExtendedAttribute::IdentList(id) => id.identifier.0 == ident,
_ => false,
})
}
/// ChromeOnly is for things that are only exposed to privileged code in Firefox.
pub fn is_chrome_only(ext_attrs: &Option<ExtendedAttributeList>) -> bool {
has_named_attribute(ext_attrs.as_ref(), "ChromeOnly")
@ -657,7 +658,8 @@ pub fn is_structural(
container_attrs: Option<&ExtendedAttributeList>,
) -> bool {
has_named_attribute(item_attrs, "Unforgeable") ||
has_named_attribute(container_attrs, "Unforgeable")
has_named_attribute(container_attrs, "Unforgeable") ||
has_ident_attribute(container_attrs, "Global")
}
/// Whether a webidl object is marked as throwing.

View File

@ -8,7 +8,7 @@ use wasm_bindgen::JsCast;
#[wasm_bindgen]
pub fn draw() {
let document = web_sys::Window::document().unwrap();
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id("canvas").unwrap();
let canvas: web_sys::HtmlCanvasElement = canvas
.dyn_into::<web_sys::HtmlCanvasElement>()

View File

@ -8,3 +8,6 @@ cargo +nightly build --target wasm32-unknown-unknown
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \
--bin wasm-bindgen -- \
../../target/wasm32-unknown-unknown/debug/fetch.wasm --out-dir .
npm install
npm run serve

View File

@ -1,10 +1,11 @@
{
"scripts": {
"serve": "webpack-serve ./webpack.config.js"
"serve": "webpack-dev-server"
},
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.16.5",
"webpack-serve": "^2.0.2"
"webpack": "^4.11.1",
"webpack-cli": "^2.0.10",
"webpack-dev-server": "^3.1.0"
}
}

View File

@ -12,7 +12,7 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::future_to_promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response, Window};
use web_sys::{Request, RequestInit, RequestMode, Response};
/// A struct to hold some data from the github Branch API.
///
@ -57,7 +57,8 @@ pub fn run() -> Promise {
.set("Accept", "application/vnd.github.v3+json")
.unwrap();
let request_promise = Window::fetch_with_request(&request);
let window = web_sys::window().unwrap();
let request_promise = window.fetch_with_request(&request);
let future = JsFuture::from(request_promise)
.and_then(|resp_value| {

View File

@ -9,7 +9,7 @@ use wasm_bindgen::JsCast;
#[wasm_bindgen]
pub fn main() {
let document = web_sys::Window::document().unwrap();
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document
.create_element("canvas")
.unwrap()