servo: Merge #12426 - Allow window elements as well as iframes to the the target of mozbrowser events (from asajeffrey:mozbrowser-event-targets); r=SimonSapin
authorAlan Jeffrey <ajeffrey@mozilla.com>
Wed, 20 Jul 2016 04:41:34 -0500
changeset 339328 bd9a1a5e45a88a8b8497f8072a3c458a655bc503
parent 339327 c240fafa26f6fdedc44dd6c3a7f3d172536e6b87
child 339329 6660b1a071d68579aa2922e6e225323cde5f8fca
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersSimonSapin
servo: Merge #12426 - Allow window elements as well as iframes to the the target of mozbrowser events (from asajeffrey:mozbrowser-event-targets); r=SimonSapin <!-- Please describe your changes on the following line: --> Allow mozbrowser events, in particular mozbrowsererror events, to target a window. Needed for https://github.com/browserhtml/browserhtml/issues/1182 --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #12420 - [X] These changes do not require tests because we're not testing our issue reporting system, which this is intended for. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: b6c0ed9a44b076928ea816ca529702eec25d0029
servo/components/constellation/constellation.rs
servo/components/constellation/pipeline.rs
servo/components/script/dom/document.rs
servo/components/script/dom/htmliframeelement.rs
servo/components/script/dom/window.rs
servo/components/script/script_thread.rs
servo/components/script_traits/lib.rs
servo/components/script_traits/script_msg.rs
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -1093,24 +1093,24 @@ impl<Message, LTF, STF> Constellation<Me
             // It's quite difficult to make Servo exit cleanly if some threads have failed.
             // Hard fail exists for test runners so we crash and that's good enough.
             error!("Pipeline failed in hard-fail mode.  Crashing!");
             process::exit(1);
         }
 
         debug!("Panic handler for pipeline {:?}: {}.", pipeline_id, reason);
 
+        // Notify the browser chrome that the pipeline has failed
+        self.trigger_mozbrowsererror(pipeline_id, reason, backtrace);
+
         if let Some(pipeline_id) = pipeline_id {
             let pipeline_url = self.pipelines.get(&pipeline_id).map(|pipeline| pipeline.url.clone());
             let parent_info = self.pipelines.get(&pipeline_id).and_then(|pipeline| pipeline.parent_info);
             let window_size = self.pipelines.get(&pipeline_id).and_then(|pipeline| pipeline.size);
 
-            // Notify the browser chrome that the pipeline has failed
-            self.trigger_mozbrowsererror(pipeline_id, reason, backtrace);
-
             self.close_pipeline(pipeline_id, ExitPipelineMode::Force);
             self.pipelines.remove(&pipeline_id);
 
             while let Some(pending_pipeline_id) = self.pending_frames.iter().find(|pending| {
                 pending.old_pipeline_id == Some(pipeline_id)
             }).map(|frame| frame.new_pipeline_id) {
                 warn!("removing pending frame change for failed pipeline");
                 self.close_pipeline(pending_pipeline_id, ExitPipelineMode::Force);
@@ -1132,22 +1132,20 @@ impl<Message, LTF, STF> Constellation<Me
 
             self.push_pending_frame(new_pipeline_id, Some(pipeline_id));
 
         }
 
         self.handled_panic = true;
     }
 
