Bug 899648 - Initial patch to make tab-modal prompts work. Original patch from Tom Schuster <evilpies@gmail.com>. r=dolske

This commit is contained in:
Blake Kaplan 2014-04-04 14:50:42 -07:00
parent 28f9c3e365
commit 3745131216
13 changed files with 289 additions and 51 deletions

View File

@ -424,7 +424,7 @@
newPrompt.clientTop; // style flush to assure binding is attached
let tab = self._getTabForContentWindow(browser.contentWindow);
let tab = self._getTabForBrowser(browser);
newPrompt.init(args, tab, onCloseCallback);
return newPrompt;
},
@ -3196,7 +3196,13 @@
// We're about to open a modal dialog, make sure the opening
// tab is brought to the front.
this.selectedTab = this._getTabForContentWindow(event.target.top);
// If this is a same-process modal dialog, then we're given its DOM
// window as the event's target. For remote dialogs, we're given the
// browser, but that's in the originalTarget.
// XXX Why originalTarget for the browser?
this.selectedTab = (event.target instanceof Window) ?
this._getTabForContentWindow(event.target.top) :
this._getTabForBrowser(event.originalTarget);
]]>
</handler>
<handler event="DOMTitleChanged">

View File

@ -81,6 +81,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemotePrompt",
"resource:///modules/RemotePrompt.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
@ -495,8 +498,10 @@ BrowserGlue.prototype = {
SessionStore.init();
BrowserUITelemetry.init();
if (Services.appinfo.browserTabsRemote)
if (Services.appinfo.browserTabsRemote) {
ContentClick.init();
RemotePrompt.init();
}
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
},

View File

@ -0,0 +1,95 @@
/* vim: set ts=2 sw=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";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "RemotePrompt" ];
Cu.import("resource:///modules/PlacesUIUtils.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
let RemotePrompt = {
init: function() {
let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
mm.addMessageListener("Prompt:Open", this);
},
receiveMessage: function(message) {
switch (message.name) {
case "Prompt:Open":
if (message.data.uri) {
this.openModalWindow(message.data, message.target);
} else {
this.openTabPrompt(message.data, message.target)
}
break;
}
},
openTabPrompt: function(args, browser) {
let window = browser.ownerDocument.defaultView;
let tabPrompt = window.gBrowser.getTabModalPromptBox(browser)
let callbackInvoked = false;
let newPrompt;
let promptId = args._remoteId;
function onPromptClose(forceCleanup) {
if (newPrompt)
tabPrompt.removePrompt(newPrompt);
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
browser.messageManager.sendAsyncMessage("Prompt:Close", args);
}
browser.messageManager.addMessageListener("Prompt:ForceClose", function listener(message) {
// If this was for another prompt in the same tab, ignore it.
if (message.data._remoteId !== promptId) {
return;
}
browser.messageManager.removeMessageListener("Prompt:ForceClose", listener);
if (newPrompt) {
newPrompt.abortPrompt();
}
});
try {
PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
args.promptActive = true;
newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
// TODO since we don't actually open a window, need to check if
// there's other stuff in nsWindowWatcher::OpenWindowInternal
// that we might need to do here as well.
} catch (ex) {
onPromptClose(true);
}
},
openModalWindow: function(args, browser) {
let window = browser.ownerDocument.defaultView;
try {
PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
let bag = PromptUtils.objectToPropBag(args);
Services.ww.openWindow(window, args.uri, "_blank",
"centerscreen,chrome,modal,titlebar", bag);
PromptUtils.propBagToObject(bag, args);
} finally {
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
browser.messageManager.sendAsyncMessage("Prompt:Close", args);
}
}
};

View File

@ -15,6 +15,7 @@ EXTRA_JS_MODULES += [
'Feeds.jsm',
'NetworkPrioritizer.jsm',
'offlineAppCache.jsm',
'RemotePrompt.jsm',
'SharedFrame.jsm',
'SitePermissions.jsm',
'Social.jsm',

View File

@ -67,6 +67,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/file/FileHandle.h"
#include "mozilla/dom/FileHandleBinding.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/IDBFactoryBinding.h"
#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
#include "mozilla/dom/quota/PersistenceType.h"
@ -3623,6 +3624,12 @@ nsDOMWindowUtils::GetIsParentWindowMainWidgetVisible(bool* aIsVisible)
nsCOMPtr<nsIWidget> parentWidget;
nsIDocShell *docShell = window->GetDocShell();
if (docShell) {
if (TabChild *tabChild = TabChild::GetFrom(docShell)) {
if (!tabChild->SendIsParentWindowMainWidgetVisible(aIsVisible))
return NS_ERROR_FAILURE;
return NS_OK;
}
nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
docShell->GetTreeOwner(getter_AddRefs(parentTreeOwner));
nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(parentTreeOwner));

View File

@ -6,7 +6,7 @@
#include "domstubs.idl"
interface nsIContentFrameMessageManager;
[uuid( 60146bc6-31d8-450b-a9eb-4000b6403d5c)]
[scriptable, uuid(2eb3bc54-78bf-40f2-b301-a5b5b70f7da0)]
interface nsITabChild : nsISupports
{
readonly attribute nsIContentFrameMessageManager messageManager;

View File

@ -208,6 +208,8 @@ parent:
int32_t cause,
int32_t focusChange);
sync IsParentWindowMainWidgetVisible() returns (bool visible);
/**
* Gets the DPI of the screen corresponding to this browser.
*/

