merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 23 Jan 2017 11:08:00 +0100
changeset 375602 f1f53d0897bfedc2ab269e338d5cc373bd880c69
parent 375581 d5e37c0a776f1f2c21ddac4612529f819e13733b (current diff)
parent 375601 726ee922483f6e3e12a2921fbcdbe7c5616aafe8 (diff)
child 375621 38db7f01d45d75547b026fbc9d739fe90e93675c
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
modules/libpref/init/all.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7873,17 +7873,20 @@ var TabContextMenu = {
     // Hide "Bookmark All Tabs" for a pinned tab.  Update its state if visible.
     let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs");
     bookmarkAllTabs.hidden = this.contextTab.pinned;
     if (!bookmarkAllTabs.hidden)
       PlacesCommandHook.updateBookmarkAllTabsCommand();
 
     // Adjust the state of the toggle mute menu item.
     let toggleMute = document.getElementById("context_toggleMuteTab");
-    if (this.contextTab.hasAttribute("muted")) {
+    if (this.contextTab.hasAttribute("blocked")) {
+      toggleMute.label = gNavigatorBundle.getString("playTab.label");
+      toggleMute.accessKey = gNavigatorBundle.getString("playTab.accesskey");
+    } else if (this.contextTab.hasAttribute("muted")) {
       toggleMute.label = gNavigatorBundle.getString("unmuteTab.label");
       toggleMute.accessKey = gNavigatorBundle.getString("unmuteTab.accesskey");
     } else {
       toggleMute.label = gNavigatorBundle.getString("muteTab.label");
       toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey");
     }
 
     this.contextTab.toggleMuteMenuItem = toggleMute;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -6998,16 +6998,22 @@
       </property>
 
       <property name="soundPlaying" readonly="true">
         <getter>
           return this.getAttribute("soundplaying") == "true";
         </getter>
       </property>
 
+      <property name="soundBlocked" readonly="true">
+        <getter>
+          return this.getAttribute("blocked") == "true";
+        </getter>
+      </property>
+
       <property name="lastAccessed">
         <getter>
           return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
         </getter>
       </property>
       <method name="updateLastAccessed">
         <parameter name="aDate"/>
         <body><![CDATA[
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -810,16 +810,18 @@ userContext.aboutPage.label = Manage con
 userContext.aboutPage.accesskey = O
 
 userContextOpenLink.label = Open Link in New %S Tab
 
 muteTab.label = Mute Tab
 muteTab.accesskey = M
 unmuteTab.label = Unmute Tab
 unmuteTab.accesskey = M
+playTab.label = Play Tab
+playTab.accesskey = P
 
 # LOCALIZATION NOTE (weakCryptoOverriding.message): %S is brandShortName
 weakCryptoOverriding.message = %S recommends that you don’t enter your password, credit card and other personal information on this website.
 revokeOverride.label = Don’t Trust This Website
 revokeOverride.accesskey = D
 
 # LOCALIZATION NOTE (certErrorDetails*.label): These are text strings that
 # appear in the about:certerror page, so that the user can copy and send them to
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -649,24 +649,42 @@ public:
         return;
       }
 
       mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
       NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
     }
   }
 
+  bool
+  ShouldResetSuspend() const
+  {
+    // The disposable-pause should be clear after media starts playing.
+    if (!mOwner->Paused() &&
+        mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE) {
+      return true;
+    }
+
+    // If the blocked media is paused, we don't need to resume it. We reset the
+    // mSuspended in order to unregister the agent.
+    if (mOwner->Paused() &&
+        mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
+      return true;
+    }
+
+    return false;
+  }
+
   void
-  NotifyPlayStarted()
+  NotifyPlayStateChanged()
   {
     MOZ_ASSERT(!mIsShutDown);
-    // Reset the suspend type because the media element might be paused by
-    // audio channel before calling play(). eg. paused by Fennec media control,
-    // but resumed it from page.
-    SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+    if (ShouldResetSuspend()) {
+      SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+    }
     UpdateAudioChannelPlayingState();
   }
 
   NS_IMETHODIMP
   WindowVolumeChanged(float aVolume, bool aMuted) override
   {
     MOZ_ASSERT(mAudioChannelAgent);
 
@@ -875,17 +893,17 @@ private:
   }
 
   void
   Resume()
   {
     if (!IsSuspended()) {
       MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
              ("HTMLMediaElement::AudioChannelAgentCallback, ResumeFromAudioChannel, "
-              "this = %p, Error : resume without suspended!\n", this));
+              "this = %p, don't need to be resumed!\n", this));
       return;
     }
 
     SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
     IgnoredErrorResult rv;
     RefPtr<Promise> toBeIgnored = mOwner->Play(rv);
     MOZ_ASSERT_IF(toBeIgnored && toBeIgnored->State() == Promise::PromiseState::Rejected,
                   rv.Failed());
@@ -2752,17 +2770,19 @@ HTMLMediaElement::Pause(ErrorResult& aRv
   }
 
   bool oldPaused = mPaused;
   mPaused = true;
   mAutoplaying = false;
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
   UpdateSrcMediaStreamPlaying();
-  UpdateAudioChannelPlayingState();
+  if (mAudioChannelWrapper) {
+    mAudioChannelWrapper->NotifyPlayStateChanged();
+  }
 
   if (!oldPaused) {
     FireTimeUpdate(false);
     DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
     AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR);
   }
 }
 
@@ -7127,17 +7147,17 @@ HTMLMediaElement::MarkAsContentSource(Ca
        this, isVisible, aAPI));
 }
 
 void
 HTMLMediaElement::UpdateCustomPolicyAfterPlayed()
 {
   OpenUnsupportedMediaWithExternalAppIfNeeded();
   if (mAudioChannelWrapper) {
-    mAudioChannelWrapper->NotifyPlayStarted();
+    mAudioChannelWrapper->NotifyPlayStateChanged();
   }
 }
 
 nsTArray<RefPtr<Promise>>
 HTMLMediaElement::TakePendingPlayPromises()
 {
   return Move(mPendingPlayPromises);
 }
--- a/dom/media/test/test_decode_error.html
+++ b/dom/media/test/test_decode_error.html
@@ -26,17 +26,17 @@ function startTest(test, token) {
     is(event.type, "error", "Expected event of type 'error'");
     ok(el.error, "Element 'error' attr expected to have a value");
     ok(el.error instanceof MediaError, "Element 'error' attr expected to be MediaError");
     if (v.readyState == v.HAVE_NOTHING) {
       is(el.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, "Expected media not supported error");
     } else {
       is(el.error.code, MediaError.MEDIA_ERR_DECODE, "Expected a decode error");
     }
-    ok(typeof el.error.message === 'string' || el.error.essage instanceof String, "Element 'message' attr expected to be a string");
+    ok(typeof el.error.message === 'string' || el.error.message instanceof String, "Element 'message' attr expected to be a string");
     ok(el.error.message.length > 0, "Element 'message' attr has content");
     el._sawError = true;
     manager.finished(token);
   });
 
   v.addEventListener("loadeddata", function () {
     ok(false, "Unexpected loadeddata event");
     manager.finished(token);
--- a/dom/media/test/test_error_in_video_document.html
+++ b/dom/media/test/test_error_in_video_document.html
@@ -44,25 +44,16 @@ function check() {
 var t = getPlayableVideo(gErrorTests);
 if (!t) {
   todo(false, "No types supported");
 } else {
   SimpleTest.waitForExplicitFinish();
 
   var f = document.createElement("iframe");
   f.src = t.name;
-  f.addEventListener("load", function() {
-    if (documentVideo().error) {
-      info("Error occured by the time we got |load| - checking directly.");
-      check();
-    } else {
-      //TODO: Fix this todo in Bug 1295923.
-      todo(false, "Error hasn't occurred yet - adding |error| event listener. This shouldn't happen, see bug 608634.");
-      documentVideo().addEventListener("error", check);
-    }
-  });
+  f.addEventListener("load", check);
   document.body.appendChild(f);
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1879,17 +1879,17 @@ nsIFrame::DisplayCaret(nsDisplayListBuil
     return;
 
   aList->AppendNewToTop(new (aBuilder) nsDisplayCaret(aBuilder, this));
 }
 
 nscolor
 nsIFrame::GetCaretColorAt(int32_t aOffset)
 {
-  return StyleColor()->CalcComplexColor(StyleUserInterface()->mCaretColor);
+  return nsLayoutUtils::GetColor(this, &nsStyleUserInterface::mCaretColor);
 }
 
 bool
 nsFrame::DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder,
                                         const nsDisplayListSet& aLists,
                                         bool aForceBackground)
 {
   // Here we don't try to detect background propagation. Frames that might
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-visited/caret-color-on-visited-1-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+@font-face {
+  font-family: Ahem;
+  src: url(Ahem.ttf);
+}
+div {
+  font: 16px/1 Ahem;
+  width: 1em;
+  background: green;
+}
+</style>
+<div><span></span></div>
+<script>
+let $div = document.querySelector('div');
+let $span = document.querySelector('span');
+window.onload = function () {
+  document.fonts.ready.then(() => {
+    $div.style.height = $span.getBoundingClientRect().height + 'px';
+  });
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-visited/caret-color-on-visited-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<style>
+@font-face {
+  font-family: Ahem;
+  src: url(Ahem.ttf);
+}
+a {
+  caret-color: red;
+}
+a:visited {
+  caret-color: green;
+}
+textarea {
+  caret-color: inherit;
+  font: 16px/1 Ahem;
+  outline: none;
+  border: 0 none;
+  resize: none;
+  padding: 0;
+  margin: 0;
+}
+</style>
+<a href="visited-page.html"><textarea></textarea></a>
+<script>
+  document.querySelector('textarea').focus();
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/transforms/green.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference: A green box</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org">
+<p>Pass if there is NO red below:</p>
+<div id="ref" style="width: 100px; height: 100px; background: green"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/transforms/perspective-zero.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test: transform: perspective(0)</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org">
+<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#funcdef-perspective">
+<meta name="assert" content="perspective(0) should behave like identity transform function.">
+<link rel="match" href="green.html">
+<style>
+#cover-me, #test {
+  width: 100px;
+  height: 100px;
+}
+#cover-me {
+  background: red;
+  position: relative;
+  margin-bottom: -100px;
+}
+#test {
+  background: green;
+  /* This should be an identity transform, since perspective(0) must be
+   * treated as perspective(infinity), and consequently translateZ()
+   * doesn't have any effect, so that it covers up #cover-me.
+   * If perspective(0) is invalid, #test would not create a stacking
+   * context, and #cover-me would be placed on top of #test showing red.
+   * If perspective(0) is handled as perspective(epsilon), #test would
+   * be invisible. */
+  transform: perspective(0) translateZ(50px);
+}
+</style>
+<p>Pass if there is NO red below:</p>
+<div id="cover-me"></div><div id="test"></div>
--- a/layout/reftests/w3c-css/submitted/transforms/reftest.list
+++ b/layout/reftests/w3c-css/submitted/transforms/reftest.list
@@ -1,4 +1,5 @@
 == transform-containing-block-dynamic-1a.html containing-block-dynamic-1-ref.html
 == transform-containing-block-dynamic-1b.html containing-block-dynamic-1-ref.html
 == perspective-containing-block-dynamic-1a.html containing-block-dynamic-1-ref.html
 == perspective-containing-block-dynamic-1b.html containing-block-dynamic-1-ref.html
+== perspective-zero.html green.html
--- a/layout/style/CSSVariableDeclarations.cpp
+++ b/layout/style/CSSVariableDeclarations.cpp
@@ -74,17 +74,17 @@ CSSVariableDeclarations::Get(const nsASt
   nsString value;
   if (!mVariables.Get(aName, &value)) {
     return false;
   }
   if (value.EqualsLiteral(INITIAL_VALUE)) {
     aType = eInitial;
     aTokenStream.Truncate();
   } else if (value.EqualsLiteral(INHERIT_VALUE)) {
-    aType = eInitial;
+    aType = eInherit;
     aTokenStream.Truncate();
   } else if (value.EqualsLiteral(UNSET_VALUE)) {
     aType = eUnset;
     aTokenStream.Truncate();
   } else {
     aType = eTokenStream;
     aTokenStream = value;
   }
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -246,28 +246,32 @@ Declaration::HasProperty(nsCSSPropertyID
                                       ? mImportantData : mData;
   const nsCSSValue *val = data->ValueFor(aProperty);
   return !!val;
 }
 
 bool
 Declaration::AppendValueToString(nsCSSPropertyID aProperty,
                                  nsAString& aResult,
-                                 nsCSSValue::Serialization aSerialization) const
+                                 nsCSSValue::Serialization aSerialization,
+                                 bool* aIsTokenStream) const
 {
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
              "property ID out of range");
 
   nsCSSCompressedDataBlock *data = GetPropertyIsImportantByID(aProperty)
                                       ? mImportantData : mData;
   const nsCSSValue *val = data->ValueFor(aProperty);
   if (!val) {
     return false;
   }
 
+  if (aIsTokenStream) {
+    *aIsTokenStream = val->GetUnit() == eCSSUnit_TokenStream;
+  }
   val->AppendToString(aProperty, aResult, aSerialization);
   return true;
 }
 
 static void
 AppendSingleImageLayerPositionValue(const nsCSSValue& aPositionX,
                                     const nsCSSValue& aPositionY,
                                     const nsCSSPropertyID aTable[],
@@ -531,23 +535,26 @@ Declaration::GetImageLayerPositionValue(
     aValue.Append(char16_t(','));
     aValue.Append(char16_t(' '));
   }
 }
 
 void
 Declaration::GetPropertyValueInternal(
     nsCSSPropertyID aProperty, nsAString& aValue,
-    nsCSSValue::Serialization aSerialization) const
+    nsCSSValue::Serialization aSerialization, bool* aIsTokenStream) const
 {
   aValue.Truncate(0);
+  if (aIsTokenStream) {
+    *aIsTokenStream = false;
+  }
 
   // simple properties are easy.
   if (!nsCSSProps::IsShorthand(aProperty)) {
-    AppendValueToString(aProperty, aValue, aSerialization);
+    AppendValueToString(aProperty, aValue, aSerialization, aIsTokenStream);
     return;
   }
 
   // DOM Level 2 Style says (when describing CSS2Properties, although
   // not CSSStyleDeclaration.getPropertyValue):
   //   However, if there is no shorthand declaration that could be added
   //   to the ruleset without changing in any way the rules already
   //   declared in the ruleset (i.e., by adding longhand rules that were
@@ -635,16 +642,19 @@ Declaration::GetPropertyValueInternal(
       unsetCount != 0 || nonMatchingTokenStreamCount != 0) {
     // Case (2): partially initial, inherit, unset or token stream.
     return;
   }
   if (tokenStream) {
     if (matchingTokenStreamCount == totalCount) {
       // Shorthand was specified using variable references and all of its
       // longhand components were set by the shorthand.
+      if (aIsTokenStream) {
+        *aIsTokenStream = true;
+      }
       aValue.Append(tokenStream->GetTokenStreamValue()->mTokenStream);
     } else {
       // In all other cases, serialize to the empty string.
     }
     return;
   }
 
   nsCSSCompressedDataBlock *data = importantCount ? mImportantData : mData;
@@ -1600,31 +1610,39 @@ Declaration::GetPropertyIsImportantByID(
       return false;
     }
   }
   return true;
 }
 
 void
 Declaration::AppendPropertyAndValueToString(nsCSSPropertyID aProperty,
+                                            nsAString& aResult,
                                             nsAutoString& aValue,
-                                            nsAString& aResult) const
+                                            bool aValueIsTokenStream) const
 {
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
              "property enum out of range");
   MOZ_ASSERT((aProperty < eCSSProperty_COUNT_no_shorthands) == aValue.IsEmpty(),
              "aValue should be given for shorthands but not longhands");
   AppendASCIItoUTF16(nsCSSProps::GetStringValue(aProperty), aResult);
