Bug 1294199: Part 2 - Fix rendering of SDK panels in private browsing windows. r?krizsa draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 12 Sep 2016 16:59:14 -0700
changeset 412792 1088e01ebeb4fc2fec502035bc786f70405c3ddb
parent 412791 359fdf8ee11ffb5d1ba8dcd51c4c8f613df9d389
child 531077 d7433665f34b30a93cebafafbf84ca53f647992b
push id29268
push usermaglione.k@gmail.com
push dateTue, 13 Sep 2016 00:01:05 +0000
reviewerskrizsa
bugs1294199
milestone51.0a1
Bug 1294199: Part 2 - Fix rendering of SDK panels in private browsing windows. r?krizsa 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,22 @@ 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 principal = Services.scriptSecurityManager.createNullPrincipal({});
+  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;
@@ -246,61 +253,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 });
     }
   }
@@ -310,42 +310,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;
@@ -362,19 +362,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) {
   /**
@@ -386,21 +384,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 + ";" +
@@ -418,29 +412,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 = {}