Bug 1342708 fix datalist failure in webext popups, r?kmag draft
authorShane Caraveo <scaraveo@mozilla.com>
Thu, 01 Jun 2017 16:20:34 -0700
changeset 587966 746aa9bd0c30fbce22448f3bf4fcdaaa2abc703e
parent 587781 62005e6aecdf95c9cffe5fb825d93123ec49c4b3
child 631422 77ecd1d04b225f98ae6839e6134cf0981870e5fd
push id61868
push usermixedpuppy@gmail.com
push dateThu, 01 Jun 2017 23:21:57 +0000
reviewerskmag
bugs1342708
milestone55.0a1
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;
       }
     }