Bug 1294199: Part 2 - Fix rendering of SDK panels in private browsing windows. r=gabor
authorKris Maglione <maglione.k@gmail.com>
Mon, 12 Sep 2016 16:59:14 -0700
changeset 355471 1430290455ed94109e973fd17c7f62865e1695c9
parent 355470 4e03f80d882f29de97006bd3ed80da20b52a40ff
child 355472 0c046f354de884a414f828be2c671f00d48168be
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgabor
bugs1294199
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1294199: Part 2 - Fix rendering of SDK panels in private browsing windows. r=gabor MozReview-Commit-ID: 6nCyoHUHyZb
addon-sdk/source/lib/sdk/panel/utils.js
addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci } = require("chrome");
+const { Services } = require("resource://gre/modules/Services.jsm");
 const { setTimeout } = require("../timers");
 const { platform } = require("../system");
 const { getMostRecentBrowserWindow, getOwnerBrowserWindow,
         getHiddenWindow, getScreenPixelsPerCSSPixel } = require("../window/utils");
 
 const { create: createFrame, swapFrameLoaders, getDocShell } = require("../frame/utils");
 const { window: addonWindow } = require("../addon/window");
 const { isNil } = require("../lang/type");
@@ -181,16 +182,23 @@ function display(panel, options, anchor)
     popupPosition = vertical + "center " + verticalInverse + horizontal;
 
     // Allow panel to flip itself if the panel can't be displayed at the
     // specified position (useful if we compute a bad position or if the
     // user moves the window and panel remains visible)
     panel.setAttribute("flip", "both");
   }
 
+  panel.viewFrame = document.importNode(panel.backgroundFrame, false);
+  panel.appendChild(panel.viewFrame);
+
+  let {privateBrowsingId} = getDocShell(panel.viewFrame).getOriginAttributes();
+  let principal = Services.scriptSecurityManager.createNullPrincipal({privateBrowsingId});
+  getDocShell(panel.viewFrame).createAboutBlankContentViewer(principal);
+
   // Resize the iframe instead of using panel.sizeTo
   // because sizeTo doesn't work with arrow panels
   panel.firstChild.style.width = width + "px";
   panel.firstChild.style.height = height + "px";
 
   panel.openPopup(anchor, popupPosition, x, y);
 }
 exports.display = display;
