Bug 1294442: Part 2 - Fix layout issues when popup's preferred size is larger than maximum. r=aswan a=ritu
authorKris Maglione <maglione.k@gmail.com>
Mon, 24 Oct 2016 22:18:08 -0700
changeset 350788 49d808e29f639134d1f1510084a962f7924d9977
parent 350787 ca38705921e6193da2d8c2a55480287fffe602d6
child 350789 c45f9369c95cb492a2dee21e9c5cefde192f573c
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, ritu
bugs1294442
milestone50.0
Bug 1294442: Part 2 - Fix layout issues when popup's preferred size is larger than maximum. r=aswan a=ritu MozReview-Commit-ID: E9gaAeQWtDb
browser/components/extensions/ext-utils.js
browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
browser/components/extensions/test/browser/head.js
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -302,35 +302,24 @@ class BasePopup {
       // Set a maximum height on the <panelview> element to our preferred
       // maximum height, so that the PanelUI resizing code can make an accurate
       // calculation. If we don't do this, the flex sizing logic will prevent us
       // from ever reporting a preferred size smaller than the height currently
       // available to us in the panel.
       height = Math.max(height, this.viewHeight);
       this.viewNode.style.maxHeight = `${height}px`;
     } else {
-      let width, height;
-      try {
-        let w = {}, h = {};
-        this.browser.docShell.contentViewer.getContentSize(w, h);
-
-        width = w.value / this.window.devicePixelRatio;
-        height = h.value / this.window.devicePixelRatio;
+      // Adjust the size of the browser based on its content's preferred size.
+      let {contentViewer} = this.browser.docShell;
+      let ratio = this.window.devicePixelRatio;
 
-        // The width calculation is imperfect, and is often a fraction of a pixel
-        // too narrow, even after taking the ceiling, which causes lines of text
-        // to wrap.
-        width += 1;
-      } catch (e) {
-        // getContentSize can throw
-        [width, height] = [400, 400];
-      }
-
-      width = Math.ceil(Math.min(width, 800));
-      height = Math.ceil(Math.min(height, 600));
+      let w = {}, h = {};
+      contentViewer.getContentSizeConstrained(800 * ratio, 600 * ratio, w, h);
+      let width = Math.ceil(w.value / ratio);
+      let height = Math.ceil(h.value / ratio);
 
       this.browser.style.width = `${width}px`;
       this.browser.style.height = `${height}px`;
     }
 
     let event = new this.window.CustomEvent("WebExtPopupResized");
     this.browser.dispatchEvent(event);
 
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
@@ -4,16 +4,19 @@
 
 function* openPanel(extension, win = window) {
   clickBrowserAction(extension, win);
 
   let {target} = yield BrowserTestUtils.waitForEvent(win.document, "load", true, (event) => {
     return event.target.location && event.target.location.href.endsWith("popup.html");
   });
 
+  yield new Promise(resolve => setTimeout(resolve, 0));
+  yield new Promise(resolve => setTimeout(resolve, 100));
+
   return target.defaultView
                .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
                .chromeEventHandler;
 }
 
 function* awaitResize(browser) {
   // Debouncing code makes this a bit racy.
   // Try to skip the first, early resize, and catch the resize event we're
@@ -37,18 +40,16 @@ add_task(function* testBrowserActionPopu
 
     files: {
       "popup.html": '<html><head><meta charset="utf-8"></head></html>',
     },
   });
 
   yield extension.startup();
 
-  clickBrowserAction(extension, window);
-
   let browser = yield openPanel(extension);
   let panelWindow = browser.contentWindow;
   let panelBody = panelWindow.document.body;
 
   function checkSize(expected) {
     is(panelWindow.innerHeight, expected, `Panel window should be ${expected}px tall`);
     is(panelBody.clientHeight, panelBody.scrollHeight,
       "Panel body should be tall enough to fit its contents");
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
@@ -1,12 +1,28 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+function* awaitResize(browser) {
+  // Debouncing code makes this a bit racy.
+  // Try to skip the first, early resize, and catch the resize event we're
+  // looking for, but don't wait longer than a few seconds.
+
+  return Promise.race([
+    BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
+      .then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
+    new Promise(resolve => setTimeout(resolve, 5000)),
+  ]);
+}
+
+let delay = ms => new Promise(resolve => {
+  setTimeout(resolve, ms);
+});
+
 add_task(function* testPageActionPopupResize() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "page_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
     },
@@ -16,55 +32,152 @@ add_task(function* testPageActionPopupRe
 
         browser.pageAction.show(tabId).then(() => {
           browser.test.sendMessage("action-shown");
         });
       });
     },
 
     files: {
-      "popup.html": "<html><head><meta charset=\"utf-8\"></head></html>",
+      "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body><div></div></body></html>`,
     },
   });
 
   yield extension.startup();
   yield extension.awaitMessage("action-shown");
 
   clickPageAction(extension, window);
 
   let {target: panelDocument} = yield BrowserTestUtils.waitForEvent(document, "load", true, (event) => {
     info(`Loaded ${event.target.location}`);
     return event.target.location && event.target.location.href.endsWith("popup.html");
   });
 
   let panelWindow = panelDocument.defaultView;
