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=nika

Behind a pref for now.

Differential Revision: https://phabricator.services.mozilla.com/D35600

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Boris Zbarsky 2020-03-12 18:45:36 +00:00
Родитель 5f4fd391ec
Коммит 16a5db7bc2
5 изменённых файлов: 93 добавлений и 17 удалений

Просмотреть файл

@ -173,6 +173,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "nsFrameLoader.h"
#include "nsFrameLoaderOwner.h"
#include "nsXPCOMCID.h"
#include "mozilla/Logging.h"
#include "prenv.h"
@ -1898,29 +1899,73 @@ void nsGlobalWindowInner::FireFrameLoadEvent() {
}
// 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 {
}
RefPtr<nsFrameLoaderOwner> loaderOwner(do_QueryObject(element));
if (!loaderOwner) {
// Pretty surprising!
MOZ_ASSERT(false, "How did we get here?");
return;
}
// Track the frameloader, not the element, for purposes of firing our load
// event. That way if the element gets a new frame loader (e.g. gets moved
// around in the DOM) while our runnable is pending we will not fire an
// incorrect (because having nothing to do with the browsing context,
// window, etc now inside the element) load event at it.
RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
if (!loader) {
// Still pretty surprising, if we got this far!
return;
}
auto fireLoadEvent = [loader]() -> void {
RefPtr<Element> currentElement = loader->GetOwnerElement();
if (!currentElement) {
// The loader has gotten torn down. Just bail.
return;
}
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);
EventDispatcher::Dispatch(currentElement, 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::Network, fireEvent.forget());
}
return;
}

Просмотреть файл

@ -18,6 +18,18 @@ function doTest() {
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();
}
}

Просмотреть файл

@ -25,9 +25,16 @@ function ok_wrapper(result, msg) {
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>

Просмотреть файл

@ -2,8 +2,13 @@
<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>

Просмотреть файл

@ -3035,6 +3035,13 @@
value: false
mirror: always
# 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.
- name: dom.cross_docgroup_iframe_async_load_event
type: bool
value: false
mirror: always
# WebIDL test prefs.
- name: dom.webidl.test1
type: bool