Bug 1534345 - Migrate searchvalue binding to custom element. r=mkmelin
authorArshad Khan <arshdkhn1@gmail.com>
Fri, 12 Apr 2019 23:24:17 +0200
changeset 26331 1aad2ba888ec2d7ba3e16259b7967da1e356aad3
parent 26330 e614af52388152d486513d7ff5b511c2a69fcb2e
child 26332 826410daf770d13ad7ac81e0720c0e9a17cb6a0e
push id15788
push usermozilla@jorgk.com
push dateFri, 12 Apr 2019 22:02:02 +0000
treeherdercomm-central@826410daf770 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1534345
Bug 1534345 - Migrate searchvalue binding to custom element. r=mkmelin
mail/base/content/mailWidgets.xml
mail/base/content/messenger.css
mail/test/mozmill/folder-display/test-mail-views.js
mail/test/mozmill/folder-widget/test-message-filters.js
mail/test/mozmill/search-window/test-search-window.js
mailnews/base/search/content/FilterEditor.js
mailnews/base/search/content/searchTerm.js
mailnews/base/search/content/searchWidgets.js
--- a/mail/base/content/mailWidgets.xml
+++ b/mail/base/content/mailWidgets.xml
@@ -669,438 +669,16 @@
             } else {
               this.value = menulist.value;
             }
           ]]>
         </body>
       </method>
     </implementation>
   </binding>