-    // TODO: trigger a mozbrowsererror even if there's no pipeline id
     fn handle_log_entry(&mut self, pipeline_id: Option<PipelineId>, thread_name: Option<String>, entry: LogEntry) {
-        match (pipeline_id, entry) {
-            (Some(pipeline_id), LogEntry::Panic(reason, backtrace)) =>
-                self.trigger_mozbrowsererror(pipeline_id, reason, backtrace),
-            (None, LogEntry::Panic(reason, _)) | (_, LogEntry::Error(reason)) | (_, LogEntry::Warn(reason)) => {
+        match entry {
+            LogEntry::Panic(reason, backtrace) => self.trigger_mozbrowsererror(pipeline_id, reason, backtrace),
+            LogEntry::Error(reason) | LogEntry::Warn(reason) => {
                 // VecDeque::truncate is unstable
                 if WARNINGS_BUFFER_SIZE <= self.handled_warnings.len() {
                     self.handled_warnings.pop_front();
                 }
                 self.handled_warnings.push_back((thread_name, reason));
             },
         }
     }
@@ -1334,17 +1332,17 @@ impl<Message, LTF, STF> Constellation<Me
                 let ancestor_info = self.get_mozbrowser_ancestor_info(pipeline_id);
                 if let Some((ancestor_id, subpage_id)) = ancestor_info {
                     if root_pipeline_id == Some(ancestor_id) {
                         match root_pipeline_id.and_then(|pipeline_id| self.pipelines.get(&pipeline_id)) {
                             Some(root_pipeline) => {
                                 // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsershowmodalprompt
                                 let event = MozBrowserEvent::ShowModalPrompt("alert".to_owned(), "Alert".to_owned(),
                                                                              String::from(message), "".to_owned());
-                                root_pipeline.trigger_mozbrowser_event(subpage_id, event);
+                                root_pipeline.trigger_mozbrowser_event(Some(subpage_id), event);
                             }
                             None => return warn!("Alert sent to Pipeline {:?} after closure.", root_pipeline_id),
                         }
                     } else {
                         warn!("A non-current frame is trying to show an alert.")
                     }
                 }
                 false
@@ -1628,17 +1626,17 @@ impl<Message, LTF, STF> Constellation<Me
         };
         if let Err(e) = result {
             self.handle_send_error(pipeline_id, e);
         }
     }
 
     fn handle_mozbrowser_event_msg(&mut self,
                                    containing_pipeline_id: PipelineId,
-                                   subpage_id: SubpageId,
+                                   subpage_id: Option<SubpageId>,
                                    event: MozBrowserEvent) {
         assert!(PREFS.is_mozbrowser_enabled());
 
         // Find the script channel for the given parent pipeline,
         // and pass the event to that script thread.
         // If the pipeline lookup fails, it is because we have torn down the pipeline,
         // so it is reasonable to silently ignore the event.
         match self.pipelines.get(&containing_pipeline_id) {
@@ -2321,54 +2319,62 @@ impl<Message, LTF, STF> Constellation<Me
         // If this is a mozbrowser iframe, then send the event with new url
         if let Some((containing_pipeline_id, subpage_id, FrameType::MozBrowserIFrame, url)) = event_info {
             if let Some(parent_pipeline) = self.pipelines.get(&containing_pipeline_id) {
                 if let Some(frame_id) = self.pipelines.get(&pipeline_id).and_then(|pipeline| pipeline.frame) {
                     if let Some(frame) = self.frames.get(&frame_id) {
                         let can_go_backward = !frame.prev.is_empty();
                         let can_go_forward = !frame.next.is_empty();
                         let event = MozBrowserEvent::LocationChange(url, can_go_backward, can_go_forward);
-                        parent_pipeline.trigger_mozbrowser_event(subpage_id, event);
+                        parent_pipeline.trigger_mozbrowser_event(Some(subpage_id), event);
                     }
                 }
             }
         }
     }
 
     // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsererror
     // Note that this does not require the pipeline to be an immediate child of the root
-    // TODO: allow the pipeline id to be optional, triggering the error on the root if it's not provided.
-    fn trigger_mozbrowsererror(&mut self, pipeline_id: PipelineId, reason: String, backtrace: String) {
+    fn trigger_mozbrowsererror(&mut self, pipeline_id: Option<PipelineId>, reason: String, backtrace: String) {
         if !PREFS.is_mozbrowser_enabled() { return; }
 
-        let ancestor_info = self.get_mozbrowser_ancestor_info(pipeline_id);
+        let mut report = String::new();
+        for (thread_name, warning) in self.handled_warnings.drain(..) {
+            report.push_str("\nWARNING: ");
+            if let Some(thread_name) = thread_name {
+                report.push_str("<");
+                report.push_str(&*thread_name);
+                report.push_str(">: ");
+            }
+            report.push_str(&*warning);
+        }
+        report.push_str("\nERROR: ");
+        report.push_str(&*reason);
+        report.push_str("\n\n");
+        report.push_str(&*backtrace);
 
-        if let Some(ancestor_info) = ancestor_info {
-            match self.pipelines.get(&ancestor_info.0) {
-                Some(ancestor) => {
-                    let mut report = String::new();
-                    for (thread_name, warning) in self.handled_warnings.drain(..) {
-                        report.push_str("\nWARNING: ");
-                        if let Some(thread_name) = thread_name {
-                            report.push_str("<");
-                            report.push_str(&*thread_name);
-                            report.push_str(">: ");
-                        }
-                        report.push_str(&*warning);
-                    }
-                    report.push_str("\nERROR: ");
-                    report.push_str(&*reason);
-                    report.push_str("\n\n");
-                    report.push_str(&*backtrace);
-                    let event = MozBrowserEvent::Error(MozBrowserErrorType::Fatal, Some(reason), Some(report));
-                    ancestor.trigger_mozbrowser_event(ancestor_info.1, event);
-                },
-                None => return warn!("Mozbrowsererror via closed pipeline {:?}.", ancestor_info.0),
+        let event = MozBrowserEvent::Error(MozBrowserErrorType::Fatal, Some(reason), Some(report));
+
+        if let Some(pipeline_id) = pipeline_id {
+            if let Some((ancestor_id, subpage_id)) = self.get_mozbrowser_ancestor_info(pipeline_id) {
+                if let Some(ancestor) = self.pipelines.get(&ancestor_id) {
+                    return ancestor.trigger_mozbrowser_event(Some(subpage_id), event);
+                }
             }
         }
+
+        if let Some(root_frame_id) = self.root_frame_id {
+            if let Some(root_frame) = self.frames.get(&root_frame_id) {
+                if let Some(root_pipeline) = self.pipelines.get(&root_frame.current) {
+                    return root_pipeline.trigger_mozbrowser_event(None, event);
+                }
+            }
+        }
+
+        warn!("Mozbrowser error after root pipeline closed.");
     }
 
     fn focused_pipeline_in_tree(&self, frame_id: FrameId) -> bool {
         self.focus_pipeline_id.map_or(false, |pipeline_id| {
             self.pipeline_exists_in_tree(pipeline_id, Some(frame_id))
         })
     }
 
--- a/servo/components/constellation/pipeline.rs
+++ b/servo/components/constellation/pipeline.rs
@@ -374,17 +374,17 @@ impl Pipeline {
     pub fn remove_child(&mut self, frame_id: FrameId) {
         match self.children.iter().position(|id| *id == frame_id) {
             None => return warn!("Pipeline remove child already removed ({:?}).", frame_id),
             Some(index) => self.children.remove(index),
         };
     }
 
     pub fn trigger_mozbrowser_event(&self,
-                                     subpage_id: SubpageId,
+                                     subpage_id: Option<SubpageId>,
                                      event: MozBrowserEvent) {
         assert!(PREFS.is_mozbrowser_enabled());
 
         let event = ConstellationControlMsg::MozBrowserEvent(self.id,
                                                              subpage_id,
                                                              event);
         if let Err(e) = self.script_chan.send(event) {
             warn!("Sending mozbrowser event to script failed ({}).", e);
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -1276,17 +1276,17 @@ impl Document {
     pub fn push_asap_in_order_script(&self, script: &HTMLScriptElement) {
         self.asap_in_order_scripts_list.borrow_mut().push(JS::from_ref(script));
     }
 
     pub fn trigger_mozbrowser_event(&self, event: MozBrowserEvent) {
         if PREFS.is_mozbrowser_enabled() {
             if let Some((containing_pipeline_id, subpage_id, _)) = self.window.parent_info() {
                 let event = ConstellationMsg::MozBrowserEvent(containing_pipeline_id,
-                                                              subpage_id,
+                                                              Some(subpage_id),
                                                               event);
                 self.window.constellation_chan().send(event).unwrap();
             }
         }
     }
 
     /// https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe
     pub fn request_animation_frame(&self, callback: Box<FnBox(f64)>) -> u32 {
--- a/servo/components/script/dom/htmliframeelement.rs
+++ b/servo/components/script/dom/htmliframeelement.rs
@@ -156,35 +156,21 @@ impl HTMLIFrameElement {
 
         let document = document_from_node(self);
         self.navigate_or_reload_child_browsing_context(
             Some(LoadData::new(url, document.get_referrer_policy(), Some(document.url().clone()))));
     }
 
     #[allow(unsafe_code)]
     pub fn dispatch_mozbrowser_event(&self, event: MozBrowserEvent) {
-        // TODO(gw): Support mozbrowser event types that have detail which is not a string.
-        // See https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API
-        // for a list of mozbrowser events.
         assert!(PREFS.is_mozbrowser_enabled());
 
         if self.Mozbrowser() {
             let window = window_from_node(self);
-            let custom_event = unsafe {
-                let cx = window.get_cx();
-                let _ac = JSAutoCompartment::new(cx, window.reflector().get_jsobject().get());
-                rooted!(in(cx) let mut detail = UndefinedValue());
-                let event_name = Atom::from(event.name());
-                self.build_mozbrowser_event_detail(event, cx, detail.handle_mut());
-                CustomEvent::new(GlobalRef::Window(window.r()),
-                                 event_name,
-                                 true,
-                                 true,
-                                 detail.handle())
-            };
+            let custom_event = build_mozbrowser_custom_event(&window, event);
             custom_event.upcast::<Event>().fire(self.upcast());
         }
     }
 
     pub fn update_subpage_id(&self, new_subpage_id: SubpageId, new_pipeline_id: PipelineId) {
         self.subpage_id.set(Some(new_subpage_id));
         self.pipeline_id.set(Some(new_pipeline_id));
 
@@ -331,107 +317,114 @@ impl HTMLIFrameElementLayoutMethods for 
                 .get_attr_for_layout(&ns!(), &atom!("height"))
                 .map(AttrValue::as_dimension)
                 .cloned()
                 .unwrap_or(LengthOrPercentageOrAuto::Auto)
         }
     }
 }
 
-pub trait MozBrowserEventDetailBuilder {
-    #[allow(unsafe_code)]
-    unsafe fn build_mozbrowser_event_detail(&self,
-                                            event: MozBrowserEvent,
-                                            cx: *mut JSContext,
-                                            rval: MutableHandleValue);
+#[allow(unsafe_code)]
+pub fn build_mozbrowser_custom_event(window: &Window, event: MozBrowserEvent) -> Root<CustomEvent> {
+    // TODO(gw): Support mozbrowser event types that have detail which is not a string.
+    // See https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API
+    // for a list of mozbrowser events.
+    let cx = window.get_cx();
+    let _ac = JSAutoCompartment::new(cx, window.reflector().get_jsobject().get());
+    rooted!(in(cx) let mut detail = UndefinedValue());
+    let event_name = Atom::from(event.name());
+    unsafe { build_mozbrowser_event_detail(event, cx, detail.handle_mut()); }
+    CustomEvent::new(GlobalRef::Window(window),
+                     event_name,
+                     true,
+                     true,
+                     detail.handle())
 }
 
-impl MozBrowserEventDetailBuilder for HTMLIFrameElement {
-    #[allow(unsafe_code)]
-    unsafe fn build_mozbrowser_event_detail(&self,
-                                            event: MozBrowserEvent,
-                                            cx: *mut JSContext,
-                                            rval: MutableHandleValue) {
-        match event {
-            MozBrowserEvent::AsyncScroll | MozBrowserEvent::Close | MozBrowserEvent::ContextMenu |
-            MozBrowserEvent::LoadEnd | MozBrowserEvent::LoadStart |
-            MozBrowserEvent::Connected | MozBrowserEvent::OpenSearch  |
-            MozBrowserEvent::UsernameAndPasswordRequired => {
-                rval.set(NullValue());
-            }
-            MozBrowserEvent::Error(error_type, description, report) => {
-                BrowserElementErrorEventDetail {
-                    type_: Some(DOMString::from(error_type.name())),
-                    description: description.map(DOMString::from),
-                    report: report.map(DOMString::from),
-                    version: Some(DOMString::from_string(servo_version().into())),
-                }.to_jsval(cx, rval);
-            },
-            MozBrowserEvent::SecurityChange(https_state) => {
-                BrowserElementSecurityChangeDetail {
-                    // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsersecuritychange
-                    state: Some(DOMString::from(match https_state {
-                        HttpsState::Modern => "secure",
-                        HttpsState::Deprecated => "broken",
-                        HttpsState::None => "insecure",
-                    }.to_owned())),
-                    // FIXME - Not supported yet:
-                    trackingContent: None,
-                    mixedContent: None,
-                    trackingState: None,
-                    extendedValidation: None,
-                    mixedState: None,
-                }.to_jsval(cx, rval);
-            }
-            MozBrowserEvent::TitleChange(ref string) => {
-                string.to_jsval(cx, rval);
-            }
-            MozBrowserEvent::LocationChange(url, can_go_back, can_go_forward) => {
-                BrowserElementLocationChangeEventDetail {
-                    url: Some(DOMString::from(url)),
-                    canGoBack: Some(can_go_back),
-                    canGoForward: Some(can_go_forward),
-                }.to_jsval(cx, rval);
-            }
-            MozBrowserEvent::OpenTab(url) => {
-                BrowserElementOpenTabEventDetail {
-                    url: Some(DOMString::from(url)),
-                }.to_jsval(cx, rval);
-            }
-            MozBrowserEvent::OpenWindow(url, target, features) => {
-                BrowserElementOpenWindowEventDetail {
-                    url: Some(DOMString::from(url)),
-                    target: target.map(DOMString::from),
-                    features: features.map(DOMString::from),
-                }.to_jsval(cx, rval);
-            }
-            MozBrowserEvent::IconChange(rel, href, sizes) => {
-                BrowserElementIconChangeEventDetail {
-                    rel: Some(DOMString::from(rel)),
-                    href: Some(DOMString::from(href)),
-                    sizes: Some(DOMString::from(sizes)),
-                }.to_jsval(cx, rval);
-            }
-            MozBrowserEvent::ShowModalPrompt(prompt_type, title, message, return_value) => {
-                BrowserShowModalPromptEventDetail {
-                    promptType: Some(DOMString::from(prompt_type)),
-                    title: Some(DOMString::from(title)),
-                    message: Some(DOMString::from(message)),
-                    returnValue: Some(DOMString::from(return_value)),
-                }.to_jsval(cx, rval)
-            }
-            MozBrowserEvent::VisibilityChange(visibility) => {
-                BrowserElementVisibilityChangeEventDetail {
-                    visible: Some(visibility),
-                }.to_jsval(cx, rval);
-            }
+#[allow(unsafe_code)]
+unsafe fn build_mozbrowser_event_detail(event: MozBrowserEvent,
+                                        cx: *mut JSContext,
+                                        rval: MutableHandleValue) {
+    match event {
+        MozBrowserEvent::AsyncScroll | MozBrowserEvent::Close | MozBrowserEvent::ContextMenu |
+        MozBrowserEvent::LoadEnd | MozBrowserEvent::LoadStart |
+        MozBrowserEvent::Connected | MozBrowserEvent::OpenSearch  |
+        MozBrowserEvent::UsernameAndPasswordRequired => {
+            rval.set(NullValue());
+        }
+        MozBrowserEvent::Error(error_type, description, report) => {
+            BrowserElementErrorEventDetail {
+                type_: Some(DOMString::from(error_type.name())),
+                description: description.map(DOMString::from),
+                report: report.map(DOMString::from),
+                version: Some(DOMString::from_string(servo_version().into())),
+            }.to_jsval(cx, rval);
+        },
+        MozBrowserEvent::SecurityChange(https_state) => {
+            BrowserElementSecurityChangeDetail {
+                // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsersecuritychange
+                state: Some(DOMString::from(match https_state {
+                    HttpsState::Modern => "secure",
+                    HttpsState::Deprecated => "broken",
+                    HttpsState::None => "insecure",
+                }.to_owned())),
+                // FIXME - Not supported yet:
+                trackingContent: None,
+                mixedContent: None,
+                trackingState: None,
+                extendedValidation: None,
+                mixedState: None,
+            }.to_jsval(cx, rval);
+        }
+        MozBrowserEvent::TitleChange(ref string) => {
+            string.to_jsval(cx, rval);
+        }
+        MozBrowserEvent::LocationChange(url, can_go_back, can_go_forward) => {
+            BrowserElementLocationChangeEventDetail {
+                url: Some(DOMString::from(url)),
+                canGoBack: Some(can_go_back),
+                canGoForward: Some(can_go_forward),
+            }.to_jsval(cx, rval);
+        }
+        MozBrowserEvent::OpenTab(url) => {
+            BrowserElementOpenTabEventDetail {
+                url: Some(DOMString::from(url)),
+            }.to_jsval(cx, rval);
+        }
+        MozBrowserEvent::OpenWindow(url, target, features) => {
+            BrowserElementOpenWindowEventDetail {
+                url: Some(DOMString::from(url)),
+                target: target.map(DOMString::from),
+                features: features.map(DOMString::from),
+            }.to_jsval(cx, rval);
+        }
+        MozBrowserEvent::IconChange(rel, href, sizes) => {
+            BrowserElementIconChangeEventDetail {
+                rel: Some(DOMString::from(rel)),
+                href: Some(DOMString::from(href)),
+                sizes: Some(DOMString::from(sizes)),
+            }.to_jsval(cx, rval);
+        }
+        MozBrowserEvent::ShowModalPrompt(prompt_type, title, message, return_value) => {
+            BrowserShowModalPromptEventDetail {
+                promptType: Some(DOMString::from(prompt_type)),
+                title: Some(DOMString::from(title)),
+                message: Some(DOMString::from(message)),
+                returnValue: Some(DOMString::from(return_value)),
+            }.to_jsval(cx, rval)
+        }
+        MozBrowserEvent::VisibilityChange(visibility) => {
+            BrowserElementVisibilityChangeEventDetail {
+                visible: Some(visibility),
+            }.to_jsval(cx, rval);
         }
     }
 }
 
+
 pub fn Navigate(iframe: &HTMLIFrameElement, direction: NavigationDirection) -> ErrorResult {
     if iframe.Mozbrowser() {
         if iframe.upcast::<Node>().is_in_doc() {
             let window = window_from_node(iframe);
 
             let pipeline_info = Some((window.pipeline(),
                                       iframe.subpage_id().unwrap()));
             let msg = ConstellationMsg::Navigate(pipeline_info, direction);
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -23,17 +23,19 @@ use dom::bindings::reflector::Reflectabl
 use dom::bindings::str::DOMString;
 use dom::bindings::utils::{GlobalStaticData, WindowProxyHandler};
 use dom::browsingcontext::BrowsingContext;
 use dom::console::Console;
 use dom::crypto::Crypto;
 use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration};
 use dom::document::Document;
 use dom::element::Element;
+use dom::event::Event;
 use dom::eventtarget::EventTarget;
+use dom::htmliframeelement::build_mozbrowser_custom_event;
 use dom::location::Location;
 use dom::navigator::Navigator;
 use dom::node::{Node, from_untrusted_node_address, window_from_node};
 use dom::performance::Performance;
 use dom::screen::Screen;
 use dom::storage::Storage;
 use euclid::{Point2D, Rect, Size2D};
 use gfx_traits::LayerId;
@@ -58,17 +60,17 @@ use script_layout_interface::TrustedNode
 use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflow};
 use script_layout_interface::reporter::CSSErrorReporter;
 use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
 use script_layout_interface::rpc::{MarginStyleResponse, ResolvedStyleResponse};
 use script_runtime::{ScriptChan, ScriptPort, maybe_take_panic_result};
 use script_thread::SendableMainThreadScriptChan;
 use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, RunnableWrapper};
 use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult};
-use script_traits::{ConstellationControlMsg, UntrustedNodeAddress};
+use script_traits::{ConstellationControlMsg, MozBrowserEvent, UntrustedNodeAddress};
 use script_traits::{DocumentState, MsDuration, TimerEvent, TimerEventId};
 use script_traits::{ScriptMsg as ConstellationMsg, TimerEventRequest, TimerSource, WindowSizeData};
 use std::ascii::AsciiExt;
 use std::borrow::ToOwned;
 use std::cell::Cell;
 use std::collections::{HashMap, HashSet};
 use std::default::Default;
 use std::ffi::CString;
@@ -1583,16 +1585,23 @@ impl Window {
     /// in a top-level `Window` global.
     #[allow(unsafe_code)]
     pub unsafe fn global_is_mozbrowser(_: *mut JSContext, obj: HandleObject) -> bool {
         match global_root_from_object(obj.get()).r() {
             GlobalRef::Window(window) => window.is_mozbrowser(),
             _ => false,
         }
     }
+
+    #[allow(unsafe_code)]
+    pub fn dispatch_mozbrowser_event(&self, event: MozBrowserEvent) {
+        assert!(PREFS.is_mozbrowser_enabled());
+        let custom_event = build_mozbrowser_custom_event(&self, event);
+        custom_event.upcast::<Event>().fire(self.upcast());
+    }
 }
 
 impl Window {
     pub fn new(runtime: Rc<Runtime>,
                script_chan: MainThreadScriptChan,
                dom_task_source: DOMManipulationTaskSource,
                user_task_source: UserInteractionTaskSource,
                network_task_source: NetworkingTaskSource,
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -1350,27 +1350,27 @@ impl ScriptThread {
                           ReflowReason::FramedContentChanged);
         }
     }
 
     /// Handles a mozbrowser event, for example see:
     /// https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart
     fn handle_mozbrowser_event_msg(&self,
                                    parent_pipeline_id: PipelineId,
-                                   subpage_id: SubpageId,
+                                   subpage_id: Option<SubpageId>,
                                    event: MozBrowserEvent) {
-        let borrowed_context = self.root_browsing_context();
-
-        let frame_element = borrowed_context.find(parent_pipeline_id).and_then(|context| {
-            let doc = context.active_document();
-            doc.find_iframe(subpage_id)
-        });
-
-        if let Some(ref frame_element) = frame_element {
-            frame_element.dispatch_mozbrowser_event(event);
+        match self.root_browsing_context().find(parent_pipeline_id) {
+            None => warn!("Mozbrowser event after pipeline {:?} closed.", parent_pipeline_id),
+            Some(context) => match subpage_id {
+                None => context.active_window().dispatch_mozbrowser_event(event),
+                Some(subpage_id) => match context.active_document().find_iframe(subpage_id) {
+                    None => warn!("Mozbrowser event after iframe {:?}/{:?} closed.", parent_pipeline_id, subpage_id),
+                    Some(frame_element) => frame_element.dispatch_mozbrowser_event(event),
+                },
+            },
         }
     }
 
     fn handle_update_subpage_id(&self,
                                 containing_pipeline_id: PipelineId,
                                 old_subpage_id: SubpageId,
                                 new_subpage_id: SubpageId,
                                 new_pipeline_id: PipelineId) {
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -173,18 +173,19 @@ pub enum ConstellationControlMsg {
     /// Notifies script thread to resume all its timers
     Thaw(PipelineId),
     /// Notifies script thread whether frame is visible
     ChangeFrameVisibilityStatus(PipelineId, bool),
     /// Notifies script thread that frame visibility change is complete
     NotifyVisibilityChange(PipelineId, PipelineId, bool),
     /// Notifies script thread that a url should be loaded in this iframe.
     Navigate(PipelineId, SubpageId, LoadData),
-    /// Requests the script thread forward a mozbrowser event to an iframe it owns
-    MozBrowserEvent(PipelineId, SubpageId, MozBrowserEvent),
+    /// Requests the script thread forward a mozbrowser event to an iframe it owns,
+    /// or to the window if no subpage id is provided.
+    MozBrowserEvent(PipelineId, Option<SubpageId>, MozBrowserEvent),
     /// Updates the current subpage and pipeline IDs of a given iframe
     UpdateSubpageId(PipelineId, SubpageId, SubpageId, PipelineId),
     /// Set an iframe to be focused. Used when an element in an iframe gains focus.
     FocusIFrame(PipelineId, SubpageId),
     /// Passes a webdriver command to the script thread for execution
     WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
     /// Notifies script thread that all animations are done
     TickAllAnimations(PipelineId),
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -83,18 +83,19 @@ pub enum ScriptMsg {
     /// Requests that the constellation retrieve the current contents of the clipboard
     GetClipboardContents(IpcSender<String>),
     /// <head> tag finished parsing
     HeadParsed,
     /// All pending loads are complete.
     LoadComplete(PipelineId),
     /// A new load has been requested.
     LoadUrl(PipelineId, LoadData),
-    /// Dispatch a mozbrowser event to a given iframe. Only available in experimental mode.
-    MozBrowserEvent(PipelineId, SubpageId, MozBrowserEvent),
+    /// Dispatch a mozbrowser event to a given iframe,
+    /// or to the window if no subpage id is provided.
+    MozBrowserEvent(PipelineId, Option<SubpageId>, MozBrowserEvent),
     /// HTMLIFrameElement Forward or Back navigation.
     Navigate(Option<(PipelineId, SubpageId)>, NavigationDirection),
     /// Favicon detected
     NewFavicon(Url),
     /// Status message to be displayed in the chrome, eg. a link URL on mouseover.
     NodeStatus(Option<String>),
     /// Notification that this iframe should be removed.
     RemoveIFrame(PipelineId, Option<IpcSender<()>>),