-  aResult.AppendLiteral(": ");
-  if (aValue.IsEmpty())
-    AppendValueToString(aProperty, aResult, nsCSSValue::eNormalized);
-  else
-    aResult.Append(aValue);
+  if (aValue.IsEmpty()) {
+    AppendValueToString(aProperty, aValue,
+                        nsCSSValue::eNormalized, &aValueIsTokenStream);
+  }
+  aResult.Append(':');
+  if (!aValueIsTokenStream) {
+    aResult.Append(' ');
+  }
+  aResult.Append(aValue);
   if (GetPropertyIsImportantByID(aProperty)) {
-    aResult.AppendLiteral(" !important");
+    if (!aValueIsTokenStream) {
+      aResult.Append(' ');
+    }
+    aResult.AppendLiteral("!important");
   }
   aResult.AppendLiteral("; ");
 }
 
 void
 Declaration::AppendVariableAndValueToString(const nsAString& aName,
                                             nsAString& aResult) const
 {
@@ -1640,24 +1658,24 @@ Declaration::AppendVariableAndValueToStr
     important = true;
   } else {
     MOZ_ASSERT(mVariables);
     MOZ_ASSERT(mVariables->Has(aName));
     mVariables->Get(aName, type, value);
     important = false;
   }
 
+  bool isTokenStream = type == CSSVariableDeclarations::eTokenStream;
+  aResult.Append(':');
+  if (!isTokenStream) {
+    aResult.Append(' ');
+  }
   switch (type) {
     case CSSVariableDeclarations::eTokenStream:
-      if (value.IsEmpty()) {
-        aResult.Append(':');
-      } else {
-        aResult.AppendLiteral(": ");
-        aResult.Append(value);
-      }
+      aResult.Append(value);
       break;
 
     case CSSVariableDeclarations::eInitial:
       aResult.AppendLiteral("initial");
       break;
 
     case CSSVariableDeclarations::eInherit:
       aResult.AppendLiteral("inherit");
@@ -1667,16 +1685,19 @@ Declaration::AppendVariableAndValueToStr
       aResult.AppendLiteral("unset");
       break;
 
     default:
       MOZ_ASSERT(false, "unexpected variable value type");
   }
 
   if (important) {
+    if (!isTokenStream) {
+      aResult.Append(' ');
+    }
     aResult.AppendLiteral("!important");
   }
   aResult.AppendLiteral("; ");
 }
 
 void
 Declaration::ToString(nsAString& aString) const
 {
@@ -1730,42 +1751,47 @@ Declaration::ToString(nsAString& aString
     for (const nsCSSPropertyID *shorthands =
            nsCSSProps::ShorthandsContaining(property);
          *shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
       // ShorthandsContaining returns the shorthands in order from those
       // that contain the most subproperties to those that contain the
       // least, which is exactly the order we want to test them.
       nsCSSPropertyID shorthand = *shorthands;
 
-      GetPropertyValueByID(shorthand, value);
+      bool isTokenStream;
+      GetPropertyValueInternal(shorthand, value,
+                               nsCSSValue::eNormalized, &isTokenStream);
 
       // in the system font case, skip over font-variant shorthand, since all
       // subproperties are already dealt with via the font shorthand
       if (shorthand == eCSSProperty_font_variant &&
           value.EqualsLiteral("-moz-use-system-font")) {
         continue;
       }
 
-      // If GetPropertyValueByID gives us a non-empty string back, we can
+      // If GetPropertyValueInternal gives us a non-empty string back, we can
       // use that value; otherwise it's not possible to use this shorthand.
       if (!value.IsEmpty()) {
-        AppendPropertyAndValueToString(shorthand, value, aString);
+        AppendPropertyAndValueToString(shorthand, aString,
+                                       value, isTokenStream);
         shorthandsUsed.AppendElement(shorthand);
         doneProperty = true;
         break;
       }
 
       if (shorthand == eCSSProperty_font) {
         if (haveSystemFont && !didSystemFont) {
           // Output the shorthand font declaration that we will
           // partially override later.  But don't add it to
           // |shorthandsUsed|, since we will have to override it.
           systemFont->AppendToString(eCSSProperty__x_system_font, value,
                                      nsCSSValue::eNormalized);
-          AppendPropertyAndValueToString(eCSSProperty_font, value, aString);
+          isTokenStream = systemFont->GetUnit() == eCSSUnit_TokenStream;
+          AppendPropertyAndValueToString(eCSSProperty_font, aString,
+                                         value, isTokenStream);
           value.Truncate();
           didSystemFont = true;
         }
 
         // That we output the system font is enough for this property if:
         //   (1) it's the hidden system font subproperty (which either
         //       means we output it or we don't have it), or
         //   (2) its value is the hidden system font value and it matches
@@ -1778,17 +1804,17 @@ Declaration::ToString(nsAString& aString
           break;
         }
       }
     }
     if (doneProperty)
       continue;
 
     MOZ_ASSERT(value.IsEmpty(), "value should be empty now");
-    AppendPropertyAndValueToString(property, value, aString);
+    AppendPropertyAndValueToString(property, aString, value, false);
   }
   if (! aString.IsEmpty()) {
     // if the string is not empty, we have trailing whitespace we
     // should remove
     aString.Truncate(aString.Length() - 1);
   }
 }
 