View File

@ -38,6 +38,7 @@
#include "nsIDOMElement.h"
#include "nsIDOMEvent.h"
#include "nsIDOMWindow.h"
#include "nsIDOMWindowUtils.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPromptFactory.h"
#include "nsIURI.h"
@ -1504,6 +1505,18 @@ TabParent::RecvSetInputContext(const int32_t& aIMEEnabled,
return true;
}
bool
TabParent::RecvIsParentWindowMainWidgetVisible(bool* aIsVisible)
{
nsCOMPtr<nsIContent> frame = do_QueryInterface(mFrameElement);
if (!frame)
return true;
nsCOMPtr<nsIDOMWindowUtils> windowUtils =
do_QueryInterface(frame->OwnerDoc()->GetWindow());
nsresult rv = windowUtils->GetIsParentWindowMainWidgetVisible(aIsVisible);
return NS_SUCCEEDED(rv);
}
bool
TabParent::RecvGetDPI(float* aValue)
{

View File

@ -172,6 +172,7 @@ public:
virtual bool RecvSetCursor(const uint32_t& aValue) MOZ_OVERRIDE;
virtual bool RecvSetBackgroundColor(const nscolor& aValue) MOZ_OVERRIDE;
virtual bool RecvSetStatus(const uint32_t& aType, const nsString& aStatus) MOZ_OVERRIDE;
virtual bool RecvIsParentWindowMainWidgetVisible(bool* aIsVisible);
virtual bool RecvShowTooltip(const uint32_t& aX, const uint32_t& aY, const nsString& aTooltip);
virtual bool RecvHideTooltip();
virtual bool RecvGetDPI(float* aValue) MOZ_OVERRIDE;

View File

@ -119,10 +119,9 @@
<destructor>
<![CDATA[
if (this.isLive) {
this.Dialog.abortPrompt();
this.shutdownPrompt();
this.abortPrompt();
}
]]>
]]>
</destructor>
<field name="ui"/>
@ -156,7 +155,10 @@
window.addEventListener("resize", this, false);
window.addEventListener("unload", this, false);
linkedTab.addEventListener("TabClose", this, false);
this.args.domWindow.addEventListener("pagehide", this, false);
// Note:
// nsPrompter.js or in e10s mode browser-parent.js call abortPrompt,
// when the domWindow, for which the prompt was created, generates
// a "pagehide" event.
let tmp = {};
Components.utils.import("resource://gre/modules/CommonDialog.jsm", tmp);
@ -165,10 +167,8 @@
// Display the tabprompt title that shows the prompt origin when
// the prompt origin is not the same as that of the top window.
let topPrincipal = this.args.domWindow.top.document.nodePrincipal;
let promptPrincipal = this.args.domWindow.document.nodePrincipal;
if (!topPrincipal.equals(promptPrincipal))
this.ui.infoTitle.removeAttribute("hidden");
if (!args.showAlertOrigin)
this.ui.infoTitle.removeAttribute("hidden");
// TODO: should unhide buttonSpacer on Windows when there are 4 buttons.
// Better yet, just drop support for 4-button dialogs. (bug 609510)
@ -186,7 +186,6 @@
window.removeEventListener("resize", this, false);
window.removeEventListener("unload", this, false);
this.linkedTab.removeEventListener("TabClose", this, false);
this.args.domWindow.removeEventListener("pagehide", this, false);
} catch(e) { }
this.isLive = false;
// invoke callback
@ -195,6 +194,16 @@
</body>
</method>
<method name="abortPrompt">
<body>
<![CDATA[
// Called from other code when the page changes.
this.Dialog.abortPrompt();
this.shutdownPrompt();
]]>
</body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body>
@ -205,9 +214,7 @@
break;
case "unload":
case "TabClose":
case "pagehide":
this.Dialog.abortPrompt();
this.shutdownPrompt();
this.abortPrompt();
break;
}
]]>

