diff options
-rw-r--r-- | components/script/dom/bindings/callback.rs | 33 | ||||
-rw-r--r-- | components/script/dom/bindings/settings_stack.rs | 96 | ||||
-rw-r--r-- | components/script/dom/globalscope.rs | 9 | ||||
-rw-r--r-- | components/script/dom/testbinding.rs | 3 | ||||
-rw-r--r-- | components/script/dom/webidls/TestBinding.webidl | 1 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/globals/entry.html | 8 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/globals/incumbent.html | 7 |
7 files changed, 152 insertions, 5 deletions
diff --git a/components/script/dom/bindings/callback.rs b/components/script/dom/bindings/callback.rs index e260195f7ef..c246e11592b 100644 --- a/components/script/dom/bindings/callback.rs +++ b/components/script/dom/bindings/callback.rs @@ -5,9 +5,9 @@ //! Base classes to work with IDL callbacks. use dom::bindings::error::{Error, Fallible, report_pending_exception}; -use dom::bindings::js::{Root, MutHeapJSVal}; +use dom::bindings::js::{JS, Root, MutHeapJSVal}; use dom::bindings::reflector::DomObject; -use dom::bindings::settings_stack::AutoEntryScript; +use dom::bindings::settings_stack::{AutoEntryScript, AutoIncumbentScript}; use dom::globalscope::GlobalScope; use js::jsapi::{Heap, MutableHandleObject}; use js::jsapi::{IsCallable, JSContext, JSObject, JS_WrapObject, AddRawValueRoot}; @@ -18,6 +18,7 @@ use js::jsval::{JSVal, UndefinedValue, ObjectValue}; use std::default::Default; use std::ffi::CString; use std::mem::drop; +use std::ops::Deref; use std::ptr; use std::rc::Rc; @@ -30,7 +31,6 @@ pub enum ExceptionHandling { Rethrow, } - /// A common base class for representing IDL callback function and /// callback interface types. #[derive(JSTraceable)] @@ -39,6 +39,19 @@ pub struct CallbackObject { /// The underlying `JSObject`. callback: Heap<*mut JSObject>, permanent_js_root: MutHeapJSVal, + + /// The ["callback context"], that is, the global to use as incumbent + /// global when calling the callback. + /// + /// Looking at the WebIDL standard, it appears as though there would always + /// be a value here, but [sometimes] callback functions are created by + /// hand-waving without defining the value of the callback context, and + /// without any JavaScript code on the stack to grab an incumbent global + /// from. + /// + /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context + /// [sometimes]: https://github.com/whatwg/html/issues/2248 + incumbent: Option<JS<GlobalScope>> } impl Default for CallbackObject { @@ -54,6 +67,7 @@ impl CallbackObject { CallbackObject { callback: Heap::default(), permanent_js_root: MutHeapJSVal::new(), + incumbent: GlobalScope::incumbent().map(|i| JS::from_ref(&*i)), } } @@ -99,6 +113,13 @@ pub trait CallbackContainer { fn callback(&self) -> *mut JSObject { self.callback_holder().get() } + /// Returns the ["callback context"], that is, the global to use as + /// incumbent global when calling the callback. + /// + /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context + fn incumbent(&self) -> Option<&GlobalScope> { + self.callback_holder().incumbent.as_ref().map(JS::deref) + } } @@ -210,6 +231,9 @@ pub struct CallSetup { /// https://heycam.github.io/webidl/#es-invoking-callback-functions /// steps 8 and 18.2. entry_script: Option<AutoEntryScript>, + /// https://heycam.github.io/webidl/#es-invoking-callback-functions + /// steps 9 and 18.1. + incumbent_script: Option<AutoIncumbentScript>, } impl CallSetup { @@ -222,12 +246,14 @@ impl CallSetup { let cx = global.get_cx(); let aes = AutoEntryScript::new(&global); + let ais = callback.incumbent().map(AutoIncumbentScript::new); CallSetup { exception_global: global, cx: cx, old_compartment: unsafe { JS_EnterCompartment(cx, callback.callback()) }, handling: handling, entry_script: Some(aes), + incumbent_script: ais, } } @@ -246,6 +272,7 @@ impl Drop for CallSetup { self.exception_global.reflector().get_jsobject().get()); report_pending_exception(self.cx, true); } + drop(self.incumbent_script.take()); drop(self.entry_script.take().unwrap()); } } diff --git a/components/script/dom/bindings/settings_stack.rs b/components/script/dom/bindings/settings_stack.rs index df4759d677a..10afc833cb4 100644 --- a/components/script/dom/bindings/settings_stack.rs +++ b/components/script/dom/bindings/settings_stack.rs @@ -5,15 +5,26 @@ use dom::bindings::js::{JS, Root}; use dom::bindings::trace::JSTraceable; use dom::globalscope::GlobalScope; +use js::jsapi::GetScriptedCallerGlobal; +use js::jsapi::HideScriptedCaller; use js::jsapi::JSTracer; +use js::jsapi::UnhideScriptedCaller; +use js::rust::Runtime; use std::cell::RefCell; thread_local!(static STACK: RefCell<Vec<StackEntry>> = RefCell::new(Vec::new())); +#[derive(PartialEq, Eq, Debug, JSTraceable)] +enum StackEntryKind { + Incumbent, + Entry, +} + #[allow(unrooted_must_root)] #[derive(JSTraceable)] struct StackEntry { global: JS<GlobalScope>, + kind: StackEntryKind, } /// Traces the script settings stack. @@ -36,6 +47,7 @@ impl AutoEntryScript { let mut stack = stack.borrow_mut(); stack.push(StackEntry { global: JS::from_ref(global), + kind: StackEntryKind::Entry, }); AutoEntryScript { global: global as *const _ as usize, @@ -53,6 +65,7 @@ impl Drop for AutoEntryScript { assert_eq!(&*entry.global as *const GlobalScope as usize, self.global, "Dropped AutoEntryScript out of order."); + assert_eq!(entry.kind, StackEntryKind::Entry); trace!("Clean up after running script with {:p}", &*entry.global); }) } @@ -64,7 +77,88 @@ impl Drop for AutoEntryScript { pub fn entry_global() -> Root<GlobalScope> { STACK.with(|stack| { stack.borrow() - .last() + .iter() + .rev() + .find(|entry| entry.kind == StackEntryKind::Entry) .map(|entry| Root::from_ref(&*entry.global)) }).unwrap() } + +/// RAII struct that pushes and pops entries from the script settings stack. +pub struct AutoIncumbentScript { + global: usize, +} + +impl AutoIncumbentScript { + /// https://html.spec.whatwg.org/multipage/#prepare-to-run-a-callback + pub fn new(global: &GlobalScope) -> Self { + // Step 2-3. + unsafe { + let cx = Runtime::get(); + assert!(!cx.is_null()); + HideScriptedCaller(cx); + } + STACK.with(|stack| { + trace!("Prepare to run a callback with {:p}", global); + // Step 1. + let mut stack = stack.borrow_mut(); + stack.push(StackEntry { + global: JS::from_ref(global), + kind: StackEntryKind::Incumbent, + }); + AutoIncumbentScript { + global: global as *const _ as usize, + } + }) + } +} + +impl Drop for AutoIncumbentScript { + /// https://html.spec.whatwg.org/multipage/#clean-up-after-running-a-callback + fn drop(&mut self) { + STACK.with(|stack| { + // Step 4. + let mut stack = stack.borrow_mut(); + let entry = stack.pop().unwrap(); + // Step 3. + assert_eq!(&*entry.global as *const GlobalScope as usize, + self.global, + "Dropped AutoIncumbentScript out of order."); + assert_eq!(entry.kind, StackEntryKind::Incumbent); + trace!("Clean up after running a callback with {:p}", &*entry.global); + }); + unsafe { + // Step 1-2. + let cx = Runtime::get(); + assert!(!cx.is_null()); + UnhideScriptedCaller(cx); + } + } +} + +/// Returns the ["incumbent"] global object. +/// +/// ["incumbent"]: https://html.spec.whatwg.org/multipage/#incumbent +pub fn incumbent_global() -> Option<Root<GlobalScope>> { + // https://html.spec.whatwg.org/multipage/#incumbent-settings-object + + // Step 1, 3: See what the JS engine has to say. If we've got a scripted + // caller override in place, the JS engine will lie to us and pretend that + // there's nothing on the JS stack, which will cause us to check the + // incumbent script stack below. + unsafe { + let cx = Runtime::get(); + assert!(!cx.is_null()); + let global = GetScriptedCallerGlobal(cx); + if !global.is_null() { + return Some(GlobalScope::from_object(global)); + } + } + + // Step 2: nothing from the JS engine. Let's use whatever's on the explicit stack. + STACK.with(|stack| { + stack.borrow() + .last() + .map(|entry| Root::from_ref(&*entry.global)) + }) +} diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 4203d0e724c..7a9e5074266 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -10,7 +10,7 @@ use dom::bindings::error::{ErrorInfo, report_pending_exception}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{MutNullableJS, Root}; use dom::bindings::reflector::DomObject; -use dom::bindings::settings_stack::{AutoEntryScript, entry_global}; +use dom::bindings::settings_stack::{AutoEntryScript, entry_global, incumbent_global}; use dom::bindings::str::DOMString; use dom::crypto::Crypto; use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; @@ -528,6 +528,13 @@ impl GlobalScope { pub fn entry() -> Root<Self> { entry_global() } + + /// Returns the ["incumbent"] global object. + /// + /// ["incumbent"]: https://html.spec.whatwg.org/multipage/#incumbent + pub fn incumbent() -> Option<Root<Self>> { + incumbent_global() + } } fn timestamp_in_ms(time: Timespec) -> u64 { diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index e556e541f60..42db5f4a964 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -777,6 +777,9 @@ impl TestBindingMethods for TestBinding { fn EntryGlobal(&self) -> Root<GlobalScope> { GlobalScope::entry() } + fn IncumbentGlobal(&self) -> Root<GlobalScope> { + GlobalScope::incumbent().unwrap() + } } impl TestBinding { diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl index acf5697ce3c..cfebfe2c570 100644 --- a/components/script/dom/webidls/TestBinding.webidl +++ b/components/script/dom/webidls/TestBinding.webidl @@ -524,6 +524,7 @@ interface TestBinding { void panic(); GlobalScope entryGlobal(); + GlobalScope incumbentGlobal(); }; callback SimpleCallback = void(any value); diff --git a/tests/wpt/mozilla/tests/mozilla/globals/entry.html b/tests/wpt/mozilla/tests/mozilla/globals/entry.html index bc896a2e0a9..f963385342a 100644 --- a/tests/wpt/mozilla/tests/mozilla/globals/entry.html +++ b/tests/wpt/mozilla/tests/mozilla/globals/entry.html @@ -5,12 +5,20 @@ <script src="/resources/testharnessreport.js"></script> <script> var entry_test = async_test("Entry global"); +var incumbent_test = async_test("Incumbent global"); var loaded = function() { entry_test.step(function() { var entry = document.querySelector("#incumbent").contentWindow.get_entry(); assert_equals(entry, window); }); entry_test.done(); + + incumbent_test.step(function() { + var expected_incumbent = document.querySelector("#incumbent").contentWindow; + var incumbent = expected_incumbent.get_incumbent(); + assert_equals(incumbent, expected_incumbent); + }); + incumbent_test.done(); } </script> <iframe id="incumbent" src="incumbent.html" onload="loaded()"></iframe> diff --git a/tests/wpt/mozilla/tests/mozilla/globals/incumbent.html b/tests/wpt/mozilla/tests/mozilla/globals/incumbent.html index 54e28d4ef3e..9baa0cdcd5a 100644 --- a/tests/wpt/mozilla/tests/mozilla/globals/incumbent.html +++ b/tests/wpt/mozilla/tests/mozilla/globals/incumbent.html @@ -10,4 +10,11 @@ function get_entry() { return current.TestBinding.prototype.entryGlobal.call(new relevant.TestBinding()); } + +function get_incumbent() { + var current = document.querySelector("#current").contentWindow; + var relevant = document.querySelector("#relevant").contentWindow; + + return current.TestBinding.prototype.incumbentGlobal.call(new relevant.TestBinding()); +} </script> |