--- a/layout/style/Declaration.h
+++ b/layout/style/Declaration.h
@@ -307,29 +307,31 @@ public:
     return nullptr;
   }
 
 private:
   Declaration& operator=(const Declaration& aCopy) = delete;
   bool operator==(const Declaration& aCopy) const = delete;
 
   void GetPropertyValueInternal(nsCSSPropertyID aProperty, nsAString& aValue,
-                                nsCSSValue::Serialization aValueSerialization)
-    const;
+                                nsCSSValue::Serialization aValueSerialization,
+                                bool* aIsTokenStream = nullptr) const;
   bool GetPropertyIsImportantByID(nsCSSPropertyID aProperty) const;
 
   static void AppendImportanceToString(bool aIsImportant, nsAString& aString);
   // return whether there was a value in |aValue| (i.e., it had a non-null unit)
   bool AppendValueToString(nsCSSPropertyID aProperty, nsAString& aResult) const;
   bool AppendValueToString(nsCSSPropertyID aProperty, nsAString& aResult,
-                           nsCSSValue::Serialization aValueSerialization) const;
+                           nsCSSValue::Serialization aValueSerialization,
+                           bool* aIsTokenStream = nullptr) const;
   // Helper for ToString with strange semantics regarding aValue.
   void AppendPropertyAndValueToString(nsCSSPropertyID aProperty,
+                                      nsAString& aResult,
                                       nsAutoString& aValue,
-                                      nsAString& aResult) const;
+                                      bool aValueIsTokenStream) const;
   // helper for ToString that serializes a custom property declaration for
   // a variable with the specified name
   void AppendVariableAndValueToString(const nsAString& aName,
                                       nsAString& aResult) const;
 
   void GetImageLayerValue(nsCSSCompressedDataBlock *data,
                           nsAString& aValue,
                           nsCSSValue::Serialization aSerialization,
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -1370,24 +1370,21 @@ ComputeTransformDistance(nsCSSValue::Arr
       // Do interpolation between perspective(100px) and perspective(1000px).
       //   1) Convert them into matrix3d, and then do matrix decomposition:
       //      perspective vector 1: perspective(0, 0, -1/100, 1);
       //      perspective vector 2: perspective(0, 0, -1/1000, 1);
       //   2) Do linear interpolation between these two vectors.
       // Therefore, we use the same rule to get the distance as what we do for
       // matrix3d.
 
-      auto clampPerspectiveDepth = [](float aDepth) {
-        // Perspective depth should be positive non-zero value.
-        return std::max(aDepth, std::numeric_limits<float>::epsilon());
-      };
+      using nsStyleTransformMatrix::ApplyPerspectiveToMatrix;
       Matrix4x4 m1;
-      m1.Perspective(clampPerspectiveDepth(a1->Item(1).GetFloatValue()));
+      ApplyPerspectiveToMatrix(m1, a1->Item(1).GetFloatValue());
       Matrix4x4 m2;
-      m2.Perspective(clampPerspectiveDepth(a2->Item(1).GetFloatValue()));
+      ApplyPerspectiveToMatrix(m2, a2->Item(1).GetFloatValue());
 
       distance = ComputeTransform3DMatrixDistance(m1, m2);
       break;
     }
     case eCSSKeyword_matrix: {
       MOZ_ASSERT(a1->Count() == 7, "unexpected count");
       MOZ_ASSERT(a2->Count() == 7, "unexpected count");
 
--- a/layout/style/nsCSSVisitedDependentPropList.h
+++ b/layout/style/nsCSSVisitedDependentPropList.h
@@ -28,8 +28,9 @@ STYLE_STRUCT(Border, (mBorderTopColor,
                       mBorderLeftColor))
 STYLE_STRUCT(Outline, (mOutlineColor))
 STYLE_STRUCT(Column, (mColumnRuleColor))
 STYLE_STRUCT(Text, (mTextEmphasisColor,
                     mWebkitTextFillColor,
                     mWebkitTextStrokeColor))
 STYLE_STRUCT(TextReset, (mTextDecorationColor))
 STYLE_STRUCT(SVG, (mFill, mStroke))
+STYLE_STRUCT(UserInterface, (mCaretColor))
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -13,18 +13,16 @@
 #include "nsPresContext.h"
 #include "nsRuleNode.h"
 #include "nsSVGUtils.h"
 #include "nsCSSKeywords.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "gfxMatrix.h"
 #include "gfxQuaternion.h"
 
-#include <limits>
-
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 namespace nsStyleTransformMatrix {
 
 /* Note on floating point precision: The transform matrix is an array
  * of single precision 'float's, and so are most of the input values
  * we get from the style system, but intermediate calculations
@@ -746,21 +744,19 @@ static void
 ProcessPerspective(Matrix4x4& aMatrix,
                    const nsCSSValue::Array* aData,
                    nsStyleContext *aContext,
                    nsPresContext *aPresContext,
                    RuleNodeCacheConditions& aConditions)
 {
   NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
 
-  float depth = std::max(ProcessTranslatePart(aData->Item(1), aContext,
-                                              aPresContext, aConditions,
-                                              nullptr),
-                         std::numeric_limits<float>::epsilon());
-  aMatrix.Perspective(depth);
+  float depth = ProcessTranslatePart(aData->Item(1), aContext,
+                                     aPresContext, aConditions, nullptr);
+  ApplyPerspectiveToMatrix(aMatrix, depth);
 }
 
 
 /**
  * SetToTransformFunction is essentially a giant switch statement that fans
  * out to many smaller helper functions.
  */
 static void
--- a/layout/style/nsStyleTransformMatrix.h
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -8,30 +8,43 @@
  */
 
 #ifndef nsStyleTransformMatrix_h_
 #define nsStyleTransformMatrix_h_
 
 #include "mozilla/EnumeratedArray.h"
 #include "nsCSSValue.h"
 
+#include <limits>
+
 class nsIFrame;
 class nsStyleContext;
 class nsPresContext;
 struct gfxQuaternion;
 struct nsRect;
 namespace mozilla {
 class RuleNodeCacheConditions;
 } // namespace mozilla
 
 /**
  * A helper to generate gfxMatrixes from css transform functions.
  */
 namespace nsStyleTransformMatrix {
 
+  // Function for applying perspective() transform function. We treat
+  // any value smaller than epsilon as perspective(infinity), which
+  // follows CSSWG's resolution on perspective(0). See bug 1316236.
+  inline void ApplyPerspectiveToMatrix(mozilla::gfx::Matrix4x4& aMatrix,
+                                       float aDepth)
+  {
+    if (aDepth >= std::numeric_limits<float>::epsilon()) {
+      aMatrix.Perspective(aDepth);
+    }
+  }
+
   /**
    * This class provides on-demand access to the 'reference box' for CSS
    * transforms (needed to resolve percentage values in 'transform',
    * 'transform-origin', etc.):
    *
    *    http://dev.w3.org/csswg/css-transforms/#reference-box
    *
    * This class helps us to avoid calculating the reference box unless and
--- a/layout/style/test/moz.build
+++ b/layout/style/test/moz.build
@@ -39,16 +39,18 @@ TEST_HARNESS_FILES.testing.mochitest.tes
 TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test['css-visited'] += [
     '/layout/reftests/css-visited/border-1-ref.html',
     '/layout/reftests/css-visited/border-1.html',
     '/layout/reftests/css-visited/border-2-ref.html',
     '/layout/reftests/css-visited/border-2a.html',
     '/layout/reftests/css-visited/border-2b.html',
     '/layout/reftests/css-visited/border-collapse-1-ref.html',
     '/layout/reftests/css-visited/border-collapse-1.html',
+    '/layout/reftests/css-visited/caret-color-on-visited-1-ref.html',
+    '/layout/reftests/css-visited/caret-color-on-visited-1.html',
     '/layout/reftests/css-visited/color-choice-1-ref.html',
     '/layout/reftests/css-visited/color-choice-1.html',
     '/layout/reftests/css-visited/color-on-bullets-1-ref.html',
     '/layout/reftests/css-visited/color-on-bullets-1.html',
     '/layout/reftests/css-visited/color-on-link-1-ref.html',
     '/layout/reftests/css-visited/color-on-link-1.html',
     '/layout/reftests/css-visited/color-on-link-before-1.html',
     '/layout/reftests/css-visited/color-on-text-decoration-1-ref.html',
@@ -100,16 +102,17 @@ TEST_HARNESS_FILES.testing.mochitest.tes
     '/layout/reftests/css-visited/subject-of-selector-descendant-2-ref.xhtml',
     '/layout/reftests/css-visited/subject-of-selector-descendant-2.xhtml',
     '/layout/reftests/css-visited/visited-page.html',
     '/layout/reftests/css-visited/white-to-transparent-1-ref.html',
     '/layout/reftests/css-visited/white-to-transparent-1.html',
     '/layout/reftests/css-visited/width-1-ref.html',
     '/layout/reftests/css-visited/width-on-link-1.html',
     '/layout/reftests/css-visited/width-on-visited-1.html',
+    '/layout/reftests/fonts/Ahem.ttf',
     '/layout/reftests/svg/as-image/lime100x100.svg',
     '/layout/reftests/svg/as-image/svg-image-visited-1-helper.svg',
     '/layout/reftests/svg/as-image/svg-image-visited-2-helper.svg',
     '/layout/reftests/svg/pseudo-classes-02-ref.svg',
     '/layout/reftests/svg/pseudo-classes-02.svg',
 ]
 
 DEFINES['MOZILLA_INTERNAL_API'] = True
--- a/layout/style/test/test_value_storage.html
+++ b/layout/style/test/test_value_storage.html
@@ -150,16 +150,17 @@ function test_property(property)
       is(gDeclaration.getPropertyValue(sh), "",
          "setting '" + value + "' on '" + property + "' (for shorthand '" + sh + "')");
     }
   }
 
   function test_value(value, resolved_value) {
     var value_has_variable_reference = resolved_value != null;
 
+    var colon = value == "var(--a)" ? ":" : ": ";
     gDeclaration.setProperty(property, value, "");
 
     var idx;
 
     var step1val = gDeclaration.getPropertyValue(property);
     var step1vals = [];
     var step1ser = gDeclaration.cssText;
     if ("subproperties" in info)
@@ -188,30 +189,30 @@ function test_property(property)
         }
       }
 
     // We don't care particularly about the whitespace or the placement of
     // semicolons, but for simplicity we'll test the current behavior.
     var expected_serialization = "";
     if (step1val != "") {
       if ("alias_for" in info) {
-        expected_serialization = info.alias_for + ": " + step1val + ";";
+        expected_serialization = info.alias_for + colon + step1val + ";";
       } else {
-        expected_serialization = property + ": " + step1val + ";";
+        expected_serialization = property + colon + step1val + ";";
       }
     }
     is(step1ser, expected_serialization,
        "serialization should match property value");
 
     gDeclaration.removeProperty(property);
     gDeclaration.setProperty(property, step1val, "");
 
     is(gDeclaration.getPropertyValue(property), step1val,
        "parse+serialize should be idempotent for '" +
-         property + ": " + value + "'");
+         property + colon + value + "'");
     if (info.type != CSS_TYPE_TRUE_SHORTHAND) {
       is(gComputedStyle.getPropertyValue(property), step1comp,
          "serialize+parse should be identity transform for '" +
          property + ": " + value + "'");
     }
 
     if ("subproperties" in info &&
         // Using setProperty over subproperties is not sufficient for
@@ -232,17 +233,17 @@ function test_property(property)
       for (idx in info.subproperties) {
         var subprop = info.subproperties[idx];
         is(gComputedStyle.getPropertyValue(subprop), step1comps[idx],
            "serialize(" + subprop + ")+parse should be the identity " +
            "transform for '" + property + ": " + value + "'");
       }
       is(gDeclaration.getPropertyValue(property), step1val,
          "parse+split+serialize should be idempotent for '" +
-         property + ": " + value + "'");
+         property + colon + value + "'");
     }
 
     if (info.type != CSS_TYPE_TRUE_SHORTHAND &&
           property != "mask") {
       gDeclaration.removeProperty(property);
       gDeclaration.setProperty(property, step1comp, "");
       var func = xfail_compute(property, value) ? todo_is : is;
       func(gComputedStyle.getPropertyValue(property), step1comp,
--- a/layout/style/test/test_variables.html
+++ b/layout/style/test/test_variables.html
@@ -24,16 +24,19 @@
 <div id="t5"></div>
 
 <style id="test6">
 </style>
 
 <style id="test7">
 </style>
 
+<style id="test8">
+</style>
+
 <script>
 var tests = [
   function() {
     // https://bugzilla.mozilla.org/show_bug.cgi?id=773296#c121
     var test1 = document.getElementById("test1");
     test1.textContent = "p { --a:123!important; }";
     var declaration = test1.sheet.cssRules[0].style;
     declaration.cssText;
@@ -94,19 +97,30 @@ var tests = [
     test6.style.color = "white";
     is(declaration.getPropertyValue("-x-system-font"), " var(--var6) hangul mongolian");
   },
 
   function() {
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1154356
     var test7 = document.getElementById("test7");
     test7.textContent = "p { --weird\\;name: green; }";
-    is(test7.sheet.cssRules[0].style.cssText, "--weird\\;name:  green;");
+    is(test7.sheet.cssRules[0].style.cssText, "--weird\\;name: green;");
     test7.textContent = "p { --0: green; }";
-    is(test7.sheet.cssRules[0].style.cssText, "--0:  green;");
+    is(test7.sheet.cssRules[0].style.cssText, "--0: green;");
+  },
+
+  function() {
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=1330172
+    var test8 = document.getElementById("test8");
+    test8.textContent = "p { --a:inHerit; }";
+    is(test8.sheet.cssRules[0].style.cssText, "--a: inherit;");
+    test8.textContent = "p { --b: initial!important; }";
+    is(test8.sheet.cssRules[0].style.cssText, "--b: initial !important;");
+    test8.textContent = "p { --c:   UNSET  !important }";
+    is(test8.sheet.cssRules[0].style.cssText, "--c: unset !important;");
   },
 ];
 
 function prepareTest() {
   // Load an external style sheet for test 4.
   var e = document.createElement("link");
   e.addEventListener("load", runTest);
   e.setAttribute("rel", "stylesheet");
--- a/layout/style/test/test_visited_reftests.html
+++ b/layout/style/test/test_visited_reftests.html
@@ -27,16 +27,17 @@ var gTests = [
   // there's also an implicit "load visited-page.html" at the start,
   // thanks to the code below.
 
   // IMPORTANT NOTE: For these tests, the test and reference are not
   // snapshotted in the same way.  The REFERENCE (second file) is
   // assumed to be complete when loaded, but we poll for visited link
   // coloring on the TEST (first file) until the test passes.
   "== pseudo-classes-02.svg pseudo-classes-02-ref.svg",
+  "needs-focus == caret-color-on-visited-1.html caret-color-on-visited-1-ref.html",
   "!= color-on-link-1-ref.html color-on-visited-1-ref.html",
   "== color-on-link-1.html color-on-link-1-ref.html",
   "== color-on-link-before-1.html color-on-link-1-ref.html",
   "== color-on-visited-1.html color-on-visited-1-ref.html",
   "== color-on-visited-before-1.html color-on-visited-1-ref.html",
   "!= content-color-on-link-before-1-ref.html content-color-on-visited-before-1-ref.html",
   "== content-color-on-link-before-1.html content-color-on-link-before-1-ref.html",
   "== content-color-on-visited-before-1.html content-color-on-visited-before-1-ref.html",
@@ -80,131 +81,110 @@ var gTests = [
   // FIXME: commented out because dynamic changes on the non-first-line
   // part of the test don't work right when the link becomes visited.
   //"== first-line-1.html first-line-1-ref.html",
   "== white-to-transparent-1.html white-to-transparent-1-ref.html",
   "== link-root-1.xhtml link-root-1-ref.xhtml",
   "== mathml-links.html mathml-links-ref.html",
 ];
 
-// Maintain a reference count of how many things we're waiting for until
-// we can say the tests are done.
-var gDelayCount = 0;
-function AddFinishDependency()
-  { ++gDelayCount; }
-function RemoveFinishDependency()
-  { if (--gDelayCount == 0) SimpleTest.finish(); }
-
 // We record the maximum number of times we had to look at a test before
 // it switched to the passing state (though we assume it's 10 to start
 // rather than 0 so that we have a reasonable default).  Then we make a
 // test "time out" if it takes more than gTimeoutFactor times that
 // amount of time.  This allows us to report a test failure rather than
 // making a test failure just show up as a timeout.
 var gMaxPassingTries = 10;
 var gTimeoutFactor = 10;
 
-function loadVisitedPage()
-{
-  var element = document.createElement("iframe");
-  element.addEventListener("load", visitedPageLoad);
-  element.src = "css-visited/visited-page.html";
-  document.body.appendChild(element);
-  AddFinishDependency();
-}
-
-function visitedPageLoad()
-{
-  for (var i = 0; i < gTests.length; ++i) {
-    startTest(i);
-  }
-  RemoveFinishDependency();
-}
-
-function takeSnapshot(iframe_element)
-{
-  return snapshotWindow(iframe_element.contentWindow, false);
-}
-
-function passes(op, shot1, shot2)
-{
-  var [correct, s1, s2] = compareSnapshots(shot1, shot2, op == "==");
-  return correct;
-}
-
-function startTest(i)
-{
-  var testLine = gTests[i];
-  var splitData = testLine.split(" ");
-  var testData =
-    { op: splitData[0], test: splitData[1], reference: splitData[2] };
-  var tries = 0;
-
-  // Maintain state specific to this test in the closure exposed to all
-  // the functions nested inside this one.
-
-  function startIframe(url)
-  {
+function startIframe(url) {
+  return new Promise(resolve => {
     var element = document.createElement("iframe");
-    element.addEventListener("load", handleLoad);
+    element.addEventListener("load", () => {
+      element.contentDocument.fonts.ready.then(() => {
+        resolve(element.contentWindow);
+      });
+    }, {once: true});
     // smaller than normal reftests, but enough for these
     element.setAttribute("style", "width: 30em; height: 10em");
     element.src = "css-visited/" + url;
     document.body.appendChild(element);
-    function handleLoad(event)
-    {
-      iframe.loaded = true;
-      if (iframe == reference) {
-        reference.snapshot = takeSnapshot(element);
-      }
-      var other = (iframe == test) ? reference : test;
-      if (other.loaded) {
-        // Always wait at least 100ms, so that any test that switches
-        // from passing to failing when the asynchronous link coloring
-        // happens should fail at least some of the time.
-        setTimeout(checkTest, 100);
-      }
+  });
+}
+
+async function runTests() {
+  SimpleTest.waitForExplicitFinish();
+  SimpleTest.requestFlakyTimeout("async link coloring");
+  // Set caret to a known size, for tests of :visited caret-color styling
+  await SpecialPowers.pushPrefEnv({'set': [['ui.caretWidth', 16]]});
+  await startIframe("visited-page.html");
+  await Promise.all(gTests.map(runTest));
+  SimpleTest.finish();
+}
+
+function passes(equal, shot1, shot2)
+{
+  let [correct] = compareSnapshots(shot1, shot2, equal);
+  return correct;
+}
+
+function waitFor100ms() {
+  return new Promise(resolve => setTimeout(resolve, 100));
+}
+
+async function runTest(testLine) {
+  let splitData = testLine.split(" ");
+  let isEqual;
+  let needsFocus = false;
+  while (true) {
+    let op = splitData.shift();
+    if (op == "needs-focus") {
+      needsFocus = true;
+    } else if (op == "==" || op == "!=") {
+      isEqual = op == "==";
+      break;
+    } else {
+      ok(false, "Unknown syntax");
+      return;
     }
-    function checkTest()
-    {
-      var test_snapshot = takeSnapshot(test.element);
-      if (passes(testData.op, test_snapshot, reference.snapshot)) {
-        if (tries > gMaxPassingTries) {
-          gMaxPassingTries = tries;
-        }
-        report(true);
-      } else {
-        ++tries;
-        if (tries > gMaxPassingTries * gTimeoutFactor) {
-          info("Giving up after " + tries + " tries, " +
-               "maxp=" + gMaxPassingTries +
-               "fact=" + gTimeoutFactor);
-          report(false);
-        } else {
-          // Links might not have been colored yet.  Try again in 100ms.
-          setTimeout(checkTest, 100);
-        }
+  }
+  let [testFile, refFile] = splitData;
+
+  let promiseTestWin = startIframe(testFile);
+  let promiseRefWin = startIframe(refFile);
+  let refSnapshot = snapshotWindow(await promiseRefWin);
+  let testWindow = await promiseTestWin;
+  // Always wait at least 100ms, so that any test that switches
+  // from passing to failing when the asynchronous link coloring
+  // happens should fail at least some of the time.
+  await waitFor100ms();
+
+  let tries;
+  let testSnapshot;
+  for (tries = 0; tries < gMaxPassingTries * gTimeoutFactor; ++tries) {
+    if (needsFocus) {
+      await SimpleTest.promiseFocus(testWindow, false);
+    }
+    testSnapshot = snapshotWindow(testWindow, true);
+    if (passes(isEqual, testSnapshot, refSnapshot)) {
+      if (tries > gMaxPassingTries) {
+        gMaxPassingTries = tries;
       }
+      break;
     }
-    function report(result)
-    {
-      ok(result, "(" + i + ") " +
-                 testData.op + " " + testData.test + " " + testData.reference);
-      RemoveFinishDependency();
-    }
-    var iframe = { element: element, loaded: false };
-
-    return iframe;
+    // Links might not have been colored yet. Try again in 100ms.
+    await waitFor100ms();
   }
 
-  AddFinishDependency();
-  var test = startIframe(testData.test);
-  var reference = startIframe(testData.reference);
+  let result = assertSnapshots(testSnapshot, refSnapshot,
+                               isEqual, null, testFile, refFile);
+  if (!result) {
+    info(`Gave up after ${tries} tries, ` +
+         `maxp=${gMaxPassingTries}, fact=${gTimeoutFactor}`);
+  }
 }
 
-SimpleTest.waitForExplicitFinish();
-SimpleTest.requestFlakyTimeout("untriaged");
-loadVisitedPage();
+runTests();
 
 </script>
 </pre>
 </body>
 </html>
--- a/mobile/android/base/java/org/mozilla/gecko/Tab.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tab.java
@@ -526,26 +526,28 @@ public class Tab {
     public void addBookmark() {
         final String url = getURL();
         if (url == null) {
             return;
         }
 
         final String pageUrl = ReaderModeUtils.stripAboutReaderUrl(getURL());
 
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                mDB.addBookmark(getContentResolver(), mTitle, pageUrl);
-                Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.BOOKMARK_ADDED);
-            }
-        });
-
         if (AboutPages.isAboutReader(url)) {
             ReadingListHelper.cacheReaderItem(pageUrl, mId, mAppContext);
+            // defer bookmarking after completely added to cache.
+        } else {
+            ThreadUtils.postToBackgroundThread(new Runnable() {
+                @Override
+                public void run() {
+                    mDB.addBookmark(getContentResolver(), mTitle, pageUrl);
+                    Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.BOOKMARK_ADDED);
+                }
+            });
+
         }
     }
 
     public void removeBookmark() {
         final String url = getURL();
         if (url == null) {
             return;
         }
--- a/mobile/android/base/java/org/mozilla/gecko/reader/SavedReaderViewHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/reader/SavedReaderViewHelper.java
@@ -1,22 +1,25 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.reader;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.util.Log;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.UrlAnnotations;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.Iterator;
@@ -141,18 +144,23 @@ public class SavedReaderViewHelper {
             Log.w(LOG_TAG, "Item insertion failed:", e);
             // This should never happen, absent any errors in our own implementation
             throw new IllegalStateException("Failure inserting into SavedReaderViewHelper json");
         }
 
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
-                UrlAnnotations annotations = BrowserDB.from(mContext).getUrlAnnotations();
-                annotations.insertReaderViewUrl(mContext.getContentResolver(), pageURL);
+                final UrlAnnotations annotations = BrowserDB.from(mContext).getUrlAnnotations();
+                final ContentResolver contentResolver = mContext.getContentResolver();
+                final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+
+                annotations.insertReaderViewUrl(contentResolver, pageURL);
+                BrowserDB.from(mContext).addBookmark(contentResolver, selectedTab.getTitle(), pageURL);
+                Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.BOOKMARK_ADDED);
 
                 commit();
             }
         });
     }
 
     protected synchronized void remove(@NonNull final String pageURL) {
         assertItemsLoaded();
--- a/mobile/android/chrome/content/content.js
+++ b/mobile/android/chrome/content/content.js
@@ -51,16 +51,22 @@ var AboutReaderListener = {
         break;
     }
   },
 
   get isAboutReader() {
     return content.document.documentURI.startsWith("about:reader");
   },
 
+  get isErrorPage() {
+    return content.document.documentURI.startsWith("about:neterror") ||
+        content.document.documentURI.startsWith("about:certerror") ||
+        content.document.documentURI.startsWith("about:blocked");
+  },
+
   handleEvent: function(aEvent) {
     if (aEvent.originalTarget.defaultView != content) {
       return;
     }
 
     switch (aEvent.type) {
       case "AboutReaderContentLoaded":
         if (!this.isAboutReader) {
@@ -95,23 +101,26 @@ var AboutReaderListener = {
         }
         break;
       case "DOMContentLoaded":
         this.updateReaderButton();
         break;
     }
   },
   updateReaderButton: function(forceNonArticle) {
-    if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
+    // Do not show Reader View icon on error pages (bug 1320900)
+    if (this.isErrorPage) {
+        sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
+    } else if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
         !(content.document instanceof content.HTMLDocument) ||
         content.document.mozSyntheticDocument) {
       return;
+    } else {
+        this.scheduleReadabilityCheckPostPaint(forceNonArticle);
     }
-
-    this.scheduleReadabilityCheckPostPaint(forceNonArticle);
   },
 
   cancelPotentialPendingReadabilityCheck: function() {
     if (this._pendingReadabilityCheck) {
       removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
       delete this._pendingReadabilityCheck;
     }
   },
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5183,16 +5183,18 @@ pref("browser.safebrowsing.allowOverride
 #ifdef MOZILLA_OFFICIAL
 // Normally the "client ID" sent in updates is appinfo.name, but for
 // official Firefox releases from Mozilla we use a special identifier.
 pref("browser.safebrowsing.id", "navclient-auto-ffox");
 #else
 pref("browser.safebrowsing.id", "Firefox");
 #endif
 
+pref("browser.safebrowsing.temporary.take_v4_completion_result", false);
+
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
 
 // Debug-only pref to force enable the AccessibleCaret. If you want to
 // control AccessibleCaret by mouse, you'll need to set
 // "layout.accessiblecaret.hide_carets_for_mouse_input" to false.
 pref("layout.accessiblecaret.enabled", false);
 
--- a/security/manager/ssl/nsISiteSecurityService.idl
+++ b/security/manager/ssl/nsISiteSecurityService.idl
@@ -3,31 +3,70 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIObserver;
 interface nsIHttpChannel;
 interface nsISSLStatus;
+interface nsISimpleEnumerator;
 
 %{C++
 #include "nsTArrayForwardDeclare.h"
 class nsCString;
 namespace mozilla
 {
   namespace pkix
   {
     class Time;
   }
 }
 %}
 [ref] native nsCStringTArrayRef(nsTArray<nsCString>);
 [ref] native mozillaPkixTime(mozilla::pkix::Time);
 
+// [infallible] attributes are only allowed on [builtinclass]
+[scriptable, uuid(31313372-842c-4110-bdf1-6aea17c845ad), builtinclass]
+interface nsISiteSecurityState : nsISupports
+{
+  readonly attribute ACString hostname;
+  [infallible] readonly attribute long long expireTime;
+  [infallible] readonly attribute short securityPropertyState;
+  [infallible] readonly attribute boolean includeSubdomains;
+
+  /*
+   * SECURITY_PROPERTY_SET and SECURITY_PROPERTY_UNSET correspond to indicating
+   * a site has or does not have the security property in question,
+   * respectively.
+   * SECURITY_PROPERTY_KNOCKOUT indicates a value on a preloaded
+   * list is being overridden, and the associated site does not have the
+   * security property in question.
+   * SECURITY_PROPERTY_NEGATIVE is used when we've gotten a negative result from
+   * HSTS priming.
+   */
+  const short SECURITY_PROPERTY_UNSET = 0;
+  const short SECURITY_PROPERTY_SET = 1;
+  const short SECURITY_PROPERTY_KNOCKOUT = 2;
+  const short SECURITY_PROPERTY_NEGATIVE = 3;
+};
+
+// This has to be a builtinclass because it derives from a builtinclass.
+[scriptable, uuid(9ff16e40-1029-496c-95c2-bc819872b216), builtinclass]
+interface nsISiteHSTSState : nsISiteSecurityState
+{
+};
+
+// This has to be a builtinclass because it derives from a builtinclass.
+[scriptable, uuid(ae395078-c7d0-474d-b147-f4aa203a9b2c), builtinclass]
+interface nsISiteHPKPState : nsISiteSecurityState
+{
+  readonly attribute nsISimpleEnumerator sha256Keys;
+};
+
 [scriptable, uuid(275127f8-dbd7-4681-afbf-6df0c6587a01)]
 interface nsISiteSecurityService : nsISupports
 {
     const uint32_t HEADER_HSTS = 0;
     const uint32_t HEADER_HPKP = 1;
     const uint32_t HEADER_OMS = 2;
 
     const uint32_t Success = 0;
@@ -205,13 +244,24 @@ interface nsISiteSecurityService : nsISu
     /**
      * Mark a host as declining to provide a given security state so that features
      * such as HSTS priming will not flood a server with requests.
      *
      * @param aURI the nsIURI that this applies to
      * @param aMaxAge lifetime (in seconds) of this negative cache
      */
     [noscript] void cacheNegativeHSTSResult(in nsIURI aURI, in unsigned long long aMaxAge);
+
+    /**
+     * Returns an enumerator of the nsISiteSecurityService storage. Each item in
+     * the enumeration is a nsISiteSecurityState that can be QueryInterfaced to
+     * the appropriate nsISiteHSTSState or nsISiteHPKPState, depending on the
+     * provided type. Doesn't include preloaded entries (either the hard-coded
+     * ones or the preloaded-delivered-by-kinto ones).
+     *
+     * @param aType the type of security state in question.
+     */
+    nsISimpleEnumerator enumerate(in uint32_t aType);
 };
 
 %{C++
 #define NS_SSSERVICE_CONTRACTID "@mozilla.org/ssservice;1"
 %}
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -6,30 +6,35 @@
 
 #include "CertVerifier.h"
 #include "PublicKeyPinningService.h"
 #include "ScopedNSSTypes.h"
 #include "SharedCertVerifier.h"
 #include "base64.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Base64.h"
+#include "mozilla/dom/PContent.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
 #include "nsCRTGlue.h"
 #include "nsISSLStatus.h"
 #include "nsISocketProvider.h"
 #include "nsIURI.h"
 #include "nsIX509Cert.h"
 #include "nsNSSComponent.h"
 #include "nsNetUtil.h"
 #include "nsPromiseFlatString.h"
+#include "nsReadableUtils.h"
 #include "nsSecurityHeaderParser.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
+#include "nsVariant.h"
 #include "plstr.h"
 #include "prnetdb.h"
 #include "prprf.h"
 
 // A note about the preload list:
 // When a site specifically disables HSTS by sending a header with
 // 'max-age: 0', we keep a "knockout" value that means "we have no information
 // regarding the HSTS state of this host" (any ancestor of "this host" can still
@@ -40,20 +45,27 @@
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
 static LazyLogModule gSSSLog("nsSSService");
 
 #define SSSLOG(args) MOZ_LOG(gSSSLog, mozilla::LogLevel::Debug, args)
 
+const char kHSTSKeySuffix[] = ":HSTS";
+const char kHPKPKeySuffix[] = ":HPKP";
+
 ////////////////////////////////////////////////////////////////////////////////
 
-SiteHSTSState::SiteHSTSState(nsCString& aStateString)
-  : mHSTSExpireTime(0)
+NS_IMPL_ISUPPORTS(SiteHSTSState, nsISiteSecurityState, nsISiteHSTSState)
+
+SiteHSTSState::SiteHSTSState(const nsCString& aHost,
+                             const nsCString& aStateString)
+  : mHostname(aHost)
+  , mHSTSExpireTime(0)
   , mHSTSState(SecurityPropertyUnset)
   , mHSTSIncludeSubdomains(false)
 {
   uint32_t hstsState = 0;
   uint32_t hstsIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
   int32_t matches = PR_sscanf(aStateString.get(), "%lld,%lu,%lu",
                               &mHSTSExpireTime, &hstsState,
                               &hstsIncludeSubdomains);
@@ -69,38 +81,74 @@ SiteHSTSState::SiteHSTSState(nsCString& 
   } else {
     SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
     mHSTSExpireTime = 0;
     mHSTSState = SecurityPropertyUnset;
     mHSTSIncludeSubdomains = false;
   }
 }
 
-SiteHSTSState::SiteHSTSState(PRTime aHSTSExpireTime,
+SiteHSTSState::SiteHSTSState(const nsCString& aHost,
+                             PRTime aHSTSExpireTime,
                              SecurityPropertyState aHSTSState,
                              bool aHSTSIncludeSubdomains)
 
-  : mHSTSExpireTime(aHSTSExpireTime)
+  : mHostname(aHost)
+  , mHSTSExpireTime(aHSTSExpireTime)
   , mHSTSState(aHSTSState)
   , mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
 {
 }
 
 void
 SiteHSTSState::ToString(nsCString& aString)
 {
   aString.Truncate();
   aString.AppendInt(mHSTSExpireTime);
   aString.Append(',');
   aString.AppendInt(mHSTSState);
   aString.Append(',');
   aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
 }
 
+NS_IMETHODIMP
+SiteHSTSState::GetHostname(nsACString& aHostname)
+{
+  aHostname = mHostname;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SiteHSTSState::GetExpireTime(int64_t* aExpireTime)
+{
+  NS_ENSURE_ARG(aExpireTime);
+  *aExpireTime = mHSTSExpireTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SiteHSTSState::GetSecurityPropertyState(int16_t* aSecurityPropertyState)
+{
+  NS_ENSURE_ARG(aSecurityPropertyState);
+  *aSecurityPropertyState = mHSTSState;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SiteHSTSState::GetIncludeSubdomains(bool* aIncludeSubdomains)
+{
+  NS_ENSURE_ARG(aIncludeSubdomains);
+  *aIncludeSubdomains = mHSTSIncludeSubdomains;
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(SiteHPKPState, nsISiteSecurityState, nsISiteHPKPState)
+
 static bool
 stringIsBase64EncodingOf256bitValue(nsCString& encodedString) {
   nsAutoCString binaryValue;
   nsresult rv = mozilla::Base64Decode(encodedString, binaryValue);
   if (NS_FAILED(rv)) {
     return false;
   }
   if (binaryValue.Length() != SHA256_LENGTH) {
@@ -111,18 +159,20 @@ stringIsBase64EncodingOf256bitValue(nsCS
 
 SiteHPKPState::SiteHPKPState()
   : mExpireTime(0)
   , mState(SecurityPropertyUnset)
   , mIncludeSubdomains(false)
 {
 }
 
-SiteHPKPState::SiteHPKPState(nsCString& aStateString)
-  : mExpireTime(0)
+SiteHPKPState::SiteHPKPState(const nsCString& aHost,
+                             const nsCString& aStateString)
+  : mHostname(aHost)
+  , mExpireTime(0)
   , mState(SecurityPropertyUnset)
   , mIncludeSubdomains(false)
 {
   uint32_t hpkpState = 0;
   uint32_t hpkpIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
   const uint32_t MaxMergedHPKPPinSize = 1024;
   char mergedHPKPins[MaxMergedHPKPPinSize];
   memset(mergedHPKPins, 0, MaxMergedHPKPPinSize);
@@ -172,42 +222,94 @@ SiteHPKPState::SiteHPKPState(nsCString& 
     mState = SecurityPropertyUnset;
     mIncludeSubdomains = false;
     if (!mSHA256keys.IsEmpty()) {
       mSHA256keys.Clear();
     }
   }
 }
 
-SiteHPKPState::SiteHPKPState(PRTime aExpireTime,
+SiteHPKPState::SiteHPKPState(const nsCString& aHost,
+                             PRTime aExpireTime,
                              SecurityPropertyState aState,
                              bool aIncludeSubdomains,
                              nsTArray<nsCString>& aSHA256keys)
-  : mExpireTime(aExpireTime)
+  : mHostname(aHost)
+  , mExpireTime(aExpireTime)
   , mState(aState)
   , mIncludeSubdomains(aIncludeSubdomains)
   , mSHA256keys(aSHA256keys)
 {
 }
 
+NS_IMETHODIMP
+SiteHPKPState::GetHostname(nsACString& aHostname)
+{
+  aHostname = mHostname;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SiteHPKPState::GetExpireTime(int64_t* aExpireTime)
+{
+  NS_ENSURE_ARG(aExpireTime);
+  *aExpireTime = mExpireTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SiteHPKPState::GetSecurityPropertyState(int16_t* aSecurityPropertyState)
+{
+  NS_ENSURE_ARG(aSecurityPropertyState);
+  *aSecurityPropertyState = mState;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SiteHPKPState::GetIncludeSubdomains(bool* aIncludeSubdomains)
+{
+  NS_ENSURE_ARG(aIncludeSubdomains);
+  *aIncludeSubdomains = mIncludeSubdomains;
+  return NS_OK;
+}
+
 void
 SiteHPKPState::ToString(nsCString& aString)
 {
   aString.Truncate();
   aString.AppendInt(mExpireTime);
   aString.Append(',');
   aString.AppendInt(mState);
   aString.Append(',');
   aString.AppendInt(static_cast<uint32_t>(mIncludeSubdomains));
   aString.Append(',');
   for (unsigned int i = 0; i < mSHA256keys.Length(); i++) {
     aString.Append(mSHA256keys[i]);
   }
 }
 
+NS_IMETHODIMP
+SiteHPKPState::GetSha256Keys(nsISimpleEnumerator** aSha256Keys)
+{
+  NS_ENSURE_ARG(aSha256Keys);
+
+  nsCOMArray<nsIVariant> keys;
+  for (const nsCString& key : mSHA256keys) {
+    nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+    nsresult rv = variant->SetAsAUTF8String(key);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    if (!keys.AppendObject(variant)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  return NS_NewArrayEnumerator(aSha256Keys, keys);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 const uint64_t kSixtyDaysInSeconds = 60 * 24 * 60 * 60;
 
 nsSiteSecurityService::nsSiteSecurityService()
   : mMaxMaxAge(kSixtyDaysInSeconds)
   , mUsePreloadList(true)
   , mPreloadListTimeOffset(0)
@@ -294,20 +396,20 @@ nsSiteSecurityService::GetHost(nsIURI* a
 }
 
 static void
 SetStorageKey(nsAutoCString& storageKey, const nsACString& hostname, uint32_t aType)
 {
   storageKey = hostname;
   switch (aType) {
     case nsISiteSecurityService::HEADER_HSTS:
-      storageKey.AppendLiteral(":HSTS");
+      storageKey.AppendASCII(kHSTSKeySuffix);
       break;
     case nsISiteSecurityService::HEADER_HPKP:
-      storageKey.AppendLiteral(":HPKP");
+      storageKey.AppendASCII(kHPKPKeySuffix);
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("SSS:SetStorageKey got invalid type");
   }
 }
 
 // Expire times are in millis.  Since Headers max-age is in seconds, and
 // PR_Now() is in micros, normalize the units at milliseconds.
@@ -333,19 +435,20 @@ nsSiteSecurityService::SetHSTSState(uint
     return RemoveStateInternal(aType, hostname, flags, aIsPreload);
   }
 
   MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
               aHSTSState == SecurityPropertyNegative),
       "HSTS State must be SecurityPropertySet or SecurityPropertyNegative");
 
   int64_t expiretime = ExpireTimeFromMaxAge(maxage);
-  SiteHSTSState siteState(expiretime, aHSTSState, includeSubdomains);
+  RefPtr<SiteHSTSState> siteState =
+    new SiteHSTSState(hostname, expiretime, aHSTSState, includeSubdomains);
   nsAutoCString stateString;
-  siteState.ToString(stateString);
+  siteState->ToString(stateString);
   SSSLOG(("SSS: setting state for %s", hostname.get()));
   bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   nsAutoCString storageKey;
   SetStorageKey(storageKey, hostname, aType);
   nsresult rv;
@@ -393,23 +496,24 @@ nsSiteSecurityService::RemoveStateIntern
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   // If this host is in the preload list, we have to store a knockout entry.
   nsAutoCString storageKey;
   SetStorageKey(storageKey, aHost, aType);
 
   nsCString value = mPreloadStateStorage->Get(storageKey,
                                               mozilla::DataStorage_Persistent);
-  SiteHSTSState dynamicState(value);
+  RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
   if (GetPreloadListEntry(aHost.get()) ||
-      dynamicState.mHSTSState != SecurityPropertyUnset) {
+      dynamicState->mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
-    SiteHSTSState siteState(0, SecurityPropertyKnockout, false);
+    RefPtr<SiteHSTSState> siteState =
+      new SiteHSTSState(aHost, 0, SecurityPropertyKnockout, false);
     nsAutoCString stateString;
-    siteState.ToString(stateString);
+    siteState->ToString(stateString);
     nsresult rv;
     if (aIsPreload) {
       rv = mPreloadStateStorage->Put(storageKey, stateString,
                                      mozilla::DataStorage_Persistent);
     } else {
       rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
     }
     NS_ENSURE_SUCCESS(rv, rv);
@@ -847,22 +951,23 @@ nsSiteSecurityService::ProcessPKPHeader(
     SSSLOG(("SSS: Pins provided by %s are invalid no backupPin\n", host.get()));
     if (aFailureResult) {
       *aFailureResult = nsISiteSecurityService::ERROR_NO_BACKUP_PIN;
     }
     return NS_ERROR_FAILURE;
   }
 
   int64_t expireTime = ExpireTimeFromMaxAge(maxAge);
-  SiteHPKPState dynamicEntry(expireTime, SecurityPropertySet,
-                             foundIncludeSubdomains, sha256keys);
+  RefPtr<SiteHPKPState> dynamicEntry =
+    new SiteHPKPState(host, expireTime, SecurityPropertySet,
+                      foundIncludeSubdomains, sha256keys);
   SSSLOG(("SSS: about to set pins for  %s, expires=%ld now=%ld maxAge=%lu\n",
            host.get(), expireTime, PR_Now() / PR_USEC_PER_MSEC, maxAge));
 
-  rv = SetHPKPState(host.get(), dynamicEntry, aFlags, false);
+  rv = SetHPKPState(host.get(), *dynamicEntry, aFlags, false);
   if (NS_FAILED(rv)) {
     SSSLOG(("SSS: failed to set pins for %s\n", host.get()));
     if (aFailureResult) {
       *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
     }
     return rv;
   }
 
@@ -1023,68 +1128,68 @@ nsSiteSecurityService::HostHasHSTSEntry(
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   nsAutoCString storageKey;
   SSSLOG(("Seeking HSTS entry for %s", aHost.get()));
   SetStorageKey(storageKey, aHost, nsISiteSecurityService::HEADER_HSTS);
   nsCString value = mSiteStateStorage->Get(storageKey, storageType);
-  SiteHSTSState siteState(value);
-  if (siteState.mHSTSState != SecurityPropertyUnset) {
+  RefPtr<SiteHSTSState> siteState = new SiteHSTSState(aHost, value);
+  if (siteState->mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("Found HSTS entry for %s", aHost.get()));
-    bool expired = siteState.IsExpired(nsISiteSecurityService::HEADER_HSTS);
+    bool expired = siteState->IsExpired(nsISiteSecurityService::HEADER_HSTS);
     if (!expired) {
       SSSLOG(("Entry for %s is not expired", aHost.get()));
       if (aCached) {
         *aCached = true;
       }
-      if (siteState.mHSTSState == SecurityPropertySet) {
-        *aResult = aRequireIncludeSubdomains ? siteState.mHSTSIncludeSubdomains
+      if (siteState->mHSTSState == SecurityPropertySet) {
+        *aResult = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
                                              : true;
         return true;
-      } else if (siteState.mHSTSState == SecurityPropertyNegative) {
+      } else if (siteState->mHSTSState == SecurityPropertyNegative) {
         *aResult = false;
         return true;
       }
     }
 
     if (expired) {
       SSSLOG(("Entry %s is expired - checking for preload state", aHost.get()));
       // If the entry is expired and is not in either the static or dynamic
       // preload lists, we can remove it.
       // First, check the dynamic preload list.
       value = mPreloadStateStorage->Get(storageKey,
                                         mozilla::DataStorage_Persistent);
-      SiteHSTSState dynamicState(value);
-      if (dynamicState.mHSTSState == SecurityPropertyUnset) {
+      RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
+      if (dynamicState->mHSTSState == SecurityPropertyUnset) {
         SSSLOG(("No dynamic preload - checking for static preload"));
         // Now check the static preload list.
         if (!GetPreloadListEntry(aHost.get())) {
           SSSLOG(("No static preload - removing expired entry"));
           mSiteStateStorage->Remove(storageKey, storageType);
         }
       }
     }
     return false;
   }
 
   // Next, look in the dynamic preload list.
   value = mPreloadStateStorage->Get(storageKey,
                                     mozilla::DataStorage_Persistent);
-  SiteHSTSState dynamicState(value);
-  if (dynamicState.mHSTSState != SecurityPropertyUnset) {
+  RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
+  if (dynamicState->mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("Found dynamic preload entry for %s", aHost.get()));
-    bool expired = dynamicState.IsExpired(nsISiteSecurityService::HEADER_HSTS);
+    bool expired = dynamicState->IsExpired(nsISiteSecurityService::HEADER_HSTS);
     if (!expired) {
-      if (dynamicState.mHSTSState == SecurityPropertySet) {
-        *aResult = aRequireIncludeSubdomains ? dynamicState.mHSTSIncludeSubdomains
+      if (dynamicState->mHSTSState == SecurityPropertySet) {
+        *aResult = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
                                              : true;
         return true;
-      } else if (dynamicState.mHSTSState == SecurityPropertyNegative) {
+      } else if (dynamicState->mHSTSState == SecurityPropertyNegative) {
         *aResult = false;
         return true;
       }
     } else {
       // if a dynamic preload has expired and is not in the static preload
       // list, we can remove it.
       if (!GetPreloadListEntry(aHost.get())) {
         mPreloadStateStorage->Remove(storageKey,
@@ -1092,18 +1197,18 @@ nsSiteSecurityService::HostHasHSTSEntry(
       }
     }
     return false;
   }
 
   const nsSTSPreload* preload = nullptr;
 
   // Finally look in the static preload list.
-  if (siteState.mHSTSState == SecurityPropertyUnset &&
-      dynamicState.mHSTSState == SecurityPropertyUnset &&
+  if (siteState->mHSTSState == SecurityPropertyUnset &&
+      dynamicState->mHSTSState == SecurityPropertyUnset &&
       (preload = GetPreloadListEntry(aHost.get())) != nullptr) {
     SSSLOG(("%s is a preloaded HSTS host", aHost.get()));
     *aResult = aRequireIncludeSubdomains ? preload->mIncludeSubdomains
                                          : true;
     if (aCached) {
       *aCached = true;
     }
     return true;
@@ -1259,36 +1364,36 @@ nsSiteSecurityService::GetKeyPinsForHost
   nsAutoCString storageKey;
   SetStorageKey(storageKey, host, nsISiteSecurityService::HEADER_HPKP);
 
   SSSLOG(("storagekey '%s'\n", storageKey.get()));
   mozilla::DataStorageType storageType = mozilla::DataStorage_Persistent;
   nsCString value = mSiteStateStorage->Get(storageKey, storageType);
 
   // decode now
-  SiteHPKPState foundEntry(value);
-  if (entryStateNotOK(foundEntry, aEvalTime)) {
+  RefPtr<SiteHPKPState> foundEntry = new SiteHPKPState(host, value);
+  if (entryStateNotOK(*foundEntry, aEvalTime)) {
     // not in permanent storage, try now private
     value = mSiteStateStorage->Get(storageKey, mozilla::DataStorage_Private);
-    SiteHPKPState privateEntry(value);
-    if (entryStateNotOK(privateEntry, aEvalTime)) {
+    RefPtr<SiteHPKPState> privateEntry = new SiteHPKPState(host, value);
+    if (entryStateNotOK(*privateEntry, aEvalTime)) {
       // not in private storage, try dynamic preload
       value = mPreloadStateStorage->Get(storageKey,
                                         mozilla::DataStorage_Persistent);
-      SiteHPKPState preloadEntry(value);
-      if (entryStateNotOK(preloadEntry, aEvalTime)) {
+      RefPtr<SiteHPKPState> preloadEntry = new SiteHPKPState(host, value);
+      if (entryStateNotOK(*preloadEntry, aEvalTime)) {
         return NS_OK;
       }
       foundEntry = preloadEntry;
     } else {
       foundEntry = privateEntry;
     }
   }
-  pinArray = foundEntry.mSHA256keys;
-  *aIncludeSubdomains = foundEntry.mIncludeSubdomains;
+  pinArray = foundEntry->mSHA256keys;
+  *aIncludeSubdomains = foundEntry->mIncludeSubdomains;
   *afound = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::SetKeyPins(const nsACString& aHost,
                                   bool aIncludeSubdomains,
                                   int64_t aExpires, uint32_t aPinCount,
@@ -1311,23 +1416,24 @@ nsSiteSecurityService::SetKeyPins(const 
   for (unsigned int i = 0; i < aPinCount; i++) {
     nsAutoCString pin(aSha256Pins[i]);
     SSSLOG(("SetPins pin=%s\n", pin.get()));
     if (!stringIsBase64EncodingOf256bitValue(pin)) {
       return NS_ERROR_INVALID_ARG;
     }
     sha256keys.AppendElement(pin);
   }
-  SiteHPKPState dynamicEntry(aExpires, SecurityPropertySet,
-                             aIncludeSubdomains, sha256keys);
   // we always store data in permanent storage (ie no flags)
   const nsCString& flatHost = PromiseFlatCString(aHost);
   nsAutoCString host(
     PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
-  return SetHPKPState(host.get(), dynamicEntry, 0, aIsPreload);
+  RefPtr<SiteHPKPState> dynamicEntry =
+    new SiteHPKPState(host, aExpires, SecurityPropertySet, aIncludeSubdomains,
+                      sha256keys);
+  return SetHPKPState(host.get(), *dynamicEntry, 0, aIsPreload);
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::SetHSTSPreload(const nsACString& aHost,
                                       bool aIncludeSubdomains,
                                       int64_t aExpires,
                               /*out*/ bool* aResult)
 {
@@ -1369,16 +1475,65 @@ nsSiteSecurityService::SetHPKPState(cons
                                    mozilla::DataStorage_Persistent);
   } else {
     rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
   }
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSiteSecurityService::Enumerate(uint32_t aType,
+                                 nsISimpleEnumerator** aEnumerator)
+{
+  NS_ENSURE_ARG(aEnumerator);
+
+  nsAutoCString keySuffix;
+  switch (aType) {
+    case nsISiteSecurityService::HEADER_HSTS:
+      keySuffix.AssignASCII(kHSTSKeySuffix);
+      break;
+    case nsISiteSecurityService::HEADER_HPKP:
+      keySuffix.AssignASCII(kHPKPKeySuffix);
+      break;
+    default:
+      return NS_ERROR_INVALID_ARG;
+  }
+
+  InfallibleTArray<mozilla::dom::DataStorageItem> items;
+  mSiteStateStorage->GetAll(&items);
+
+  nsCOMArray<nsISiteSecurityState> states;
+  for (const mozilla::dom::DataStorageItem& item : items) {
+    if (!StringEndsWith(item.key(), keySuffix)) {
+      // The key does not end with correct suffix, so is not the type we want.
+      continue;
+    }
+
+    nsCString hostname(
+      StringHead(item.key(), item.key().Length() - keySuffix.Length()));
+    nsCOMPtr<nsISiteSecurityState> state;
+    switch(aType) {
+      case nsISiteSecurityService::HEADER_HSTS:
+        state = new SiteHSTSState(hostname, item.value());
+        break;
+      case nsISiteSecurityService::HEADER_HPKP:
+        state = new SiteHPKPState(hostname, item.value());
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("SSS:Enumerate got invalid type");
+    }
+
+    states.AppendObject(state);
+  }
+
+  NS_NewArrayEnumerator(aEnumerator, states);
+  return NS_OK;
+}
+
 //------------------------------------------------------------
 // nsSiteSecurityService::nsIObserver
 //------------------------------------------------------------
 
 NS_IMETHODIMP
 nsSiteSecurityService::Observe(nsISupports* /*subject*/, const char* topic,
                                const char16_t* /*data*/)
 {
--- a/security/manager/ssl/nsSiteSecurityService.h
+++ b/security/manager/ssl/nsSiteSecurityService.h
@@ -1,16 +1,17 @@
 /* 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/. */
 
 #ifndef __nsSiteSecurityService_h__
 #define __nsSiteSecurityService_h__
 
 #include "mozilla/DataStorage.h"
+#include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsISiteSecurityService.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "pkix/pkixtypes.h"
 #include "prtime.h"
 
@@ -27,71 +28,87 @@ class nsISSLStatus;
  * a security property can be in.
  * SecurityPropertySet and SecurityPropertyUnset correspond to indicating
  * a site has or does not have the security property in question, respectively.
  * SecurityPropertyKnockout indicates a value on a preloaded list is being
  * overridden, and the associated site does not have the security property
  * in question.
  */
 enum SecurityPropertyState {
-  SecurityPropertyUnset = 0,
-  SecurityPropertySet = 1,
-  SecurityPropertyKnockout = 2,
-  SecurityPropertyNegative = 3,
+  SecurityPropertyUnset = nsISiteSecurityState::SECURITY_PROPERTY_UNSET,
+  SecurityPropertySet = nsISiteSecurityState::SECURITY_PROPERTY_SET,
+  SecurityPropertyKnockout = nsISiteSecurityState::SECURITY_PROPERTY_KNOCKOUT,
+  SecurityPropertyNegative = nsISiteSecurityState::SECURITY_PROPERTY_NEGATIVE,
 };
 
 /**
  * SiteHPKPState: A utility class that encodes/decodes a string describing
  * the public key pins of a site.
  * HPKP state consists of:
+ *  - Hostname (nsCString)
  *  - Expiry time (PRTime (aka int64_t) in milliseconds)
  *  - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
  *  - An include subdomains flag (bool, default false)
  *  - An array of sha-256 hashed base 64 encoded fingerprints of required keys
  */
-class SiteHPKPState
+class SiteHPKPState : public nsISiteHPKPState
 {
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISITEHPKPSTATE
+  NS_DECL_NSISITESECURITYSTATE
+
   SiteHPKPState();
-  explicit SiteHPKPState(nsCString& aStateString);
-  SiteHPKPState(PRTime aExpireTime, SecurityPropertyState aState,
-                bool aIncludeSubdomains, nsTArray<nsCString>& SHA256keys);
+  SiteHPKPState(const nsCString& aHost, const nsCString& aStateString);
+  SiteHPKPState(const nsCString& aHost, PRTime aExpireTime,
+                SecurityPropertyState aState, bool aIncludeSubdomains,
+                nsTArray<nsCString>& SHA256keys);
 
+  nsCString mHostname;
   PRTime mExpireTime;
   SecurityPropertyState mState;
   bool mIncludeSubdomains;
   nsTArray<nsCString> mSHA256keys;
 
   bool IsExpired(mozilla::pkix::Time aTime)
   {
     if (aTime > mozilla::pkix::TimeFromEpochInSeconds(mExpireTime /
                                                       PR_MSEC_PER_SEC)) {
       return true;
     }
     return false;
   }
 
   void ToString(nsCString& aString);
+
+protected:
+  virtual ~SiteHPKPState() {};
 };
 
 /**
  * SiteHSTSState: A utility class that encodes/decodes a string describing
  * the security state of a site. Currently only handles HSTS.
  * HSTS state consists of:
+ *  - Hostname (nsCString)
  *  - Expiry time (PRTime (aka int64_t) in milliseconds)
  *  - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
  *  - An include subdomains flag (bool, default false)
  */
-class SiteHSTSState
+class SiteHSTSState : public nsISiteHSTSState
 {
 public:
-  explicit SiteHSTSState(nsCString& aStateString);
-  SiteHSTSState(PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
-                bool aHSTSIncludeSubdomains);
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISITEHSTSSTATE
+  NS_DECL_NSISITESECURITYSTATE
 
+  SiteHSTSState(const nsCString& aHost, const nsCString& aStateString);
+  SiteHSTSState(const nsCString& aHost, PRTime aHSTSExpireTime,
+                SecurityPropertyState aHSTSState, bool aHSTSIncludeSubdomains);
+
+  nsCString mHostname;
   PRTime mHSTSExpireTime;
   SecurityPropertyState mHSTSState;
   bool mHSTSIncludeSubdomains;
 
   bool IsExpired(uint32_t aType)
   {
     // If mHSTSExpireTime is 0, this entry never expires (this is the case for
     // knockout entries).
@@ -103,16 +120,19 @@ public:
     if (now > mHSTSExpireTime) {
       return true;
     }
 
     return false;
   }
 
   void ToString(nsCString &aString);
+
+protected:
+  virtual ~SiteHSTSState() {}
 };
 
 struct nsSTSPreload;
 
 class nsSiteSecurityService : public nsISiteSecurityService
                             , public nsIObserver
 {
 public:
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_sss_enumerate.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// This had better not be larger than the maximum maxAge for HPKP.
+const NON_ISSUED_KEY_HASH = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+const PINNING_ROOT_KEY_HASH = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
+const KEY_HASHES = [ NON_ISSUED_KEY_HASH, PINNING_ROOT_KEY_HASH ];
+const SECS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000;
+const TESTCASES = [
+  {
+    hostname: "a.pinning2.example.com",
+    includeSubdomains: true,
+    expireTime: Date.now() + 12 * SECS_IN_A_WEEK * 1000,
+  },
+  {
+    hostname: "b.pinning2.example.com",
+    includeSubdomains: false,
+    expireTime: Date.now() + 13 * SECS_IN_A_WEEK * 1000,
+  },
+].sort((a, b) => a.expireTime - b.expireTime);
+
+do_register_cleanup(() => {
+  Services.prefs.clearUserPref(
+    "security.cert_pinning.process_headers_from_non_builtin_roots");
+  Services.prefs.clearUserPref("security.cert_pinning.max_max_age_seconds");
+});
+
+do_get_profile();
+
+Services.prefs.setBoolPref(
+  "security.cert_pinning.process_headers_from_non_builtin_roots", true);
+Services.prefs.setIntPref("security.cert_pinning.max_max_age_seconds",
+                          20 * SECS_IN_A_WEEK);
+
+let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+addCertFromFile(certdb, "test_pinning_dynamic/pinningroot.pem", "CTu,CTu,CTu");
+
+let sss = Cc["@mozilla.org/ssservice;1"].getService(Ci.nsISiteSecurityService);
+
+function insertEntries() {
+  for (let testcase of TESTCASES) {
+    let uri = Services.io.newURI("https://" + testcase.hostname);
+    let sslStatus = new FakeSSLStatus(constructCertFromFile(
+      `test_pinning_dynamic/${testcase.hostname}-pinningroot.pem`));
+    // MaxAge is in seconds.
+    let maxAge = Math.round((testcase.expireTime - Date.now()) / 1000);
+    let header = `max-age=${maxAge}`;
+    if (testcase.includeSubdomains) {
+      header += "; includeSubdomains";
+    }
+    sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, header,
+                      sslStatus, 0);
+    for (let key of KEY_HASHES) {
+      header += `; pin-sha256="${key}"`;
+    }
+    sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header,
+                      sslStatus, 0);
+  }
+}
+
+function getEntries(type) {
+  let entryEnumerator = sss.enumerate(type);
+  let entries = [];
+  while (entryEnumerator.hasMoreElements()) {
+    let entry = entryEnumerator.getNext();
+    entries.push(entry.QueryInterface(Ci.nsISiteSecurityState));
+  }
+  return entries;
+}
+
+function checkSiteSecurityStateAttrs(entries) {
+  entries.sort((a, b) => a.expireTime - b.expireTime);
+  equal(entries.length, TESTCASES.length,
+        "Should get correct number of entries");
+  for (let i = 0; i < TESTCASES.length; i++) {
+    equal(entries[i].hostname, TESTCASES[i].hostname, "Hostnames should match");
+    equal(entries[i].securityPropertyState,
+          Ci.nsISiteSecurityState.SECURITY_PROPERTY_SET,
+          "Entries should have security property set");
+    equal(entries[i].includeSubdomains, TESTCASES[i].includeSubdomains,
+          "IncludeSubdomains should match");
+    // There's a delay from our "now" and the "now" that the implementation uses.
+    less(Math.abs(entries[i].expireTime - TESTCASES[i].expireTime), 60000,
+         "ExpireTime should be within 60-second error");
+  }
+}
+
+function checkSha256Keys(hpkpEntries) {
+  for (let hpkpEntry of hpkpEntries) {
+    let enumerator = hpkpEntry.QueryInterface(Ci.nsISiteHPKPState).sha256Keys;
+    let keys = [];
+    while (enumerator.hasMoreElements()) {
+      keys.push(enumerator.getNext().QueryInterface(Ci.nsIVariant));
+    }
+    equal(keys.length, KEY_HASHES.length, "Should get correct number of keys");
+    keys.sort();
+    for (let i = 0; i < KEY_HASHES.length; i++) {
+      equal(keys[i], KEY_HASHES[i], "Should get correct keys");
+    }
+  }
+}
+
+function run_test() {
+  sss.clearAll();
+
+  insertEntries();
+
+  let hstsEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HSTS);
+  let hpkpEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HPKP);
+
+  checkSiteSecurityStateAttrs(hstsEntries);
+  checkSiteSecurityStateAttrs(hpkpEntries);
+
+  checkSha256Keys(hpkpEntries);
+
+  sss.clearAll();
+  hstsEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HSTS);
+  hpkpEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HPKP);
+
+  equal(hstsEntries.length, 0, "Should clear all HSTS entries");
+  equal(hpkpEntries.length, 0, "Should clear all HPKP entries");
+}
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -119,16 +119,17 @@ requesttimeoutfactor = 2
 [test_pinning_header_parsing.js]
 [test_sdr.js]
 [test_session_resumption.js]
 run-sequentially = hardcoded ports
 [test_signed_apps.js]
 [test_signed_apps-marketplace.js]
 [test_signed_dir.js]
 tags = addons psm
+[test_sss_enumerate.js]
 [test_sss_eviction.js]
 [test_sss_readstate.js]
 [test_sss_readstate_child.js]
 support-files = sss_readstate_child_worker.js
 # bug 1124289 - run_test_in_child violates the sandbox on android
 skip-if = toolkit == 'android'
 [test_sss_readstate_empty.js]
 [test_sss_readstate_garbage.js]
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -57,16 +57,18 @@ user_pref("media.gmp-manager.updateEnabl
 user_pref("dom.w3c_touch_events.enabled", 1);
 user_pref("layout.accessiblecaret.enabled_on_touch", false);
 user_pref("dom.webcomponents.enabled", true);
 user_pref("dom.webcomponents.customelements.enabled", true);
 user_pref("dom.htmlimports.enabled", true);
 // Existing tests assume there is no font size inflation.
 user_pref("font.size.inflation.emPerLine", 0);
 user_pref("font.size.inflation.minTwips", 0);
+// Disable the caret blinking so we get stable snapshot
+user_pref("ui.caretBlinkTime", -1);
 
 // AddonManager tests require that the experiments provider be present.
 user_pref("experiments.supported", true);
 // Point the manifest at something local so we don't risk it hitting production
 // data and installing experiments that may vary over time.
 user_pref("experiments.manifest.uri", "http://%(server)s/experiments-dummy/manifest");
 
 // Don't allow background tabs to be zombified, otherwise for tests that
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -117,16 +117,21 @@ LazyLogModule gUrlClassifierDbServiceLog
 #define BLOCKED_TABLE_PREF              "urlclassifier.blockedTable"
 #define DOWNLOAD_BLOCK_TABLE_PREF       "urlclassifier.downloadBlockTable"
 #define DOWNLOAD_ALLOW_TABLE_PREF       "urlclassifier.downloadAllowTable"
 #define DISALLOW_COMPLETION_TABLE_PREF  "urlclassifier.disallow_completions"
 
 #define CONFIRM_AGE_PREF        "urlclassifier.max-complete-age"
 #define CONFIRM_AGE_DEFAULT_SEC (45 * 60)
 
+// TODO: The following two prefs are to be removed after we
+//       roll out full v4 hash completion. See Bug 1331534.
+#define TAKE_V4_COMPLETION_RESULT_PREF    "browser.safebrowsing.temporary.take_v4_completion_result"
+#define TAKE_V4_COMPLETION_RESULT_DEFAULT false
+
 class nsUrlClassifierDBServiceWorker;
 
 // Singleton instance.
 static nsUrlClassifierDBService* sUrlClassifierDBService;
 
 nsIThread* nsUrlClassifierDBService::gDbBackgroundThread = nullptr;
 
 // Once we've committed to shutting down, don't do work in the background
@@ -1050,16 +1055,25 @@ nsUrlClassifierLookupCallback::Completio
 
 NS_IMETHODIMP
 nsUrlClassifierLookupCallback::Completion(const nsACString& completeHash,
                                           const nsACString& tableName,
                                           uint32_t chunkId)
 {
   LOG(("nsUrlClassifierLookupCallback::Completion [%p, %s, %d]",
        this, PromiseFlatCString(tableName).get(), chunkId));
+
+  if (StringEndsWith(tableName, NS_LITERAL_CSTRING("-proto")) &&
+      !Preferences::GetBool(TAKE_V4_COMPLETION_RESULT_PREF,
+                            TAKE_V4_COMPLETION_RESULT_DEFAULT)) {
+    // Bug 1331534 - We temporarily ignore hash completion result
+    // for v4 tables.
+    return NS_OK;
+  }
+
   mozilla::safebrowsing::Completion hash;
   hash.Assign(completeHash);
 
   // Send this completion to the store for caching.
   if (!mCacheResults) {
     mCacheResults = new CacheResultArray();
     if (!mCacheResults)
       return NS_ERROR_OUT_OF_MEMORY;
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -8,16 +8,21 @@ support-files =
 tags = audiochannel
 support-files =
   file_multipleAudio.html
 [browser_autoscroll_disabled.js]
 [browser_block_autoplay_media.js]
 tags = audiochannel
 support-files =
   file_multipleAudio.html
+[browser_block_autoplay_media_pausedAfterPlay.js]
+ tags = audiochannel
+support-files =
+  file_blockMedia_shouldPlay.html
+  file_blockMedia_shouldNotPlay.html
 [browser_bug295977_autoscroll_overflow.js]
 [browser_bug451286.js]
 skip-if = !e10s
 [browser_bug594509.js]
 [browser_bug982298.js]
 [browser_bug1198465.js]
 [browser_contentTitle.js]
 [browser_crash_previous_frameloader.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_block_autoplay_media_pausedAfterPlay.js
@@ -0,0 +1,82 @@
+const PAGE_SHOULD_PLAY = "https://example.com/browser/toolkit/content/tests/browser/file_blockMedia_shouldPlay.html";
+const PAGE_SHOULD_NOT_PLAY = "https://example.com/browser/toolkit/content/tests/browser/file_blockMedia_shouldNotPlay.html";
+
+var SuspendedType = {
+  NONE_SUSPENDED             : 0,
+  SUSPENDED_PAUSE            : 1,
+  SUSPENDED_BLOCK            : 2,
+  SUSPENDED_PAUSE_DISPOSABLE : 3
+};
+
+function check_audio_suspended(suspendedType) {
+  var audio = content.document.getElementById("testAudio");
+  if (!audio) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio.computedSuspended, suspendedType,
+     "The suspeded state of audio is correct.");
+}
+
+function check_audio_pause_state(expectPause) {
+  var audio = content.document.getElementById("testAudio");
+  if (!audio) {
+    ok(false, "Can't get the audio element!");
+  }
+
+  is(audio.paused, expectPause,
+    "The pause state of audio is corret.")
+}
+
+add_task(function* setup_test_preference() {
+  yield SpecialPowers.pushPrefEnv({"set": [
+    ["media.useAudioChannelService.testing", true],
+    ["media.block-autoplay-until-in-foreground", true]
+  ]});
+});
+
+add_task(function* block_autoplay_media() {
+  info("- open new background tab1, and check tab1's media suspend type -");
+  let tab1 = window.gBrowser.addTab("about:blank");
+  tab1.linkedBrowser.loadURI(PAGE_SHOULD_NOT_PLAY);
+  yield BrowserTestUtils.browserLoaded(tab1.linkedBrowser);
+  yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.NONE_SUSPENDED,
+                          check_audio_suspended);
+
+  info("- the tab1 should not be blocked -");
+  yield waitForTabBlockEvent(tab1, false);
+
+  info("- open new background tab2, and check tab2's media suspend type -");
+  let tab2 = window.gBrowser.addTab("about:blank");
+  tab2.linkedBrowser.loadURI(PAGE_SHOULD_PLAY);
+  yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+  yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.SUSPENDED_BLOCK,
+                          check_audio_suspended);
+
+  info("- the tab2 should be blocked -");
+  yield waitForTabBlockEvent(tab2, true);
+
+  info("- select tab1 as foreground tab, and tab1's media should be paused -");
+  yield BrowserTestUtils.switchTab(window.gBrowser, tab1);
+  yield ContentTask.spawn(tab1.linkedBrowser, true,
+                          check_audio_pause_state);
+
+  info("- the tab1 should not be blocked -");
+  yield waitForTabBlockEvent(tab1, false);
+
+  info("- select tab2 as foreground tab, and tab2's media should be playing -");
+  yield BrowserTestUtils.switchTab(window.gBrowser, tab2);
+  yield ContentTask.spawn(tab2.linkedBrowser, false,
+                          check_audio_pause_state);
+
+  info("- the tab2 should not be blocked -");
+  yield waitForTabBlockEvent(tab2, false);
+
+  info("- check tab2's media suspend type -");
+  yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.NONE_SUSPENDED,
+                          check_audio_suspended);
+
+  info("- remove tabs -");
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_blockMedia_shouldNotPlay.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<head>
+  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+  <meta content="utf-8" http-equiv="encoding">
+</head>
+<body>
+<audio id="testAudio" src="audio.ogg" preload="none"></audio>
+<script type="text/javascript">
+
+var audio = document.getElementById("testAudio");
+audio.play();
+audio.pause();
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_blockMedia_shouldPlay.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<head>
+  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+  <meta content="utf-8" http-equiv="encoding">
+</head>
+<body>
+<audio id="testAudio" src="audio.ogg"></audio>
+<script type="text/javascript">
+
+var audio = document.getElementById("testAudio");
+audio.play();
+audio.pause();
+audio.play();
+
+</script>
+</body>
--- a/toolkit/content/tests/browser/head.js
+++ b/toolkit/content/tests/browser/head.js
@@ -26,8 +26,25 @@ function closeFindbarAndWait(findbar) {
   });
 }
 
 function pushPrefs(...aPrefs) {
   let deferred = Promise.defer();
   SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
   return deferred.promise;
 }
+
+/**
+ * Used to check whether the audio unblocking icon is in the tab.
+ */
+function* waitForTabBlockEvent(tab, expectBlocked) {
+  if (tab.soundBlocked == expectBlocked) {
+    ok(true, "The tab should " + (expectBlocked ? "" : "not ") + "be blocked");
+  } else {
+    yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
+      if (event.detail.changed.indexOf("blocked") >= 0) {
+        is(tab.soundBlocked, expectBlocked, "The tab should " + (expectBlocked ? "" : "not ") + "be blocked");
+        return true;
+      }
+      return false;
+    });
+  }
+}
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -215,45 +215,45 @@ private:
   public:
     explicit AllPromiseHolder(size_t aDependentPromises)
       : mPromise(new typename AllPromiseType::Private(__func__))
       , mOutstandingPromises(aDependentPromises)
     {
       mResolveValues.SetLength(aDependentPromises);
     }
 
-    void Resolve(size_t aIndex, const ResolveValueType& aResolveValue)
+    void Resolve(size_t aIndex, ResolveValueType&& aResolveValue)
     {
       if (!mPromise) {
         // Already rejected.
         return;
       }
 
-      mResolveValues[aIndex].emplace(aResolveValue);
+      mResolveValues[aIndex].emplace(Move(aResolveValue));
       if (--mOutstandingPromises == 0) {
         nsTArray<ResolveValueType> resolveValues;
         resolveValues.SetCapacity(mResolveValues.Length());
         for (size_t i = 0; i < mResolveValues.Length(); ++i) {
-          resolveValues.AppendElement(mResolveValues[i].ref());
+          resolveValues.AppendElement(Move(mResolveValues[i].ref()));
         }
 
-        mPromise->Resolve(resolveValues, __func__);
+        mPromise->Resolve(Move(resolveValues), __func__);
         mPromise = nullptr;
         mResolveValues.Clear();
       }
     }
 
-    void Reject(const RejectValueType& aRejectValue)
+    void Reject(RejectValueType&& aRejectValue)
     {
       if (!mPromise) {
         // Already rejected.
         return;
       }
 
-      mPromise->Reject(aRejectValue, __func__);
+      mPromise->Reject(Move(aRejectValue), __func__);
       mPromise = nullptr;
       mResolveValues.Clear();
     }
 
     AllPromiseType* Promise() { return mPromise; }
 
   private:
     nsTArray<Maybe<ResolveValueType>> mResolveValues;
@@ -262,18 +262,18 @@ private:
   };
 public:
 
   static RefPtr<AllPromiseType> All(AbstractThread* aProcessingThread, nsTArray<RefPtr<MozPromise>>& aPromises)
   {
     RefPtr<AllPromiseHolder> holder = new AllPromiseHolder(aPromises.Length());
     for (size_t i = 0; i < aPromises.Length(); ++i) {
       aPromises[i]->Then(aProcessingThread, __func__,
-        [holder, i] (ResolveValueType aResolveValue) -> void { holder->Resolve(i, aResolveValue); },
-        [holder] (RejectValueType aRejectValue) -> void { holder->Reject(aRejectValue); }
+        [holder, i] (ResolveValueType aResolveValue) -> void { holder->Resolve(i, Move(aResolveValue)); },
+        [holder] (RejectValueType aRejectValue) -> void { holder->Reject(Move(aRejectValue)); }
       );
     }
     return holder->Promise();
   }
 
   class Request : public MozPromiseRefcountable
   {
   public:
@@ -1117,17 +1117,17 @@ public:
 };
 
 template<typename PromiseType, typename MethodType, typename ThisType,
          typename... Storages>
 class MethodCall : public MethodCallBase
 {
 public:
   template<typename... Args>
-  MethodCall(MethodType aMethod, ThisType* aThisVal, Args... aArgs)
+  MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs)
     : mMethod(aMethod)
     , mThisVal(aThisVal)
     , mArgs(Forward<Args>(aArgs)...)
   {
     static_assert(sizeof...(Storages) == sizeof...(Args), "Storages and Args should have equal sizes");
   }
 
   RefPtr<PromiseType> Invoke()