Bug 1631362 - Bind prompts to JSWindowActor lifetime instead of closing them on pagehide. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D73882
This commit is contained in:
pbz 2020-05-14 12:21:49 +00:00
Родитель c85367f9d5
Коммит 225f10d76a
7 изменённых файлов: 117 добавлений и 157 удалений

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

@ -1,20 +0,0 @@
/* 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 = ["PromptChild"];
class PromptChild extends JSWindowActorChild {
constructor(dispatcher) {
super(dispatcher);
}
handleEvent(aEvent) {
if (aEvent.type !== "pagehide") {
return;
}
this.sendAsyncMessage("Prompt:OnPageHide", {});
}
}

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

@ -39,7 +39,7 @@ class PromptParent extends JSWindowActorParent {
didDestroy() {
// In the event that the subframe or tab crashed, make sure that
// we close any active Prompts.
this.forceClosePrompts(this.browsingContext);
this.forceClosePrompts();
}
/**
@ -47,8 +47,6 @@ class PromptParent extends JSWindowActorParent {
* We need to track a Prompt so that we can, for example, force-close the
* TabModalPrompt if the originating subframe or tab unloads or crashes.
*
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the request to open the Prompt came.
* @param {Object} tabModalPrompt
* The TabModalPrompt that will be shown to the user.
* @param {string} id
@ -59,11 +57,11 @@ class PromptParent extends JSWindowActorParent {
* Resolves with the arguments returned from the TabModalPrompt when it
* is dismissed.
*/
registerPrompt(browsingContext, tabModalPrompt, id) {
let prompts = gBrowserPrompts.get(browsingContext);
registerPrompt(tabModalPrompt, id) {
let prompts = gBrowserPrompts.get(this.browsingContext);
if (!prompts) {
prompts = new Map();
gBrowserPrompts.set(browsingContext, prompts);
gBrowserPrompts.set(this.browsingContext, prompts);
}
let promise = new Promise(resolve => {
@ -80,45 +78,22 @@ class PromptParent extends JSWindowActorParent {
* Removes a Prompt for a BrowsingContext with a particular ID from the registry.
* This needs to be done to avoid leaking <xul:browser>'s.
*
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the request to open the Prompt came.
* @param {string} id
* A unique ID to differentiate multiple Prompts coming from the same
* BrowsingContext.
*/
unregisterPrompt(browsingContext, id) {
let prompts = gBrowserPrompts.get(browsingContext);
unregisterPrompt(id) {
let prompts = gBrowserPrompts.get(this.browsingContext);
if (prompts) {
prompts.delete(id);
}
}
/**
* Programmatically closes a Prompt, without waiting for the TabModalPrompt to
* return with any arguments.
*
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the request to open the Prompt came.
* @param {string} id
* A unique ID to differentiate multiple Prompts coming from the same
* BrowsingContext.
* Programmatically closes all Prompts for the current BrowsingContext.
*/
forceClosePrompt(browsingContext, id) {
let prompts = gBrowserPrompts.get(browsingContext);
let prompt = prompts.get(id);
if (prompt && prompt.tabModalPrompt) {
prompt.tabModalPrompt.abortPrompt();
}
}
/**
* Programmatically closes all Prompts for a BrowsingContext.
*
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the request to open the Prompts came.
*/
forceClosePrompts(browsingContext) {
let prompts = gBrowserPrompts.get(browsingContext) || [];
forceClosePrompts() {
let prompts = gBrowserPrompts.get(this.browsingContext) || [];
for (let [, prompt] of prompts) {
prompt.tabModalPrompt && prompt.tabModalPrompt.abortPrompt();
@ -127,27 +102,14 @@ class PromptParent extends JSWindowActorParent {
receiveMessage(message) {
let args = message.data;
let browsingContext = args.browsingContext || this.browsingContext;
let id = args._remoteId;
switch (message.name) {
case "Prompt:Open": {
let topPrincipal =
browsingContext.top.currentWindowGlobal.documentPrincipal;
args.showAlertOrigin = topPrincipal.equals(args.promptPrincipal);
if (args.modalType === Ci.nsIPrompt.MODAL_TYPE_WINDOW) {
return this.openWindowPrompt(args, browsingContext);
return this.openWindowPrompt(args);
}
return this.openTabPrompt(args, browsingContext, id);
}
case "Prompt:ForceClose": {
this.forceClosePrompt(browsingContext, id);
break;
}
case "Prompt:OnPageHide": {
// User navigates away, close all non window prompts
this.forceClosePrompts(browsingContext);
break;
return this.openTabPrompt(args, id);
}
}
@ -161,8 +123,6 @@ class PromptParent extends JSWindowActorParent {
* @param {Object} args
* The arguments passed up from the BrowsingContext to be passed directly
* to the TabModalPrompt.
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the request to open the Prompts came.
* @param {string} id
* A unique ID to differentiate multiple Prompts coming from the same
* BrowsingContext.
@ -171,8 +131,8 @@ class PromptParent extends JSWindowActorParent {
* @resolves {Object}
* The arguments returned from the TabModalPrompt.
*/
openTabPrompt(args, browsingContext = this.browsingContext, id) {
let browser = browsingContext.top.embedderElement;
openTabPrompt(args, id) {
let browser = this.browsingContext.top.embedderElement;
if (!browser) {
throw new Error("Cannot tab-prompt without a browser!");
}
@ -181,8 +141,15 @@ class PromptParent extends JSWindowActorParent {
let newPrompt;
let needRemove = false;
let onPromptClose = forceCleanup => {
let promptData = gBrowserPrompts.get(browsingContext);
// If the page which called the prompt is different from the the top context
// where we show the prompt, ask the prompt implementation to display the origin.
// For example, this can happen if a cross origin subframe shows a prompt.
args.showCallerOrigin =
args.promptPrincipal &&
!browser.contentPrincipal.equals(args.promptPrincipal);
let onPromptClose = () => {
let promptData = gBrowserPrompts.get(this.browsingContext);
if (!promptData || !promptData.has(id)) {
throw new Error(
"Failed to close a prompt since it wasn't registered for some reason."
@ -200,7 +167,7 @@ class PromptParent extends JSWindowActorParent {
needRemove = true;
}
this.unregisterPrompt(browsingContext, id);
this.unregisterPrompt(id);
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
resolver(args);
@ -224,7 +191,7 @@ class PromptParent extends JSWindowActorParent {
args.promptActive = true;
newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
let promise = this.registerPrompt(browsingContext, newPrompt, id);
let promise = this.registerPrompt(newPrompt, id);
if (needRemove) {
tabPrompt.removePrompt(newPrompt);
@ -246,30 +213,33 @@ class PromptParent extends JSWindowActorParent {
* @param {Object} args
* The arguments passed up from the BrowsingContext to be passed
* directly to the modal window.
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the request to open the window-modal
* prompt came.
* @return {Promise}
* Resolves when the window prompt is dismissed.
* @resolves {Object}
* The arguments returned from the window prompt.
*/
async openWindowPrompt(args, browsingContext = this.browsingContext) {
async openWindowPrompt(args) {
const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml";
let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
let browser = browsingContext.top.embedderElement;
// If can't get the browser, because the BC does not have an embedder element,
// use window associated with the BC.
// This happens if we are passed a browsingContext of a chrome window.
let win = (browser && browser.ownerGlobal) || browsingContext.top.window;
let browsingContext = this.browsingContext.top;
let browser = browsingContext.embedderElement;
let win;
// If we are a chrome actor we can use the associated chrome win.
if (!browsingContext.isContent && browsingContext.window) {
win = browsingContext.window;
} else {
win = browser?.ownerGlobal;
}
// There's a requirement for prompts to be blocked if a window is
// passed and that window is hidden (eg, auth prompts are suppressed if the
// passed window is the hidden window).
// See bug 875157 comment 30 for more..
if (win && win.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
if (win?.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
throw new Error("Cannot call openModalWindow on a hidden window");
}

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

@ -66,7 +66,6 @@ FINAL_TARGET_FILES.actors += [
'PageStyleParent.jsm',
'PluginChild.jsm',
'PluginParent.jsm',
'PromptChild.jsm',
'PromptParent.jsm',
'RFPHelperChild.jsm',
'RFPHelperParent.jsm',

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

@ -43,7 +43,6 @@ const whitelist = {
"resource:///actors/BrowserTabChild.jsm",
"resource:///actors/LinkHandlerChild.jsm",
"resource:///actors/SearchTelemetryChild.jsm",
"resource:///actors/PromptChild.jsm",
"resource://gre/actors/AutoCompleteChild.jsm",
"resource://gre/modules/ActorManagerChild.jsm",
"resource://gre/modules/E10SUtils.jsm",

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

@ -437,14 +437,6 @@ let ACTORS = {
parent: {
moduleURI: "resource:///actors/PromptParent.jsm",
},
child: {
moduleURI: "resource:///actors/PromptChild.jsm",
events: {
pagehide: {
capture: true,
},
},
},
includeChrome: true,
allFrames: true,
},

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

@ -242,9 +242,12 @@ var TabModalPrompt = class {
this.Dialog = new tmp.CommonDialog(args, this.ui);
this.Dialog.onLoad(null);
// Display the tabprompt title that shows the prompt origin when
// For content prompts display the tabprompt title that shows the prompt origin when
// the prompt origin is not the same as that of the top window.
if (!args.showAlertOrigin) {
if (
args.modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT &&
args.showCallerOrigin
) {
this.ui.infoTitle.removeAttribute("hidden");
}

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

@ -1011,21 +1011,12 @@ class ModalPrompter {
if (browsingContext && domWin) {
throw new Error("Pass either browsingContext or domWin");
}
this.browsingContext = browsingContext;
this._domWin = domWin;
if (this._domWin) {
if (domWin) {
// We have a domWin, get the associated browsing context
this.browsingContext = BrowsingContext.getFromWindow(this._domWin);
} else if (this.browsingContext) {
// We have a browsingContext, get the associated dom window
if (this.browsingContext.window) {
this._domWin = this.browsingContext.window;
} else {
this._domWin =
this.browsingContext.embedderElement &&
this.browsingContext.embedderElement.ownerGlobal;
}
this.browsingContext = BrowsingContext.getFromWindow(domWin);
} else {
this.browsingContext = browsingContext;
}
// Use given modal type or fallback to default
@ -1048,16 +1039,12 @@ class ModalPrompter {
return;
}
// If we have a chrome window and the browsing context isn't embedded
// in a browser, we can't use tab/content prompts.
// Or if we don't allow tab or content prompts, override modalType
// argument to use window prompts
// We can't use content / tab prompts if they are disabled by pref,
// or we are not given a parent.
if (
!ModalPrompter.tabModalEnabled ||
!this.browsingContext ||
!this._domWin ||
(this._domWin.isChromeWindow &&
!this.browsingContext.top.embedderElement) ||
!ModalPrompter.tabModalEnabled
!this.browsingContext.isContent
) {
modalType = Ci.nsIPrompt.MODAL_TYPE_WINDOW;
@ -1116,44 +1103,59 @@ class ModalPrompter {
args.modalType = this.modalType;
}
args.browsingContext = this.browsingContext;
const IS_CONTENT =
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
let actor;
try {
actor = this._domWin.windowGlobalChild.getActor("Prompt");
} catch (error) {
Cu.reportError(error);
if (IS_CONTENT) {
// When in the content, get the PromptChild actor.
actor = this.browsingContext.window.windowGlobalChild.getActor(
"Prompt"
);
} else {
// When in the parent, get the PromptParent actor.
actor = this.browsingContext.currentWindowGlobal.getActor("Prompt");
}
} catch (_) {
// We can't get the prompt actor, fallback to window prompt.
this.openWindowPrompt(this._domWin, args);
let parentWin;
// If given a chrome BC we can try to get its window
if (!this.browsingContext.isContent && this.browsingContext.window) {
parentWin = this.browsingContext.window;
} else {
// Try to get the window which is the browsers parent
parentWin = this.browsingContext.top?.embedderElement?.ownerGlobal;
}
this.openWindowPrompt(parentWin, args);
return args;
}
let docShell =
(this.browsingContext && this.browsingContext.docShell) ||
this._domWin.docShell;
let inPermitUnload =
docShell.contentViewer && docShell.contentViewer.inPermitUnload;
let eventDetail = Cu.cloneInto(
{
tabPrompt: this.modalType != Ci.nsIPrompt.MODAL_TYPE_WINDOW,
inPermitUnload,
},
this._domWin
);
PromptUtils.fireDialogEvent(
this._domWin,
"DOMWillOpenModalDialog",
null,
eventDetail
);
if (IS_CONTENT) {
args.promptPrincipal = this.browsingContext.window?.document.nodePrincipal;
let windowUtils =
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT &&
this._domWin.windowUtils;
let docShell = this.browsingContext.docShell;
let inPermitUnload = docShell?.contentViewer?.inPermitUnload;
args.inPermitUnload = inPermitUnload;
let eventDetail = Cu.cloneInto(
{
tabPrompt: this.modalType != Ci.nsIPrompt.MODAL_TYPE_WINDOW,
inPermitUnload,
},
this.browsingContext.window
);
PromptUtils.fireDialogEvent(
this.browsingContext.window,
"DOMWillOpenModalDialog",
null,
eventDetail
);
// Put content windows in the modal state while the prompt is open.
if (windowUtils) {
windowUtils.enterModalState();
// Put content window in the modal state while the prompt is open.
let windowUtils = this.browsingContext.window?.windowUtils;
if (windowUtils) {
windowUtils.enterModalState();
}
}
// It is technically possible for multiple prompts to be sent from a single
@ -1166,25 +1168,40 @@ class ModalPrompter {
.generateUUID()
.toString();
args.promptPrincipal = this._domWin.document.nodePrincipal;
args.inPermitUnload = inPermitUnload;
args._remoteId = id;
let returnedArgs;
try {
returnedArgs = await actor.sendQuery("Prompt:Open", args);
if (returnedArgs && returnedArgs.promptAborted) {
if (IS_CONTENT) {
// If we're in the content process, send a message to the PromptParent
// window actor.
returnedArgs = await actor.sendQuery("Prompt:Open", args);
} else {
// If we're in the parent process we already have the parent actor.
// We can call its message handler directly.
returnedArgs = await actor.receiveMessage({
name: "Prompt:Open",
data: args,
});
}
if (returnedArgs?.promptAborted) {
throw Components.Exception(
"prompt aborted by user",
Cr.NS_ERROR_NOT_AVAILABLE
);
}
} finally {
if (windowUtils) {
windowUtils.leaveModalState();
if (IS_CONTENT) {
let windowUtils = this.browsingContext.window?.windowUtils;
if (windowUtils) {
windowUtils.leaveModalState();
}
PromptUtils.fireDialogEvent(
this.browsingContext.window,
"DOMModalDialogClosed"
);
}
PromptUtils.fireDialogEvent(this._domWin, "DOMModalDialogClosed");
}
return returnedArgs;
}