-  let panelBody = panelDocument.body;
+  let panelBody = panelDocument.body.firstChild;
+  let body = panelDocument.body;
+  let root = panelDocument.documentElement;
 
   function checkSize(expected) {
     is(panelWindow.innerHeight, expected, `Panel window should be ${expected}px tall`);
-    is(panelBody.clientHeight, panelBody.scrollHeight,
+    is(body.clientHeight, body.scrollHeight,
       "Panel body should be tall enough to fit its contents");
+    is(root.clientHeight, root.scrollHeight,
+      "Panel root should be tall enough to fit its contents");
 
     // Tolerate if it is 1px too wide, as that may happen with the current resizing method.
     ok(Math.abs(panelWindow.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
-    is(panelBody.clientWidth, panelBody.scrollWidth,
-      "Panel body should be wide enough to fit its contents");
+    is(body.clientWidth, body.scrollWidth,
+       "Panel body should be wide enough to fit its contents");
   }
 
   function setSize(size) {
     panelBody.style.height = `${size}px`;
     panelBody.style.width = `${size}px`;
+
+    return BrowserTestUtils.waitForEvent(panelWindow, "resize");
   }
 
   let sizes = [
     200,
     400,
     300,
   ];
 
   for (let size of sizes) {
-    setSize(size);
-    yield BrowserTestUtils.waitForEvent(panelWindow, "resize");
+    yield setSize(size);
     checkSize(size);
   }
 
+  yield setSize(1400);
+
+  // Windows has a particular slow resize animation for this panel.
+  if (AppConstants.platform == "win") {
+    while (panelWindow.innerWidth < 750) {
+      info(`Window width: ${panelWindow.innerWidth}px\n`);
+      yield delay(50);
+    }
+  }
+
+  if (AppConstants.platform == "win") {
+    ok(panelWindow.innerWidth <= 800 && panelWindow.innerWidth >= 750,
+       `Panel window width (${panelWindow.innerWidth}) within tolerance`);
+  } else {
+    is(panelWindow.innerWidth, 800, "Panel window width");
+  }
+  ok(body.clientWidth <= 800, `Panel body width ${body.clientWidth} is less than 800`);
+  is(body.scrollWidth, 1400, "Panel body scroll width");
+
+  is(panelWindow.innerHeight, 600, "Panel window height");
+  ok(root.clientHeight <= 600, `Panel root height (${root.clientHeight}px) is less than 600px`);
+  is(root.scrollHeight, 1400, "Panel root scroll height");
+
   yield extension.unload();
 });
+
+add_task(function* testPageActionPopupReflow() {
+  let browser;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "page_action": {
+        "default_popup": "popup.html",
+        "browser_style": true,
+      },
+    },
+    background: function() {
+      browser.tabs.query({active: true, currentWindow: true}, tabs => {
+        const tabId = tabs[0].id;
+
+        browser.pageAction.show(tabId).then(() => {
+          browser.test.sendMessage("action-shown");
+        });
+      });
+    },
+
+    files: {
+      "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head>
+        <body>
+          The quick mauve fox jumps over the opalescent toad, with its glowing
+          eyes, and its vantablack mouth, and its bottomless chasm where you
+          would hope to find a heart, that looks straight into the deepest
+          pits of hell. The fox shivers, and cowers, and tries to run, but
+          the toad is utterly without pity. It turns, ever so slightly...
+        </body>
+      </html>`,
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitMessage("action-shown");
+
+  clickPageAction(extension, window);
+
+  browser = yield awaitExtensionPanel(extension);
+  yield new Promise(resolve => setTimeout(resolve, 100));
+
+  let win = browser.contentWindow;
+  let body = win.document.body;
+  let root = win.document.documentElement;
+
+  function setSize(size) {
+    body.style.fontSize = `${size}px`;
+
+    return awaitResize(browser);
+  }
+
+  yield setSize(18);
+
+  is(win.innerWidth, 800, "Panel window should be 800px wide");
+  is(body.clientWidth, 800, "Panel body should be 800px wide");
+  is(body.clientWidth, body.scrollWidth,
+     "Panel body should be wide enough to fit its contents");
+
+  ok(win.innerHeight > 36,
+     `Panel window height (${win.innerHeight}px) should be taller than two lines of text.`);
+
+  is(body.clientHeight, body.scrollHeight,
+    "Panel body should be tall enough to fit its contents");
+  is(root.clientHeight, root.scrollHeight,
+    "Panel root should be tall enough to fit its contents");
+
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -6,16 +6,17 @@
  *          getBrowserActionWidget
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
  *          openContextMenu closeContextMenu
  *          openExtensionContextMenu closeExtensionContextMenu
  *          imageBuffer getListStyleImage getPanelForNode
+ *          awaitExtensionPanel
  */
 
 var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
 var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
 
 // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
 // times in debug builds, which results in intermittent timeouts. Until we have
 // a better solution, we force a GC after certain strategic tests, which tend to
@@ -85,16 +86,35 @@ function promisePopupHidden(popup) {
 
 function getPanelForNode(node) {
   while (node.localName != "panel") {
     node = node.parentNode;
   }
   return node;
 }
 
+var awaitExtensionPanel = Task.async(function* (extension, win = window, filename = "popup.html") {
+  let {target} = yield BrowserTestUtils.waitForEvent(win.document, "load", true, (event) => {
+    return event.target.location && event.target.location.href.endsWith(filename);
+  });
+
+  let browser = target.defaultView
+                      .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
+                      .chromeEventHandler;
+
+  if (browser.matches(".webextension-preload-browser")) {
+    let event = yield BrowserTestUtils.waitForEvent(browser, "SwapDocShells");
+    browser = event.detail;
+  }
+
+  yield promisePopupShown(getPanelForNode(browser));
+
+  return browser;
+});
+
 function getBrowserActionWidget(extension) {
   return CustomizableUI.getWidget(makeWidgetId(extension.id) + "-browser-action");
 }
 
 function getBrowserActionPopup(extension, win = window) {
   let group = getBrowserActionWidget(extension);
 
   if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {