Bug 1440212 part 2. Add the ability to fire the load event on a frame element async from the load event on the window inside if they are in different docgroups. r=mystor draft
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 21 Jun 2019 20:13:43 -0400
changeset 2108779 6786a069a7eaae6589505a78f92c604e7c5a6828
parent 2108778 365dcadafa1dd948ec30733852c2ba9a8c68e1c3
child 2108780 1de1c9a6587b18e3c356f7327fdfb1f869211e15
child 2108784 97204a3c006920e8e6ec29c47807ff14b3eb347e
push id380528
push userbzbarsky@mozilla.com
push dateSat, 22 Jun 2019 00:15:04 +0000
treeherdertry@1de1c9a6587b [default view] [failures only]
reviewersmystor
bugs1440212
milestone69.0a1
Bug 1440212 part 2. Add the ability to fire the load event on a frame element async from the load event on the window inside if they are in different docgroups. r=mystor Behind a pref for now.
dom/base/nsGlobalWindowInner.cpp
dom/html/test/file_iframe_sandbox_d_if11.html
dom/html/test/file_iframe_sandbox_d_if13.html
layout/printing/crashtests/793844.html
modules/libpref/init/StaticPrefList.h
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -1931,34 +1931,57 @@ void nsGlobalWindowInner::FireFrameLoadE
   }
 
   // If embedder is same-process, fire the event on our embedder element.
   //
   // XXX: Bug 1440212 is looking into potentially changing this behaviour to act
   // more like the remote case when in-process.
   RefPtr<Element> element = GetBrowsingContext()->GetEmbedderElement();
   if (element) {
-    nsEventStatus status = nsEventStatus_eIgnore;
-    WidgetEvent event(/* aIsTrusted = */ true, eLoad);
-    event.mFlags.mBubbles = false;
-    event.mFlags.mCancelable = false;
-
     if (mozilla::dom::DocGroup::TryToLoadIframesInBackground()) {
       nsDocShell* ds = nsDocShell::Cast(GetDocShell());
 
-      if (ds && !ds->HasFakeOnLoadDispatched()) {
-        EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status);
+      if (!ds || ds->HasFakeOnLoadDispatched()) {
+        return;
       }
-    } else {
+    }
+
+    auto fireLoadEvent = [element]() -> void {
+      nsEventStatus status = nsEventStatus_eIgnore;
+      WidgetEvent event(/* aIsTrusted = */ true, eLoad);
+      event.mFlags.mBubbles = false;
+      event.mFlags.mCancelable = false;
+
       // Most of the time we could get a pres context to pass in here,
       // but not always (i.e. if this window is not shown there won't
       // be a pres context available). Since we're not firing a GUI
       // event we don't need a pres context anyway so we just pass
       // null as the pres context all the time here.
       EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status);
+    };
+
+    if (GetDocGroup() == element->GetDocGroup() ||
+        !StaticPrefs::dom_cross_docgroup_iframe_async_load_event()) {
+      fireLoadEvent();
+    } else {
+      // Make sure we don't fire the load event on |element|'s document before
+      // we fire it on the element.
+      RefPtr<Document> doc = element->OwnerDoc();
+      doc->BlockOnload();
+      RefPtr<Runnable> fireEvent = NS_NewRunnableFunction(
+          "Cross-docgroup frame load", [doc, fireLoadEvent]() -> void {
+            fireLoadEvent();
+            // Sync unblocking is OK here, because we're coming from a
+            // runnable anyway.  Note that we capture "doc" here,
+            // instead of using element->OwnerDoc(), because the
+            // latter could change before our runnable runs and then
+            // we will get incorrectly paired block/unblock calls.
+            doc->UnblockOnload(true);
+          });
+      doc->Dispatch(TaskCategory::Other, fireEvent.forget());
     }
     return;
   }
 
   // We don't have an in-process embedder. Try to get our `BrowserChild` actor
   // to send a message to that embedder. We want to double-check that our outer
   // window is actually the one at the root of this browserChild though, just in
   // case.