View File

@ -0,0 +1,42 @@
this.EXPORTED_SYMBOLS = [ "PromptUtils" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
this.PromptUtils = {
// Fire a dialog open/close event. Used by tabbrowser to focus the
// tab which is triggering a prompt.
// For remote dialogs, we pass in a different DOM window and a separate
// target. If the caller doesn't pass in the target, then we'll simply use
// the passed-in DOM window.
fireDialogEvent : function (domWin, eventName, maybeTarget) {
let target = maybeTarget || domWin;
let event = domWin.document.createEvent("Events");
event.initEvent(eventName, true, true);
let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.dispatchEventToChromeOnly(target, event);
},
objectToPropBag : function (obj) {
let bag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag2);
bag.QueryInterface(Ci.nsIWritablePropertyBag);
for (let propName in obj)
bag.setProperty(propName, obj[propName]);
return bag;
},
propBagToObject : function (propBag, obj) {
// Here we iterate over the object's original properties, not the bag
// (ie, the prompt can't return more/different properties than were
// passed in). This just helps ensure that the caller provides default
// values, lest the prompt forget to set them.
for (let propName in obj)
obj[propName] = propBag.getProperty(propName);
},
};

View File

@ -11,5 +11,6 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES += [
'CommonDialog.jsm',
'SharedPromptUtils.jsm',
]

View File