-  <!-- searchvalue - a widget which dynamically changes its user interface
-       depending on what type of data it's supposed to be showing
-       currently handles arbitrary text entry, and menulists for
-       priority, status, junk status, tags, hasAttachment status,
-       and addressbook
-  -->
-  <binding id="searchvalue" name="searchValue">
-    <content>
-      <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/>
-      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
-        <xul:menupopup class="search-value-popup">
-          <xul:menuitem value="6" stringTag="priorityHighest" class="search-value-menuitem"/>
-          <xul:menuitem value="5" stringTag="priorityHigh" class="search-value-menuitem"/>
-          <xul:menuitem value="4" stringTag="priorityNormal" class="search-value-menuitem"/>
-          <xul:menuitem value="3" stringTag="priorityLow" class="search-value-menuitem"/>
-          <xul:menuitem value="2" stringTag="priorityLowest" class="search-value-menuitem"/>
-        </xul:menupopup>
-      </xul:menulist>
-      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
-        <xul:menupopup class="search-value-popup">
-          <xul:menuitem value="2" stringTag="replied" class="search-value-menuitem"/>
-          <xul:menuitem value="1" stringTag="read" class="search-value-menuitem"/>
-          <xul:menuitem value="65536" stringTag="new" class="search-value-menuitem"/>
-          <xul:menuitem value="4096" stringTag="forwarded" class="search-value-menuitem"/>
-          <xul:menuitem value="4" stringTag="flagged" class="search-value-menuitem"/>
-        </xul:menupopup>
-      </xul:menulist>
-      <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/>
-      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
-        <xul:menupopup class="addrbooksPopup" localonly="true"/>
-      </xul:menulist>
-      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
-        <xul:menupopup class="search-value-popup">
-        </xul:menupopup>
-      </xul:menulist>
-      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
-        <xul:menupopup class="search-value-popup">
-          <xul:menuitem value="2" stringTag="junk" class="search-value-menuitem"/>
-        </xul:menupopup>
-      </xul:menulist>
-      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
-        <xul:menupopup class="search-value-popup">
-          <xul:menuitem value="0" stringTag="hasAttachments" class="search-value-menuitem"/>
-        </xul:menupopup>
-      </xul:menulist>
-      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
-        <xul:menupopup class="search-value-popup">
-          <xul:menuitem value="plugin" stringTag="junkScoreOriginPlugin"
-                        class="search-value-menuitem"/>
-          <xul:menuitem value="user" stringTag="junkScoreOriginUser"
-                        class="search-value-menuitem"/>
-          <xul:menuitem value="filter" stringTag="junkScoreOriginFilter"
-                        class="search-value-menuitem"/>
-          <xul:menuitem value="whitelist" stringTag="junkScoreOriginWhitelist"
-                        class="search-value-menuitem"/>
-          <xul:menuitem value="imapflag" stringTag="junkScoreOriginImapFlag"
-                        class="search-value-menuitem"/>
-        </xul:menupopup>
-      </xul:menulist>
-      <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"
-                   type="number"/>
-      <xul:hbox flex="1" class="search-value-custom" xbl:inherits="disabled"/>
-    </content>
-    <implementation>
-      <field name="internalOperator">null</field>
-      <field name="internalAttribute">null</field>
-      <field name="internalValue">null</field>
-      <property name="opParentValue" onget="return this.internalOperator;">
-        <setter>
-          <![CDATA[
-            // noop if we're not changing it
-            if (this.internalOperator == val)
-              return val;
-
-            // Keywords has the null field IsEmpty
-            if (this.searchAttribute == Ci.nsMsgSearchAttrib.Keywords) {
-              if (val == Ci.nsMsgSearchOp.IsEmpty ||
-                  val == Ci.nsMsgSearchOp.IsntEmpty)
-                this.setAttribute("selectedIndex", "-1");
-              else
-                this.setAttribute("selectedIndex", "5");
-            }
-
-            // JunkStatus has the null field IsEmpty
-            if (this.searchAttribute == Ci.nsMsgSearchAttrib.JunkStatus) {
-              if (val == Ci.nsMsgSearchOp.IsEmpty ||
-                  val == Ci.nsMsgSearchOp.IsntEmpty)
-                this.setAttribute("selectedIndex", "-1");
-              else
-                this.setAttribute("selectedIndex", "6");
-            }
-
-              // if it's not sender, to, cc, alladdresses, or toorcc, we don't care
-              if (this.searchAttribute != Ci.nsMsgSearchAttrib.Sender &&
-                this.searchAttribute != Ci.nsMsgSearchAttrib.To &&
-                this.searchAttribute != Ci.nsMsgSearchAttrib.ToOrCC &&
-                this.searchAttribute != Ci.nsMsgSearchAttrib.AllAddresses &&
-                this.searchAttribute != Ci.nsMsgSearchAttrib.CC) {
-              this.internalOperator = val;
-              return val;
-            }
-
-            var children = document.getAnonymousNodes(this);
-            if (val == Ci.nsMsgSearchOp.IsntInAB ||
-                val == Ci.nsMsgSearchOp.IsInAB) {
-              // if the old internalOperator was
-              // IsntInAB or IsInAB, and the new internalOperator is
-              // IsntInAB or IsInAB, noop because the search value
-              // was an ab type, and it still is.
-              // otherwise, switch to the ab picker and select the PAB
-              if (this.internalOperator != Ci.nsMsgSearchOp.IsntInAB &&
-                  this.internalOperator != Ci.nsMsgSearchOp.IsInAB) {
-                var abs = children[4].querySelector(`[value="moz-abmdbdirectory://abook.mab"]`);
-                if (abs)
-                  children[4].selectedItem = abs;
-                this.setAttribute("selectedIndex", "4");
-              }
-            } else if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
-                       this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
-              // if the old internalOperator wasn't
-              // IsntInAB or IsInAB, and the new internalOperator isn't
-              // IsntInAB or IsInAB, noop because the search value
-              // wasn't an ab type, and it still isn't.
-              // otherwise, switch to the textbox and clear it
-              children[0].value = "";
-              this.setAttribute("selectedIndex", "0");
-            }
-
-            this.internalOperator = val;
-            return val;
-          ]]>
-        </setter>
-      </property>
-      <!-- parentValue forwards to the attribute -->
-      <property name="parentValue" onset="return this.searchAttribute=val;"
-                                   onget="return this.searchAttribute;"/>
-      <property name="searchAttribute" onget="return this.internalAttribute;">
-        <setter>
-          <![CDATA[
-            // noop if we're not changing it
-            if (this.internalAttribute == val) return val;
-            this.internalAttribute = val;
-
-            // if the searchAttribute changing, null out the internalOperator
-            this.internalOperator = null;
-
-            // we inherit from a deck, so just use it's index attribute
-            // to hide/show widgets
-            if (isNaN(val)) { // Is this a custom attribute?
-              this.setAttribute("selectedIndex", "10");
-              let customHbox = document.getAnonymousNodes(this)[10];
-              if (this.internalValue)
-                customHbox.setAttribute("value", this.internalValue.str);
-              // the searchAttribute attribute is intended as a selector in
-              // CSS for custom search terms to bind a custom value
-              customHbox.setAttribute("searchAttribute", val);
-            } else if (val == Ci.nsMsgSearchAttrib.Priority) {
-              this.setAttribute("selectedIndex", "1");
-            } else if (val == Ci.nsMsgSearchAttrib.MsgStatus) {
-              this.setAttribute("selectedIndex", "2");
-            } else if (val == Ci.nsMsgSearchAttrib.Date) {
-              this.setAttribute("selectedIndex", "3");
-            } else if (val == Ci.nsMsgSearchAttrib.Sender) {
-              // since the internalOperator is null
-              // this is the same as the initial state
-              // the initial state for Sender isn't an ab type search
-              // it's a text search, so show the textbox
-              this.setAttribute("selectedIndex", "0");
-            } else if (val == Ci.nsMsgSearchAttrib.Keywords) {
-              this.setAttribute("selectedIndex", "5");
-            } else if (val == Ci.nsMsgSearchAttrib.JunkStatus) {
-              this.setAttribute("selectedIndex", "6");
-            } else if (val == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
-              this.setAttribute("selectedIndex", "7");
-            } else if (val == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
-              this.setAttribute("selectedIndex", "8");
-            } else if (val == Ci.nsMsgSearchAttrib.AgeInDays) {
-              let valueBox = document.getAnonymousNodes(this)[9];
-              valueBox.min = -40000; // ~-100 years
-              valueBox.max = 40000; // ~100 years
-              this.setAttribute("selectedIndex", "9");
-            } else if (val == Ci.nsMsgSearchAttrib.Size) {
-              let valueBox = document.getAnonymousNodes(this)[9];
-              valueBox.min = 0;
-              valueBox.max = 1000000000;
-              this.setAttribute("selectedIndex", "9");
-            } else if (val == Ci.nsMsgSearchAttrib.JunkPercent) {
-              let valueBox = document.getAnonymousNodes(this)[9];
-              valueBox.min = 0;
-              valueBox.max = 100;
-              this.setAttribute("selectedIndex", "9");
-            } else {
-              // a normal text field
-              this.setAttribute("selectedIndex", "0");
-            }
-            return val;
-          ]]>
-        </setter>
-      </property>
-      <property name="value" onget="return this.internalValue;">
-        <setter>
-          <![CDATA[
-          // val is a nsIMsgSearchValue object
-          this.internalValue = val;
-          var attrib = this.internalAttribute;
-          var children = document.getAnonymousNodes(this);
-          this.searchAttribute = attrib;
-          if (isNaN(attrib)) { // a custom term
-            let customHbox = document.getAnonymousNodes(this)[10];
-            customHbox.setAttribute("value", val.str);
-            return val;
-          }
-          if (attrib == Ci.nsMsgSearchAttrib.Priority) {
-            var matchingPriority =
-              children[1].querySelector(`[value="${val.priority}"]`);
-            if (matchingPriority)
-              children[1].selectedItem = matchingPriority;
-          } else if (attrib == Ci.nsMsgSearchAttrib.MsgStatus) {
-            var matchingStatus =
-              children[2].querySelector(`[value="${val.status}"]`);
-            if (matchingStatus)
-              children[2].selectedItem = matchingStatus;
-          } else if (attrib == Ci.nsMsgSearchAttrib.AgeInDays) {
-            children[9].value = val.age;
-          } else if (attrib == Ci.nsMsgSearchAttrib.Date) {
-            children[3].value = convertPRTimeToString(val.date);
-          } else if (attrib == Ci.nsMsgSearchAttrib.Sender ||
-                     attrib == Ci.nsMsgSearchAttrib.To ||
-                     attrib == Ci.nsMsgSearchAttrib.CC ||
-                     attrib == Ci.nsMsgSearchAttrib.AllAddresses ||
-                     attrib == Ci.nsMsgSearchAttrib.ToOrCC) {
-            if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
-                this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
-              var abs = children[4].querySelector(`[value="${val.str}"]`);
-              if (abs)
-                children[4].selectedItem = abs;
-            } else {
-              children[0].value = val.str;
-            }
-          } else if (attrib == Ci.nsMsgSearchAttrib.Keywords) {
-            var keywordVal = children[5].querySelector(`[value="${val.str}"]`);
-            if (keywordVal) {
-              children[5].value = val.str;
-              children[5].selectedItem = keywordVal;
-            }
-          } else if (attrib == Ci.nsMsgSearchAttrib.JunkStatus) {
-            var junkStatus =
-              children[6].querySelector(`[value="${val.junkStatus}"]`);
-            if (junkStatus)
-              children[6].selectedItem = junkStatus;
-          } else if (attrib == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
-            var hasAttachmentStatus =
-              children[7].querySelector(`[value="${val.hasAttachmentStatus}"]`);
-            if (hasAttachmentStatus)
-              children[7].selectedItem = hasAttachmentStatus;
-          } else if (attrib == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
-            var junkScoreOrigin =
-              children[8].querySelector(`[value="${val.str}"]`);
-            if (junkScoreOrigin)
-              children[8].selectedItem = junkScoreOrigin;
-          } else if (attrib == Ci.nsMsgSearchAttrib.JunkPercent) {
-            children[9].value = val.junkPercent;
-          } else if (attrib == Ci.nsMsgSearchAttrib.Size) {
-            children[9].value = val.size;
-          } else {
-            children[0].value = val.str;
-          }
-          return val;
-          ]]>
-        </setter>
-      </property>
-      <method name="save">
-        <body>
-          <![CDATA[
-            var searchValue = this.value;
-            var searchAttribute = this.searchAttribute;
-            var children = document.getAnonymousNodes(this);
-
-            searchValue.attrib = searchAttribute;
-            if (searchAttribute == Ci.nsMsgSearchAttrib.Priority) {
-              searchValue.priority = children[1].selectedItem.value;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.MsgStatus) {
-              searchValue.status = children[2].value;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.AgeInDays) {
-              searchValue.age = children[9].value;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.Date) {
-              searchValue.date = convertStringToPRTime(children[3].value);
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.Sender ||
-                       searchAttribute == Ci.nsMsgSearchAttrib.To ||
-                       searchAttribute == Ci.nsMsgSearchAttrib.CC ||
-                       searchAttribute == Ci.nsMsgSearchAttrib.AllAddresses ||
-                       searchAttribute == Ci.nsMsgSearchAttrib.ToOrCC) {
-              if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
-                  this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
-                searchValue.str = children[4].selectedItem.value;
-              } else {
-                searchValue.str = children[0].value;
-              }
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.Keywords) {
-              searchValue.str = children[5].value;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.JunkStatus) {
-              searchValue.junkStatus = children[6].value;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.JunkPercent) {
-              searchValue.junkPercent = children[9].value;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.Size) {
-              searchValue.size = children[9].value;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
-              searchValue.status = Ci.nsMsgMessageFlags.Attachment;
-            } else if (searchAttribute == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
-              searchValue.str = children[8].value;
-            } else if (isNaN(searchAttribute)) { // a custom term
-              searchValue.attrib = Ci.nsMsgSearchAttrib.Custom;
-              searchValue.str = children[10].getAttribute("value");
-            } else {
-              searchValue.str = children[0].value;
-            }
-          ]]>
-        </body>
-      </method>
-      <method name="saveTo">
-        <parameter name="searchValue"/>
-        <body>
-          <![CDATA[
-            this.internalValue = searchValue;
-            this.save();
-          ]]>
-        </body>
-      </method>
-      <method name="fillInTags">
-        <body>
-          <![CDATA[
-            let menulist = document.getAnonymousNodes(this)[5];
-            // Force initialization of the menulist custom element first.
-            customElements.upgrade(menulist);
-            let tagArray = MailServices.tags.getAllTags({});
-            for (var i = 0; i < tagArray.length; ++i) {
-              var taginfo = tagArray[i];
-              let newMenuItem = menulist.appendItem(taginfo.tag, taginfo.key);
-              if (!i)
-                menulist.selectedItem = newMenuItem;
-            }
-          ]]>
-        </body>
-      </method>
-      <method name="fillStringsForChildren">
-        <parameter name="parentNode"/>
-        <parameter name="bundle"/>
-        <body>
-          <![CDATA[
-            var children = parentNode.childNodes;
-            var len = children.length;
-            for (var i = 0; i < len; i++) {
-              var node = children[i];
-              var stringTag = node.getAttribute("stringTag");
-              if (stringTag) {
-                var attr = (node.tagName == "label") ? "value" : "label";
-                node.setAttribute(attr, bundle.GetStringFromName(stringTag));
-              }
-            }
-            // Force initialization of the menulist custom element.
-            customElements.upgrade(parentNode);
-          ]]>
-        </body>
-      </method>
-      <method name="initialize">
-        <parameter name="menulist"/>
-        <parameter name="bundle"/>
-        <body>
-          <![CDATA[
-            this.fillStringsForChildren(menulist.firstChild, bundle);
-          ]]>
-        </body>
-      </method>
-      <constructor>
-      <![CDATA[
-        // initialize strings
-        var bundle = Services.strings.createBundle("chrome://messenger/locale/messenger.properties");
-
-        // initialize the priority picker
-        this.initialize(document.getAnonymousNodes(this)[1], bundle);
-
-        // initialize the status picker
-        this.initialize(document.getAnonymousNodes(this)[2], bundle);
-
-        // initialize the date picker
-        var datePicker = document.getAnonymousNodes(this)[3];
-        var searchAttribute = this.searchAttribute;
-        var time;
-        if (searchAttribute == Ci.nsMsgSearchAttrib.Date)
-         time = datePicker.value;
-        else
-         time = new Date();
-        // do .value instead of .setAttribute("value", xxx);
-        // to work around for bug #179412
-        // (caused by bug #157210)
-        //
-        // the searchvalue widget has two textboxes
-        // one for text, one as a placeholder for a date / calendar widget
-        datePicker.value = convertDateToString(time);
-
-        // initialize the address book picker
-        this.initialize(document.getAnonymousNodes(this)[4], bundle);
-
-        // initialize the junk status picker
-        this.initialize(document.getAnonymousNodes(this)[6], bundle);
-
-        // initialize the has attachment status picker
-        this.initialize(document.getAnonymousNodes(this)[7], bundle);
-
-        // initialize the junk score origin picker
-        this.initialize(document.getAnonymousNodes(this)[8], bundle);
-
-        // initialize the tag list
-        this.fillInTags();
-      ]]>
-      </constructor>
-    </implementation>
-    <handlers>
-      <handler event="keypress" keycode="VK_RETURN" modifiers="accel any"
-               action="onEnterInSearchTerm(event);" preventdefault="true"/>
-    </handlers>
-  </binding>
 
   <binding id="splitmenu">
     <content>
       <xul:hbox anonid="menuitem" flex="1"
                 class="splitmenu-menuitem"
                 xbl:inherits="iconic,label,disabled,onclick=oncommand,_moz-menuactive=active"/>
       <xul:menu anonid="menu" class="splitmenu-menu"
                 xbl:inherits="disabled,_moz-menuactive=active"
