Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Mon, 06 Aug 2018 12:25:13 +0300
changeset 485316 0dbeb72b579816a450db3430f35174655a975c53
parent 485306 260bf1050ee206a918b45ff5b0280fe1ee8e9979 (current diff)
parent 485315 a8b367f1fd79e12b2f51a3aafb1e2d2202d4a331 (diff)
child 485318 4468af18d56eef25571d6d63f22b8342e7d6ec68
child 485328 dbe94893492ecc536dd48c96d10a1709cb038687
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
0dbeb72b5798 / 63.0a1 / 20180806100140 / files
nightly linux64
0dbeb72b5798 / 63.0a1 / 20180806100140 / files
nightly mac
0dbeb72b5798 / 63.0a1 / 20180806100140 / files
nightly win32
0dbeb72b5798 / 63.0a1 / 20180806100140 / files
nightly win64
0dbeb72b5798 / 63.0a1 / 20180806100140 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -138,17 +138,17 @@ var gIdentityHandler = {
   get _identityPopupContentVerif() {
     delete this._identityPopupContentVerif;
     return this._identityPopupContentVerif =
       document.getElementById("identity-popup-content-verifier");
   },
   get _identityPopupMixedContentLearnMore() {
     delete this._identityPopupMixedContentLearnMore;
     return this._identityPopupMixedContentLearnMore =
-      document.getElementById("identity-popup-mcb-learn-more");
+      [...document.querySelectorAll(".identity-popup-mcb-learn-more")];
   },
   get _identityPopupInsecureLoginFormsLearnMore() {
     delete this._identityPopupInsecureLoginFormsLearnMore;
     return this._identityPopupInsecureLoginFormsLearnMore =
       document.getElementById("identity-popup-insecure-login-forms-learn-more");
   },
   get _identityIconLabels() {
     delete this._identityIconLabels;
@@ -621,18 +621,18 @@ var gIdentityHandler = {
         if (siteData && siteData.length) {
           this._clearSiteDataFooter.hidden = false;
         }
       });
     }
 
     // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
-    this._identityPopupMixedContentLearnMore
-        .setAttribute("href", baseURL + "mixed-content");
+    this._identityPopupMixedContentLearnMore.forEach(
+      e => e.setAttribute("href", baseURL + "mixed-content"));
     this._identityPopupInsecureLoginFormsLearnMore
         .setAttribute("href", baseURL + "insecure-password");
 
     // This is in the properties file because the expander used to switch its tooltip.
     this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
 
     // Determine connection security information.
     let connection = "not-secure";
--- a/browser/base/content/test/siteIdentity/browser_insecureLoginForms.js
+++ b/browser/base/content/test/siteIdentity/browser_insecureLoginForms.js
@@ -70,20 +70,18 @@ add_task(async function test_simple() {
          "url(\"chrome://browser/skin/connection-mixed-active-loaded.svg\")",
          "Using expected icon image in the identity block");
       is(securityViewBG,
          "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
          "Using expected icon image in the Control Center main view");
       is(securityContentBG,
          "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
          "Using expected icon image in the Control Center subview");
-      is(Array.filter(document.getElementById("identity-popup-securityView")
-                              .querySelectorAll("[observes=identity-popup-insecure-login-forms-learn-more]"),
-                      element => !BrowserTestUtils.is_hidden(element)).length, 1,
-         "The 'Learn more' link should be visible once.");
+      ok(!BrowserTestUtils.is_hidden(document.getElementById("identity-popup-insecure-login-forms-learn-more")),
+         "The 'Learn more' link should be visible.");
     }
 
     // Messages should be visible when the scheme is HTTP, and invisible when
     // the scheme is HTTPS.
     is(Array.every(document.getElementById("identity-popup-securityView")
                            .querySelectorAll("[when-loginforms=insecure]"),
                    element => !BrowserTestUtils.is_hidden(element)),
        expectWarning,
--- a/browser/base/content/test/siteIdentity/head.js
+++ b/browser/base/content/test/siteIdentity/head.js
@@ -237,17 +237,17 @@ async function assertMixedContentBlockin
     }
   }
 
   if (activeLoaded || activeBlocked || passiveLoaded) {
     let promiseViewShown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "ViewShown");
     doc.getElementById("identity-popup-security-expander").click();
     await promiseViewShown;
     is(Array.filter(doc.getElementById("identity-popup-securityView")
-                       .querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
+                       .querySelectorAll(".identity-popup-mcb-learn-more"),
                     element => !BrowserTestUtils.is_hidden(element)).length, 1,
        "The 'Learn more' link should be visible once.");
   }
 
   if (gIdentityHandler._identityPopup.state != "closed") {
     let hideEvent = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
     info("Hiding identity popup");
     gIdentityHandler._identityPopup.hidePopup();
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -6,21 +6,16 @@
        type="arrow"
        hidden="true"
        photon="true"
        role="alertdialog"
        onpopupshown="gIdentityHandler.onPopupShown(event);"
        onpopuphidden="gIdentityHandler.onPopupHidden(event);"
        orient="vertical">
 
-  <broadcasterset>
-    <broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
-    <broadcaster id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/>
-  </broadcasterset>
-
   <panelmultiview id="identity-popup-multiView"
                   mainViewId="identity-popup-mainView">
     <panelview id="identity-popup-mainView"
                descriptionheightworkaround="true">
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox class="identity-popup-security-content" flex="1">
           <label class="plain">
@@ -170,46 +165,46 @@
                 accesskey="&identity.removeCertException.accesskey;"
                 oncommand="gIdentityHandler.removeCertException()"/>
 
         <!-- Connection is Not Secure -->
         <description when-connection="not-secure"
                      and-when-loginforms="secure">&identity.description.insecure;</description>
 
         <!-- Insecure login forms -->
-        <description when-loginforms="insecure">&identity.description.insecureLoginForms; <label observes="identity-popup-insecure-login-forms-learn-more"/></description>
+        <description when-loginforms="insecure">&identity.description.insecureLoginForms; <label id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/></description>
 
         <!-- Weak Cipher -->
         <description when-ciphers="weak">&identity.description.weakCipher;</description>
         <description class="identity-popup-warning-yellow"
                      when-ciphers="weak">&identity.description.weakCipher2;</description>
 
         <!-- Active Mixed Content Blocked -->
         <description class="identity-popup-warning-gray"
-                     when-mixedcontent="active-blocked">&identity.description.activeBlocked; <label observes="identity-popup-mcb-learn-more"/></description>
+                     when-mixedcontent="active-blocked">&identity.description.activeBlocked; <label class="identity-popup-mcb-learn-more text-link plain" value="&identity.learnMore;"/></description>
 
         <!-- Passive Mixed Content Loaded -->
         <description when-mixedcontent="passive-loaded">&identity.description.passiveLoaded;</description>
         <description class="identity-popup-warning-yellow"
-                     when-mixedcontent="passive-loaded">&identity.description.passiveLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
+                     when-mixedcontent="passive-loaded">&identity.description.passiveLoaded2; <label class="identity-popup-mcb-learn-more text-link plain" value="&identity.learnMore;"/></description>
 
         <!-- Passive Mixed Content Loaded, Active Mixed Content Blocked -->
         <description when-mixedcontent="passive-loaded active-blocked">&identity.description.passiveLoaded;</description>
         <description when-mixedcontent="passive-loaded active-blocked"
-                     class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
+                     class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label class="identity-popup-mcb-learn-more text-link plain" value="&identity.learnMore;"/></description>
 
         <!-- Active Mixed Content Blocking Disabled -->
         <description when-mixedcontent="active-loaded"
                      and-when-loginforms="secure">&identity.description.activeLoaded;</description>
         <description when-mixedcontent="active-loaded"
-                     and-when-loginforms="secure">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
+                     and-when-loginforms="secure">&identity.description.activeLoaded2; <label class="identity-popup-mcb-learn-more text-link plain" value="&identity.learnMore;"/></description>
         <!-- Show only the first message when there are insecure login forms,
              and make sure the Learn More link is included. -->
         <description when-mixedcontent="active-loaded"
-                     and-when-loginforms="insecure">&identity.description.activeLoaded; <label observes="identity-popup-mcb-learn-more"/></description>
+                     and-when-loginforms="insecure">&identity.description.activeLoaded; <label class="identity-popup-mcb-learn-more text-link plain" value="&identity.learnMore;"/></description>
 
         <!-- Buttons to enable/disable mixed content blocking. -->
         <button when-mixedcontent="active-blocked"
                 label="&identity.disableMixedContentBlocking.label;"
                 accesskey="&identity.disableMixedContentBlocking.accesskey;"
                 oncommand="gIdentityHandler.disableMixedContentProtection()"/>
         <button when-mixedcontent="active-loaded"
                 label="&identity.enableMixedContentBlocking.label;"
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -118,23 +118,16 @@ JS_FRIEND_API(size_t) MemoryReportingSun
  */
 struct InefficientNonFlatteningStringHashPolicy
 {
     typedef JSString* Lookup;
     static HashNumber hash(const Lookup& l);
     static bool match(const JSString* const& k, const Lookup& l);
 };
 
-struct CStringHashPolicy
-{
-    typedef const char* Lookup;
-    static HashNumber hash(const Lookup& l);
-    static bool match(const char* const& k, const Lookup& l);
-};
-
 // This file features many classes with numerous size_t fields, and each such
 // class has one or more methods that need to operate on all of these fields.
 // Writing these individually is error-prone -- it's easy to add a new field
 // without updating all the required methods.  So we define a single macro list
 // in each class to name the fields (and notable characteristics of them), and
 // then use the following macros to transform those lists into the required
 // methods.
 //
@@ -599,17 +592,17 @@ struct RuntimeSizes
     // FineGrained, we subtract the measurements of the notable script sources
     // and move them into |notableScriptSources|.
     FOR_EACH_SIZE(DECL_SIZE)
     ScriptSourceInfo scriptSourceInfo;
     CodeSizes code;
     GCSizes gc;
 
     typedef js::HashMap<const char*, ScriptSourceInfo,
-                        js::CStringHashPolicy,
+                        mozilla::CStringHasher,
                         js::SystemAllocPolicy> ScriptSourcesHashMap;
 
     // |allScriptSources| is only used transiently.  During the reporting phase
     // it is filled with info about every script source in the runtime.  It's
     // then used to fill in |notableScriptSources| (which actually gets
     // reported), and immediately discarded afterwards.
     ScriptSourcesHashMap* allScriptSources;
     js::Vector<NotableScriptSourceInfo, 0, js::SystemAllocPolicy> notableScriptSources;
@@ -896,17 +889,17 @@ struct RealmStats
     // The class measurements in |classInfo| are initially for all classes.  At
     // the end, if the measurement granularity is FineGrained, we subtract the
     // measurements of the notable classes and move them into |notableClasses|.
     FOR_EACH_SIZE(DECL_SIZE)
     ClassInfo classInfo;
     void* extra;            // This field can be used by embedders.
 
     typedef js::HashMap<const char*, ClassInfo,
-                        js::CStringHashPolicy,
+                        mozilla::CStringHasher,
                         js::SystemAllocPolicy> ClassesHashMap;
 
     // These are similar to |allStrings| and |notableStrings| in ZoneStats.
     ClassesHashMap* allClasses;
     js::Vector<NotableClassInfo, 0, js::SystemAllocPolicy> notableClasses;
     bool isTotals;
 
 #undef FOR_EACH_SIZE
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -117,28 +117,16 @@ InefficientNonFlatteningStringHashPolicy
                : EqualStringsPure<Latin1Char, char16_t>(s1, l);
     }
 
     return l->hasLatin1Chars()
            ? EqualStringsPure<char16_t, Latin1Char>(s1, l)
            : EqualStringsPure<char16_t, char16_t>(s1, l);
 }
 