@ -10,6 +10,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
function Prompter() {
// Note that EmbedPrompter clones this implementation.
@ -115,7 +116,8 @@ Prompter.prototype = {
// Common utils not specific to a particular prompter style.
let PromptUtils = {
let PromptUtilsTemp = {
__proto__ : PromptUtils,
getLocalizedString : function (key, formatArgs) {
if (formatArgs)
@ -170,16 +172,6 @@ let PromptUtils = {
return [buttonLabels[0], buttonLabels[1], buttonLabels[2], defaultButtonNum, isDelayEnabled];
},
// Fire a dialog open/close event. Used by tabbrowser to focus the
// tab which is triggering a prompt.
fireDialogEvent : function (domWin, eventName) {
let event = domWin.document.createEvent("Events");
event.initEvent(eventName, true, true);
let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.dispatchEventToChromeOnly(domWin, event);
},
getAuthInfo : function (authInfo) {
let username, password;
@ -300,26 +292,6 @@ let PromptUtils = {
return text;
},
objectToPropBag : function (obj) {
let bag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag2);
bag.QueryInterface(Ci.nsIWritablePropertyBag);
for (let propName in obj)
bag.setProperty(propName, obj[propName]);
return bag;
},
propBagToObject : function (propBag, obj) {
// Here we iterate over the object's original properties, not the bag
// (ie, the prompt can't return more/different properties than were
// passed in). This just helps ensure that the caller provides default
// values, lest the prompt forget to set them.
for (let propName in obj)
obj[propName] = propBag.getProperty(propName);
},
getTabModalPrompt : function (domWin) {
var promptBox = null;
@ -345,6 +317,8 @@ let PromptUtils = {
},
};
PromptUtils = PromptUtilsTemp;
XPCOMUtils.defineLazyGetter(PromptUtils, "strBundle", function () {
let bunService = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
@ -377,6 +351,7 @@ function openModalWindow(domWin, uri, args) {
// a domWin was passed, so we can apply the check for it being hidden.
let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (winUtils && !winUtils.isParentWindowMainWidgetVisible) {
throw Components.Exception("Cannot call openModalWindow on a hidden window",
Cr.NS_ERROR_NOT_AVAILABLE);
@ -406,6 +381,7 @@ function openTabPrompt(domWin, tabPrompt, args) {
// prompts on the call stack would in this dialog appearing unresponsive
// until the other prompts had been closed.
let callbackInvoked = false;
let newPrompt;
function onPromptClose(forceCleanup) {
if (!newPrompt && !forceCleanup)
return;
@ -418,11 +394,19 @@ function openTabPrompt(domWin, tabPrompt, args) {
PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
}
let newPrompt;
domWin.addEventListener("pagehide", pagehide);
function pagehide() {
domWin.removeEventListener("pagehide", pagehide);
if (newPrompt) {
newPrompt.abortPrompt();
}
}
try {
// tab-modal prompts need to watch for navigation changes, give it the
// domWindow to watch for pagehide events.
args.domWindow = domWin;
let topPrincipal = domWin.top.document.nodePrincipal;
let promptPrincipal = domWin.document.nodePrincipal;
args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
args.promptActive = true;
newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
@ -445,6 +429,69 @@ function openTabPrompt(domWin, tabPrompt, args) {
}
}
function openRemotePrompt(domWin, args, tabPrompt) {
let messageManager = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsITabChild)
.messageManager;
PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog");
let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.enterModalState();
let closed = false;
// It should be hard or impossible to cause a window to create multiple
// prompts, but just in case, give our prompt an ID.
let id = "id" + Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator).generateUUID().toString();
messageManager.addMessageListener("Prompt:Close", function listener(message) {
if (message.data._remoteId !== id) {
return;
}
messageManager.removeMessageListener("Prompt:Close", listener);
domWin.removeEventListener("pagehide", pagehide);
winUtils.leaveModalState();
PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
// Copy the response from the closed prompt into our args, it will be
// read by our caller.
if (message.data) {
for (let key in message.data) {
args[key] = message.data[key];
}
}
// Exit our nested event loop when we unwind.
closed = true;
});
domWin.addEventListener("pagehide", pagehide);
function pagehide() {
domWin.removeEventListener("pagehide", pagehide);
messageManager.sendAsyncMessage("Prompt:ForceClose", { _remoteId: id });
}
let topPrincipal = domWin.top.document.nodePrincipal;
let promptPrincipal = domWin.document.nodePrincipal;
args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
args._remoteId = id;
messageManager.sendAsyncMessage("Prompt:Open", args, {});
let thread = Services.tm.currentThread;
while (!closed) {
thread.processNextEvent(true);
}
}
function ModalPrompter(domWin) {
this.domWin = domWin;
}
@ -475,6 +522,11 @@ ModalPrompter.prototype = {
let allowTabModal = this.allowTabModal && prefValue;
if (allowTabModal && this.domWin) {
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
openRemotePrompt(this.domWin, args, true);
return;
}
let tabPrompt = PromptUtils.getTabModalPrompt(this.domWin);
if (tabPrompt) {
openTabPrompt(this.domWin, tabPrompt, args);
@ -488,6 +540,12 @@ ModalPrompter.prototype = {
let uri = (args.promptType == "select") ? SELECT_DIALOG : COMMON_DIALOG;
if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
args.uri = uri;
openRemotePrompt(this.domWin, args);
return;
}
let propBag = PromptUtils.objectToPropBag(args);
openModalWindow(this.domWin, uri, propBag);
PromptUtils.propBagToObject(propBag, args);