Bug 1533949 - Make BrowserChild functions Fission-compatible, and move to BrowserElementChild. r=NeilDeakin

Differential Revision: https://phabricator.services.mozilla.com/D30725
This commit is contained in:
Mike Conley
2019-05-30 19:01:29 +00:00
parent 1463bc8ac7
commit 92452c88b5
10 changed files with 517 additions and 169 deletions

View File

@@ -41,6 +41,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
PageThumbs: "resource://gre/modules/PageThumbs.jsm",
PanelMultiView: "resource:///modules/PanelMultiView.jsm",
PanelView: "resource:///modules/PanelMultiView.jsm",
PermitUnloader: "resource://gre/actors/BrowserElementParent.jsm",
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",

View File

@@ -44,7 +44,6 @@ window._gBrowser = {
let messageManager = window.getGroupMessageManager("browsers");
if (gMultiProcessBrowser) {
messageManager.addMessageListener("DOMTitleChanged", this);
messageManager.addMessageListener("DOMWindowClose", this);
window.messageManager.addMessageListener("contextmenu", this);
messageManager.addMessageListener("Browser:Init", this);
} else {
@@ -2868,9 +2867,10 @@ window._gBrowser = {
_hasBeforeUnload(aTab) {
let browser = aTab.linkedBrowser;
return browser.isRemoteBrowser && browser.frameLoader &&
browser.frameLoader.remoteTab &&
browser.frameLoader.remoteTab.hasBeforeUnload;
if (browser.isRemoteBrowser && browser.frameLoader) {
return PermitUnloader.hasBeforeUnload(browser.frameLoader);
}
return false;
},
_beginRemoveTab(aTab, {
@@ -4335,25 +4335,6 @@ window._gBrowser = {
tab.setAttribute("titlechanged", "true");
break;
}
case "DOMWindowClose":
{
if (this.tabs.length == 1) {
// We already did PermitUnload in the content process
// for this tab (the only one in the window). So we don't
// need to do it again for any tabs.
window.skipNextCanClose = true;
window.close();
return undefined;
}
let tab = this.getTabForBrowser(browser);
if (tab) {
// Skip running PermitUnload since it already happened in
// the content process.
this.removeTab(tab, { skipPermitUnload: true });
}
break;
}
case "contextmenu":
{
openContextMenu(aMessage);
@@ -4540,25 +4521,48 @@ window._gBrowser = {
});
this.addEventListener("DOMWindowClose", (event) => {
if (!event.isTrusted)
return;
let browser = event.target;
if (!browser.isRemoteBrowser) {
if (!event.isTrusted) {
// If the browser is not remote, then we expect the event to be trusted.
// In the remote case, the DOMWindowClose event is captured in content,
// a message is sent to the parent, and another DOMWindowClose event
// is re-dispatched on the actual browser node. In that case, the event
// won't be marked as trusted, since it's synthesized by JavaScript.
return;
}
// In the parent-process browser case, it's possible that the browser
// that fired DOMWindowClose is actually a child of another browser. We
// want to find the top-most browser to determine whether or not this is
// for a tab or not. The chromeEventHandler will be the top-most browser.
browser = event.target.docShell.chromeEventHandler;
}
if (this.tabs.length == 1) {
// We already did PermitUnload in nsGlobalWindow::Close
// for this tab. There are no other tabs we need to do
// PermitUnload for.
// We already did PermitUnload in the content process
// for this tab (the only one in the window). So we don't
// need to do it again for any tabs.
window.skipNextCanClose = true;
// In the parent-process browser case, the nsCloseEvent will actually take
// care of tearing down the window, but we need to do this ourselves in the
// content-process browser case. Doing so in both cases doesn't appear to
// hurt.
window.close();
return;
}
let browser = event.target.docShell.chromeEventHandler;
let tab = this.getTabForBrowser(browser);
if (tab) {
// Skip running PermitUnload since it already happened.
// Skip running PermitUnload since it already happened in
// the content process.
this.removeTab(tab, { skipPermitUnload: true });
// If we don't preventDefault on the DOMWindowClose event, then
// in the parent-process browser case, we're telling the platform
// to close the entire window. Calling preventDefault is our way of
// saying we took care of this close request by closing the tab.
event.preventDefault();
}
}, true);
});
this.addEventListener("DOMWillOpenModalDialog", (event) => {
if (!event.isTrusted)

View File

@@ -1227,6 +1227,7 @@ pref("editor.positioning.offset", 0);
// Scripts & Windows prefs
pref("dom.disable_beforeunload", false);
pref("dom.beforeunload_timeout_ms", 1000);
pref("dom.disable_window_flip", false);
pref("dom.disable_window_move_resize", false);

View File

@@ -1,49 +0,0 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["BrowserChild"];
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
class BrowserChild extends ActorChild {
handleEvent(event) {
if (event.type == "DOMWindowClose") {
this.mm.sendAsyncMessage("DOMWindowClose");
}
}
receiveMessage(message) {
switch (message.name) {
case "Browser:CreateAboutBlank":
if (!this.content.document || this.content.document.documentURI != "about:blank") {
throw new Error("Can't create a content viewer unless on about:blank");
}
let principal = message.data;
principal = BrowserUtils.principalWithMatchingOA(principal, this.content.document.nodePrincipal);
this.mm.docShell.createAboutBlankContentViewer(principal);
break;
case "InPermitUnload":
let inPermitUnload = this.mm.docShell.contentViewer && this.mm.docShell.contentViewer.inPermitUnload;
this.mm.sendAsyncMessage("InPermitUnload", {id: message.data.id, inPermitUnload});
break;
case "PermitUnload":
this.mm.sendAsyncMessage("PermitUnload", {id: message.data.id, kind: "start"});
let permitUnload = true;
if (this.mm.docShell && this.mm.docShell.contentViewer) {
permitUnload = this.mm.docShell.contentViewer.permitUnload(message.data.aPermitUnloadFlags);
}
this.mm.sendAsyncMessage("PermitUnload", {id: message.data.id, kind: "end", permitUnload});
break;
}
}
}

View File

@@ -0,0 +1,35 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["BrowserElementChild"];
class BrowserElementChild extends JSWindowActorChild {
handleEvent(event) {
if (event.type == "DOMWindowClose" &&
!this.manager.browsingContext.parent) {
this.sendAsyncMessage("DOMWindowClose", {});
}
}
receiveMessage(message) {
let context = this.manager.browsingContext;
let docShell = context.docShell;
switch (message.name) {
case "PermitUnload": {
this.sendAsyncMessage("Running", {});
let permitUnload = true;
if (docShell && docShell.contentViewer) {
permitUnload = docShell.contentViewer.permitUnload(message.data.flags);
}
this.sendAsyncMessage("Done", { permitUnload });
break;
}
}
}
}

View File

@@ -0,0 +1,383 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["BrowserElementParent", "PermitUnloader"];
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
let {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "unloadTimeoutMs",
"dom.beforeunload_timeout_ms");
// Out-of-process subframes might be in the following states following a request
// to permit closing:
//
// We're waiting to hear back from the frame on whether or not we should permit
// unloading.
const STATE_WAITING = 0;
// The out-of-process iframe is running its permitUnload routine. The frame stays
// in this state when the permit unload modal dialog is being displayed.
const STATE_RUNNING = 1;
// The permitUnload routine has completed, and any modal dialogs for that subframe
// have been cleared. This frame, at this point, has answered the questions of whether
// or not it wants to be unloaded.
const STATE_DONE = 2;
/**
* The BrowserElementParent is for performing actions on one or more subframes of
* a <xul:browser> from the browser element binding.
*/
class BrowserElementParent extends JSWindowActorParent {
/**
* NOTE: It is expected that this function is only called from PermitUnloader.
* Callers who want to check if a <xul:browser> wants to allow being unloaded
* should use PermitUnloader instead.
*
* Sends a request to a subframe to run the docShell's permitUnload routine
* with the passed flags.
*
* @param {Number} flags See nsIContentViewer.idl for the types of flags that
* can be passed.
*/
sendPermitUnload(flags) {
this.sendAsyncMessage("PermitUnload", { flags });
}
receiveMessage(message) {
switch (message.name) {
case "Running": {
PermitUnloader._transitionFrameState(this.manager, STATE_RUNNING);
break;
}
case "Done": {
let permitUnload = message.data.permitUnload;
if (!permitUnload) {
PermitUnloader._doNotPermitUnload(this.manager);
} else {
PermitUnloader._transitionFrameState(this.manager, STATE_DONE);
}
break;
}
case "DOMWindowClose": {
// This message is sent whenever window.close() is called within a window
// that had originally been opened via window.open. Double-check that this is
// coming from a top-level frame, and then dispatch the DOMWindowClose event
// on the browser so that the front-end code can do the right thing with the
// request to close.
if (!this.manager.browsingContext.parent) {
let browser = this.manager.browsingContext.embedderElement;
let win = browser.ownerGlobal;
// If this is a non-remote browser, the DOMWindowClose event will bubble
// up naturally, and doesn't need to be re-dispatched.
if (browser.isRemoteBrowser) {
browser.dispatchEvent(new win.CustomEvent("DOMWindowClose", {
bubbles: true,
}));
}
}
break;
}
}
}
}
/**
* PermitUnloader is a parent-process singleton that manages checking to see whether
* or not <xul:browser> elements would prefer to be unloaded or not.
*
* PermitUnloader works by first finding the root nodes of process-contiguous trees
* of frames. This means that if a document has the following frame structure:
*
* a
* / \
* b c
* / \ / \
* b d c c
* / \
* d d
*
* where each letter represents the process that each frame runs in, then we consider
* process-contiguous trees of frames to be ones where all frames are directly connected
* and running in the same process. Process-contiguous subtrees are denoted in the following
* graph with shared numbers.
*
* a1
* / \
* b2 c3
* / \ / \
* b2 d4 c3 c3
* / \
* d4 d5
*
* Specifically, note that the d5 leaf node, while belonging to the same process as the
* d4 nodes are not directly connected, so it's not process-contiguous.
*
* Messaging the roots of these process-contiguous subtrees concurrently allows us to ask
* each of these subtrees to simultaneously run their permit unload checks in
* breadth-first order. This appears to be Chromium's algorithm.
*/
var PermitUnloader = {
// Maps a frameLoader to the state of a permitUnload request. If the frameLoader
// doesn't exist in the map, then either permitUnload has never been called on
// the frameLoader, or a previous permitUnload check has already completed.
//
// See permitUnload for the mapping structure.
inProgressPermitUnload: new WeakMap(),
/* Public methods */
/**
* Returns true if the frameLoader associated with a tab is still determining
* whether unloading it is preferred. This might mean that beforeunload events
* are still running, or the modal UI to prevent unloading is waiting on the
* user to respond.
*
* @param {FrameLoader} frameLoader the frameLoader associated with a <xul:browser>
* that might still be determining unload-ability.
* @return {Boolean} true if unload-ability is still being determined.
*/
inPermitUnload(frameLoader) {
return this.inProgressPermitUnload.has(frameLoader);
},
/**
* Returns an Object indicating whether or not a <xul:browser> wants to be
* unloaded or not.
*
* Note: this function spins a nested event loop while it waits for a response
* from each process-contiguous subtree, and will throw to avoid re-entry for
* the same frameLoader.
*
* @param {FrameLoader} frameLoader the frameLoader that should be checked for
* unload-ability.
* @param {Number} flags See nsIContentViewer.idl for the types of flags that
* can be passed.
*
* @return {Object} an Object with the following structure:
*
* {
* permitUnload: Boolean (true if all frames prefer to be unloaded.)
* timedOut: Boolean (true if contacting all frames timed out.)
* }
*/
permitUnload(frameLoader, flags) {
// Don't allow re-entry for the same tab
if (this.inPermitUnload(frameLoader)) {
throw new Error("permitUnload is already running for this tab.");
}
let frameStates = new Map();
// Until JS Window Actor teardown methods are implemented, we'll use the
// message-manager-close observer notification to notice if the top-level
// context has gone away.
let mm = frameLoader.messageManager;
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
let roots = this.getProcessContiguousRoots(frameLoader.browsingContext);
let state = {
frameStates,
timedOut: false,
permitUnload: true,
timer,
waitingCount: roots.length,
};
let observer = subject => {
if (subject == mm) {
this._finish(state);
}
};
try {
this.inProgressPermitUnload.set(frameLoader, state);
Services.obs.addObserver(observer, "message-manager-close");
for (let windowGlobalParent of roots) {
let actor = windowGlobalParent.getActor("BrowserElement");
frameStates.set(actor.manager, STATE_WAITING);
actor.sendPermitUnload(flags);
}
timer.initWithCallback(() => {
this._onTimeout(frameLoader);
}, unloadTimeoutMs, timer.TYPE_ONE_SHOT);
Services.tm.spinEventLoopUntilOrShutdown(() => {
return this._finishedPermitUnload(frameStates);
});
} finally {
this._finish(state);
this.inProgressPermitUnload.delete(frameLoader);
Services.obs.removeObserver(observer, "message-manager-close");
}
return {
permitUnload: state.permitUnload,
timedOut: state.timedOut,
};
},
/**
* Given a BrowsingContext, returns that BrowsingContext's WindowGlobalParent
* along with all of the WindowGlobalParents for the roots of the process
* contiguous subtrees under the passed BrowsingContext. See the documentation
* for PermitUnloader for a detailed illustration of what roots of
* process-contiguous subtrees are.
*
* @param {BrowsingContext} browsingContext the BrowsingContext whose descendants
* should be searched. The WindowGlobalParent of this BrowsingContext will be
* included in the returned Array.
*
* @return {Array<WindowGlobalParent>} The WindowGlobalParent's for all roots of
* process-contiguous subtrees of the passed in BrowsingContext, along with the
* WindowGlobalParent of the passed in BrowsingContext.
*/
getProcessContiguousRoots(browsingContext) {
let contextsToWalk = [browsingContext];
let roots = [];
while (contextsToWalk.length) {
let currentContext = contextsToWalk.pop();
let windowGlobal = currentContext.currentWindowGlobal;
if (!windowGlobal) {
continue;
}
if (windowGlobal.isProcessRoot) {
roots.push(windowGlobal);
}
contextsToWalk.push(...currentContext.getChildren());
}
return roots;
},
/**
* Returns true if any of the subframes associated with the frameLoader
* have a beforeunload event handler set. If this returns false, there's
* no point in running permitUnload on the frameLoader, as we know that
* no frames will prevent it.
*
* @param {FrameLoader} frameLoader the frameLoader that should be checked for
* beforeunload event handlers.
*
* @return {Boolean} true if there's at least one beforeunload event handler
* set in any of the frameLoader's frames.
*/
hasBeforeUnload(frameLoader) {
if (frameLoader.remoteTab) {
return frameLoader.remoteTab.hasBeforeUnload;
}
return false;
},
/**
* Private methods - the following methods are only expected to be called
* from PermitUnloader or BrowserElementParent.
*/
/**
* This is called when the BrowserElementParent receives a message from the
* process-contiguous subtree root alerting it that it is either starting to
* run the permitUnload routine, or has completed running the permitUnload
* routine.
*
* @param {WindowGlobalParent} windowGlobal the WindowGlobalParent for the
* root of the process-contiguous subtree that we're receiving the state
* update from.
* @param Number newFrameState one of STATE_RUNNING or STATE_DONE.
*/
_transitionFrameState(windowGlobal, newFrameState) {
let frameLoader = windowGlobal.rootFrameLoader;
let state = this.inProgressPermitUnload.get(frameLoader);
let oldFrameState = state.frameStates.get(windowGlobal);
if (oldFrameState == STATE_WAITING) {
state.waitingCount--;
if (!state.waitingCount) {
state.timer.cancel();
}
}
state.frameStates.set(windowGlobal, newFrameState);
},
/**
* Called from the permitUnload nested event loop to check whether we've
* gotten enough information to exit that loop. Enough information means
* either that we've heard from all process-contiguous subtrees, or we've
* gotten an answer from one of the process-contiguous subtrees saying that
* we shouldn't unload the <xul:browser>, or that we've timed out waiting
* for all responses to come back.
*
* @param {Map} frameStates the mapping of process-contiguous subtree roots
* to their loading states as set by permitUnload.
*
* @return {Boolean} true if the nested event loop should exit.
*/
_finishedPermitUnload(frameStates) {
for (let [, state] of frameStates) {
if (state != STATE_DONE) {
return false;
}
}
return true;
},
/**
* Called when we no longer want to run the nested event loop, and no longer want
* to fire the messaging timeout function.
*
* @param {Object} state the state for the frameLoader that we're currently checking
* for unload-ability.
*/
_finish(state) {
state.frameStates.clear();
state.timer.cancel();
},
/**
* Called when the timeout for checking for unload-ability has fired. This means
* we haven't heard back from all of the process-contiguous subtrees in time,
* in which case, we assume that it's safe to unload the <xul:browser>.
*
* @param {FrameLoader} frameLoader the frameLoader that took too long to detect
* unload-ability.
*/
_onTimeout(frameLoader) {
let state = this.inProgressPermitUnload.get(frameLoader);
state.timedOut = true;
this._finish(state);
// Dispatch something to ensure that the main thread wakes up.
Services.tm.dispatchToMainThread(function() {});
},
/**
* Called by the BrowserElementParent if a process-contiguous subtree reports that
* it shouldn't be unloaded.
*
* @param {WindowGlobalParent} windowGlobal the WindowGlobalParent for the
* root of the process-contiguous subtree that requested that unloading not
* occur.
*/
_doNotPermitUnload(windowGlobal) {
let frameLoader = windowGlobal.rootFrameLoader;
let state = this.inProgressPermitUnload.get(frameLoader);
// We might have already heard from a previous process-contiguous subtree
// that unload should not be permitted, in which case this state will have
// been cleared. In that case, we'll just ignore the message.
if (state) {
this._finish(state);
state.permitUnload = false;
}
},
};

View File

@@ -21,7 +21,8 @@ TESTING_JS_MODULES += [
FINAL_TARGET_FILES.actors += [
'AudioPlaybackChild.jsm',
'AutoplayChild.jsm',
'BrowserChild.jsm',
'BrowserElementChild.jsm',
'BrowserElementParent.jsm',
'ControllersChild.jsm',
'DateTimePickerChild.jsm',
'ExtFindChild.jsm',

View File

@@ -4,6 +4,9 @@
/* eslint-env mozilla/frame-script */
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
const {WebProgressChild} = ChromeUtils.import("resource://gre/modules/WebProgressChild.jsm");
this.WebProgress = new WebProgressChild(this);
@@ -28,6 +31,21 @@ addEventListener("ImageContentLoaded", function(aEvent) {
}
}, false);
// This is here for now until we find a better way of forcing an about:blank load
// with a particular principal that doesn't involve the message manager. We can't
// do this with JS Window Actors for now because JS Window Actors are tied to the
// document principals themselves, so forcing the load with a new principal is
// self-destructive in that case.
addMessageListener("BrowserElement:CreateAboutBlank", message => {
if (!content.document || content.document.documentURI != "about:blank") {
throw new Error("Can't create a content viewer unless on about:blank");
}
let principal = message.data;
principal = BrowserUtils.principalWithMatchingOA(principal,
content.document.nodePrincipal);
docShell.createAboutBlankContentViewer(principal);
});
// We may not get any responses to Browser:Init if the browser element
// is torn down too quickly.
var outerWindowID = docShell.outerWindowID;

View File

@@ -9,6 +9,11 @@
{
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
let LazyModules = {};
ChromeUtils.defineModuleGetter(LazyModules, "PermitUnloader",
"resource://gre/actors/BrowserElementParent.jsm");
const elementsToDestroyOnUnload = new Set();
window.addEventListener("unload", () => {
@@ -306,8 +311,6 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
this._autoScrollScrollId = null;
this._autoScrollPresShellId = null;
this._permitUnloadId = 0;
}
connectedCallback() {
@@ -1430,8 +1433,20 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
this.frameLoader.remoteTab.transmitPermissionsForPrincipal(permissionPrincipal);
// Create the about blank content viewer in the content process
this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
// This still uses the message manager, for the following reasons:
//
// 1. Due to bug 1523638, it's virtually certain that, if we've just created
// this <xul:browser>, that the WindowGlobalParent for the top-level frame
// of this browser doesn't exist yet, so it's not possible to get at a
// JS Window Actor for it.
//
// 2. JS Window Actors are tied to the principals for the frames they're running
// in - switching principals is therefore self-destructive and unexpected.
//
// So we'll continue to use the message manager until we come up with a better
// solution.
this.messageManager.sendAsyncMessage("BrowserElement:CreateAboutBlank",
aPrincipal);
return;
}
let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
@@ -1754,16 +1769,13 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
getInPermitUnload(aCallback) {
if (this.isRemoteBrowser) {
let id = this._permitUnloadId++;
let mm = this.messageManager;
mm.sendAsyncMessage("InPermitUnload", { id });
mm.addMessageListener("InPermitUnload", function listener(msg) {
if (msg.data.id != id) {
return;
}
mm.removeMessageListener("InPermitUnload", listener);
aCallback(msg.data.inPermitUnload);
});
let { remoteTab } = this.frameLoader;
if (!remoteTab) {
// If we're crashed, we're definitely not in this state anymore.
aCallback(false);
return;
}
aCallback(LazyModules.PermitUnloader.inPermitUnload(this.frameLoader));
return;
}
@@ -1776,68 +1788,11 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
permitUnload(aPermitUnloadFlags) {
if (this.isRemoteBrowser) {
let { remoteTab } = this.frameLoader;
if (!remoteTab.hasBeforeUnload) {
if (!LazyModules.PermitUnloader.hasBeforeUnload(this.frameLoader)) {
return { permitUnload: true, timedOut: false };
}
const kTimeout = 1000;
let finished = false;
let responded = false;
let permitUnload;
let id = this._permitUnloadId++;
let mm = this.messageManager;
let {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
let msgListener = msg => {
if (msg.data.id != id) {
return;
}
if (msg.data.kind == "start") {
responded = true;
return;
}
done(msg.data.permitUnload);
};
let observer = subject => {
if (subject == mm) {
done(true);
}
};
function done(result) {
finished = true;
permitUnload = result;
mm.removeMessageListener("PermitUnload", msgListener);
Services.obs.removeObserver(observer, "message-manager-close");
}
mm.sendAsyncMessage("PermitUnload", { id, aPermitUnloadFlags });
mm.addMessageListener("PermitUnload", msgListener);
Services.obs.addObserver(observer, "message-manager-close");
let timedOut = false;
function timeout() {
if (!responded) {
timedOut = true;
}
// Dispatch something to ensure that the main thread wakes up.
Services.tm.dispatchToMainThread(function() {});
}
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
while (!finished && !timedOut) {
Services.tm.currentThread.processNextEvent(true);
}
return { permitUnload, timedOut };
return LazyModules.PermitUnloader.permitUnload(this.frameLoader, aPermitUnloadFlags);
}
if (!this.docShell || !this.docShell.contentViewer) {

View File

@@ -101,6 +101,20 @@ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {DefaultMap} = ExtensionUtils;
let ACTORS = {
BrowserElement: {
parent: {
moduleURI: "resource://gre/actors/BrowserElementParent.jsm",
},
child: {
moduleURI: "resource://gre/actors/BrowserElementChild.jsm",
events: {
"DOMWindowClose": {},
},
},
allFrames: true,
},
};
let LEGACY_ACTORS = {
@@ -125,21 +139,6 @@ let LEGACY_ACTORS = {
},
},
Browser: {
child: {
module: "resource://gre/actors/BrowserChild.jsm",
events: {
"DOMWindowClose": {},
},
messages: [
"Browser:CreateAboutBlank",
"InPermitUnload",
"PermitUnload",
],
},
},
Controllers: {
child: {
module: "resource://gre/actors/ControllersChild.jsm",