--- a/dom/html/test/file_iframe_sandbox_d_if11.html
+++ b/dom/html/test/file_iframe_sandbox_d_if11.html
@@ -24,16 +24,28 @@ function navigateAway() {
 }
 
 function doTest() {
   try {
     // this should fail the first time, but work the second
     window.parent.ok_wrapper(true, "a document that was loaded, navigated to another document, had 'allow-same-origin' added and then was" +
       " navigated back should be same-origin with its parent");
   } catch (e) {
+  }
+}
+
+// We depend on our link click adding a new session history entry for the new
+// load.  That means we need to make sure it runs after our parent is done
+// loading.  Otherwise the load can get turned into a replace load, not create a
+// new session history entry, and then the back() call in the parent will fail.
+//
+// Since we are not same-origin with the parent, we wait for it to tell us when
+// it's done loading.
+onmessage = function(e) {
+  if (e.data == "start") {
     navigateAway();
   }
 }
 
 </script>
 <body onload='doTest()'>
   I am sandboxed with 'allow-scripts'
   <a href='file_iframe_sandbox_d_if12.html' id='anchor'>CLICK ME</a>
--- a/dom/html/test/file_iframe_sandbox_d_if13.html
+++ b/dom/html/test/file_iframe_sandbox_d_if13.html
@@ -20,16 +20,23 @@ function receiveMessage(event) {
 function ok_wrapper(result, msg) {
   window.opener.postMessage({ok: result, desc: msg}, "*");
   window.close();
 }
 
 function doIf11TestPart2() {
   var if_11 = document.getElementById('if_11');
   if_11.sandbox = 'allow-scripts allow-same-origin';
-  // window.history is no longer cross-origin accessible in gecko.
-  SpecialPowers.wrap(if_11).contentWindow.history.back();
-} 
+  // History is unified for the entire toplevel window and all its
+  // subframes, so we can just call history.back() on ourselves; we
+  // don't have to do it on our child.
+  history.back();
+}
+
+onload = function() {
+  document.getElementById('if_11').contentWindow.postMessage("start", "*");
+}
+
 </script>
 <body>
   <iframe sandbox='allow-scripts' id="if_11" src="file_iframe_sandbox_d_if11.html" height="10" width="10"></iframe>
 </body>
 </html>
--- a/layout/printing/crashtests/793844.html
+++ b/layout/printing/crashtests/793844.html
@@ -1,10 +1,15 @@
 <html class="reftest-paged">
 <head>
 <title>crash in nsContentList::nsContentList on print preview</title>
 </head><body>
-
-<iframe  onload="window.location.reload()" src="data:text/html,">
+<script>
+  onmessage = function () {
+    window.location.reload();
+  }
+</script>
+<!-- Give our load event a chance to fire too -->
+<iframe onload="postMessage(null, '*')" src="data:text/html,">
 </iframe>
 
 
 </body></html>
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -689,25 +689,16 @@ VARCACHE_PREF(
 
 VARCACHE_PREF(
   Live,
   "full-screen-api.mouse-event-allow-left-button-only",
   full_screen_api_mouse_event_allow_left_button_only,
   bool, true
 )
 
-// When this pref is set, parent documents may consider child iframes've loaded
-// while they are still loading
-VARCACHE_PREF(
-  Live,
-  "dom.cross_origin_iframes_loaded_in_background",
-   dom_cross_origin_iframes_loaded_in_background,
-  bool, false
-)
-
 //---------------------------------------------------------------------------
 // Prefs starting with "beacon."
 //---------------------------------------------------------------------------
 
 // Is support for Navigator.sendBeacon enabled?
 VARCACHE_PREF(
   Live,
   "beacon.enabled",
@@ -1271,16 +1262,34 @@ VARCACHE_PREF(
 // system group or not in content.
 VARCACHE_PREF(
   Live,
   "dom.compositionevent.text.dispatch_only_system_group_in_content",
   dom_compositionevent_text_dispatch_only_system_group_in_content,
   bool, true
 )
 
+// When this pref is set, parent documents may consider child iframes've loaded
+// while they are still loading
+VARCACHE_PREF(
+  Live,
+  "dom.cross_origin_iframes_loaded_in_background",
+   dom_cross_origin_iframes_loaded_in_background,
+  bool, false
+)
+
+// When this pref is set, we can fire load events on iframe elements async if
+// the iframe is in a different docgroup from the document it contains.
+VARCACHE_PREF(
+  Live,
+  "dom.cross_docgroup_iframe_async_load_event",
+  dom_cross_docgroup_iframe_async_load_event,
+  bool, false
+)
+
 // Any how many seconds we allow external protocol URLs in iframe when not in
 // single events
 VARCACHE_PREF(
   Live,
   "dom.delay.block_external_protocol_in_iframes",
   dom_delay_block_external_protocol_in_iframes,
   uint32_t, 10 // in seconds
 )