Bug 1299998 - Fix closing of share panel. r=markh, a=gchang
authorShane Caraveo <scaraveo@mozilla.com>
Thu, 22 Sep 2016 08:40:07 -0700
changeset 340253 5a19443bce1d30eed8d832c9c10fe80d0ccf4902
parent 340252 185dcfd9c1e127aa6f18cca270ae4d4bf02eafac
child 340254 d2a4f2fc6716a1ba6e78c93342eac761f7524599
push id10069
push userryanvm@gmail.com
push dateTue, 27 Sep 2016 21:28:07 +0000
treeherdermozilla-aurora@1283217c5463 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh, gchang
bugs1299998
milestone51.0a2
Bug 1299998 - Fix closing of share panel. r=markh, a=gchang MozReview-Commit-ID: 2D4c2AiS1qQ
browser/base/content/browser-social.js
browser/base/content/social-content.js
browser/modules/PanelFrame.jsm
browser/modules/moz.build
--- 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',
     'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',