--- a/mail/base/content/messenger.css
+++ b/mail/base/content/messenger.css
@@ -34,19 +34,18 @@
 mail-tagfield[collapsed="true"] {
   display: none;
 }
 
 #searchTermList > richlistitem {
   -moz-binding: url("chrome://messenger/content/searchWidgets.xml#filterlistitem");
 }
 
-searchvalue {
+search-value {
   display: -moz-deck;
-  -moz-binding: url("chrome://messenger/content/mailWidgets.xml#searchvalue");
 }
 
 searchterm {
   -moz-binding: url("chrome://messenger/content/mailWidgets.xml#searchterm");
 }
 
 .ruleaction {
   -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleaction");
--- a/mail/test/mozmill/folder-display/test-mail-views.js
+++ b/mail/test/mozmill/folder-display/test-mail-views.js
@@ -8,16 +8,17 @@ var MODULE_NAME = 'test-mail-views';
 
 var RELATIVE_ROOT = '../shared-modules';
 var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
 
 var baseFolder, savedFolder;
 var setUntagged, setTagged;
 
 var {MailViewConstants} = ChromeUtils.import("resource:///modules/MailViewManager.jsm");
+var elib = ChromeUtils.import("chrome://mozmill/content/modules/elementslib.jsm");
 
 var setupModule = function(module) {
   let fdh = collector.getModule('folder-display-helpers');
   fdh.installInto(module);
   let wh = collector.getModule('window-helpers');
   wh.installInto(module);
 
   // Create a folder with some messages that have no tags and some that are
@@ -57,18 +58,27 @@ function test_save_view_as_folder() {
   mc.window.ViewChange(MailViewConstants.kViewItemVirtual);
   wait_for_modal_dialog("mailnews:virtualFolderProperties");
 }
 
 function subtest_save_mail_view(savc) {
   // - make sure the name is right
   savc.assertValue(savc.eid("name"), baseFolder.prettyName + "-Important");
 
+  let elem = savc.window.document.getElementById("searchVal0");
+  let index = 0;
+
+  if (elem.hasAttribute("selectedIndex")) {
+    index = parseInt(elem.getAttribute("selectedIndex"));
+  }
+
+  elem = elem.childNodes[index];
+
   // - make sure the constraint is right
-  savc.assertValue(savc.aid("searchVal0", {crazyDeck: 0}), "$label1");
+  savc.assertValue(new elib.Elem(elem), "$label1");
 
   // - save it
   savc.window.onOK();
 }
 
 function test_verify_saved_mail_view() {
   // - make sure the folder got created
   savedFolder = baseFolder.getChildNamed(baseFolder.prettyName + "-Important");
--- a/mail/test/mozmill/folder-widget/test-message-filters.js
+++ b/mail/test/mozmill/folder-widget/test-message-filters.js
@@ -136,17 +136,17 @@ function create_simple_filter() {
     filterName.value = "A Simple Filter";
     let searchAttr = fec.e("searchAttr0");
     let attrList = searchAttr.childNodes[0];
     attrList.value = Ci.nsMsgSearchAttrib.To;
     let searchOp = fec.e("searchOp0");
     let opList = searchOp.childNodes[0];
     opList.value = Ci.nsMsgSearchOp.Is;
     let searchValList = fec.e("searchVal0");
-    let searchVal = fec.window.document.getAnonymousNodes(searchValList)[0];
+    let searchVal = searchValList.childNodes[0];
     searchVal.setAttribute("value", "test@foo.invalid");
 
     let filterActions = fec.e("filterActionList");
     let firstAction = filterActions.getItemAtIndex(0);
     firstAction.setAttribute("value", "markasflagged");
     fec.e("FilterEditor").acceptDialog();
   }
 
@@ -183,18 +183,18 @@ function test_address_books_appear_in_me
     let attrList = searchAttr.childNodes[0];
     attrList.value = Ci.nsMsgSearchAttrib.To;
     let searchOp = fec.e("searchOp0");
     let opList = searchOp.childNodes[0];
     opList.value = Ci.nsMsgSearchOp.IsInAB;
     let searchValue = fec.e("searchVal0");
 
     // The magic number "4" is because the address book list is the
-    // 4th child node of the searchvalue widget.
-    let abList = fec.window.document.getAnonymousNodes(searchValue)[4];
+    // 4th child node of the search-value widget.
+    let abList = searchValue.childNodes[4];
 
     // We should have 2 address books here - one for the Personal Address
     // Book, and one for Collected Addresses.  The LDAP address book should
     // not be shown, since it isn't a local address book.
     assert_equals(2, abList.itemCount, "Did not display the correct number "
                   + "of address books in the filter menu list.");
   }
 
--- a/mail/test/mozmill/search-window/test-search-window.js
+++ b/mail/test/mozmill/search-window/test-search-window.js
@@ -9,16 +9,17 @@
 
 "use strict";
 
 var MODULE_NAME = 'test-search-window';
 
 var RELATIVE_ROOT = '../shared-modules';
 var MODULE_REQUIRES = ['folder-display-helpers', 'search-window-helpers',
                        'window-helpers'];
+var elib = ChromeUtils.import("chrome://mozmill/content/modules/elementslib.jsm");
 
 function setupModule(module) {
   let fdh = collector.getModule('folder-display-helpers');
   fdh.installInto(module);
   let wh = collector.getModule('window-helpers');
   wh.installInto(module);
   let sh = collector.getModule('search-window-helpers');
   sh.installInto(module);
@@ -66,25 +67,39 @@ function test_enter_some_stuff() {
   // - put "foo" in the subject contains box
   // Each filter criterion is a listitem in the listbox with id=searchTermList.
   // Each filter criterion has id "searchRowN", and the textbox has id
   //  "searchValN" exposing the value on attribute "value".
   // XXX I am having real difficulty getting the click/type pair to actually
   //  get the text in there reliably.  I am just going to poke things directly
   //  into the text widget. (We used to use .aid instead of .a with swc.click
   //  and swc.type.)
-  let searchVal0 = swc.a("searchVal0", {crazyDeck: 0});
+  let searchVal0 = swc.window.document.getElementById("searchVal0");
+  let index = 0;
+
+  if (searchVal0.hasAttribute("selectedIndex")) {
+    index = parseInt(searchVal0.getAttribute("selectedIndex"));
+  }
+
+  searchVal0 = searchVal0.childNodes[index];
   searchVal0.value = "foo";
 
   // - add another subject box
   let plusButton = swc.eid("searchRow0", {tagName: "button", label: "+"});
   swc.click(plusButton);
 
   // - put "bar" in it
-  let searchVal1 = swc.a("searchVal1", {crazyDeck: 0});
+  let searchVal1 = swc.window.document.getElementById("searchVal1");
+  index = 0;
+
+  if (searchVal1.hasAttribute("selectedIndex")) {
+    index = parseInt(searchVal1.getAttribute("selectedIndex"));
+  }
+
+  searchVal1 = searchVal1.childNodes[index];
   searchVal1.value = "bar";
 }
 
 /**
  * Trigger the search, make sure the right results show up.
  */
 function test_go_search() {
   // - Trigger the search
@@ -226,20 +241,37 @@ function test_open_search_result_in_exis
 
 /**
  * Save the search, making sure the constraints propagated.
  */
 function subtest_save_search(savc) {
   // - make sure our constraint propagated
   // The query constraints are displayed using the same widgets (and code) that
   //  we used to enter them, so it's very similar to check.
-  let searchVal0 = savc.aid("searchVal0", {crazyDeck: 0});
+  let searchVal0 = swc.window.document.getElementById("searchVal0");
+  let index = 0;
+
+  if (searchVal0.hasAttribute("selectedIndex")) {
+    index = parseInt(searchVal0.getAttribute("selectedIndex"));
+  }
+
+  searchVal0 = new elib.Elem(searchVal0.childNodes[index]);
+
   savc.assertNode(searchVal0);
   savc.assertValue(searchVal0, "foo");
-  let searchVal1 = savc.aid("searchVal1", {crazyDeck: 0});
+
+  let searchVal1 = swc.window.document.getElementById("searchVal1");
+  index = 0;
+
+  if (searchVal1.hasAttribute("selectedIndex")) {
+    index = parseInt(searchVal1.getAttribute("selectedIndex"));
+  }
+
+  searchVal1 = new elib.Elem(searchVal1.childNodes[index]);
+
   savc.assertNode(searchVal1);
   savc.assertValue(searchVal1, "bar");
 
   // - name the search
   savc.type(savc.eid("name"), "SearchSaved");
 
   // - save it!
   // this will close the dialog, which wait_for_modal_dialog is making sure
--- a/mailnews/base/search/content/FilterEditor.js
+++ b/mailnews/base/search/content/FilterEditor.js
@@ -643,17 +643,17 @@ function AssignMeaningfulName()
   // If this is a Match All Messages Filter, we already know the name to assign.
   if (termRoot.matchAll)
     stub = gFilterBundle.getString( "matchAllFilterName" );
   else
   {
     // Assign a name based on the first search term.
     let searchValue = termRoot.searchvalue;
     let selIndex = searchValue.getAttribute( "selectedIndex" );
-    let children = document.getAnonymousNodes(searchValue);
+    let children = searchValue.childNodes;
     let activeItem = children[selIndex];
     let attribs = Ci.nsMsgSearchAttrib;
 
     // Term, Operator and Value are the three parts of a filter match
     // Term and Operator are easy to retrieve
     let term = termRoot.searchattribute.label;
     let operator = termRoot.searchoperator.label;
 
--- a/mailnews/base/search/content/searchTerm.js
+++ b/mailnews/base/search/content/searchTerm.js
@@ -188,17 +188,17 @@ function initializeSearchRows(scope, sea
  * @param matchAllValue boolean value from the first search term
  */
 function updateSearchTermsListbox(matchAllValue)
 {
   var searchTerms = document.getElementById("searchTermList");
   searchTerms.setAttribute("disabled", matchAllValue);
   var searchAttributeList = searchTerms.getElementsByTagName("search-attribute");
   var searchOperatorList = searchTerms.getElementsByTagName("search-operator");
-  var searchValueList = searchTerms.getElementsByTagName("searchvalue");
+  var searchValueList = searchTerms.getElementsByTagName("search-value");
   for (var i = 0; i < searchAttributeList.length; i++) {
       searchAttributeList[i].setAttribute("disabled", matchAllValue);
       searchOperatorList[i].setAttribute("disabled", matchAllValue);
       searchValueList[i].setAttribute("disabled", matchAllValue);
       if (!matchAllValue)
         searchValueList[i].removeAttribute("disabled");
   }
   var moreOrLessButtonsList = searchTerms.getElementsByTagName("button");
@@ -316,17 +316,17 @@ function booleanChanged(event) {
  * @param searchTerm  nsIMsgSearchTerm object to hold the search term
  * @param aUserAdded  boolean indicating if the row addition was initiated by the user
  *                    (e.g. via the '+' button)
  */
 function createSearchRow(index, scope, searchTerm, aUserAdded)
 {
     var searchAttr = document.createElement("search-attribute");
     var searchOp = document.createElement("search-operator");
-    var searchVal = document.createElement("searchvalue");
+    var searchVal = document.createElement("search-value");
 
     var moreButton = document.createElement("button");
     var lessButton = document.createElement("button");
     moreButton.setAttribute("class", "small-button");
     moreButton.setAttribute("oncommand", "onMore(event);");
     moreButton.setAttribute('label', '+');
     moreButton.setAttribute('tooltiptext', gMoreButtonTooltipText);
     lessButton.setAttribute("class", "small-button");
--- a/mailnews/base/search/content/searchWidgets.js
+++ b/mailnews/base/search/content/searchWidgets.js
@@ -1,14 +1,15 @@
 /**
  * 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/. */
 
-/* global MozXULElement, gFilter, gFilterList */
+/* global MozXULElement, gFilter, gFilterList, onEnterInSearchTerm, convertDateToString */
+/* global convertPRTimeToString, convertStringToPRTime */
 
 var {MailUtils} = ChromeUtils.import("resource:///modules/MailUtils.jsm");
 
 const updateParentNode = (parentNode) => {
   if (parentNode.hasAttribute("initialActionIndex")) {
     let actionIndex = parentNode.getAttribute("initialActionIndex");
     let filterAction = gFilter.getActionAt(actionIndex);
     parentNode.initWithAction(filterAction);
@@ -590,8 +591,438 @@ class MozSearchOperator extends MozSearc
     return val;
   }
 
   get parentValue() {
     return this.searchAttribute;
   }
 }
 customElements.define("search-operator", MozSearchOperator);
+
+/**
+ * MozSearchValue is a widget that allows selecting the value to search or filter on. It can be a
+ * text entry, priority, status, junk status, tags, hasAttachment status, and addressbook etc.
+ *
+ * @extends MozXULElement
+ */
+class MozSearchValue extends MozXULElement {
+  static get observedAttributes() {
+    return ["disabled"];
+  }
+
+  constructor() {
+    super();
+
+    this.addEventListener("keypress", (event) => {
+      if (event.keyCode != KeyEvent.DOM_VK_RETURN) {
+        return;
+      }
+      onEnterInSearchTerm(event);
+    });
+
+    this.internalOperator = null;
+    this.internalAttribute = null;
+    this.internalValue = null;
+  }
+
+  connectedCallback() {
+    if (this.delayConnectedCallback()) {
+        return;
+    }
+
+    // Initialize strings.
+    const bundle = Services.strings.createBundle("chrome://messenger/locale/messenger.properties");
+
+    if (!this.hasChildNodes()) {
+      this.appendChild(MozXULElement.parseXULToFragment(`
+        <textbox flex="1" class="search-value-textbox" inherits="disabled"></textbox>
+        <menulist flex="1" class="search-value-menulist" inherits="disabled">
+          <menupopup class="search-value-popup">
+            <menuitem value="6" stringTag="priorityHighest" class="search-value-menuitem"></menuitem>
+            <menuitem value="5" stringTag="priorityHigh" class="search-value-menuitem"></menuitem>
+            <menuitem value="4" stringTag="priorityNormal" class="search-value-menuitem"></menuitem>
+            <menuitem value="3" stringTag="priorityLow" class="search-value-menuitem"></menuitem>
+            <menuitem value="2" stringTag="priorityLowest" class="search-value-menuitem"></menuitem>
+          </menupopup>
+        </menulist>
+        <menulist flex="1" class="search-value-menulist" inherits="disabled">
+          <menupopup class="search-value-popup">
+            <menuitem value="2" stringTag="replied" class="search-value-menuitem"></menuitem>
+            <menuitem value="1" stringTag="read" class="search-value-menuitem"></menuitem>
+            <menuitem value="65536" stringTag="new" class="search-value-menuitem"></menuitem>
+            <menuitem value="4096" stringTag="forwarded" class="search-value-menuitem"></menuitem>
+            <menuitem value="4" stringTag="flagged" class="search-value-menuitem"></menuitem>
+          </menupopup>
+        </menulist>
+        <textbox flex="1" class="search-value-textbox" inherits="disabled"></textbox>
+        <menulist flex="1" class="search-value-menulist" inherits="disabled">
+          <menupopup class="addrbooksPopup" localonly="true"></menupopup>
+        </menulist>
+        <menulist flex="1" class="search-value-menulist" inherits="disabled">
+          <menupopup class="search-value-popup"></menupopup>
+        </menulist>
+        <menulist flex="1" class="search-value-menulist" inherits="disabled">
+          <menupopup class="search-value-popup">
+            <menuitem value="2" stringTag="junk" class="search-value-menuitem"></menuitem>
+          </menupopup>
+        </menulist>
+        <menulist flex="1" class="search-value-menulist" inherits="disabled">
+          <menupopup class="search-value-popup">
+            <menuitem value="0" stringTag="hasAttachments" class="search-value-menuitem"></menuitem>
+          </menupopup>
+        </menulist>
+        <menulist flex="1" class="search-value-menulist" inherits="disabled">
+          <menupopup class="search-value-popup">
+            <menuitem value="plugin" stringTag="junkScoreOriginPlugin" class="search-value-menuitem"></menuitem>
+            <menuitem value="user" stringTag="junkScoreOriginUser" class="search-value-menuitem"></menuitem>
+            <menuitem value="filter" stringTag="junkScoreOriginFilter" class="search-value-menuitem"></menuitem>
+            <menuitem value="whitelist" stringTag="junkScoreOriginWhitelist" class="search-value-menuitem"></menuitem>
+            <menuitem value="imapflag" stringTag="junkScoreOriginImapFlag" class="search-value-menuitem"></menuitem>
+          </menupopup>
+        </menulist>
+        <textbox flex="1" class="search-value-textbox" inherits="disabled" type="number"></textbox>
+        <hbox flex="1" class="search-value-custom" inherits="disabled"></hbox>
+      `));
+
+      // Initialize the priority picker.
+      this.fillStringsForChildren(this.childNodes[1].querySelector("menupopup"), bundle);
+
+      // Initialize the status picker.
+      this.fillStringsForChildren(this.childNodes[2].querySelector("menupopup"), bundle);
+
+      // initialize the address book picker
+      this.fillStringsForChildren(this.childNodes[4].querySelector("menupopup"), bundle);
+
+      // initialize the junk status picker
+      this.fillStringsForChildren(this.childNodes[6].querySelector("menupopup"), bundle);
+
+      // initialize the has attachment status picker
+      this.fillStringsForChildren(this.childNodes[7].querySelector("menupopup"), bundle);
+
+      // initialize the junk score origin picker
+      this.fillStringsForChildren(this.childNodes[8].querySelector("menupopup"), bundle);
+    }
+
+    // Initialize the date picker.
+    const datePicker = this.childNodes[3];
+    const searchAttribute = this.searchAttribute;
+    const time = searchAttribute == Ci.nsMsgSearchAttrib.Date ? datePicker.value : new Date();
+
+    // The search-value widget has two textboxes one for text, one as a placeholder for a
+    // date/calendar widget.
+    datePicker.setAttribute("value", convertDateToString(time));
+
+    // initialize the tag list
+    this.fillInTags();
+
+    this._updateAttributes();
+  }
+
+  attributeChangedCallback() {
+    if (!this.isConnectedAndReady) {
+      return;
+    }
+
+    this._updateAttributes();
+  }
+
+  _updateAttributes() {
+    this.querySelectorAll("[inherits='disabled']").forEach(elem => {
+      if (this.hasAttribute("disabled")) {
+        elem.setAttribute("disabled", this.getAttribute("disabled"));
+      } else {
+        elem.removeAttribute("disabled");
+      }
+    });
+  }
+
+  set opParentValue(val) {
+    // Noop if we're not changing it.
+    if (this.internalOperator == val) {
+      return val;
+    }
+
+    // Keywords has the null field IsEmpty.
+    if (this.searchAttribute == Ci.nsMsgSearchAttrib.Keywords) {
+      if (val == Ci.nsMsgSearchOp.IsEmpty || val == Ci.nsMsgSearchOp.IsntEmpty) {
+        this.setAttribute("selectedIndex", "-1");
+      } else {
+        this.setAttribute("selectedIndex", "5");
+      }
+    }
+
+    // JunkStatus has the null field IsEmpty.
+    if (this.searchAttribute == Ci.nsMsgSearchAttrib.JunkStatus) {
+      if (val == Ci.nsMsgSearchOp.IsEmpty || val == Ci.nsMsgSearchOp.IsntEmpty) {
+        this.setAttribute("selectedIndex", "-1");
+      } else {
+        this.setAttribute("selectedIndex", "6");
+      }
+    }
+
+    // If it's not sender, to, cc, alladdresses, or to or cc, we don't care.
+    if (this.searchAttribute != Ci.nsMsgSearchAttrib.Sender &&
+      this.searchAttribute != Ci.nsMsgSearchAttrib.To &&
+      this.searchAttribute != Ci.nsMsgSearchAttrib.ToOrCC &&
+      this.searchAttribute != Ci.nsMsgSearchAttrib.AllAddresses &&
+      this.searchAttribute != Ci.nsMsgSearchAttrib.CC) {
+      this.internalOperator = val;
+      return val;
+    }
+
+    const children = this.childNodes;
+    if (val == Ci.nsMsgSearchOp.IsntInAB || val == Ci.nsMsgSearchOp.IsInAB) {
+      // If the old internalOperator was IsntInAB or IsInAB, and the new internalOperator is
+      // IsntInAB or IsInAB, noop because the search value was an ab type, and it still is.
+      // Otherwise, switch to the ab picker and select the PAB.
+      if (this.internalOperator != Ci.nsMsgSearchOp.IsntInAB &&
+        this.internalOperator != Ci.nsMsgSearchOp.IsInAB) {
+        const abs = children[4].querySelector(`[value="moz-abmdbdirectory://abook.mab"]`);
+        if (abs) {
+          children[4].selectedItem = abs;
+        }
+        this.setAttribute("selectedIndex", "4");
+      }
+    } else if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
+      this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
+      // If the old internalOperator wasn't IsntInAB or IsInAB, and the new internalOperator isn't
+      // IsntInAB or IsInAB, noop because the search value wasn't an ab type, and it still isn't.
+      // Otherwise, switch to the textbox and clear it
+      children[0].value = "";
+      this.setAttribute("selectedIndex", "0");
+    }
+
+    this.internalOperator = val;
+    return val;
+  }
+
+  get opParentValue() {
+    return this.internalOperator;
+  }
+
+  set parentValue(val) {
+    return this.searchAttribute = val;
+  }
+
+  get parentValue() {
+    return this.searchAttribute;
+  }
+
+  set searchAttribute(val) {
+    // noop if we're not changing it.
+    if (this.internalAttribute == val) {
+      return val;
+    }
+    this.internalAttribute = val;
+
+    // If the searchAttribute changing, null out the internalOperator.
+    this.internalOperator = null;
+
+    // We inherit from a deck, so just use it's index attribute to hide/show widgets.
+    if (isNaN(val)) { // Is this a custom attribute?
+      this.setAttribute("selectedIndex", "10");
+      let customHbox = this.childNodes[10];
+      if (this.internalValue) {
+        customHbox.setAttribute("value", this.internalValue.str);
+      }
+      // the searchAttribute attribute is intended as a selector in
+      // CSS for custom search terms to bind a custom value
+      customHbox.setAttribute("searchAttribute", val);
+    } else if (val == Ci.nsMsgSearchAttrib.Priority) {
+      this.setAttribute("selectedIndex", "1");
+    } else if (val == Ci.nsMsgSearchAttrib.MsgStatus) {
+      this.setAttribute("selectedIndex", "2");
+    } else if (val == Ci.nsMsgSearchAttrib.Date) {
+      this.setAttribute("selectedIndex", "3");
+    } else if (val == Ci.nsMsgSearchAttrib.Sender) {
+      // Since the internalOperator is null, this is the same as the initial state.
+      // The initial state for Sender isn't an ab type search, it's a text search,
+      // so show the textbox.
+      this.setAttribute("selectedIndex", "0");
+    } else if (val == Ci.nsMsgSearchAttrib.Keywords) {
+      this.setAttribute("selectedIndex", "5");
+    } else if (val == Ci.nsMsgSearchAttrib.JunkStatus) {
+      this.setAttribute("selectedIndex", "6");
+    } else if (val == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
+      this.setAttribute("selectedIndex", "7");
+    } else if (val == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
+      this.setAttribute("selectedIndex", "8");
+    } else if (val == Ci.nsMsgSearchAttrib.AgeInDays) {
+      let valueBox = this.childNodes[9];
+      valueBox.min = -40000; // ~-100 years
+      valueBox.max = 40000; // ~100 years
+      this.setAttribute("selectedIndex", "9");
+    } else if (val == Ci.nsMsgSearchAttrib.Size) {
+      let valueBox = this.childNodes[9];
+      valueBox.min = 0;
+      valueBox.max = 1000000000;
+      this.setAttribute("selectedIndex", "9");
+    } else if (val == Ci.nsMsgSearchAttrib.JunkPercent) {
+      let valueBox = this.childNodes[9];
+      valueBox.min = 0;
+      valueBox.max = 100;
+      this.setAttribute("selectedIndex", "9");
+    } else {
+      // a normal text field
+      this.setAttribute("selectedIndex", "0");
+    }
+    return val;
+  }
+
+  get searchAttribute() {
+    return this.internalAttribute;
+  }
+
+  set value(val) {
+    // val is a nsIMsgSearchValue object
+    this.internalValue = val;
+    const attrib = this.internalAttribute;
+    const children = this.childNodes;
+    this.searchAttribute = attrib;
+    if (isNaN(attrib)) { // a custom term
+      let customHbox = this.childNodes[10];
+      customHbox.setAttribute("value", val.str);
+      return val;
+    }
+    if (attrib == Ci.nsMsgSearchAttrib.Priority) {
+      const matchingPriority = children[1].querySelector(`[value="${val.priority}"]`);
+      if (matchingPriority) {
+        children[1].selectedItem = matchingPriority;
+      }
+    } else if (attrib == Ci.nsMsgSearchAttrib.MsgStatus) {
+      const matchingStatus = children[2].querySelector(`[value="${val.status}"]`);
+      if (matchingStatus) {
+        children[2].selectedItem = matchingStatus;
+      }
+    } else if (attrib == Ci.nsMsgSearchAttrib.AgeInDays) {
+      children[9].value = val.age;
+    } else if (attrib == Ci.nsMsgSearchAttrib.Date) {
+      children[3].value = convertPRTimeToString(val.date);
+    } else if (attrib == Ci.nsMsgSearchAttrib.Sender ||
+      attrib == Ci.nsMsgSearchAttrib.To ||
+      attrib == Ci.nsMsgSearchAttrib.CC ||
+      attrib == Ci.nsMsgSearchAttrib.AllAddresses ||
+      attrib == Ci.nsMsgSearchAttrib.ToOrCC) {
+      if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
+        this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
+        const abs = children[4].querySelector(`[value="${val.str}"]`);
+        if (abs) {
+          children[4].selectedItem = abs;
+        }
+      } else {
+        children[0].value = val.str;
+      }
+    } else if (attrib == Ci.nsMsgSearchAttrib.Keywords) {
+      const keywordVal = children[5].querySelector(`[value="${val.str}"]`);
+      if (keywordVal) {
+        children[5].value = val.str;
+        children[5].selectedItem = keywordVal;
+      }
+    } else if (attrib == Ci.nsMsgSearchAttrib.JunkStatus) {
+      const junkStatus =
+        children[6].querySelector(`[value="${val.junkStatus}"]`);
+      if (junkStatus) {
+        children[6].selectedItem = junkStatus;
+      }
+    } else if (attrib == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
+      const hasAttachmentStatus =
+        children[7].querySelector(`[value="${val.hasAttachmentStatus}"]`);
+      if (hasAttachmentStatus) {
+        children[7].selectedItem = hasAttachmentStatus;
+      }
+    } else if (attrib == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
+      const junkScoreOrigin =
+        children[8].querySelector(`[value="${val.str}"]`);
+      if (junkScoreOrigin) {
+        children[8].selectedItem = junkScoreOrigin;
+      }
+    } else if (attrib == Ci.nsMsgSearchAttrib.JunkPercent) {
+      children[9].value = val.junkPercent;
+    } else if (attrib == Ci.nsMsgSearchAttrib.Size) {
+      children[9].value = val.size;
+    } else {
+      children[0].value = val.str;
+    }
+    return val;
+  }
+
+  get value() {
+    return this.internalValue;
+  }
+
+  save() {
+    const searchValue = this.value;
+    const searchAttribute = this.searchAttribute;
+    const children = this.childNodes;
+
+    searchValue.attrib = searchAttribute;
+    if (searchAttribute == Ci.nsMsgSearchAttrib.Priority) {
+      searchValue.priority = children[1].selectedItem.value;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.MsgStatus) {
+      searchValue.status = children[2].value;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.AgeInDays) {
+      searchValue.age = children[9].value;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.Date) {
+      searchValue.date = convertStringToPRTime(children[3].value);
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.Sender ||
+      searchAttribute == Ci.nsMsgSearchAttrib.To ||
+      searchAttribute == Ci.nsMsgSearchAttrib.CC ||
+      searchAttribute == Ci.nsMsgSearchAttrib.AllAddresses ||
+      searchAttribute == Ci.nsMsgSearchAttrib.ToOrCC) {
+      if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
+        this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
+        searchValue.str = children[4].selectedItem.value;
+      } else {
+        searchValue.str = children[0].value;
+      }
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.Keywords) {
+      searchValue.str = children[5].value;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.JunkStatus) {
+      searchValue.junkStatus = children[6].value;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.JunkPercent) {
+      searchValue.junkPercent = children[9].value;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.Size) {
+      searchValue.size = children[9].value;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
+      searchValue.status = Ci.nsMsgMessageFlags.Attachment;
+    } else if (searchAttribute == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
+      searchValue.str = children[8].value;
+    } else if (isNaN(searchAttribute)) { // a custom term
+      searchValue.attrib = Ci.nsMsgSearchAttrib.Custom;
+      searchValue.str = children[10].getAttribute("value");
+    } else {
+      searchValue.str = children[0].value;
+    }
+  }
+
+  saveTo(searchValue) {
+    this.internalValue = searchValue;
+    this.save();
+  }
+
+  fillInTags() {
+    let menulist = this.childNodes[5];
+    // Force initialization of the menulist custom element first.
+    customElements.upgrade(menulist);
+    let tagArray = MailServices.tags.getAllTags({});
+    for (let i = 0; i < tagArray.length; i++) {
+      const taginfo = tagArray[i];
+      const newMenuItem = menulist.appendItem(taginfo.tag, taginfo.key);
+      if (i == 0) {
+        menulist.selectedItem = newMenuItem;
+      }
+    }
+  }
+
+  fillStringsForChildren(parentNode, bundle) {
+    for (let node of parentNode.childNodes) {
+      const stringTag = node.getAttribute("stringTag");
+      if (stringTag) {
+        const attr = (node.tagName == "label") ? "value" : "label";
+        node.setAttribute(attr, bundle.GetStringFromName(stringTag));
+      }
+    }
+
+    // Force initialization of the menulist custom element.
+    customElements.upgrade(parentNode);
+  }
+}
+customElements.define("search-value", MozSearchValue);