Bug 1342708 fix datalist failure in webext popups, r=kmag
authorShane Caraveo <scaraveo@mozilla.com>
Thu, 01 Jun 2017 16:20:34 -0700
changeset 410045 937da0dba852353dfe7898f31207144fdf24b7ff
parent 410044 bf5e8ffae40db7b2c2afcb1851fb8eb80b98e5b6
child 410046 8c962337cfa65cad59f228f062d52bc6271e868b
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1342708
milestone55.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 1342708 fix datalist failure in webext popups, r=kmag MozReview-Commit-ID: FbTB9h3TTdq
browser/base/content/webext-panels.js
browser/base/content/webext-panels.xul
browser/components/extensions/ExtensionPopups.jsm
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_autocompletepopup.js
toolkit/components/satchel/AutoCompletePopup.jsm
toolkit/content/browser-content.js
--- a/browser/base/content/webext-panels.js
+++ b/browser/base/content/webext-panels.js
@@ -27,16 +27,17 @@ function getBrowser(sidebar) {
   browser = document.createElementNS(XUL_NS, "browser");
   browser.setAttribute("id", "webext-panels-browser");
   browser.setAttribute("type", "content");
   browser.setAttribute("flex", "1");
   browser.setAttribute("disableglobalhistory", "true");
   browser.setAttribute("webextension-view-type", "sidebar");
   browser.setAttribute("context", "contentAreaContextMenu");
   browser.setAttribute("tooltip", "aHTMLTooltip");
+  browser.setAttribute("autocompletepopup", "PopupAutoComplete");
   browser.setAttribute("onclick", "window.parent.contentAreaClick(event, true);");
 
   let readyPromise;
   if (sidebar.remote) {
     browser.setAttribute("remote", "true");
     browser.setAttribute("remoteType",
                          E10SUtils.getRemoteTypeForURI(sidebar.uri, true,
                                                        E10SUtils.EXTENSION_REMOTE_TYPE));
--- a/browser/base/content/webext-panels.xul
+++ b/browser/base/content/webext-panels.xul
@@ -45,16 +45,24 @@
              oncommand="getPanelBrowser().webNavigation.goForward();"
              disabled="true"/>
     <command id="Browser:Stop" oncommand="PanelBrowserStop();"/>
     <command id="Browser:Reload" oncommand="PanelBrowserReload();"/>
   </commandset>
 
   <popupset id="mainPopupSet">
     <tooltip id="aHTMLTooltip" page="true"/>
+
+    <panel type="autocomplete-richlistbox"
+           id="PopupAutoComplete"
+           noautofocus="true"
+           hidden="true"
+           overflowpadding="4"
+           norolluponanchor="true" />
+
     <menupopup id="contentAreaContextMenu" pagemenu="start"
                onpopupshowing="if (event.target != this)
                                  return true;
                                gContextMenu = new nsContextMenu(this, event.shiftKey);
                                if (gContextMenu.shouldDisplay)
                                  document.popupNode = this.triggerNode;
                                return gContextMenu.shouldDisplay;"
                onpopuphiding="if (event.target != this)
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -223,16 +223,17 @@ class BasePopup {
     let browser = document.createElementNS(XUL_NS, "browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("transparent", "true");
     browser.setAttribute("class", "webextension-popup-browser");
     browser.setAttribute("webextension-view-type", "popup");
     browser.setAttribute("tooltip", "aHTMLTooltip");
     browser.setAttribute("contextmenu", "contentAreaContextMenu");
+    browser.setAttribute("autocompletepopup", "PopupAutoComplete");
 
     if (this.extension.remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
     }
 
     // We only need flex sizing for the sake of the slide-in sub-views of the
     // main menu panel, so that the browser occupies the full width of the view,
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 tags = webextensions in-process-webextensions
 
+[browser_ext_autocompletepopup.js]
 [browser_ext_legacy_extension_context_contentscript.js]
 [browser_ext_windows_allowScriptsToClose.js]
 
 [include:browser-common.ini]
 [parent:browser-common.ini]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_autocompletepopup.js
@@ -0,0 +1,84 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testAutocompletePopup() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "browser_action": {
+        "default_popup": "page.html",
+        "browser_style": false,
+      },
+      "page_action": {
+        "default_popup": "page.html",
+        "browser_style": false,
+      },
+    },
+    background: async function() {
+      let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+      await browser.pageAction.show(tab.id);
+      browser.test.sendMessage("ready");
+    },
+    files: {
+      "page.html": `<!DOCTYPE html>
+        <html>
+          <head><meta charset="utf-8"></head>
+          <body>
+          <div>
+          <input placeholder="Test input" id="test-input" list="test-list" />
+          <datalist id="test-list">
+            <option value="aa">
+            <option value="ab">
+            <option value="ae">
+            <option value="af">
+            <option value="ak">
+            <option value="am">
+            <option value="an">
+            <option value="ar">
+          </datalist>
+          </div>
+          </body>
+        </html>`,
+    },
+  });
+
+  function* testDatalist(browser, doc) {
+    let autocompletePopup = doc.getElementById("PopupAutoComplete");
+    let opened = promisePopupShown(autocompletePopup);
+    info("click in test-input now");
+    // two clicks to open
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#test-input", {}, browser);
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#test-input", {}, browser);
+    info("wait for opened event");
+    yield opened;
+    // third to close
+    let closed = promisePopupHidden(autocompletePopup);
+    info("click in test-input now");
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#test-input", {}, browser);
+    info("wait for closed event");
+    yield closed;
+    // If this didn't work, we hang. Other tests deal with testing the actual functionality of datalist.
+    ok(true, "datalist popup has been shown");
+  }
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+  yield extension.startup();
+  yield extension.awaitMessage("ready");
+
+  clickPageAction(extension);
+  // intentional misspell so eslint is ok with browser in background script.
+  let bowser = yield awaitExtensionPanel(extension);
+  ok(!!bowser, "panel opened with browser");
+  yield testDatalist(bowser, document);
+  closePageAction(extension);
+  yield new Promise(resolve => setTimeout(resolve, 0));
+
+  clickBrowserAction(extension);
+  bowser = yield awaitExtensionPanel(extension);
+  ok(!!bowser, "panel opened with browser");
+  yield testDatalist(bowser, document);
+  closeBrowserAction(extension);
+  yield new Promise(resolve => setTimeout(resolve, 0));
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/toolkit/components/satchel/AutoCompletePopup.jsm
+++ b/toolkit/components/satchel/AutoCompletePopup.jsm
@@ -138,19 +138,18 @@ this.AutoCompletePopup = {
     if (!results.length || this.openedPopup) {
       // We shouldn't ever be showing an empty popup, and if we
       // already have a popup open, the old one needs to close before
       // we consider opening a new one.
       return;
     }
 
     let window = browser.ownerGlobal;
-    let tabbrowser = window.gBrowser;
-    if (Services.focus.activeWindow != window ||
-        tabbrowser.selectedBrowser != browser) {
+    // Also check window top in case this is a sidebar.
+    if (Services.focus.activeWindow !== window.top) {
       // We were sent a message from a window or tab that went into the
       // background, so we'll ignore it for now.
       return;
     }
 
     this.weakBrowser = Cu.getWeakReference(browser);
     this.openedPopup = browser.autoCompletePopup;
     // the layout varies according to different result type
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1447,16 +1447,19 @@ let AutoCompletePopup = {
     "FormAutoComplete:PopupClosed",
     "FormAutoComplete:PopupOpened",
     "FormAutoComplete:RequestFocus",
   ],
 
   init() {
     addEventListener("unload", this);
     addEventListener("DOMContentLoaded", this);
+    // WebExtension browserAction is preloaded and does not receive DCL, wait
+    // on pageshow so we can hookup the formfill controller.
+    addEventListener("pageshow", this, true);
 
     for (let messageName of this.MESSAGES) {
       addMessageListener(messageName, this);
     }
 
     this._input = null;
     this._popupOpen = false;
   },
@@ -1464,40 +1467,53 @@ let AutoCompletePopup = {
   destroy() {
     if (this._connected) {
       let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                          .getService(Ci.nsIFormFillController);
       controller.detachFromBrowser(docShell);
       this._connected = false;
     }
 
+    removeEventListener("pageshow", this);
     removeEventListener("unload", this);
     removeEventListener("DOMContentLoaded", this);
 
     for (let messageName of this.MESSAGES) {
       removeMessageListener(messageName, this);
     }
   },
 
+  connect() {
+    if (this._connected) {
+      return;
+    }
+    // We need to wait for a content viewer to be available
+    // before we can attach our AutoCompletePopup handler,
+    // since nsFormFillController assumes one will exist
+    // when we call attachToBrowser.
+
+    // Hook up the form fill autocomplete controller.
+    let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
+                       .getService(Ci.nsIFormFillController);
+    controller.attachToBrowser(docShell,
+                               this.QueryInterface(Ci.nsIAutoCompletePopup));
+    this._connected = true;
+  },
+
   handleEvent(event) {
     switch (event.type) {
+      case "pageshow": {
+        removeEventListener("pageshow", this);
+        this.connect();
+        break;
+      }
+
       case "DOMContentLoaded": {
         removeEventListener("DOMContentLoaded", this);
-
-        // We need to wait for a content viewer to be available
-        // before we can attach our AutoCompletePopup handler,
-        // since nsFormFillController assumes one will exist
-        // when we call attachToBrowser.
-
-        // Hook up the form fill autocomplete controller.
-        let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
-                           .getService(Ci.nsIFormFillController);
-        controller.attachToBrowser(docShell,
-                                   this.QueryInterface(Ci.nsIAutoCompletePopup));
-        this._connected = true;
+        this.connect();
         break;
       }
 
       case "unload": {
         this.destroy();
         break;
       }
     }