-/* static */ HashNumber
-CStringHashPolicy::hash(const Lookup& l)
-{
-    return mozilla::HashString(l);
-}
-
-/* static */ bool
-CStringHashPolicy::match(const char* const& k, const Lookup& l)
-{
-    return strcmp(k, l) == 0;
-}
-
 } // namespace js
 
 namespace JS {
 
 NotableStringInfo::NotableStringInfo()
   : StringInfo(),
     buffer(0),
     length(0)
--- a/mfbt/HashTable.h
+++ b/mfbt/HashTable.h
@@ -13,24 +13,24 @@
 //
 // Both hash tables have two optional template parameters.
 //
 // - HashPolicy. This defines the operations for hashing and matching keys. The
 //   default HashPolicy is appropriate when both of the following two
 //   conditions are true.
 //
 //   - The key type stored in the table (|Key| for |HashMap<Key, Value>|, |T|
-//     for |HashSet<T>|) is an integer, pointer, UniquePtr, float, double, or
-//     char*.
+//     for |HashSet<T>|) is an integer, pointer, UniquePtr, float, or double.
 //
 //   - The type used for lookups (|Lookup|) is the same as the key type. This
 //     is usually the case, but not always.
 //
-//   Otherwise, you must provide your own hash policy; see the "Hash Policy"
-//   section below.
+//   There is also a |CStringHasher| policy for |char*| keys. If your keys
+//   don't match any of the above cases, you must provide your own hash policy;
+//   see the "Hash Policy" section below.
 //
 // - AllocPolicy. This defines how allocations are done by the table.
 //
 //   - |MallocAllocPolicy| is the default and is usually appropriate; note that
 //     operations (such as insertions) that might cause allocations are
 //     fallible and must be checked for OOM. These checks are enforced by the
 //     use of MOZ_MUST_USE.
 //
@@ -136,16 +136,22 @@ using Generation = Opaque<uint64_t>;
 // - Due to the lack of exception handling, the user must call |init()|.
 //
 template<class Key,
          class Value,
          class HashPolicy = DefaultHasher<Key>,
          class AllocPolicy = MallocAllocPolicy>
 class HashMap
 {
+  // -- Implementation details -----------------------------------------------
+
+  // HashMap is not copyable or assignable.
+  HashMap(const HashMap& hm) = delete;
+  HashMap& operator=(const HashMap& hm) = delete;
+
   using TableEntry = HashMapEntry<Key, Value>;
 
   struct MapHashPolicy : HashPolicy
   {
     using Base = HashPolicy;
     using KeyType = Key;
 
     static const Key& getKey(TableEntry& aEntry) { return aEntry.key(); }
@@ -154,34 +160,85 @@ class HashMap
     {
       HashPolicy::rekey(aEntry.mutableKey(), aKey);
     }
   };
 
   using Impl = detail::HashTable<TableEntry, MapHashPolicy, AllocPolicy>;
   Impl mImpl;
 
+  friend class Impl::Enum;
+
 public:
   using Lookup = typename HashPolicy::Lookup;
   using Entry = TableEntry;
 
+  // -- Initialization -------------------------------------------------------
+
   // HashMap construction is fallible (due to possible OOM). The user must
   // call init() after construction and check the return value.
   explicit HashMap(AllocPolicy aPolicy = AllocPolicy())
     : mImpl(aPolicy)
   {
   }
 
+  // HashMap is movable.
+  HashMap(HashMap&& aRhs)
+    : mImpl(std::move(aRhs.mImpl))
+  {
+  }
+  void operator=(HashMap&& aRhs)
+  {
+    MOZ_ASSERT(this != &aRhs, "self-move assignment is prohibited");
+    mImpl = std::move(aRhs.mImpl);
+  }
+
   // Initialize the map for use. Must be called after construction, before
   // any other operations (other than initialized()).
   MOZ_MUST_USE bool init(uint32_t aLen = 16) { return mImpl.init(aLen); }
 
+  // -- Status and sizing ----------------------------------------------------
+
   // Has the map been initialized?
   bool initialized() const { return mImpl.initialized(); }
 
+  // The map's current generation.
+  Generation generation() const { return mImpl.generation(); }
+
+  // Is the map empty?
+  bool empty() const { return mImpl.empty(); }
+
+  // Number of keys/values in the map.
+  uint32_t count() const { return mImpl.count(); }
+
+  // Number of key/value slots in the map. Note: resize will happen well before
+  // count() == capacity().
+  size_t capacity() const { return mImpl.capacity(); }
+
+  // The size of the map's entry storage, in bytes. If the keys/values contain
+  // pointers to other heap blocks, you must iterate over the map and measure
+  // them separately; hence the "shallow" prefix.
+  size_t shallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
+  }
+  size_t shallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) +
+           mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  // -- Lookups --------------------------------------------------------------
+
+  // Does the map contain a key/value matching |aLookup|?
+  bool has(const Lookup& aLookup) const
+  {
+    return mImpl.lookup(aLookup).found();
+  }
+
   // Return a Ptr indicating whether a key/value matching |aLookup| is
   // present in the map. E.g.:
   //
   //   using HM = HashMap<int,char>;
   //   HM h;
   //   if (HM::Ptr p = h.lookup(3)) {
   //     assert(p->key() == 3);
   //     char val = p->value();
@@ -195,19 +252,61 @@ public:
 
   // Like lookup(), but does not assert if two threads call it at the same
   // time. Only use this method when none of the threads will modify the map.
   MOZ_ALWAYS_INLINE Ptr readonlyThreadsafeLookup(const Lookup& aLookup) const
   {
     return mImpl.readonlyThreadsafeLookup(aLookup);
   }
 
