Bug 1459556 - Part 3 - Remove the "handlers" binding. r=bgrins draft
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Thu, 10 May 2018 21:39:40 +0100
changeset 793819 f07dee9c42507b276dce9f7786d61019e39f27b2
parent 792517 f2d9ecc6d1fd19369bd868d0e447744c53e1a5a6
push id109507
push userpaolo.mozmail@amadzone.org
push dateThu, 10 May 2018 20:40:21 +0000
reviewersbgrins
bugs1459556
milestone62.0a1
Bug 1459556 - Part 3 - Remove the "handlers" binding. r=bgrins MozReview-Commit-ID: GOUOKuoR1rs
browser/components/preferences/handlers.css
browser/components/preferences/handlers.xml
browser/components/preferences/in-content/main.js
browser/components/preferences/jar.mn
--- a/browser/components/preferences/handlers.css
+++ b/browser/components/preferences/handlers.css
@@ -1,16 +1,12 @@
 /* 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/. */
 
-#handlersView > richlistitem {
-  -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler");
-}
-
 #containersView > richlistitem {
   -moz-binding: none;
 }
 
 /**
  * Make the icons appear.
  * Note: we display the icon box for every item whether or not it has an icon
  * so the labels of all the items align vertically.
deleted file mode 100644
--- a/browser/components/preferences/handlers.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- 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/. -->
-<!-- import-globals-from in-content/main.js -->
-
-<bindings id="handlerBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="handler" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <content>
-      <!-- This has been copied to "main.js" -->
-      <xul:hbox flex="1" equalsize="always">
-        <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription">
-          <xul:image src="moz-icon://goat?size=16" class="typeIcon"
-                     xbl:inherits="src=typeIcon" height="16" width="16"/>
-          <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/>
-        </xul:hbox>
-        <xul:hbox anonid="not-selected" flex="1" align="center" xbl:inherits="tooltiptext=actionDescription">
-          <xul:image xbl:inherits="src=actionIcon" height="16" width="16" class="actionIcon"/>
-          <xul:label flex="1" crop="end" xbl:inherits="value=actionDescription"/>
-        </xul:hbox>
-        <xul:hbox hidden="true" anonid="selected" flex="1">
-          <xul:menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1"
-                        xbl:inherits="tooltiptext=actionDescription"
-                        oncommand="gMainPane.onSelectAction(event.originalTarget)">
-            <xul:menupopup/>
-          </xul:menulist>
-        </xul:hbox>
-      </xul:hbox>
-    </content>
-  </binding>
-
-</bindings>
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1537,18 +1537,17 @@ var gMainPane = {
 
     var visibleTypes = this._visibleTypes;
 
     // If the user is filtering the list, then only show matching types.
     if (this._filter.value)
       visibleTypes = visibleTypes.filter(this._matchesFilter, this);
 
     for (let visibleType of visibleTypes) {
-      let item = new HandlerListItem(visibleType);
-      this._list.appendChild(item.node);
+      let item = new HandlerListItem(visibleType, this._list);
 
       if (visibleType.type === lastSelectedType) {
         this._list.selectedItem = item.node;
       }
     }
   },
 
   _matchesFilter(aType) {
@@ -1603,18 +1602,17 @@ var gMainPane = {
 
   /**
    * Rebuild the actions menu for the selected entry.  Gets called by
    * the richlistitem constructor when an entry in the list gets selected.
    */
   rebuildActionsMenu() {
     var typeItem = this._list.selectedItem;
     var handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper;
-    var menu =
-      document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+    var menu = typeItem.querySelector("[class=actionsMenu]");
 
     var menuPopup = menu.menupopup;
 
     // Clear out existing items.
     while (menuPopup.hasChildNodes())
       menuPopup.removeChild(menuPopup.lastChild);
 
     let internalMenuItem;
@@ -1998,18 +1996,17 @@ var gMainPane = {
       // Rebuild the actions menu whether the user picked an app or canceled.
       // If they picked an app, we want to add the app to the menu and select it.
       // If they canceled, we want to go back to their previous selection.
       this.rebuildActionsMenu();
 
       // If the user picked a new app from the menu, select it.
       if (aHandlerApp) {
         let typeItem = this._list.selectedItem;
-        let actionsMenu =
-          document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+        let actionsMenu = typeItem.querySelector("[class=actionsMenu]");
         let menuItems = actionsMenu.menupopup.childNodes;
         for (let i = 0; i < menuItems.length; i++) {
           let menuItem = menuItems[i];
           if (menuItem.handlerApp && menuItem.handlerApp.equals(aHandlerApp)) {
             actionsMenu.selectedIndex = i;
             this.onSelectAction(menuItem);
             break;
           }
@@ -2443,79 +2440,110 @@ ArrayEnumerator.prototype = {
     return this._contents[this._index++];
   }
 };
 
 function isFeedType(t) {
   return t == TYPE_MAYBE_FEED || t == TYPE_MAYBE_VIDEO_FEED || t == TYPE_MAYBE_AUDIO_FEED;
 }
 
+// Helper function needed to create a DOM that will still get XBL attached
+// when accessing DOM nodes. This is needed because:
+// If you do something like `textbox = document.createElement("textbox")`,
+// then `textbox` won't have a XBL binding attached, since the JS reflector
+// gets created before it's in the document. This is usually fine, since
+// the textbox will get a binding attached after layout, but if the
+// parent node is hidden, then the textbox never will get it.
+// Using this function, we append the elements into the DOM before accessing
+// them directly from JS, so the constructor will run eagerly on the next
+// JS access once it's appended into the parent.
+function parseDOM(str) {
+  const d = new DOMParser();
+  d.forceEnableXULXBL();
+  const doc = d.parseFromString(`
+    <box xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+      ${str.replace(/>\s+</g, "><")}
+    </box>
+  `, "application/xml");
+  const range = doc.createRange();
+  range.selectNodeContents(doc.firstChild);
+  return range.extractContents();
+}
+
+let gHandlerListItemFragment = parseDOM(`
+  <xul:richlistitem>
+    <xul:hbox flex="1" equalsize="always">
+      <xul:hbox flex="1" align="center" inherits="tooltiptext=typeDescription">
+        <xul:image src="moz-icon://goat?size=16" class="typeIcon"
+                   inherits="src=typeIcon" height="16" width="16"/>
+        <xul:label flex="1" crop="end" inherits="value=typeDescription"/>
+      </xul:hbox>
+      <xul:hbox anonid="not-selected" flex="1" align="center" inherits="tooltiptext=actionDescription">
+        <xul:image inherits="src=actionIcon" height="16" width="16" class="actionIcon"/>
+        <xul:label flex="1" crop="end" inherits="value=actionDescription"/>
+      </xul:hbox>
+      <xul:hbox hidden="true" anonid="selected" flex="1">
+        <xul:menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1"
+                      inherits="tooltiptext=actionDescription">
+          <xul:menupopup/>
+        </xul:menulist>
+      </xul:hbox>
+    </xul:hbox>
+  </xul:richlistitem>
+`);
+
 /**
  * This is associated to <richlistitem> elements in the handlers view.
  */
 class HandlerListItem {
   static forNode(node) {
     return gNodeToObjectMap.get(node);
   }
 
-  constructor(handlerInfoWrapper) {
+  constructor(handlerInfoWrapper, list) {
     this.handlerInfoWrapper = handlerInfoWrapper;
-    this.node = document.createElement("richlistitem");
-    /*
-      <xul:hbox flex="1" equalsize="always">
-        <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription">
-          <xul:image src="moz-icon://goat?size=16" class="typeIcon"
-                     xbl:inherits="src=typeIcon" height="16" width="16"/>
-          <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/>
-        </xul:hbox>
-        <xul:hbox anonid="not-selected" flex="1" align="center" xbl:inherits="tooltiptext=actionDescription">
-          <xul:image xbl:inherits="src=actionIcon" height="16" width="16" class="actionIcon"/>
-          <xul:label flex="1" crop="end" xbl:inherits="value=actionDescription"/>
-        </xul:hbox>
-        <xul:hbox hidden="true" anonid="selected" flex="1">
-          <xul:menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1"
-                        xbl:inherits="tooltiptext=actionDescription"
-                        oncommand="gMainPane.onSelectAction(event.originalTarget)">
-            <xul:menupopup/>
-          </xul:menulist>
-        </xul:hbox>
-      </xul:hbox>
-    */
+
+    list.appendChild(document.importNode(gHandlerListItemFragment, true));
+    this.node = list.lastElementChild;
     gNodeToObjectMap.set(this.node, this);
 
+    this.node.querySelector('[class="actionsMenu"]').addEventListener("command",
+      event => gMainPane.onSelectAction(event.originalTarget));
+
     this.node.setAttribute("type", this.handlerInfoWrapper.type);
-    this.node.setAttribute("typeDescription",
-                           this.handlerInfoWrapper.typeDescription);
+    this.node.querySelector('[inherits="value=typeDescription"]')
+             .setAttribute("value", this.handlerInfoWrapper.typeDescription);
     if (this.handlerInfoWrapper.smallIcon) {
-      this.node.setAttribute("typeIcon", this.handlerInfoWrapper.smallIcon);
-    } else {
-      this.node.removeAttribute("typeIcon");
+      this.node.querySelector('[inherits="src=typeIcon"]')
+               .setAttribute("src", this.handlerInfoWrapper.smallIcon);
     }
 
     this.refreshAction();
   }
 
   refreshAction() {
-    this.node.setAttribute("actionDescription",
-                           this.handlerInfoWrapper.actionDescription);
+    this.node.querySelector('[inherits="value=actionDescription"]')
+             .setAttribute("value", this.handlerInfoWrapper.actionDescription);
     if (this.handlerInfoWrapper.actionIconClass) {
       this.node.setAttribute(APP_ICON_ATTR_NAME,
                              this.handlerInfoWrapper.actionIconClass);
-      this.node.removeAttribute("actionIcon");
+      this.node.querySelector('[inherits="src=actionIcon"]')
+               .removeAttribute("src");
     } else {
       this.node.removeAttribute(APP_ICON_ATTR_NAME);
-      this.node.setAttribute("actionIcon", this.handlerInfoWrapper.actionIcon);
+      this.node.querySelector('[inherits="src=actionIcon"]')
+               .setAttribute("src", this.handlerInfoWrapper.actionIcon);
     }
   }
 
   set showActionsMenu(value) {
-    document.getAnonymousElementByAttribute(this.node, "anonid", "selected")
-            .setAttribute("hidden", !value);
-    document.getAnonymousElementByAttribute(this.node, "anonid", "not-selected")
-            .setAttribute("hidden", !!value);
+    this.node.querySelector('[anonid="selected"]')
+             .setAttribute("hidden", !value);
+    this.node.querySelector('[anonid="not-selected"]')
+             .setAttribute("hidden", !!value);
   }
 }
 
 /**
  * This object wraps nsIHandlerInfo with some additional functionality
  * the Applications prefpane needs to display and allow modification of
  * the list of handled types.
  *
--- a/browser/components/preferences/jar.mn
+++ b/browser/components/preferences/jar.mn
@@ -11,17 +11,16 @@ browser.jar:
     content/browser/preferences/clearSiteData.js
     content/browser/preferences/clearSiteData.xul
 *   content/browser/preferences/colors.xul
     content/browser/preferences/colors.js
     content/browser/preferences/connection.xul
     content/browser/preferences/connection.js
     content/browser/preferences/fonts.xul
     content/browser/preferences/fonts.js
-    content/browser/preferences/handlers.xml
     content/browser/preferences/handlers.css
     content/browser/preferences/languages.xul
     content/browser/preferences/languages.js
     content/browser/preferences/permissions.xul
     content/browser/preferences/sitePermissions.xul
     content/browser/preferences/sitePermissions.js
     content/browser/preferences/sitePermissions.css
     content/browser/preferences/containers.xul