Bug 1367081 - 4. Load SelectHelper and InputWidgetHelper in PromptService.js; r=droeh
☠☠ backed out by c685c902f89a ☠ ☠
authorJim Chen <nchen@mozilla.com>
Fri, 02 Jun 2017 16:16:11 -0400
changeset 410280 949bae6eaa96e9590aa7b1eba2c36398d3caece3
parent 410279 6065144fe5b32eab26374fd7ad67930d0c6d9403
child 410281 6a99686b566a55298d7fe38a97717f1731aa740a
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)
reviewersdroeh
bugs1367081
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 1367081 - 4. Load SelectHelper and InputWidgetHelper in PromptService.js; r=droeh Right now SelectHelper and InputWidgetHelper are loaded in browser.js, which means they only work for GeckoApp. This patch loads them in PromptService.js instead, which means they will work in all windows. The patch also changes some code in SelectHelper and InputWidgetHelper that used to assume they are running under the browser.xul chrome window. MozReview-Commit-ID: Jfe6ODyYKVf
mobile/android/chrome/content/InputWidgetHelper.js
mobile/android/chrome/content/SelectHelper.js
mobile/android/chrome/content/browser.js
mobile/android/components/MobileComponents.manifest
mobile/android/components/PromptService.js
--- a/mobile/android/chrome/content/InputWidgetHelper.js
+++ b/mobile/android/chrome/content/InputWidgetHelper.js
@@ -1,16 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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";
 
 var InputWidgetHelper = {
   _uiBusy: false,
 
+  strings: function() {
+    if (!this._strings) {
+      this._strings = Services.strings.createBundle(
+          "chrome://browser/locale/browser.properties");
+    }
+    return this._strings;
+  },
+
   handleEvent: function(aEvent) {
     this.handleClick(aEvent.target);
   },
 
   handleClick: function(aTarget) {
     // if we're busy looking at a InputWidget we want to eat any clicks that
     // come to us, but not to process them
     if (this._uiBusy || !this.hasInputWidget(aTarget) || this._isDisabledElement(aTarget))
@@ -20,21 +28,21 @@ var InputWidgetHelper = {
     this.show(aTarget);
     this._uiBusy = false;
   },
 
   show: function(aElement) {
     let type = aElement.getAttribute('type');
     let p = new Prompt({
       window: aElement.ownerGlobal,
-      title: Strings.browser.GetStringFromName("inputWidgetHelper." + aElement.getAttribute('type')),
+      title: this.strings().GetStringFromName("inputWidgetHelper." + aElement.getAttribute('type')),
       buttons: [
-        Strings.browser.GetStringFromName("inputWidgetHelper.set"),
-        Strings.browser.GetStringFromName("inputWidgetHelper.clear"),
-        Strings.browser.GetStringFromName("inputWidgetHelper.cancel")
+        this.strings().GetStringFromName("inputWidgetHelper.set"),
+        this.strings().GetStringFromName("inputWidgetHelper.clear"),
+        this.strings().GetStringFromName("inputWidgetHelper.cancel")
       ],
     }).addDatePicker({
       value: aElement.value,
       type: type,
       min: aElement.min,
       max: aElement.max,
     }).show(data => {
       let changed = false;
@@ -58,34 +66,36 @@ var InputWidgetHelper = {
       // Else the user canceled the input.
 
       if (changed)
         this.fireOnChange(aElement);
     });
   },
 
   hasInputWidget: function(aElement) {
-    if (!(aElement instanceof HTMLInputElement))
+    let win = aElement.ownerGlobal;
+    if (!(aElement instanceof win.HTMLInputElement))
       return false;
 
     let type = aElement.getAttribute('type');
     if (type == "date" || type == "datetime" || type == "datetime-local" ||
         type == "week" || type == "month" || type == "time") {
       return true;
     }
 
     return false;
   },
 
   fireOnChange: function(aElement) {
+    let win = aElement.ownerGlobal;
     let evt = aElement.ownerDocument.createEvent("Events");
     evt.initEvent("change", true, true, aElement.defaultView, 0,
                   false, false,
                   false, false, null);
-    setTimeout(function() {
+    win.setTimeout(function() {
       aElement.dispatchEvent(evt);
     }, 0);
   },
 
   _isDisabledElement : function(aElement) {
     let currentElement = aElement;
     while (currentElement) {
       if (currentElement.disabled)
--- a/mobile/android/chrome/content/SelectHelper.js
+++ b/mobile/android/chrome/content/SelectHelper.js
@@ -1,16 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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";
 
 var SelectHelper = {
   _uiBusy: false,
 
+  strings: function() {
+    if (!this._strings) {
+      this._strings = Services.strings.createBundle(
+          "chrome://browser/locale/browser.properties");
+    }
+    return this._strings;
+  },
+
   handleEvent: function(event) {
     this.handleClick(event.target);
   },
 
   handleClick: function(target) {
     // if we're busy looking at a select we want to eat any clicks that
     // come to us, but not to process them
     if (this._uiBusy || !this._isMenu(target) || this._isDisabledElement(target)) {
@@ -21,24 +29,25 @@ var SelectHelper = {
     this.show(target);
     this._uiBusy = false;
   },
 
   // This is a callback function to be provided to prompt.show(callBack).
   // It will update which Option elements in a Select have been selected
   // or unselected and fire the onChange event.
   _promptCallBack: function(data, element) {
+    let win = element.ownerGlobal;
     let selected = data.list;
 
     if (element instanceof Ci.nsIDOMXULMenuListElement) {
       if (element.selectedIndex != selected[0]) {
         element.selectedIndex = selected[0];
         this.fireOnCommand(element);
       }
-    } else if (element instanceof HTMLSelectElement) {
+    } else if (element instanceof win.HTMLSelectElement) {
       let changed = false;
       let i = 0; // The index for the element from `data.list` that we are currently examining.
       this.forVisibleOptions(element, function(node) {
         if (node.selected && selected.indexOf(i) == -1) {
           changed = true;
           node.selected = false;
         } else if (!node.selected && selected.indexOf(i) != -1) {
           changed = true;
@@ -56,29 +65,30 @@ var SelectHelper = {
   show: function(element) {
     let list = this.getListForElement(element);
     let p = new Prompt({
       window: element.ownerGlobal
     });
 
     if (element.multiple) {
       p.addButton({
-        label: Strings.browser.GetStringFromName("selectHelper.closeMultipleSelectDialog")
+        label: this.strings().GetStringFromName("selectHelper.closeMultipleSelectDialog")
       }).setMultiChoiceItems(list);
     } else {
       p.setSingleChoiceItems(list);
     }
 
     p.show((data) => {
       this._promptCallBack(data,element)
     });
   },
 
   _isMenu: function(element) {
-    return (element instanceof HTMLSelectElement || element instanceof Ci.nsIDOMXULMenuListElement);
+    let win = element.ownerGlobal;
+    return (element instanceof win.HTMLSelectElement || element instanceof Ci.nsIDOMXULMenuListElement);
   },
 
   // Return a list of Option elements within a Select excluding
   // any that were not visible.
   getListForElement: function(element) {
     let index = 0;
     let items = [];
     this.forVisibleOptions(element, function(node, options,parent) {
@@ -97,57 +107,60 @@ var SelectHelper = {
       items.push(item);
       index++;
     });
     return items;
   },
 
   // Apply a function to all visible Option elements in a Select
   forVisibleOptions: function(element, aFunction, parent = null) {
+    let win = element.ownerGlobal;
     if (element instanceof Ci.nsIDOMXULMenuListElement) {
       element = element.menupopup;
     }
     let children = element.children;
     let numChildren = children.length;
 
 
     // if there are no children in this select, we add a dummy row so that at least something appears
     if (numChildren == 0) {
       aFunction.call(this, {label: ""}, {isGroup: false}, parent);
     }
 
     for (let i = 0; i < numChildren; i++) {
       let child = children[i];
-      let style = window.getComputedStyle(child);
+      let style = win.getComputedStyle(child);
       if (style.display !== "none") {
-        if (child instanceof HTMLOptionElement ||
+        if (child instanceof win.HTMLOptionElement ||
             child instanceof Ci.nsIDOMXULSelectControlItemElement) {
           aFunction.call(this, child, {isGroup: false}, parent);
-        } else if (child instanceof HTMLOptGroupElement) {
+        } else if (child instanceof win.HTMLOptGroupElement) {
           aFunction.call(this, child, {isGroup: true});
           this.forVisibleOptions(child, aFunction, child);
         }
       }
     }
   },
 
   fireOnChange: function(element) {
+    let win = element.ownerGlobal;
     let event = element.ownerDocument.createEvent("Events");
     event.initEvent("change", true, true, element.defaultView, 0,
         false, false, false, false, null);
-    setTimeout(function() {
+    win.setTimeout(function() {
       element.dispatchEvent(event);
     }, 0);
   },
 
   fireOnCommand: function(element) {
+    let win = element.ownerGlobal;
     let event = element.ownerDocument.createEvent("XULCommandEvent");
     event.initCommandEvent("command", true, true, element.defaultView, 0,
         false, false, false, false, null);
-    setTimeout(function() {
+    win.setTimeout(function() {
       element.dispatchEvent(event);
     }, 0);
   },
 
   _isDisabledElement : function(element) {
     let currentElement = element;
     while (currentElement) {
       // Must test with === in case a form has a field named "disabled". See bug 1263589.
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -124,18 +124,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator",
   "@mozilla.org/gfx/fontenumerator;1",
   "nsIFontEnumerator");
 
 var GlobalEventDispatcher = EventDispatcher.instance;
 var WindowEventDispatcher = EventDispatcher.for(window);
 
 var lazilyLoadedBrowserScripts = [
-  ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
-  ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
   ["MasterPassword", "chrome://browser/content/MasterPassword.js"],
   ["PluginHelper", "chrome://browser/content/PluginHelper.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
   ["Linkifier", "chrome://browser/content/Linkify.js"],
   ["CastingApps", "chrome://browser/content/CastingApps.js"],
   ["RemoteDebugger", "chrome://browser/content/RemoteDebugger.js"],
 ];
 if (!AppConstants.RELEASE_OR_BETA) {
@@ -4736,19 +4734,16 @@ Tab.prototype = {
 var BrowserEventHandler = {
   init: function init() {
     BrowserApp.deck.addEventListener("touchend", this, true);
 
     BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport);
     BrowserApp.deck.addEventListener("MozMouseHittest", this, true);
     BrowserApp.deck.addEventListener("OpenMediaWithExternalApp", this, true);
 
-    InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
-    InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
-
     // ReaderViews support backPress listeners.
     WindowEventDispatcher.registerListener((event, data, callback) => {
       callback.onSuccess(Reader.onBackPress(BrowserApp.selectedTab.id));
     }, "Browser:OnBackPressed");
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -36,16 +36,17 @@ component {C6E8C44D-9F39-4AF7-BCC0-76E38
 contract @mozilla.org/content-permission/prompt;1 {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}
 
 # PromptService.js
 component {9a61149b-2276-4a0a-b79c-be994ad106cf} PromptService.js
 contract @mozilla.org/prompter;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
 contract @mozilla.org/embedcomp/prompt-service;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
 component {80dae1e9-e0d2-4974-915f-f97050fa8068} PromptService.js
 contract @mozilla.org/network/authprompt-adapter-factory;1 {80dae1e9-e0d2-4974-915f-f97050fa8068}
+category app-startup PromptService service,@mozilla.org/prompter;1
 
 # PresentationDevicePrompt.js
 component {388bd149-c919-4a43-b646-d7ec57877689} PresentationDevicePrompt.js
 contract @mozilla.org/presentation-device/prompt;1 {388bd149-c919-4a43-b646-d7ec57877689}
 
 # PresentationRequestUIGlue.js
 component {9c550ef7-3ff6-4bd1-9ad1-5a3735b90d21} PresentationRequestUIGlue.js
 contract @mozilla.org/presentation/requestuiglue;1 {9c550ef7-3ff6-4bd1-9ad1-5a3735b90d21}
--- a/mobile/android/components/PromptService.js
+++ b/mobile/android/components/PromptService.js
@@ -16,17 +16,57 @@ var gPromptService = null;
 
 function PromptService() {
   gPromptService = this;
 }
 
 PromptService.prototype = {
   classID: Components.ID("{9a61149b-2276-4a0a-b79c-be994ad106cf}"),
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+  QueryInterface: XPCOMUtils.generateQI([
+      Ci.nsIObserver, Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+
+  loadSubscript: function(aName, aScript) {
+    let sandbox = {};
+    Services.scriptloader.loadSubScript(aScript, sandbox);
+    return sandbox[aName];
+  },
+
+  /* ----------  nsIObserver  ---------- */
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "app-startup": {
+        Services.obs.addObserver(this, "chrome-document-global-created");
+        Services.obs.addObserver(this, "content-document-global-created");
+        break;
+      }
+      case "chrome-document-global-created":
+      case "content-document-global-created": {
+        let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
+                          .rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindow);
+        if (win !== aSubject) {
+          // Only attach to top-level windows.
+          return;
+        }
+        if (!this.selectHelper) {
+          this.selectHelper = this.loadSubscript(
+              "SelectHelper", "chrome://browser/content/SelectHelper.js");
+        }
+        if (!this.inputWidgetHelper) {
+          this.inputWidgetHelper = this.loadSubscript(
+              "InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js");
+        }
+        win.addEventListener("click", this.selectHelper); // non-capture
+        win.addEventListener("click", this.inputWidgetHelper); // non-capture
+        break;
+      }
+    }
+  },
 
   /* ----------  nsIPromptFactory  ---------- */
   // XXX Copied from nsPrompter.js.
   getPrompt: function getPrompt(domWin, iid) {
     // This is still kind of dumb; the C++ code delegated to login manager
     // here, which in turn calls back into us via nsIPromptService2.
     if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPrompt)) {
       try {