-  // Remove a previously found key/value (assuming aPtr.found()). The map
-  // must not have been mutated in the interim.
-  void remove(Ptr aPtr) { mImpl.remove(aPtr); }
+  // -- Insertions -----------------------------------------------------------
+
+  // Overwrite existing value with |aValue|, or add it if not present. Returns
+  // false on OOM.
+  template<typename KeyInput, typename ValueInput>
+  MOZ_MUST_USE bool put(KeyInput&& aKey, ValueInput&& aValue)
+  {
+    AddPtr p = lookupForAdd(aKey);
+    if (p) {
+      p->value() = std::forward<ValueInput>(aValue);
+      return true;
+    }
+    return add(
+      p, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
+  }
+
+  // Like put(), but asserts that the given key is not already present.
+  template<typename KeyInput, typename ValueInput>
+  MOZ_MUST_USE bool putNew(KeyInput&& aKey, ValueInput&& aValue)
+  {
+    return mImpl.putNew(
+      aKey, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
+  }
+
+  // Only call this to populate an empty map after reserving space with init().
+  template<typename KeyInput, typename ValueInput>
+  void putNewInfallible(KeyInput&& aKey, ValueInput&& aValue)
+  {
+    mImpl.putNewInfallible(
+      aKey, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
+  }
+
+  // Add (aKey,aDefaultValue) if |aKey| is not found. Return a false-y Ptr on
+  // OOM.
+  Ptr lookupWithDefault(const Key& aKey, const Value& aDefaultValue)
+  {
+    AddPtr p = lookupForAdd(aKey);
+    if (p) {
+      return p;
+    }
+    bool ok = add(p, aKey, aDefaultValue);
+    MOZ_ASSERT_IF(!ok, !p); // p is left false-y on OOM.
+    (void)ok;
+    return p;
+  }
 
   // Like |lookup(l)|, but on miss, |p = lookupForAdd(l)| allows efficient
   // insertion of Key |k| (where |HashPolicy::match(k,l) == true|) using
   // |add(p,k,v)|. After |add(p,k,v)|, |p| points to the new key/value. E.g.:
   //
   //   using HM = HashMap<int,char>;
   //   HM h;
   //   HM::AddPtr p = h.lookupForAdd(3);
@@ -264,16 +363,55 @@ public:
                                   ValueInput&& aValue)
   {
     return mImpl.relookupOrAdd(aPtr,
                                aKey,
                                std::forward<KeyInput>(aKey),
                                std::forward<ValueInput>(aValue));
   }
 
+  // -- Removal --------------------------------------------------------------
+
+  // Lookup and remove the key/value matching |aLookup|, if present.
+  void remove(const Lookup& aLookup)
+  {
+    if (Ptr p = lookup(aLookup)) {
+      remove(p);
+    }
+  }
+
+  // Remove a previously found key/value (assuming aPtr.found()). The map must
+  // not have been mutated in the interim.
+  void remove(Ptr aPtr) { mImpl.remove(aPtr); }
+
+  // -- Rekeying -------------------------------------------------------------
+
+  // Infallibly rekey one entry, if necessary. Requires that template
+  // parameters Key and HashPolicy::Lookup are the same type.
+  void rekeyIfMoved(const Key& aOldKey, const Key& aNewKey)
+  {
+    if (aOldKey != aNewKey) {
+      rekeyAs(aOldKey, aNewKey, aNewKey);
+    }
+  }
+
+  // Infallibly rekey one entry if present, and return whether that happened.
+  bool rekeyAs(const Lookup& aOldLookup,
+               const Lookup& aNewLookup,
+               const Key& aNewKey)
+  {
+    if (Ptr p = lookup(aOldLookup)) {
+      mImpl.rekeyAndMaybeRehash(p, aNewLookup, aNewKey);
+      return true;
+    }
+    return false;
+  }
+
+  // -- Iteration ------------------------------------------------------------
+
   // |iter()| returns an Iterator:
   //
   //   HashMap<int, char> h;
   //   for (auto iter = h.iter(); !iter.done(); iter.next()) {
   //     char c = iter.get().value();
   //   }
   //
   using Iterator = typename Impl::Iterator;
@@ -293,150 +431,27 @@ public:
   ModIterator modIter() { return mImpl.modIter(); }
 
   // These are similar to Iterator/ModIterator/iter(), but use different
   // terminology.
   using Range = typename Impl::Range;
   using Enum = typename Impl::Enum;
   Range all() const { return mImpl.all(); }
 
+  // -- Clearing -------------------------------------------------------------
+
   // Remove all keys/values without changing the capacity.
   void clear() { mImpl.clear(); }
 
   // Remove all keys/values and attempt to minimize the capacity.
   void clearAndShrink() { mImpl.clearAndShrink(); }
 
   // Remove all keys/values and release entry storage. The map must be
   // initialized via init() again before further use.
   void finish() { mImpl.finish(); }
-
-  // Is the map empty?
-  bool empty() const { return mImpl.empty(); }
-
-  // Number of keys/values in the map.
-  uint32_t count() const { return mImpl.count(); }
-
-  // Number of key/value slots in the map. Note: resize will happen well before
-  // count() == capacity().
-  size_t capacity() const { return mImpl.capacity(); }
-
-  // The size of the map's entry storage, in bytes. If the keys/values contain
-  // pointers to other heap blocks, you must iterate over the map and measure
-  // them separately; hence the "shallow" prefix.
-  size_t shallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
-  {
-    return mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
-  }
-  size_t shallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
-  {
-    return aMallocSizeOf(this) +
-           mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
-  }
-
-  // The map's current generation.
-  Generation generation() const { return mImpl.generation(); }
-
-  /************************************************** Shorthand operations */
-
-  // Does the map contain a key/value matching |aLookup|?
-  bool has(const Lookup& aLookup) const
-  {
-    return mImpl.lookup(aLookup).found();
-  }
-
-  // Overwrite existing value with |aValue|, or add it if not present. Returns
-  // false on OOM.
-  template<typename KeyInput, typename ValueInput>
-  MOZ_MUST_USE bool put(KeyInput&& aKey, ValueInput&& aValue)
-  {
-    AddPtr p = lookupForAdd(aKey);
-    if (p) {
-      p->value() = std::forward<ValueInput>(aValue);
-      return true;
-    }
-    return add(
-      p, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
-  }
-
-  // Like put(), but asserts that the given key is not already present.
-  template<typename KeyInput, typename ValueInput>
-  MOZ_MUST_USE bool putNew(KeyInput&& aKey, ValueInput&& aValue)
-  {
-    return mImpl.putNew(
-      aKey, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
-  }
-
-  // Only call this to populate an empty map after reserving space with init().
-  template<typename KeyInput, typename ValueInput>
-  void putNewInfallible(KeyInput&& aKey, ValueInput&& aValue)
-  {
-    mImpl.putNewInfallible(
-      aKey, std::forward<KeyInput>(aKey), std::forward<ValueInput>(aValue));
-  }
-
-  // Add (aKey,aDefaultValue) if |aKey| is not found. Return a false-y Ptr on
-  // OOM.
-  Ptr lookupWithDefault(const Key& aKey, const Value& aDefaultValue)
-  {
-    AddPtr p = lookupForAdd(aKey);
-    if (p) {
-      return p;
-    }
-    bool ok = add(p, aKey, aDefaultValue);
-    MOZ_ASSERT_IF(!ok, !p); // p is left false-y on OOM.
-    (void)ok;
-    return p;
-  }
-
-  // Lookup and remove the key/value matching |aLookup|, if present.
-  void remove(const Lookup& aLookup)
-  {
-    if (Ptr p = lookup(aLookup)) {
-      remove(p);
-    }
-  }
-
-  // Infallibly rekey one entry, if necessary. Requires that template
-  // parameters Key and HashPolicy::Lookup are the same type.
-  void rekeyIfMoved(const Key& aOldKey, const Key& aNewKey)
-  {
-    if (aOldKey != aNewKey) {
-      rekeyAs(aOldKey, aNewKey, aNewKey);
-    }
-  }
-
-  // Infallibly rekey one entry if present, and return whether that happened.
-  bool rekeyAs(const Lookup& aOldLookup,
-               const Lookup& aNewLookup,
-               const Key& aNewKey)
-  {
-    if (Ptr p = lookup(aOldLookup)) {
-      mImpl.rekeyAndMaybeRehash(p, aNewLookup, aNewKey);
-      return true;
-    }
-    return false;
-  }
-
-  // HashMap is movable.
-  HashMap(HashMap&& aRhs)
-    : mImpl(std::move(aRhs.mImpl))
-  {
-  }
-  void operator=(HashMap&& aRhs)
-  {
-    MOZ_ASSERT(this != &aRhs, "self-move assignment is prohibited");
-    mImpl = std::move(aRhs.mImpl);
-  }
-
-private:
-  // HashMap is not copyable or assignable.
-  HashMap(const HashMap& hm) = delete;
-  HashMap& operator=(const HashMap& hm) = delete;
-
-  friend class Impl::Enum;
 };
 
 //---------------------------------------------------------------------------
 // HashSet
 //---------------------------------------------------------------------------
 
 // HashSet is a fast hash-based set of values.
 //
@@ -450,47 +465,104 @@ private:
 //   HashSet must not call back into the same HashSet object.
 // - Due to the lack of exception handling, the user must call |init()|.
 //
 template<class T,
          class HashPolicy = DefaultHasher<T>,
          class AllocPolicy = MallocAllocPolicy>
 class HashSet
 {
+  // -- Implementation details -----------------------------------------------
+
+  // HashSet is not copyable or assignable.
+  HashSet(const HashSet& hs) = delete;
+  HashSet& operator=(const HashSet& hs) = delete;
+
   struct SetHashPolicy : HashPolicy
   {
     using Base = HashPolicy;
     using KeyType = T;
 
     static const KeyType& getKey(const T& aT) { return aT; }
 
     static void setKey(T& aT, KeyType& aKey) { HashPolicy::rekey(aT, aKey); }
   };
 
   using Impl = detail::HashTable<const T, SetHashPolicy, AllocPolicy>;
   Impl mImpl;
 
+  friend class Impl::Enum;
+
 public:
   using Lookup = typename HashPolicy::Lookup;
   using Entry = T;
 
+  // -- Initialization -------------------------------------------------------
+
   // HashSet construction is fallible (due to possible OOM). The user must call
   // init() after construction and check the return value.
   explicit HashSet(AllocPolicy a = AllocPolicy())
     : mImpl(a)
   {
   }
 
+  // HashSet is movable.
+  HashSet(HashSet&& aRhs)
+    : mImpl(std::move(aRhs.mImpl))
+  {
+  }
+  void operator=(HashSet&& aRhs)
+  {
+    MOZ_ASSERT(this != &aRhs, "self-move assignment is prohibited");
+    mImpl = std::move(aRhs.mImpl);
+  }
+
   // Initialize the set for use. Must be called after construction, before
   // any other operations (other than initialized()).
   MOZ_MUST_USE bool init(uint32_t aLen = 16) { return mImpl.init(aLen); }
 
+  // -- Status and sizing ----------------------------------------------------
+
   // Has the set been initialized?
   bool initialized() const { return mImpl.initialized(); }
 
+  // The set's current generation.
+  Generation generation() const { return mImpl.generation(); }
+
+  // Is the set empty?
+  bool empty() const { return mImpl.empty(); }
+
+  // Number of elements in the set.
+  uint32_t count() const { return mImpl.count(); }
+
+  // Number of element slots in the set. Note: resize will happen well before
+  // count() == capacity().
+  size_t capacity() const { return mImpl.capacity(); }
+
+  // The size of the set's entry storage, in bytes. If the elements contain
+  // pointers to other heap blocks, you must iterate over the set and measure
+  // them separately; hence the "shallow" prefix.
+  size_t shallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
+  }
+  size_t shallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) +
+           mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  // -- Lookups --------------------------------------------------------------
+
+  // Does the set contain an element matching |aLookup|?
+  bool has(const Lookup& aLookup) const
+  {
+    return mImpl.lookup(aLookup).found();
+  }
+
   // Return a Ptr indicating whether an element matching |aLookup| is present
   // in the set. E.g.:
   //
   //   using HS = HashSet<int>;
   //   HS h;
   //   if (HS::Ptr p = h.lookup(3)) {
   //     assert(*p == 3);   // p acts like a pointer to int
   //   }
@@ -503,19 +575,46 @@ public:
 
   // Like lookup(), but does not assert if two threads call it at the same
   // time. Only use this method when none of the threads will modify the set.
   MOZ_ALWAYS_INLINE Ptr readonlyThreadsafeLookup(const Lookup& aLookup) const
   {
     return mImpl.readonlyThreadsafeLookup(aLookup);
   }
 
-  // Remove a previously found element (assuming aPtr.found()). The set must
-  // not have been mutated in the interim.
-  void remove(Ptr aPtr) { mImpl.remove(aPtr); }
+  // -- Insertions -----------------------------------------------------------
+
+  // Add |aU| if it is not present already. Returns false on OOM.
+  template<typename U>
+  MOZ_MUST_USE bool put(U&& aU)
+  {
+    AddPtr p = lookupForAdd(aU);
+    return p ? true : add(p, std::forward<U>(aU));
+  }
+
+  // Like put(), but asserts that the given key is not already present.
+  template<typename U>
+  MOZ_MUST_USE bool putNew(U&& aU)
+  {
+    return mImpl.putNew(aU, std::forward<U>(aU));
+  }
+
+  // Like the other putNew(), but for when |Lookup| is different to |T|.
+  template<typename U>
+  MOZ_MUST_USE bool putNew(const Lookup& aLookup, U&& aU)
+  {
+    return mImpl.putNew(aLookup, std::forward<U>(aU));
+  }
+
+  // Only call this to populate an empty set after reserving space with init().
+  template<typename U>
+  void putNewInfallible(const Lookup& aLookup, U&& aU)
+  {
+    mImpl.putNewInfallible(aLookup, std::forward<U>(aU));
+  }
 
   // Like |lookup(l)|, but on miss, |p = lookupForAdd(l)| allows efficient
   // insertion of T value |t| (where |HashPolicy::match(t,l) == true|) using
   // |add(p,t)|. After |add(p,t)|, |p| points to the new element. E.g.:
   //
   //   using HS = HashSet<int>;
   //   HS h;
   //   HS::AddPtr p = h.lookupForAdd(3);
@@ -559,126 +658,32 @@ public:
 
   // See the comment above lookupForAdd() for details.
   template<typename U>
   MOZ_MUST_USE bool relookupOrAdd(AddPtr& aPtr, const Lookup& aLookup, U&& aU)
   {
     return mImpl.relookupOrAdd(aPtr, aLookup, std::forward<U>(aU));
   }
 
-  // |iter()| returns an Iterator:
-  //
-  //   HashSet<int> h;
-  //   for (auto iter = h.iter(); !iter.done(); iter.next()) {
-  //     int i = iter.get();
-  //   }
-  //
-  typedef typename Impl::Iterator Iterator;
-  Iterator iter() const { return mImpl.iter(); }
-
-  // |modIter()| returns a ModIterator:
-  //
-  //   HashSet<int> h;
-  //   for (auto iter = h.modIter(); !iter.done(); iter.next()) {
-  //     if (iter.get() == 42) {
-  //       iter.remove();
-  //     }
-  //   }
-  //
-  // Table resize may occur in ModIterator's destructor.
-  typedef typename Impl::ModIterator ModIterator;
-  ModIterator modIter() { return mImpl.modIter(); }
-
-  // These are similar to Iterator/ModIterator/iter(), but use different
-  // terminology.
-  using Range = typename Impl::Range;
-  using Enum = typename Impl::Enum;
-  Range all() const { return mImpl.all(); }
-
-  // Remove all elements without changing the capacity.
-  void clear() { mImpl.clear(); }
-
-  // Remove all elements and attempt to minimize the capacity.
-  void clearAndShrink() { mImpl.clearAndShrink(); }
-
-  // Remove all keys/values and release entry storage. The set must be
-  // initialized via init() again before further use.
-  void finish() { mImpl.finish(); }
-
-  // Is the set empty?
-  bool empty() const { return mImpl.empty(); }
-
-  // Number of elements in the set.
-  uint32_t count() const { return mImpl.count(); }
-
-  // Number of element slots in the set. Note: resize will happen well before
-  // count() == capacity().
-  size_t capacity() const { return mImpl.capacity(); }
-
-  // The size of the HashSet's entry storage, in bytes. If the elements contain
-  // pointers to other heap blocks, you must iterate over the set and measure
-  // them separately; hence the "shallow" prefix.
-  size_t shallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
-  {
-    return mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
-  }
-  size_t shallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
-  {
-    return aMallocSizeOf(this) +
-           mImpl.shallowSizeOfExcludingThis(aMallocSizeOf);
-  }
-
-  // The set's current generation.
-  Generation generation() const { return mImpl.generation(); }
-
-  /************************************************** Shorthand operations */
-
-  // Does the set contain an element matching |aLookup|?
-  bool has(const Lookup& aLookup) const
-  {
-    return mImpl.lookup(aLookup).found();
-  }
-
-  // Add |aU| if it is not present already. Returns false on OOM.
-  template<typename U>
-  MOZ_MUST_USE bool put(U&& aU)
-  {
-    AddPtr p = lookupForAdd(aU);
-    return p ? true : add(p, std::forward<U>(aU));
-  }
-
-  // Like put(), but asserts that the given key is not already present.
-  template<typename U>
-  MOZ_MUST_USE bool putNew(U&& aU)
-  {
-    return mImpl.putNew(aU, std::forward<U>(aU));
-  }
-
-  // Like the other putNew(), but for when |Lookup| is different to |T|.
-  template<typename U>
-  MOZ_MUST_USE bool putNew(const Lookup& aLookup, U&& aU)
-  {
-    return mImpl.putNew(aLookup, std::forward<U>(aU));
-  }
-
-  // Only call this to populate an empty set after reserving space with init().
-  template<typename U>
-  void putNewInfallible(const Lookup& aLookup, U&& aU)
-  {
-    mImpl.putNewInfallible(aLookup, std::forward<U>(aU));
-  }
+  // -- Removal --------------------------------------------------------------
 
   // Lookup and remove the element matching |aLookup|, if present.
   void remove(const Lookup& aLookup)
   {
     if (Ptr p = lookup(aLookup)) {
       remove(p);
     }
   }
 
+  // Remove a previously found element (assuming aPtr.found()). The set must
+  // not have been mutated in the interim.
+  void remove(Ptr aPtr) { mImpl.remove(aPtr); }
+
+  // -- Rekeying -------------------------------------------------------------
+
   // Infallibly rekey one entry, if present. Requires that template parameters
   // T and HashPolicy::Lookup are the same type.
   void rekeyIfMoved(const Lookup& aOldValue, const T& aNewValue)
   {
     if (aOldValue != aNewValue) {
       rekeyAs(aOldValue, aNewValue, aNewValue);
     }
   }
@@ -703,33 +708,58 @@ public:
   {
     MOZ_ASSERT(aPtr.found());
     MOZ_ASSERT(*aPtr != aNewValue);
     MOZ_ASSERT(HashPolicy::hash(*aPtr) == HashPolicy::hash(aNewValue));
     MOZ_ASSERT(HashPolicy::match(*aPtr, aNewValue));
     const_cast<T&>(*aPtr) = aNewValue;
   }
 
-  // HashSet is movable.
-  HashSet(HashSet&& aRhs)
-    : mImpl(std::move(aRhs.mImpl))
-  {
-  }
-  void operator=(HashSet&& aRhs)
-  {
-    MOZ_ASSERT(this != &aRhs, "self-move assignment is prohibited");
-    mImpl = std::move(aRhs.mImpl);
-  }
+  // -- Iteration ------------------------------------------------------------
+
+  // |iter()| returns an Iterator:
+  //
+  //   HashSet<int> h;
+  //   for (auto iter = h.iter(); !iter.done(); iter.next()) {
+  //     int i = iter.get();
+  //   }
+  //
+  using Iterator = typename Impl::Iterator;
+  Iterator iter() const { return mImpl.iter(); }
 
-private:
-  // HashSet is not copyable or assignable.
-  HashSet(const HashSet& hs) = delete;
-  HashSet& operator=(const HashSet& hs) = delete;
+  // |modIter()| returns a ModIterator:
+  //
+  //   HashSet<int> h;
+  //   for (auto iter = h.modIter(); !iter.done(); iter.next()) {
+  //     if (iter.get() == 42) {
+  //       iter.remove();
+  //     }
+  //   }
+  //
+  // Table resize may occur in ModIterator's destructor.
+  using ModIterator = typename Impl::ModIterator;
+  ModIterator modIter() { return mImpl.modIter(); }
 
-  friend class Impl::Enum;
+  // These are similar to Iterator/ModIterator/iter(), but use different
+  // terminology.
+  using Range = typename Impl::Range;
+  using Enum = typename Impl::Enum;
+  Range all() const { return mImpl.all(); }
+
+  // -- Clearing -------------------------------------------------------------
+
+  // Remove all elements without changing the capacity.
+  void clear() { mImpl.clear(); }
+
+  // Remove all elements and attempt to minimize the capacity.
+  void clearAndShrink() { mImpl.clearAndShrink(); }
+
+  // Remove all keys/values and release entry storage. The set must be
+  // initialized via init() again before further use.
+  void finish() { mImpl.finish(); }
 };
 
 //---------------------------------------------------------------------------
 // Hash Policy
 //---------------------------------------------------------------------------
 
 // A hash policy |HP| for a hash table with key-type |Key| must provide:
 //
@@ -1509,36 +1539,16 @@ public:
   uint64_t mHashShift : 8; // multiplicative hash shift
   Entry* mTable;           // entry storage
   uint32_t mEntryCount;    // number of entries in mTable
   uint32_t mRemovedCount;  // removed entry sentinels in mTable
 
 #ifdef DEBUG
   uint64_t mMutationCount;
   mutable bool mEntered;
-
-  // Note that some updates to these stats are not thread-safe. See the
-  // comment on the three-argument overloading of HashTable::lookup().
-  mutable struct Stats
-  {
-    uint32_t mSearches;       // total number of table searches
-    uint32_t mSteps;          // hash chain links traversed
-    uint32_t mHits;           // searches that found key
-    uint32_t mMisses;         // searches that didn't find key
-    uint32_t mAddOverRemoved; // adds that recycled a removed entry
-    uint32_t mRemoves;        // calls to remove
-    uint32_t mRemoveFrees;    // calls to remove that freed the entry
-    uint32_t mGrows;          // table expansions
-    uint32_t mShrinks;        // table contractions
-    uint32_t mCompresses;     // table compressions
-    uint32_t mRehashes;       // tombstone decontaminations
-  } mStats;
-#define METER(x) x
-#else
-#define METER(x)
 #endif
 
   // The default initial capacity is 32 (enough to hold 16 elements), but it
   // can be as low as 4.
   static const uint32_t sMinCapacity = 4;
   static const uint32_t sMaxInit = 1u << (CAP_BITS - 1);
   static const uint32_t sMaxCapacity = 1u << CAP_BITS;
 
@@ -1663,17 +1673,16 @@ public:
     MOZ_ASSERT(newCapacity >= aLen);
     MOZ_ASSERT(newCapacity <= sMaxCapacity);
 
     mTable = createTable(*this, newCapacity);
     if (!mTable) {
       return false;
     }
     setTableSizeLog2(log2);
-    METER(memset(&mStats, 0, sizeof(mStats)));
     return true;
   }
 
   bool initialized() const { return !!mTable; }
 
   ~HashTable()
   {
     if (mTable) {
@@ -1730,42 +1739,36 @@ private:
 
   enum LookupReason
   {
     ForNonAdd,
     ForAdd
   };
 
   // Warning: in order for readonlyThreadsafeLookup() to be safe this
-  // function must not modify the table in any way when |collisionBit| is 0.
-  // (The use of the METER() macro to increment stats violates this
-  // restriction but we will live with that for now because it's enabled so
-  // rarely.)
+  // function must not modify the table in any way when Reason==ForNonAdd.
   template<LookupReason Reason>
   MOZ_ALWAYS_INLINE Entry& lookup(const Lookup& aLookup,
                                   HashNumber aKeyHash) const
   {
     MOZ_ASSERT(isLiveHash(aKeyHash));
     MOZ_ASSERT(!(aKeyHash & sCollisionBit));
     MOZ_ASSERT(mTable);
-    METER(mStats.mSearches++);
 
     // Compute the primary hash address.
     HashNumber h1 = hash1(aKeyHash);
     Entry* entry = &mTable[h1];
 
     // Miss: return space for a new entry.
     if (entry->isFree()) {
-      METER(mStats.mMisses++);
       return *entry;
     }
 
     // Hit: return entry.
     if (entry->matchHash(aKeyHash) && match(*entry, aLookup)) {
-      METER(mStats.mHits++);
       return *entry;
     }
 
     // Collision: double hash.
     DoubleHash dh = hash2(aKeyHash);
 
     // Save the first removed entry pointer so we can recycle later.
     Entry* firstRemoved = nullptr;
@@ -1774,69 +1777,62 @@ private:
       if (Reason == ForAdd && !firstRemoved) {
         if (MOZ_UNLIKELY(entry->isRemoved())) {
           firstRemoved = entry;
         } else {
           entry->setCollision();
         }
       }
 
-      METER(mStats.mSteps++);
       h1 = applyDoubleHash(h1, dh);
 
       entry = &mTable[h1];
       if (entry->isFree()) {
-        METER(mStats.mMisses++);
         return firstRemoved ? *firstRemoved : *entry;
       }
 
       if (entry->matchHash(aKeyHash) && match(*entry, aLookup)) {
-        METER(mStats.mHits++);
         return *entry;
       }
     }
   }
 
   // This is a copy of lookup hardcoded to the assumptions:
   //   1. the lookup is a lookupForAdd
   //   2. the key, whose |keyHash| has been passed is not in the table,
   //   3. no entries have been removed from the table.
   // This specialized search avoids the need for recovering lookup values
   // from entries, which allows more flexible Lookup/Key types.
   Entry& findFreeEntry(HashNumber aKeyHash)
   {
     MOZ_ASSERT(!(aKeyHash & sCollisionBit));
     MOZ_ASSERT(mTable);
-    METER(mStats.mSearches++);
 
     // We assume 'aKeyHash' has already been distributed.
 
     // Compute the primary hash address.
     HashNumber h1 = hash1(aKeyHash);
     Entry* entry = &mTable[h1];
 
     // Miss: return space for a new entry.
     if (!entry->isLive()) {
-      METER(mStats.mMisses++);
       return *entry;
     }
 
     // Collision: double hash.
     DoubleHash dh = hash2(aKeyHash);
 
     while (true) {
       MOZ_ASSERT(!entry->isRemoved());
       entry->setCollision();
 
-      METER(mStats.mSteps++);
       h1 = applyDoubleHash(h1, dh);
 
       entry = &mTable[h1];
       if (!entry->isLive()) {
-        METER(mStats.mMisses++);
         return *entry;
       }
     }
   }
 
   enum RebuildStatus
   {
     NotOverloaded,
@@ -1894,60 +1890,49 @@ private:
   }
 
   RebuildStatus checkOverloaded(FailureBehavior aReportFailure = ReportFailure)
   {
     if (!overloaded()) {
       return NotOverloaded;
     }
 
-    int deltaLog2;
-    if (shouldCompressTable()) {
-      METER(mStats.mCompresses++);
-      deltaLog2 = 0;
-    } else {
-      METER(mStats.mGrows++);
-      deltaLog2 = 1;
-    }
-
+    int deltaLog2 = shouldCompressTable() ? 0 : 1;
     return changeTableSize(deltaLog2, aReportFailure);
   }
 
   // Infallibly rehash the table if we are overloaded with removals.
   void checkOverRemoved()
   {
     if (overloaded()) {
       if (checkOverloaded(DontReportFailure) == RehashFailed) {
         rehashTableInPlace();
       }
     }
   }
 
   void remove(Entry& aEntry)
   {
     MOZ_ASSERT(mTable);
-    METER(mStats.mRemoves++);
 
     if (aEntry.hasCollision()) {
       aEntry.removeLive();
       mRemovedCount++;
     } else {
-      METER(mStats.mRemoveFrees++);
       aEntry.clearLive();
     }
     mEntryCount--;
 #ifdef DEBUG
     mMutationCount++;
 #endif
   }
 
   void checkUnderloaded()
   {
     if (underloaded()) {
-      METER(mStats.mShrinks++);
       (void)changeTableSize(-1, DontReportFailure);
     }
   }
 
   // Resize the table down to the largest capacity which doesn't underload the
   // table.  Since we call checkUnderloaded() on every remove, you only need
   // to call this after a bulk removal of items done without calling remove().
   void compactIfUnderloaded()
@@ -1966,17 +1951,16 @@ private:
 
   // This is identical to changeTableSize(currentSize), but without requiring
   // a second table.  We do this by recycling the collision bits to tell us if
   // the element is already inserted or still waiting to be inserted.  Since
   // already-inserted elements win any conflicts, we get the same table as we
   // would have gotten through random insertion order.
   void rehashTableInPlace()
   {
-    METER(mStats.mRehashes++);
     mRemovedCount = 0;
     mGen++;
     for (size_t i = 0; i < capacity(); ++i) {
       mTable[i].unsetCollision();
     }
     for (size_t i = 0; i < capacity();) {
       Entry* src = &mTable[i];
 
@@ -2018,17 +2002,16 @@ private:
   {
     MOZ_ASSERT(mTable);
 
     HashNumber keyHash = prepareHash(aLookup);
     Entry* entry = &findFreeEntry(keyHash);
     MOZ_ASSERT(entry);
 
     if (entry->isRemoved()) {
-      METER(mStats.mAddOverRemoved++);
       mRemovedCount--;
       keyHash |= sCollisionBit;
     }
 
     entry->setLive(keyHash, std::forward<Args>(aArgs)...);
     mEntryCount++;
 #ifdef DEBUG
     mMutationCount++;
@@ -2178,17 +2161,16 @@ public:
 #endif
 
     // Changing an entry from removed to live does not affect whether we
     // are overloaded and can be handled separately.
     if (aPtr.mEntry->isRemoved()) {
       if (!this->checkSimulatedOOM()) {
         return false;
       }
-      METER(mStats.mAddOverRemoved++);
       mRemovedCount--;
       aPtr.mKeyHash |= sCollisionBit;
     } else {
       // Preserve the validity of |aPtr.mEntry|.
       RebuildStatus status = checkOverloaded();
       if (status == RehashFailed) {
         return false;
       }
@@ -2284,16 +2266,14 @@ public:
     putNewInfallibleInternal(aLookup, std::move(t));
   }
 
   void rekeyAndMaybeRehash(Ptr aPtr, const Lookup& aLookup, const Key& aKey)
   {
     rekeyWithoutRehash(aPtr, aLookup, aKey);
     checkOverRemoved();
   }
-
-#undef METER
 };
 
 } // namespace detail
 } // namespace mozilla
 
 #endif /* mozilla_HashTable_h */
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -333,16 +333,23 @@ VARCACHE_PREF(
 
 // Should the :visited selector ever match (otherwise :link matches instead)?
 VARCACHE_PREF(
   "layout.css.visited_links_enabled",
    layout_css_visited_links_enabled,
   bool, true
 )
 
+// Is the '-webkit-appearance' alias for '-moz-appearance' enabled?
+VARCACHE_PREF(
+  "layout.css.webkit-appearance.enabled",
+   layout_css_webkit_appearance_enabled,
+  bool, false
+)
+
 // Pref to control whether @-moz-document rules are enabled in content pages.
 VARCACHE_PREF(
   "layout.css.moz-document.content.enabled",
    layout_css_moz_document_content_enabled,
   bool, false
 )
 
 // Pref to control whether @-moz-document url-prefix() is parsed in content
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3095,19 +3095,16 @@ pref("layout.float-fragments-inside-colu
 // The number of frames times the frame rate is the time required to
 // pass without painting used to guess that we'll not paint again soon
 pref("layout.idle_period.required_quiescent_frames", 2);
 
 // The amount of time (milliseconds) needed between an idle period's
 // end and the start of the next tick to avoid jank.
 pref("layout.idle_period.time_limit", 1);
 
-// Whether -webkit-appearance is aliased to -moz-appearance
-pref("layout.css.webkit-appearance.enabled", false);
-
 // Is support for the core interfaces of Web Animations API enabled?
 pref("dom.animations-api.core.enabled", true);
 
 // Pref to throttle offsreen animations
 pref("dom.animations.offscreen-throttling", true);
 
 // Prefs to control the maximum area to pre-render when animating a large
 // element on the compositor.
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -30,16 +30,17 @@
 #include "nsNativeThemeColors.h"
 #include "nsIScrollableFrame.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Range.h"
 #include "mozilla/RelativeLuminanceUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMeterElement.h"
 #include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/StaticPrefs.h"
 #include "nsLookAndFeel.h"
 #include "VibrancyManager.h"
 
 #include "gfxContext.h"
 #include "gfxQuartzSurface.h"
 #include "gfxQuartzNativeDrawing.h"
 #include <algorithm>
 
@@ -3064,16 +3065,21 @@ IsHiDPIContext(nsDeviceContext* aContext
 
 Maybe<nsNativeThemeCocoa::WidgetInfo>
 nsNativeThemeCocoa::ComputeWidgetInfo(nsIFrame* aFrame,
                                       WidgetType aWidgetType,
                                       const nsRect& aRect)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // setup to draw into the correct port
   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
 
   gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
   nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
   float originalHeight = nativeWidgetRect.Height();
   nativeWidgetRect.Round();
   if (nativeWidgetRect.IsEmpty()) {
@@ -3861,16 +3867,18 @@ nsNativeThemeCocoa::CreateWebRenderComma
     case StyleAppearance::Spinner:
     case StyleAppearance::SpinnerUpbutton:
     case StyleAppearance::SpinnerDownbutton:
     case StyleAppearance::Toolbarbutton:
     case StyleAppearance::Separator:
     case StyleAppearance::Toolbar:
     case StyleAppearance::MozWindowTitlebar:
     case StyleAppearance::Statusbar:
+    // NOTE: if you change Menulist and MenulistButton to behave differently,
+    // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistTextfield:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
     case StyleAppearance::Groupbox:
     case StyleAppearance::Textfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Searchfield:
@@ -4028,16 +4036,18 @@ nsNativeThemeCocoa::GetWidgetBorder(nsDe
     case StyleAppearance::Radio:
     {
       // nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight
       // assume a border width of 2px.
       result.SizeTo(2, 2, 2, 2);
       break;
     }
 
+    // NOTE: if you change Menulist and MenulistButton to behave differently,
+    // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
       result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
       break;
 
     case StyleAppearance::MenulistTextfield:
       result = DirectionAwareMargin(kAquaComboboxBorder, aFrame);
@@ -4156,16 +4166,18 @@ nsNativeThemeCocoa::GetWidgetOverflow(ns
     case StyleAppearance::MozMacDisclosureButtonClosed:
     case StyleAppearance::MozMacHelpButton:
     case StyleAppearance::Toolbarbutton:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     case StyleAppearance::TextfieldMultiline:
     case StyleAppearance::Searchfield:
     case StyleAppearance::Listbox:
+    // NOTE: if you change Menulist and MenulistButton to behave differently,
+    // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
     case StyleAppearance::MenulistTextfield:
     case StyleAppearance::Checkbox:
     case StyleAppearance::Radio:
     case StyleAppearance::Tab:
     case StyleAppearance::FocusOutline:
@@ -4287,16 +4299,18 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
           buttonHeight /= 2;
         }
       }
       aResult->SizeTo(buttonWidth, buttonHeight);
       *aIsOverridable = true;
       break;
     }
 
+    // NOTE: if you change Menulist and MenulistButton to behave differently,
+    // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
     {
       SInt32 popupHeight = 0;
       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
       aResult->SizeTo(0, popupHeight);
       break;
@@ -4615,16 +4629,21 @@ nsNativeThemeCocoa::ThemeChanged()
   // nsLookAndFeel::SystemWantsDarkTheme.
   return NS_OK;
 }
 
 bool
 nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
                                       WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // if this is a dropdown button in a combobox the answer is always no
   if (aWidgetType == StyleAppearance::MenulistButton ||
       aWidgetType == StyleAppearance::MozMenulistButton) {
     nsIFrame* parentFrame = aFrame->GetParent();
     if (parentFrame && parentFrame->IsComboboxControlFrame())
       return false;
   }
 
@@ -4754,16 +4773,21 @@ nsNativeThemeCocoa::ThemeSupportsWidget(
   }
 
   return false;
 }
 
 bool
 nsNativeThemeCocoa::WidgetIsContainer(WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // flesh this out at some point
   switch (aWidgetType) {
    case StyleAppearance::MenulistButton:
    case StyleAppearance::MozMenulistButton:
    case StyleAppearance::Radio:
    case StyleAppearance::Checkbox:
    case StyleAppearance::Progressbar:
    case StyleAppearance::Meterbar:
@@ -4776,16 +4800,21 @@ nsNativeThemeCocoa::WidgetIsContainer(Wi
     break;
   }
   return true;
 }
 
 bool
 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   if (aWidgetType == StyleAppearance::Menulist ||
       aWidgetType == StyleAppearance::MenulistTextfield ||
       aWidgetType == StyleAppearance::Button ||
       aWidgetType == StyleAppearance::MozMacHelpButton ||
       aWidgetType == StyleAppearance::MozMacDisclosureButtonOpen ||
       aWidgetType == StyleAppearance::MozMacDisclosureButtonClosed ||
       aWidgetType == StyleAppearance::Radio ||
       aWidgetType == StyleAppearance::Range ||
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -35,16 +35,17 @@
 #include "gfxContext.h"
 #include "gfxPlatformGtk.h"
 #include "gfxGdkNativeRenderer.h"
 #include "mozilla/gfx/BorrowedContext.h"
 #include "mozilla/gfx/HelpersCairo.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/StaticPrefs.h"
 
 #ifdef MOZ_X11
 #  ifdef CAIRO_HAS_XLIB_SURFACE
 #    include "cairo-xlib.h"
 #  endif
 #  ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
 #    include "cairo-xlib-xrender.h"
 #  endif
@@ -225,16 +226,21 @@ static bool ShouldScrollbarButtonBeDisab
 }
 
 bool
 nsNativeThemeGTK::GetGtkWidgetAndState(StyleAppearance aWidgetType, nsIFrame* aFrame,
                                        WidgetNodeType& aGtkWidgetType,
                                        GtkWidgetState* aState,
                                        gint* aWidgetFlags)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   if (aState) {
     // For XUL checkboxes and radio buttons, the state of the parent
     // determines our state.
     nsIFrame *stateFrame = aFrame;
     if (aFrame && ((aWidgetFlags && (aWidgetType == StyleAppearance::Checkbox ||
                                      aWidgetType == StyleAppearance::Radio)) ||
                    aWidgetType == StyleAppearance::CheckboxLabel ||
                    aWidgetType == StyleAppearance::RadioLabel)) {
@@ -1387,16 +1393,21 @@ nsNativeThemeGTK::GetWidgetBorder(nsDevi
 }
 
 bool
 nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
                                    nsIFrame* aFrame,
                                    StyleAppearance aWidgetType,
                                    LayoutDeviceIntMargin* aResult)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   switch (aWidgetType) {
     case StyleAppearance::ButtonFocus:
     case StyleAppearance::Toolbarbutton:
     case StyleAppearance::MozWindowButtonClose:
     case StyleAppearance::MozWindowButtonMinimize:
     case StyleAppearance::MozWindowButtonMaximize:
     case StyleAppearance::MozWindowButtonRestore:
     case StyleAppearance::Dualbutton:
@@ -1477,16 +1488,21 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n
                                        nsIFrame* aFrame,
                                        StyleAppearance aWidgetType,
                                        LayoutDeviceIntSize* aResult,
                                        bool* aIsOverridable)
 {
   aResult->width = aResult->height = 0;
   *aIsOverridable = true;
 
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   switch (aWidgetType) {
     case StyleAppearance::ScrollbarbuttonUp:
     case StyleAppearance::ScrollbarbuttonDown:
       {
         const ScrollbarGTKMetrics* metrics =
           GetActiveScrollbarMetrics(GTK_ORIENTATION_VERTICAL);
 
         aResult->width = metrics->size.button.width;
@@ -1845,16 +1861,21 @@ nsNativeThemeGTK::ThemeChanged()
 NS_IMETHODIMP_(bool)
 nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
                                       nsIFrame* aFrame,
                                       StyleAppearance aWidgetType)
 {
   if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType))
     return false;
 
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   switch (aWidgetType) {
   // Combobox dropdowns don't support native theming in vertical mode.
   case StyleAppearance::Menulist:
   case StyleAppearance::MenulistText:
   case StyleAppearance::MenulistTextfield:
     if (aFrame && aFrame->GetWritingMode().IsVertical()) {
       return false;
     }
@@ -1975,16 +1996,21 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns
   }
 
   return false;
 }
 
 NS_IMETHODIMP_(bool)
 nsNativeThemeGTK::WidgetIsContainer(StyleAppearance aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // XXXdwh At some point flesh all of this out.
   if (aWidgetType == StyleAppearance::MenulistButton ||
       aWidgetType == StyleAppearance::MozMenulistButton ||
       aWidgetType == StyleAppearance::Radio ||
       aWidgetType == StyleAppearance::RangeThumb ||
       aWidgetType == StyleAppearance::Checkbox ||
       aWidgetType == StyleAppearance::TabScrollArrowBack ||
       aWidgetType == StyleAppearance::TabScrollArrowForward ||
@@ -1994,17 +2020,22 @@ nsNativeThemeGTK::WidgetIsContainer(Styl
       aWidgetType == StyleAppearance::ButtonArrowPrevious)
     return false;
   return true;
 }
 
 bool
 nsNativeThemeGTK::ThemeDrawsFocusForWidget(StyleAppearance aWidgetType)
 {
-   if (aWidgetType == StyleAppearance::Menulist ||
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
+  if (aWidgetType == StyleAppearance::Menulist ||
       aWidgetType == StyleAppearance::Button ||
       aWidgetType == StyleAppearance::Treeheadercell)
     return true;
 
   return false;
 }
 
 bool
--- a/widget/headless/HeadlessThemeGTK.cpp
+++ b/widget/headless/HeadlessThemeGTK.cpp
@@ -1,14 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * 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/. */
 
 #include "HeadlessThemeGTK.h"
+
+#include "mozilla/StaticPrefs.h"
 #include "nsStyleConsts.h"
 #include "nsIFrame.h"
 
 
 namespace mozilla {
 namespace widget {
 
 NS_IMPL_ISUPPORTS_INHERITED(HeadlessThemeGTK, nsNativeTheme, nsITheme)
@@ -22,16 +24,21 @@ HeadlessThemeGTK::DrawWidgetBackground(g
 {
   return NS_OK;
 }
 
 LayoutDeviceIntMargin
 HeadlessThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
                                   WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   LayoutDeviceIntMargin result;
   // The following values are generated from the Ubuntu GTK theme.
   switch (aWidgetType) {
     case StyleAppearance::Button:
     case StyleAppearance::Toolbarbutton:
       result.top = 6;
       result.right = 7;
       result.bottom = 6;
@@ -117,16 +124,21 @@ HeadlessThemeGTK::GetWidgetBorder(nsDevi
   return result;
 }
 
 bool
 HeadlessThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
                                    nsIFrame* aFrame, WidgetType aWidgetType,
                                    LayoutDeviceIntMargin* aResult)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // The following values are generated from the Ubuntu GTK theme.
   switch (aWidgetType) {
     case StyleAppearance::Radio:
     case StyleAppearance::Checkbox:
     case StyleAppearance::Toolbarbutton:
     case StyleAppearance::Dualbutton:
     case StyleAppearance::ToolbarbuttonDropdown:
     case StyleAppearance::ButtonArrowUp:
@@ -166,16 +178,21 @@ NS_IMETHODIMP
 HeadlessThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
                                        nsIFrame* aFrame, WidgetType aWidgetType,
                                        LayoutDeviceIntSize* aResult,
                                        bool* aIsOverridable)
 {
   aResult->width = aResult->height = 0;
   *aIsOverridable = true;
 
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // The following values are generated from the Ubuntu GTK theme.
   switch (aWidgetType) {
     case StyleAppearance::Splitter:
       if (IsHorizontal(aFrame)) {
         aResult->width = 6;
         aResult->height = 0;
       } else {
         aResult->width = 0;
@@ -327,16 +344,21 @@ static bool IsFrameContentNodeInNamespac
   return content->IsInNamespace(aNamespace);
 }
 
 NS_IMETHODIMP_(bool)
 HeadlessThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
                                       nsIFrame* aFrame,
                                       WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   switch (aWidgetType) {
     case StyleAppearance::Button:
     case StyleAppearance::Radio:
     case StyleAppearance::Checkbox:
     case StyleAppearance::FocusOutline:
     case StyleAppearance::Toolbox:
     case StyleAppearance::Toolbar:
     case StyleAppearance::Toolbarbutton:
@@ -419,16 +441,21 @@ HeadlessThemeGTK::ThemeSupportsWidget(ns
       break;
   }
   return false;
 }
 
 NS_IMETHODIMP_(bool)
 HeadlessThemeGTK::WidgetIsContainer(WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
     if (aWidgetType == StyleAppearance::MenulistButton ||
         aWidgetType == StyleAppearance::MozMenulistButton ||
         aWidgetType == StyleAppearance::Radio ||
         aWidgetType == StyleAppearance::RangeThumb ||
         aWidgetType == StyleAppearance::Checkbox ||
         aWidgetType == StyleAppearance::TabScrollArrowBack ||
         aWidgetType == StyleAppearance::TabScrollArrowForward ||
         aWidgetType == StyleAppearance::ButtonArrowUp ||
--- a/widget/nsNativeTheme.cpp
+++ b/widget/nsNativeTheme.cpp
@@ -22,16 +22,17 @@
 #include "nsMenuFrame.h"
 #include "nsRangeFrame.h"
 #include "nsCSSRendering.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLProgressElement.h"
+#include "mozilla/StaticPrefs.h"
 #include "nsIDocumentInlines.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsNativeTheme::nsNativeTheme()
 : mAnimatedContentTimeout(UINT32_MAX)
@@ -344,17 +345,19 @@ nsNativeTheme::IsWidgetStyled(nsPresCont
     }
   }
 
   return (aWidgetType == StyleAppearance::NumberInput ||
           aWidgetType == StyleAppearance::Button ||
           aWidgetType == StyleAppearance::Textfield ||
           aWidgetType == StyleAppearance::TextfieldMultiline ||
           aWidgetType == StyleAppearance::Listbox ||
-          aWidgetType == StyleAppearance::Menulist) &&
+          aWidgetType == StyleAppearance::Menulist ||
+          (aWidgetType == StyleAppearance::MenulistButton &&
+           StaticPrefs::layout_css_webkit_appearance_enabled())) &&
          aFrame->GetContent()->IsHTMLElement() &&
          aPresContext->HasAuthorSpecifiedRules(aFrame,
                                                NS_AUTHOR_SPECIFIED_BORDER |
                                                NS_AUTHOR_SPECIFIED_BACKGROUND);
 }
 
 bool
 nsNativeTheme::IsDisabled(nsIFrame* aFrame, EventStates aEventStates)
--- a/widget/windows/nsNativeThemeWin.cpp
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -3,16 +3,17 @@
  * 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/. */
 
 #include "nsNativeThemeWin.h"
 
 #include "mozilla/EventStates.h"
 #include "mozilla/Logging.h"
 #include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsColor.h"
 #include "nsDeviceContext.h"
 #include "nsRect.h"
 #include "nsSize.h"
 #include "nsTransform2D.h"
 #include "nsStyleConsts.h"
 #include "nsIPresShell.h"
@@ -778,16 +779,18 @@ mozilla::Maybe<nsUXThemeClass> nsNativeT
     case StyleAppearance::SpinnerUpbutton:
     case StyleAppearance::SpinnerDownbutton:
       return Some(eUXSpin);
     case StyleAppearance::Statusbar:
     case StyleAppearance::Statusbarpanel:
     case StyleAppearance::Resizerpanel:
     case StyleAppearance::Resizer:
       return Some(eUXStatus);
+    // NOTE: if you change Menulist and MenulistButton to behave differently,
+    // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
       return Some(eUXCombobox);
     case StyleAppearance::Treeheadercell:
     case StyleAppearance::Treeheadersortarrow:
       return Some(eUXHeader);
     case StyleAppearance::Listbox:
@@ -868,16 +871,21 @@ nsNativeThemeWin::IsMenuActive(nsIFrame 
  * us; 0 means that we should use part code 0, which isn't a real part code
  * but elicits some kind of default behaviour from UXTheme when drawing
  * (but isThemeBackgroundPartiallyTransparent may not work).
  */
 nsresult 
 nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, WidgetType aWidgetType, 
                                        int32_t& aPart, int32_t& aState)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   switch (aWidgetType) {
     case StyleAppearance::Button: {
       aPart = BP_BUTTON;
       if (!aFrame) {
         aState = TS_NORMAL;
         return NS_OK;
       }
 
@@ -1539,16 +1547,21 @@ GetThemeDpiScaleFactor(nsIFrame* aFrame)
 
 NS_IMETHODIMP
 nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext,
                                        nsIFrame* aFrame,
                                        WidgetType aWidgetType,
                                        const nsRect& aRect,
                                        const nsRect& aDirtyRect)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   if (IsWidgetScrollbarPart(aWidgetType)) {
     ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
     if (style->StyleUserInterface()->HasCustomScrollbars()) {
       return DrawCustomScrollbarPart(aContext, aFrame, style,
                                      aWidgetType, aRect, aDirtyRect);
     }
   }
 
@@ -2103,16 +2116,21 @@ nsNativeThemeWin::GetWidgetBorder(nsDevi
 }
 
 bool
 nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext, 
                                    nsIFrame* aFrame,
                                    WidgetType aWidgetType,
                                    LayoutDeviceIntMargin* aResult)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   switch (aWidgetType) {
     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
     // and have a meaningful baseline, so they can't have
     // author-specified padding.
     case StyleAppearance::Checkbox:
     case StyleAppearance::Radio:
       aResult->SizeTo(0, 0, 0, 0);
       return true;
@@ -2298,16 +2316,21 @@ nsNativeThemeWin::GetWidgetOverflow(nsDe
   return false;
 }
 
 NS_IMETHODIMP
 nsNativeThemeWin::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
                                        WidgetType aWidgetType,
                                        LayoutDeviceIntSize* aResult, bool* aIsOverridable)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   aResult->width = aResult->height = 0;
   *aIsOverridable = true;
   nsresult rv = NS_OK;
 
   mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
   HTHEME theme = NULL;
   if (!themeClass.isNothing()) {
     theme = nsUXThemeData::GetTheme(themeClass.value());
@@ -2530,16 +2553,21 @@ nsNativeThemeWin::GetMinimumWidgetSize(n
   return rv;
 }
 
 NS_IMETHODIMP
 nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, WidgetType aWidgetType, 
                                      nsAtom* aAttribute, bool* aShouldRepaint,
                                      const nsAttrValue* aOldValue)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // Some widget types just never change state.
   if (aWidgetType == StyleAppearance::Toolbox ||
       aWidgetType == StyleAppearance::MozWinMediaToolbox ||
       aWidgetType == StyleAppearance::MozWinCommunicationsToolbox ||
       aWidgetType == StyleAppearance::MozWinBrowsertabbarToolbox ||
       aWidgetType == StyleAppearance::Toolbar ||
       aWidgetType == StyleAppearance::Statusbar || aWidgetType == StyleAppearance::Statusbarpanel ||
       aWidgetType == StyleAppearance::Resizerpanel ||
@@ -2567,16 +2595,19 @@ nsNativeThemeWin::WidgetStateChanged(nsI
       aWidgetType == StyleAppearance::MozWindowButtonMaximize ||
       aWidgetType == StyleAppearance::MozWindowButtonRestore) {
     *aShouldRepaint = true;
     return NS_OK;
   }
 
   // We need to repaint the dropdown arrow in vista HTML combobox controls when
   // the control is closed to get rid of the hover effect.
+  //
+  // NOTE: if you change Menulist and MenulistButton to behave differently,
+  // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
   if ((aWidgetType == StyleAppearance::Menulist ||
        aWidgetType == StyleAppearance::MenulistButton ||
        aWidgetType == StyleAppearance::MozMenulistButton) &&
       IsHTMLContent(aFrame))
   {
     *aShouldRepaint = true;
     return NS_OK;
   }
@@ -2644,16 +2675,21 @@ nsNativeThemeWin::ThemeSupportsWidget(ns
     return (!IsWidgetStyled(aPresContext, aFrame, aWidgetType));
 
   return false;
 }
 
 bool 
 nsNativeThemeWin::WidgetIsContainer(WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   // XXXdwh At some point flesh all of this out.
   if (aWidgetType == StyleAppearance::MenulistButton || 
       aWidgetType == StyleAppearance::MozMenulistButton || 
       aWidgetType == StyleAppearance::Radio ||
       aWidgetType == StyleAppearance::Checkbox)
     return false;
   return true;
 }
@@ -2791,25 +2827,27 @@ nsNativeThemeWin::ClassicThemeSupportsWi
     case StyleAppearance::ScrollbarVertical:
     case StyleAppearance::ScrollbarHorizontal:
     case StyleAppearance::ScrollbarNonDisappearing:
     case StyleAppearance::Scrollcorner:
     case StyleAppearance::ScaleHorizontal:
     case StyleAppearance::ScaleVertical:
     case StyleAppearance::ScalethumbHorizontal:
     case StyleAppearance::ScalethumbVertical:
+    // NOTE: if you change Menulist and MenulistButton to behave differently,
+    // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
+    case StyleAppearance::Menulist:
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
     case StyleAppearance::InnerSpinButton:
     case StyleAppearance::SpinnerUpbutton:
     case StyleAppearance::SpinnerDownbutton:
     case StyleAppearance::Listbox:
     case StyleAppearance::Treeview:
-    case StyleAppearance::MenulistTextfield:
-    case StyleAppearance::Menulist:
     case StyleAppearance::Tooltip:
     case StyleAppearance::Statusbar:
     case StyleAppearance::Statusbarpanel:
     case StyleAppearance::Resizerpanel:
     case StyleAppearance::Progressbar:
     case StyleAppearance::ProgressbarVertical:
     case StyleAppearance::Progresschunk:
     case StyleAppearance::ProgresschunkVertical:
@@ -2840,16 +2878,21 @@ nsNativeThemeWin::ClassicThemeSupportsWi
   return false;
 }
 
 LayoutDeviceIntMargin
 nsNativeThemeWin::ClassicGetWidgetBorder(nsDeviceContext* aContext, 
                                          nsIFrame* aFrame,
                                          WidgetType aWidgetType)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   LayoutDeviceIntMargin result;
   switch (aWidgetType) {
     case StyleAppearance::Groupbox:
     case StyleAppearance::Button:
       result.top = result.left = result.bottom = result.right = 2;
       break;
     case StyleAppearance::Statusbar:
       result.bottom = result.left = result.right = 0;
@@ -2936,16 +2979,21 @@ nsNativeThemeWin::ClassicGetWidgetPaddin
 }
 
 nsresult
 nsNativeThemeWin::ClassicGetMinimumWidgetSize(nsIFrame* aFrame,
                                               WidgetType aWidgetType,
                                               LayoutDeviceIntSize* aResult,
                                               bool* aIsOverridable)
 {
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   (*aResult).width = (*aResult).height = 0;
   *aIsOverridable = true;
   switch (aWidgetType) {
     case StyleAppearance::Radio:
     case StyleAppearance::Checkbox:
       (*aResult).width = (*aResult).height = 13;
       break;
     case StyleAppearance::Menucheckbox:
@@ -3115,16 +3163,21 @@ nsNativeThemeWin::ClassicGetMinimumWidge
   }  
   return NS_OK;
 }
 
 
 nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame, WidgetType aWidgetType,
                                  int32_t& aPart, int32_t& aState, bool& aFocused)
 {  
+  if (aWidgetType == StyleAppearance::MenulistButton &&
+      StaticPrefs::layout_css_webkit_appearance_enabled()) {
+    aWidgetType = StyleAppearance::Menulist;
+  }
+
   aFocused = false;
   switch (aWidgetType) {
     case StyleAppearance::Button: {
       EventStates contentState;
 
       aPart = DFC_BUTTON;
       aState = DFCS_BUTTONPUSH;
       aFocused = false;