@@ -247,61 +255,54 @@ function setupPanelFrame(frame) {
     frame.style.padding = "1px";
   }
 }
 
 function make(document, options) {
   document = document || getMostRecentBrowserWindow().document;
   let panel = document.createElementNS(XUL_NS, "panel");
   panel.setAttribute("type", "arrow");
-  panel.setAttribute("sdkscriptenabled", "" + options.allowJavascript);
+  panel.setAttribute("sdkscriptenabled", options.allowJavascript);
 
-  // Note that panel is a parent of `viewFrame` who's `docShell` will be
-  // configured at creation time. If `panel` and there for `viewFrame` won't
-  // have an owner document attempt to access `docShell` will throw. There
-  // for we attach panel to a document.
+  // The panel needs to be attached to a browser window in order for us
+  // to copy browser styles to the content document when it loads.
   attach(panel, document);
 
   let frameOptions =  {
     allowJavascript: options.allowJavascript,
     allowPlugins: true,
     allowAuth: true,
     allowWindowControl: false,
     // Need to override `nodeName` to use `iframe` as `browsers` save session
     // history and in consequence do not dispatch "inner-window-destroyed"
     // notifications.
     browser: false,
-    // Note that use of this URL let's use swap frame loaders earlier
-    // than if we used default "about:blank".
-    uri: "data:text/plain;charset=utf-8,"
   };
 
   let backgroundFrame = createFrame(addonWindow, frameOptions);
   setupPanelFrame(backgroundFrame);
 
-  let viewFrame = createFrame(panel, frameOptions);
-  setupPanelFrame(viewFrame);
+  getDocShell(backgroundFrame).inheritPrivateBrowsingId = false;
 
-  function onDisplayChange({type, target}) {
-    // Events from child element like <select /> may propagate (dropdowns are
-    // popups too), in which case frame loader shouldn't be swapped.
-    // See Bug 886329
-    if (target !== this) return;
+  function onPopupShowing({type, target}) {
+    if (target === this) {
+      let attrs = getDocShell(backgroundFrame).getOriginAttributes();
+      getDocShell(panel.viewFrame).setOriginAttributes(attrs);
 
-    try {
-      swapFrameLoaders(backgroundFrame, viewFrame);
-      // We need to re-set this because... swapFrameLoaders. Or something.
-      let shouldEnableScript = panel.getAttribute("sdkscriptenabled") == "true";
-      getDocShell(backgroundFrame).allowJavascript = shouldEnableScript;
-      getDocShell(viewFrame).allowJavascript = shouldEnableScript;
+      swapFrameLoaders(backgroundFrame, panel.viewFrame);
     }
-    catch(error) {
-      console.exception(error);
+  }
+
+  function onPopupHiding({type, target}) {
+    if (target === this) {
+      swapFrameLoaders(backgroundFrame, panel.viewFrame);
+
+      panel.viewFrame.remove();
+      panel.viewFrame = null;
     }
-    events.emit(type, { subject: panel });
   }
 
   function onContentReady({target, type}) {
     if (target === getContentDocument(panel)) {
       style(panel);
       events.emit(type, { subject: panel });
     }
   }
@@ -311,42 +312,42 @@ function make(document, options) {
       events.emit(type, { subject: panel });
   }
 
   function onContentChange({subject: document, type}) {
     if (document === getContentDocument(panel) && document.defaultView)
       events.emit(type, { subject: panel });
   }
 
-  function onPanelStateChange({type}) {
-    events.emit(type, { subject: panel })
+  function onPanelStateChange({target, type}) {
+    if (target === this)
+      events.emit(type, { subject: panel })
   }
 
-  panel.addEventListener("popupshowing", onDisplayChange, false);
-  panel.addEventListener("popuphiding", onDisplayChange, false);
-  panel.addEventListener("popupshown", onPanelStateChange, false);
-  panel.addEventListener("popuphidden", onPanelStateChange, false);
+  panel.addEventListener("popupshowing", onPopupShowing);
+  panel.addEventListener("popuphiding", onPopupHiding);
+  for (let event of ["popupshowing", "popuphiding", "popupshown", "popuphidden"])
+    panel.addEventListener(event, onPanelStateChange);
 
   panel.addEventListener("click", onPanelClick, false);
 
   // Panel content document can be either in panel `viewFrame` or in
   // a `backgroundFrame` depending on panel state. Listeners are set
   // on both to avoid setting and removing listeners on panel state changes.
 
   panel.addEventListener("DOMContentLoaded", onContentReady, true);
   backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true);
 
   panel.addEventListener("load", onContentLoad, true);
   backgroundFrame.addEventListener("load", onContentLoad, true);
 
   events.on("document-element-inserted", onContentChange);
 
-
   panel.backgroundFrame = backgroundFrame;
-  panel.viewFrame = viewFrame;
+  panel.viewFrame = null;
 
   // Store event listener on the panel instance so that it won't be GC-ed
   // while panel is alive.
   panel.onContentChange = onContentChange;
 
   return panel;
 }
 exports.make = make;
@@ -363,19 +364,17 @@ exports.attach = attach;
 
 function detach(panel) {
   if (panel.parentNode) panel.parentNode.removeChild(panel);
 }
 exports.detach = detach;
 
 function dispose(panel) {
   panel.backgroundFrame.remove();
-  panel.viewFrame.remove();
   panel.backgroundFrame = null;
-  panel.viewFrame = null;
   events.off("document-element-inserted", panel.onContentChange);
   panel.onContentChange = null;
   detach(panel);
 }
 exports.dispose = dispose;
 
 function style(panel) {
   /**
@@ -387,21 +386,17 @@ function style(panel) {
   chrome for example.
   **/
 
   try {
     let document = panel.ownerDocument;
     let contentDocument = getContentDocument(panel);
     let window = document.defaultView;
     let node = document.getAnonymousElementByAttribute(panel, "class",
-                                                       "panel-arrowcontent") ||
-               // Before bug 764755, anonymous content was different:
-               // TODO: Remove this when targeting FF16+
-                document.getAnonymousElementByAttribute(panel, "class",
-                                                        "panel-inner-arrowcontent");
+                                                       "panel-arrowcontent");
 
     let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);
 
     let style = contentDocument.createElement("style");
     style.id = "sdk-panel-style";
     style.textContent = "body { " +
       "color: " + color + ";" +
       "font-family: " + fontFamily + ";" +
@@ -419,29 +414,29 @@ function style(panel) {
   }
   catch (error) {
     console.error("Unable to apply panel style");
     console.exception(error);
   }
 }
 exports.style = style;
 
-var getContentFrame = panel =>
-    (isOpen(panel) || isOpening(panel)) ?
-    panel.firstChild :
-    panel.backgroundFrame
+var getContentFrame = panel => panel.viewFrame || panel.backgroundFrame;
 exports.getContentFrame = getContentFrame;
 
 function getContentDocument(panel) {
   return getContentFrame(panel).contentDocument;
 }
 exports.getContentDocument = getContentDocument;
 
 function setURL(panel, url) {
-  getContentFrame(panel).setAttribute("src", url ? data.url(url) : url);
+  let frame = getContentFrame(panel);
+  let webNav = getDocShell(frame).QueryInterface(Ci.nsIWebNavigation);
+
+  webNav.loadURI(url ? data.url(url) : "about:blank", 0, null, null, null);
 }
 
 exports.setURL = setURL;
 
 function allowContextMenu(panel, allow) {
   if (allow) {
     panel.setAttribute("context", "contentAreaContextMenu");
   }
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
@@ -2,100 +2,98 @@
  * 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';
 
 const { open, focus, close } = require('sdk/window/helpers');
 const { isPrivate } = require('sdk/private-browsing');
 const { defer } = require('sdk/core/promise');
 const { browserWindows: windows } = require('sdk/windows');
+const { getInnerId, getMostRecentBrowserWindow } = require('sdk/window/utils');
+const { getActiveView } = require('sdk/view/core');
 
 const BROWSER = 'chrome://browser/content/browser.xul';
 
 exports.testRequirePanel = function(assert) {
   require('sdk/panel');
   assert.ok('the panel module should not throw an error');
 };
 
 exports.testShowPanelInPrivateWindow = function(assert, done) {
   let panel = require('sdk/panel').Panel({
-    contentURL: "data:text/html;charset=utf-8,"
+    contentURL: "data:text/html;charset=utf-8,I'm a leaf on the wind"
   });
 
   assert.ok(windows.length > 0, 'there is at least one open window');
   for (let window of windows) {
     assert.equal(isPrivate(window), false, 'open window is private');
   }
 
-  testShowPanel(assert, panel).
+  let panelView = getActiveView(panel);
+  let expectedWindowId = getInnerId(panelView.backgroundFrame.contentWindow);
+
+  function checkPanelFrame() {
+    let iframe = panelView.firstChild;
+
+    assert.equal(panelView.viewFrame, iframe, 'panel has the correct viewFrame value');
+
+    let windowId = getInnerId(iframe.contentWindow);
+
+    assert.equal(windowId, expectedWindowId, 'panel has the correct window visible');
+
+    assert.equal(iframe.contentDocument.body.textContent,
+                 "I'm a leaf on the wind",
+                 'the panel has the expected content');
+  }
+
+  function testPanel(window) {
+    let { promise, resolve } = defer();
+
+    assert.ok(!panel.isShowing, 'the panel is not showing [1]');
+
+    panel.once('show', function() {
+      assert.ok(panel.isShowing, 'the panel is showing');
+
+      checkPanelFrame();
+
+      panel.once('hide', function() {
+        assert.ok(!panel.isShowing, 'the panel is not showing [2]');
+
+        resolve(window);
+      });
+
+      panel.hide();
+    });
+
+    panel.show();
+
+    return promise;
+  };
+
+  let initialWindow = getMostRecentBrowserWindow();
+
+  testPanel(initialWindow).
     then(makeEmptyPrivateBrowserWindow).
     then(focus).
     then(function(window) {
       assert.equal(isPrivate(window), true, 'opened window is private');
       assert.pass('private window was focused');
       return window;
     }).
-    then(function(window) {
-      let { promise, resolve } = defer();
-
-      assert.ok(!panel.isShowing, 'the panel is not showing [1]');
-
-      panel.once('show', function() {
-        assert.ok(panel.isShowing, 'the panel is showing');
-
-        panel.once('hide', function() {
-          assert.ok(!panel.isShowing, 'the panel is not showing [2]');
-
-          resolve(window);
-        });
-
-        panel.hide();
-      });
-
-      panel.show();
-
-      return promise;
-    }).
+    then(testPanel).
     then(close).
+    then(() => focus(initialWindow)).
+    then(testPanel).
     then(done).
     then(null, assert.fail);
 };
 
 
 function makeEmptyPrivateBrowserWindow(options) {
   options = options || {};
   return open(BROWSER, {
     features: {
       chrome: true,
       toolbar: true,
       private: true
     }
   });
 }
-
-function testShowPanel(assert, panel) {
-  let { promise, resolve } = defer();
-  let shown = false;
-
-  assert.ok(!panel.isShowing, 'the panel is not showing [1]');
-
-  panel.once('hide', function() {
-    assert.ok(!panel.isShowing, 'the panel is not showing [2]');
-    assert.ok(shown, 'the panel was shown')
-
-    resolve(null);
-  });
-
-  panel.once('show', function() {
-    shown = true;
-
-    assert.ok(panel.isShowing, 'the panel is showing');
-
-    panel.hide();
-  });
-
-  panel.show();
-
-  return promise;
-}
-
-//Test disabled because of bug 911071
-module.exports = {}