Bug 1263887 - Update <select>'s on mutation for e10s. r=Felipe
☠☠ backed out by 8ba674386af9 ☠ ☠
authorMike Conley <mconley@mozilla.com>
Fri, 15 Apr 2016 22:00:36 -0400
changeset 332451 c0730eb0c5fc863837cbf641f659a482069d7b72
parent 332450 3c2a87839422d7baa1cae7c1433e9dcaa2aa129d
child 332452 3ae9a6bb08211560f6c27a362de4243e676266e9
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersFelipe
bugs1263887
milestone48.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 1263887 - Update <select>'s on mutation for e10s. r=Felipe MozReview-Commit-ID: Ev9w4C4AzOE
toolkit/modules/SelectContentHelper.jsm
toolkit/modules/SelectParentHelper.jsm
--- a/toolkit/modules/SelectContentHelper.jsm
+++ b/toolkit/modules/SelectContentHelper.jsm
@@ -9,50 +9,62 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
                                    "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+                                  "resource://gre/modules/DeferredTask.jsm");
 
 const kStateHover = 0x00000004; // NS_EVENT_STATE_HOVER
 
 this.EXPORTED_SYMBOLS = [
   "SelectContentHelper"
 ];
 
 this.SelectContentHelper = function (aElement, aGlobal) {
   this.element = aElement;
   this.initialSelection = aElement[aElement.selectedIndex] || null;
   this.global = aGlobal;
   this.init();
   this.showDropDown();
+  this._updateTimer = new DeferredTask(this._update.bind(this), 0);
 }
 
 this.SelectContentHelper.prototype = {
   init: function() {
     this.global.addMessageListener("Forms:SelectDropDownItem", this);
     this.global.addMessageListener("Forms:DismissedDropDown", this);
     this.global.addMessageListener("Forms:MouseOver", this);
     this.global.addMessageListener("Forms:MouseOut", this);
     this.global.addEventListener("pagehide", this);
-    this.global.addEventListener("mozhidedropdown", this);
+    let MutationObserver = this.element.ownerDocument.defaultView.MutationObserver;
+    this.mut = new MutationObserver(mutations => {
+      // Something changed the <select> while it was open, so
+      // we'll poke a DeferredTask to update the parent sometime
+      // in the very near future.
+      this._updateTimer.arm();
+    });
+    this.mut.observe(this.element, {childList: true, subtree: true});
   },
 
   uninit: function() {
     this.global.removeMessageListener("Forms:SelectDropDownItem", this);
     this.global.removeMessageListener("Forms:DismissedDropDown", this);
     this.global.removeMessageListener("Forms:MouseOver", this);
     this.global.removeMessageListener("Forms:MouseOut", this);
     this.global.removeEventListener("pagehide", this);
-    this.global.removeEventListener("mozhidedropdown", this);
     this.element = null;
     this.global = null;
+    this.mut.disconnect();
+    this._updateTimer.disarm();
+    this._updateTimer = null;
   },
 
   showDropDown: function() {
     let rect = this._getBoundingContentRect();
 
     this.global.sendAsyncMessage("Forms:ShowDropDown", {
       rect: rect,
       options: this._buildOptionList(),
@@ -64,16 +76,25 @@ this.SelectContentHelper.prototype = {
   _getBoundingContentRect: function() {
     return BrowserUtils.getElementBoundingScreenRect(this.element);
   },
 
   _buildOptionList: function() {
     return buildOptionListForChildren(this.element);
   },
 
+  _update() {
+    // The <select> was updated while the dropdown was open.
+    // Let's send up a new list of options.
+    this.global.sendAsyncMessage("Forms:UpdateDropDown", {
+      options: this._buildOptionList(),
+      selectedIndex: this.element.selectedIndex,
+    });
+  },
+
   receiveMessage: function(message) {
     switch (message.name) {
       case "Forms:SelectDropDownItem":
         this.element.selectedIndex = message.data.value;
         break;
 
       case "Forms:DismissedDropDown":
         if (this.initialSelection != this.element.item(this.element.selectedIndex)) {
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -4,28 +4,32 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "SelectParentHelper"
 ];
 
 var currentBrowser = null;
+var currentMenulist = null;
+var currentZoom = 1;
 
 this.SelectParentHelper = {
   populate: function(menulist, items, selectedIndex, zoom) {
     // Clear the current contents of the popup
     menulist.menupopup.textContent = "";
+    currentZoom = zoom;
+    currentMenulist = menulist;
     populateChildren(menulist, items, selectedIndex, zoom);
   },
 
   open: function(browser, menulist, rect) {
     menulist.hidden = false;
     currentBrowser = browser;
-    this._registerListeners(menulist.menupopup);
+    this._registerListeners(browser, menulist.menupopup);
 
     menulist.menupopup.openPopupAtScreenRect("after_start", rect.left, rect.top, rect.width, rect.height, false, false);
     menulist.selectedItem.scrollIntoView();
   },
 
   hide: function(menulist, browser) {
     if (currentBrowser == browser) {
       menulist.menupopup.hidePopup();
@@ -47,36 +51,54 @@ this.SelectParentHelper = {
           currentBrowser.messageManager.sendAsyncMessage("Forms:SelectDropDownItem", {
             value: event.target.value
           });
         }
         break;
 
       case "popuphidden":
         currentBrowser.messageManager.sendAsyncMessage("Forms:DismissedDropDown", {});
+        let popup = event.target;
+        this._unregisterListeners(currentBrowser, popup);
+        popup.parentNode.hidden = true;
         currentBrowser = null;
-        let popup = event.target;
-        this._unregisterListeners(popup);
-        popup.parentNode.hidden = true;
+        currentMenulist = null;
+        currentZoom = 1;
         break;
     }
   },
 
-  _registerListeners: function(popup) {
+  receiveMessage(msg) {
+    if (msg.name == "Forms:UpdateDropDown") {
+      // Sanity check - we'd better know what the currently
+      // opened menulist is, and what browser it belongs to...
+      if (!currentMenulist || !currentBrowser) {
+        return;
+      }
+
+      let options = msg.data.options;
+      let selectedIndex = msg.data.selectedIndex;
+      this.populate(currentMenulist, options, selectedIndex, currentZoom);
+    }
+  },
+
+  _registerListeners: function(browser, popup) {
     popup.addEventListener("command", this);
     popup.addEventListener("popuphidden", this);
     popup.addEventListener("mouseover", this);
     popup.addEventListener("mouseout", this);
+    browser.messageManager.addMessageListener("Forms:UpdateDropDown", this);
   },
 
-  _unregisterListeners: function(popup) {
+  _unregisterListeners: function(browser, popup) {
     popup.removeEventListener("command", this);
     popup.removeEventListener("popuphidden", this);
     popup.removeEventListener("mouseover", this);
     popup.removeEventListener("mouseout", this);
+    browser.messageManager.removeMessageListener("Forms:UpdateDropDown", this);
   },
 
 };
 
 function populateChildren(menulist, options, selectedIndex, zoom,
                           isInGroup = false, isGroupDisabled = false, adjustedTextSize = -1) {
   let element = menulist.menupopup;