Bug 1299998 fix closing of share panel, r=markh
MozReview-Commit-ID: 2D4c2AiS1qQ
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -286,24 +286,17 @@ SocialShare = {
this.panel.hidePopup();
break;
}
},
handleEvent: function(event) {
switch (event.type) {
case "load": {
- let iframe = this.iframe;
- iframe.parentNode.removeAttribute("loading");
- // to support standard share endpoints mimick window.open by setting
- // window.opener, some share endpoints rely on w.opener to know they
- // should close the window when done.
- iframe.contentWindow.opener = iframe.contentWindow;
- this.messageManager.sendAsyncMessage("Social:HookWindowCloseForPanelClose");
- this.messageManager.sendAsyncMessage("Social:DisableDialogs", {});
+ this.iframe.parentNode.removeAttribute("loading");
if (this.currentShare)
SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
}
}
},
getSelectedProvider: function() {
let provider;
--- a/browser/base/content/social-content.js
+++ b/browser/base/content/social-content.js
@@ -1,214 +1,102 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */
-/* This content script should work in any browser or iframe and should not
- * depend on the frame being contained in tabbrowser. */
+/* This content script is intended for use by iframes in the share panel. */
-var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+var {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
-// Tie `content` to this frame scripts' global scope explicitly. If we don't, then
-// `content` might be out of eval's scope and GC'ed before this script is done.
-// See bug 1229195 for empirical proof.
-var gContent = content;
-
// social frames are always treated as app tabs
docShell.isAppTab = true;
-var gHookedWindowCloseForPanelClose = false;
-var gDOMContentLoaded = false;
addEventListener("DOMContentLoaded", function(event) {
- if (event.target == content.document) {
- gDOMContentLoaded = true;
- sendAsyncMessage("DOMContentLoaded");
+ if (event.target != content.document)
+ return;
+ // Some share panels (e.g. twitter and facebook) check content.opener, and if
+ // it doesn't exist they act like they are in a browser tab. We want them to
+ // act like they are in a dialog (which is the typical case).
+ if (content && !content.opener) {
+ content.opener = content;
}
-});
-addEventListener("unload", function(event) {
- if (event.target == content.document) {
- gDOMContentLoaded = false;
- gHookedWindowCloseForPanelClose = false;
- }
-}, true);
-
-var gDOMTitleChangedByUs = false;
-addEventListener("DOMTitleChanged", function(e) {
- if (!gDOMTitleChangedByUs) {
- sendAsyncMessage("Social:DOMTitleChanged", {
- title: e.target.title
- });
- }
- gDOMTitleChangedByUs = false;
+ hookWindowClose();
+ disableDialogs();
});
addMessageListener("Social:OpenGraphData", (message) => {
let ev = new content.CustomEvent("OpenGraphData", { detail: JSON.stringify(message.data) });
content.dispatchEvent(ev);
});
-addMessageListener("Social:ClearFrame", (message) => {
+addMessageListener("Social:ClearFrame", () => {
docShell.createAboutBlankContentViewer(null);
});
+addEventListener("DOMWindowClose", (evt) => {
+ // preventDefault stops the default window.close() function being called,
+ // which doesn't actually close anything but causes things to get into
+ // a bad state (an internal 'closed' flag is set and debug builds start
+ // asserting as the window is used.).
+ // None of the windows we inject this API into are suitable for this
+ // default close behaviour, so even if we took no action above, we avoid
+ // the default close from doing anything.
+ evt.preventDefault();
+
+ // Tells the SocialShare class to close the panel
+ sendAsyncMessage("Social:DOMWindowClose");
+});
+
+function hookWindowClose() {
+ // Allow scripts to close the "window". Because we are in a panel and not
+ // in a full dialog, the DOMWindowClose listener above will only receive the
+ // event if we do this.
+ let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ dwu.allowScriptsToClose();
+}
+
+function disableDialogs() {
+ let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+ windowUtils.disableDialogs();
+}
+
// Error handling class used to listen for network errors in the social frames
// and replace them with a social-specific error page
const SocialErrorListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
Ci.nsISupports]),
defaultTemplate: "about:socialerror?mode=tryAgainOnly&url=%{url}&origin=%{origin}",
urlTemplate: null,
init() {
- addMessageListener("Loop:MonitorPeerConnectionLifecycle", this);
- addMessageListener("Loop:GetAllWebrtcStats", this);
- addMessageListener("Social:CustomEvent", this);
- addMessageListener("Social:EnsureFocus", this);
- addMessageListener("Social:EnsureFocusElement", this);
- addMessageListener("Social:HookWindowCloseForPanelClose", this);
- addMessageListener("Social:ListenForEvents", this);
- addMessageListener("Social:SetDocumentTitle", this);
addMessageListener("Social:SetErrorURL", this);
- addMessageListener("Social:DisableDialogs", this);
- addMessageListener("Social:WaitForDocumentVisible", this);
- addMessageListener("WaitForDOMContentLoaded", this);
let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
Ci.nsIWebProgress.NOTIFY_LOCATION);
},
receiveMessage(message) {
- let document = content.document;
-
switch (message.name) {
- case "Loop:GetAllWebrtcStats":
- content.WebrtcGlobalInformation.getAllStats(allStats => {
- content.WebrtcGlobalInformation.getLogging("", logs => {
- sendAsyncMessage("Loop:GetAllWebrtcStats", {
- allStats: allStats,
- logs: logs
- });
- });
- }, message.data.peerConnectionID);
- break;
- case "Loop:MonitorPeerConnectionLifecycle":
- let ourID = content.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
-
- let onPCLifecycleChange = (pc, winID, type) => {
- if (winID != ourID) {
- return;
- }
-
- sendAsyncMessage("Loop:PeerConnectionLifecycleChange", {
- iceConnectionState: pc.iceConnectionState,
- locationHash: content.location.hash,
- peerConnectionID: pc.id,
- type: type
- });
- };
-
- let pc_static = new content.RTCPeerConnectionStatic();
- pc_static.registerPeerConnectionLifecycleCallback(onPCLifecycleChange);
- break;
- case "Social:CustomEvent":
- let ev = new content.CustomEvent(message.data.name, message.data.detail ?
- { detail: message.data.detail } : null);
- content.dispatchEvent(ev);
- break;
- case "Social:EnsureFocus":
- Services.focus.focusedWindow = content;
- sendAsyncMessage("Social:FocusEnsured");
- break;
- case "Social:EnsureFocusElement":
- let fm = Services.focus;
- fm.moveFocus(document.defaultView, null, fm.MOVEFOCUS_FIRST, fm.FLAG_NOSCROLL);
- sendAsyncMessage("Social:FocusEnsured");
- break;
- case "Social:HookWindowCloseForPanelClose":
- if (gHookedWindowCloseForPanelClose) {
- break;
- }
- gHookedWindowCloseForPanelClose = true;
- // We allow window.close() to close the panel, so add an event handler for
- // this, then cancel the event (so the window itself doesn't die) and
- // close the panel instead.
- // However, this is typically affected by the dom.allow_scripts_to_close_windows
- // preference, but we can avoid that check by setting a flag on the window.
- let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- dwu.allowScriptsToClose();
-
- content.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) {
- // preventDefault stops the default window.close() function being called,
- // which doesn't actually close anything but causes things to get into
- // a bad state (an internal 'closed' flag is set and debug builds start
- // asserting as the window is used.).
- // None of the windows we inject this API into are suitable for this
- // default close behaviour, so even if we took no action above, we avoid
- // the default close from doing anything.
- evt.preventDefault();
-
- sendAsyncMessage("Social:DOMWindowClose");
- }, true);
- break;
- case "Social:ListenForEvents":
- for (let eventName of message.data.eventNames) {
- content.addEventListener(eventName, this);
- }
- break;
- case "Social:SetDocumentTitle":
- let title = message.data.title;
- if (title && (title = title.trim())) {
- gDOMTitleChangedByUs = true;
- document.title = title;
- }
- break;
case "Social:SetErrorURL":
// Either a url or null to reset to default template.
this.urlTemplate = message.data.template;
break;
- case "Social:WaitForDocumentVisible":
- if (!document.hidden) {
- sendAsyncMessage("Social:DocumentVisible");
- break;
- }
-
- document.addEventListener("visibilitychange", function onVisibilityChanged() {
- document.removeEventListener("visibilitychange", onVisibilityChanged);
- sendAsyncMessage("Social:DocumentVisible");
- });
- break;
- case "Social:DisableDialogs":
- let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor).
- getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.disableDialogs();
- break;
- case "WaitForDOMContentLoaded":
- if (gDOMContentLoaded) {
- sendAsyncMessage("DOMContentLoaded");
- }
- break;
}
},
- handleEvent: function(event) {
- sendAsyncMessage("Social:CustomEvent", {
- name: event.type
- });
- },
-
setErrorPage() {
// if this is about:providerdirectory, use the directory iframe
let frame = docShell.chromeEventHandler;
let origin = frame.getAttribute("origin");
let src = frame.getAttribute("src");
if (src == "about:providerdirectory") {
frame = content.document.getElementById("activation-frame");
src = frame.getAttribute("src");
deleted file mode 100644
--- a/browser/modules/PanelFrame.jsm
+++ /dev/null
@@ -1,194 +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";
-
-// A module for working with panels with iframes shared across windows.
-
-this.EXPORTED_SYMBOLS = ["PanelFrame"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DynamicResizeWatcher", "resource:///modules/Social.jsm");
-
-// The minimum sizes for the auto-resize panel code.
-const PANEL_MIN_HEIGHT = 100;
-const PANEL_MIN_WIDTH = 330;
-
-var PanelFrameInternal = {
- /**
- * Helper function to get and hold a single instance of a DynamicResizeWatcher.
- */
- get _dynamicResizer() {
- delete this._dynamicResizer;
- this._dynamicResizer = new DynamicResizeWatcher();
- return this._dynamicResizer;
- },
-
- /**
- * Status panels are one-per button per-process, we swap the docshells between
- * windows when necessary.
- *
- * @param {DOMWindow} aWindow The window in which to show the popup.
- * @param {PanelUI} aPanelUI The panel UI object that represents the application menu.
- * @param {DOMElement} aButton The button element that is pressed to show the popup.
- * @param {String} aType The type of panel this is, e.g. "social" or "loop".
- * @param {String} aOrigin Optional, the origin to use for the iframe.
- * @param {String} aSrc The url to load into the iframe.
- * @param {String} aSize The initial size of the panel (width and height are the same
- * if specified).
- */
- _attachNotificatonPanel: function(aWindow, aParent, aButton, aType, aOrigin, aSrc, aSize) {
- aParent.hidden = false;
- let notificationFrameId = aOrigin ? aType + "-status-" + aOrigin : aType + "-panel-iframe";
- let doc = aWindow.document;
- let frame = doc.getElementById(notificationFrameId);
-
- // If the button was customized to a new location, destroy the
- // iframe and start fresh.
- if (frame && frame.parentNode != aParent) {
- frame.parentNode.removeChild(frame);
- frame = null;
- }
-
- if (!frame) {
- let {width, height} = aSize ? aSize : {width: PANEL_MIN_WIDTH, height: PANEL_MIN_HEIGHT};
- frame = doc.createElement("browser");
- let attrs = {
- "type": "content",
- "mozbrowser": "true",
- // All frames use social-panel-frame as the class.
- "class": "social-panel-frame",
- "id": notificationFrameId,
- "tooltip": "aHTMLTooltip",
- "context": "contentAreaContextMenu",
- "flex": "1",
-
- // work around bug 793057 - by making the panel roughly the final size
- // we are more likely to have the anchor in the correct position.
- "style": "width: " + width + "px; height: " + height + "px;",
- "dynamicresizer": !aSize,
-
- "origin": aOrigin,
- "src": aSrc
- };
- if (aType == "social") {
- attrs["message"] = "true";
- attrs["messagemanagergroup"] = aType;
- }
- for (let [k, v] of Object.entries(attrs)) {
- frame.setAttribute(k, v);
- }
- aParent.appendChild(frame);
- } else {
- frame.setAttribute("origin", aOrigin);
- frame.setAttribute("src", aSrc);
- }
- aButton.setAttribute("notificationFrameId", notificationFrameId);
- }
-};
-
-/**
- * The exported PanelFrame object
- */
-var PanelFrame = {
- /**
- * Shows a popup in a pop-up panel, or in a sliding panel view in the application menu.
- * It will move the iframe to different DOM locations depending on where it needs to be
- * shown, enabling one iframe to be used for the entire session.
- *
- * @param {DOMWindow} aWindow The window in which to show the popup.
- * @param {PanelUI} aPanelUI The panel UI object that represents the application menu.
- * @param {DOMElement} aToolbarButton The button element that is pressed to show the popup.
- * @param {String} aType The type of panel this is, e.g. "social" or "loop".
- * @param {String} aOrigin Optional, the origin to use for the iframe.
- * @param {String} aSrc The url to load into the iframe.
- * @param {String} aSize The initial size of the panel (width and height are the same
- * if specified).
- * @param {Function} aCallback Optional, callback to be called with the iframe when it is
- * set up.
- */
- showPopup: function(aWindow, aToolbarButton, aType, aOrigin, aSrc, aSize, aCallback) {
- // if we're overflowed, our anchor needs to be the overflow button
- let widgetGroup = CustomizableUI.getWidget(aToolbarButton.getAttribute("id"));
- let widget = widgetGroup.forWindow(aWindow);
- // if we're a slice in the hamburger, our anchor will be the menu button,
- // this panel will replace the menu panel when the button is clicked on
- let anchorBtn = widget.anchor;
-
- let panel = aWindow.document.getElementById(aType + "-notification-panel");
- PanelFrameInternal._attachNotificatonPanel(aWindow, panel, aToolbarButton, aType, aOrigin, aSrc, aSize);
-
- let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
- let notificationFrame = aWindow.document.getElementById(notificationFrameId);
-
- // the xbl bindings for the iframe probably don't exist yet, so we can't
- // access iframe.messageManager directly - but can get at it with this dance.
- let mm = notificationFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
-
- // Clear dimensions on all browsers so the panel size will
- // only use the selected browser.
- let frameIter = panel.firstElementChild;
- while (frameIter) {
- frameIter.collapsed = (frameIter != notificationFrame);
- frameIter = frameIter.nextElementSibling;
- }
-
- function dispatchPanelEvent(name) {
- mm.sendAsyncMessage("Social:CustomEvent", { name: name });
- }
-
- // we only use a dynamic resizer when we're located the toolbar.
- let dynamicResizer;
- if (notificationFrame.getAttribute("dynamicresizer") == "true") {
- dynamicResizer = PanelFrameInternal._dynamicResizer;
- }
- panel.addEventListener("popuphidden", function onpopuphiding() {
- panel.removeEventListener("popuphidden", onpopuphiding);
- anchorBtn.removeAttribute("open");
- if (dynamicResizer)
- dynamicResizer.stop();
- notificationFrame.docShellIsActive = false;
- dispatchPanelEvent(aType + "FrameHide");
- });
-
- panel.addEventListener("popupshowing", function onpopupshowing() {
- panel.removeEventListener("popupshowning", onpopupshowing);
- // This attribute is needed on both the button and the
- // containing toolbaritem since the buttons on OS X have
- // moz-appearance:none, while their container gets
- // moz-appearance:toolbarbutton due to the way that toolbar buttons
- // get combined on OS X.
- anchorBtn.setAttribute("open", "true");
- });
-
- panel.addEventListener("popupshown", function onpopupshown() {
- panel.removeEventListener("popupshown", onpopupshown);
-
- mm.sendAsyncMessage("WaitForDOMContentLoaded");
- mm.addMessageListener("DOMContentLoaded", function onloaded() {
- mm.removeMessageListener("DOMContentLoaded", onloaded);
- mm = notificationFrame.messageManager;
- notificationFrame.docShellIsActive = true;
- if (dynamicResizer)
- dynamicResizer.start(panel, notificationFrame);
- dispatchPanelEvent(aType + "FrameShow");
- });
- });
-
- let anchor = aWindow.document.getAnonymousElementByAttribute(anchorBtn, "class", "toolbarbutton-icon");
- // Bug 849216 - open the popup asynchronously so we avoid the auto-rollup
- // handling from preventing it being opened in some cases.
- Services.tm.mainThread.dispatch(function() {
- panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
- }, Ci.nsIThread.DISPATCH_NORMAL);
-
- if (aCallback)
- aCallback(notificationFrame);
- }
-};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -28,17 +28,16 @@ EXTRA_JS_MODULES += [
'E10SUtils.jsm',
'Feeds.jsm',
'FormSubmitObserver.jsm',
'FormValidationHandler.jsm',
'HiddenFrame.jsm',
'LaterRun.jsm',
'NetworkPrioritizer.jsm',
'offlineAppCache.jsm',
- 'PanelFrame.jsm',
'PermissionUI.jsm',
'PluginContent.jsm',
'ProcessHangMonitor.jsm',
'ReaderParent.jsm',
'RecentWindow.jsm',
'RemotePrompt.jsm',
'Sanitizer.jsm',
'SelfSupportBackend.jsm',