Merge mozilla-central to inbound. a=merge CLOSED TREE
authorCosmin Sabou <csabou@mozilla.com>
Sun, 15 Apr 2018 03:07:11 +0300
changeset 466977 f291b07fa1f869fd3f9af9f5fe940d47b842edcf
parent 466976 244ecba50f11b3cf54288242c80262b5342ba665 (current diff)
parent 466916 a79d460bf2a33fd79c6646236f9c4df78e66e7b7 (diff)
child 466978 b81ac6c5c2074e02b5b89a2de4d104abc3397e72
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.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 mozilla-central to inbound. a=merge CLOSED TREE
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -51,17 +51,16 @@
       xmlns:html="http://www.w3.org/1999/xhtml"
       disablefastfind="true"
       data-l10n-id="pref-page"
       data-l10n-attrs="title">
 
   <link rel="localization" href="branding/brand.ftl"/>
   <link rel="localization" href="browser/branding/sync-brand.ftl"/>
   <link rel="localization" href="browser/preferences/preferences.ftl"/>
-  <link rel="localization" href="browser/preferences/containers.ftl"/>
 
   <!-- Links below are only used for search-l10n-ids into subdialogs -->
   <link rel="localization" href="browser/preferences/blocklists.ftl"/>
   <link rel="localization" href="browser/preferences/clearSiteData.ftl"/>
   <link rel="localization" href="browser/preferences/colors.ftl"/>
   <link rel="localization" href="browser/preferences/connection.ftl"/>
   <link rel="localization" href="browser/preferences/fonts.ftl"/>
   <link rel="localization" href="browser/preferences/languages.ftl"/>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -616,16 +616,18 @@ var gPrivacyPane = {
    * value of the private browsing auto-start preference.
    */
   updatePrivacyMicroControls() {
     // Set "Keep cookies until..." to "I close Nightly" and disable the setting
     // when we're in auto private mode (or reset it back otherwise).
     document.getElementById("keepCookiesUntil").value = this.readKeepCookiesUntil();
     this.readAcceptCookies();
 
+    let clearDataSettings = document.getElementById("clearDataSettings");
+
     if (document.getElementById("historyMode").value == "custom") {
       let disabled = Preferences.get("browser.privatebrowsing.autostart").value;
       this.dependentControls.forEach(function(aElement) {
         let control = document.getElementById(aElement);
         let preferenceId = control.getAttribute("preference");
         if (!preferenceId) {
           let dependentControlId = control.getAttribute("control");
           if (dependentControlId) {
@@ -633,30 +635,34 @@ var gPrivacyPane = {
             preferenceId = dependentControl.getAttribute("preference");
           }
         }
 
         let preference = preferenceId ? Preferences.get(preferenceId) : {};
         control.disabled = disabled || preference.locked;
       });
 
+      clearDataSettings.removeAttribute("hidden");
+
       // adjust the checked state of the sanitizeOnShutdown checkbox
       document.getElementById("alwaysClear").checked = disabled ? false :
         Preferences.get("privacy.sanitize.sanitizeOnShutdown").value;
 
       // adjust the checked state of the remember history checkboxes
       document.getElementById("rememberHistory").checked = disabled ? false :
         Preferences.get("places.history.enabled").value;
       document.getElementById("rememberForms").checked = disabled ? false :
         Preferences.get("browser.formfill.enable").value;
 
       if (!disabled) {
         // adjust the Settings button for sanitizeOnShutdown
         this._updateSanitizeSettingsButton();
       }
+    } else {
+      clearDataSettings.setAttribute("hidden", "true");
     }
   },
 
   // CLEAR PRIVATE DATA
 
   /*
    * Preferences:
    *
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -124,42 +124,42 @@
                       data-l10n-id="history-remember-search-option"
                       preference="browser.formfill.enable"/>
             <hbox id="clearDataBox"
                   align="center">
               <checkbox id="alwaysClear"
                         preference="privacy.sanitize.sanitizeOnShutdown"
                         data-l10n-id="history-clear-on-close-option"
                         flex="1" />
-              <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
-              <hbox>
-                <button id="clearDataSettings"
-                        class="accessory-button"
-                        data-l10n-id="history-clear-on-close-settings"
-                        searchkeywords="&clearDataSettings3.label;
-                                        &historySection.label;
-                                        &itemHistoryAndDownloads.label;
-                                        &itemCookies.label;
-                                        &itemActiveLogins.label;
-                                        &itemCache.label;
-                                        &itemFormSearchHistory.label;
-                                        &dataSection.label;
-                                        &itemSitePreferences.label;
-                                        &itemOfflineApps.label;"/>
-              </hbox>
             </hbox>
           </vbox>
         </vbox>
       </vbox>
     </deck>
-    <vbox align="end">
+    <vbox id="historyButtons" align="end">
       <button id="clearHistoryButton"
               class="accessory-button"
               icon="clear"
               data-l10n-id="history-clear-button"/>
+      <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
+      <hbox>
+        <button id="clearDataSettings"
+                class="accessory-button"
+                data-l10n-id="history-clear-on-close-settings"
+                searchkeywords="&clearDataSettings3.label;
+                                &historySection.label;
+                                &itemHistoryAndDownloads.label;
+                                &itemCookies.label;
+                                &itemActiveLogins.label;
+                                &itemCache.label;
+                                &itemFormSearchHistory.label;
+                                &dataSection.label;
+                                &itemSitePreferences.label;
+                                &itemOfflineApps.label;"/>
+      </hbox>
     </vbox>
   </hbox>
 </groupbox>
 
 <!-- Site Data -->
 <groupbox id="siteDataGroup" data-category="panePrivacy">
   <caption><label data-l10n-id="sitedata-header"/></caption>
 
--- a/browser/components/preferences/in-content/tests/browser_search_subdialogs_within_preferences_site_data.js
+++ b/browser/components/preferences/in-content/tests/browser_search_subdialogs_within_preferences_site_data.js
@@ -7,29 +7,29 @@ add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
 });
 
 /**
  * Test for searching for the "Settings - Site Data" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  await evaluateSearchResults("cookies", ["siteDataGroup", "historyGroup"]);
+  await evaluateSearchResults("cookies", ["siteDataGroup"]);
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  await evaluateSearchResults("site data", ["siteDataGroup", "historyGroup"]);
+  await evaluateSearchResults("site data", ["siteDataGroup"]);
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  await evaluateSearchResults("cache", ["siteDataGroup", "historyGroup"]);
+  await evaluateSearchResults("cache", ["siteDataGroup"]);
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   await evaluateSearchResults("third-party", "siteDataGroup");
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/locales/en-US/browser/preferences/containers.ftl
+++ b/browser/locales/en-US/browser/preferences/containers.ftl
@@ -39,29 +39,16 @@ containers-icon-label = Icon
 containers-color-label = Color
     .accesskey = o
     .style = { -containers-labels-style }
 
 containers-button-done =
     .label = Done
     .accesskey = D
 
-containers-remove-alert-title = Remove This Container?
-
-# Variables:
-#   $count (Number) - Number of tabs that will be closed.
-containers-remove-alert-msg =
-    { $count ->
-       *[one] If you remove this Container now, { $count } container tab will be closed. Are you sure you want to remove this Container?
-        [other] If you remove this Container now, { $count } container tabs will be closed. Are you sure you want to remove this Container?
-    }
-
-containers-remove-ok-button = Remove this Container
-containers-remove-cancel-button = Don’t remove this Container
-
 containers-color-blue =
     .label = Blue
 containers-color-turquoise =
     .label = Turquoise
 containers-color-green =
     .label = Green
 containers-color-yellow =
     .label = Yellow
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -170,16 +170,30 @@ containers-disable-alert-desc =
 
 containers-disable-alert-ok-button =
     { $tabCount ->
         [one] Close { $tabCount } Container Tab
        *[other] Close { $tabCount } Container Tabs
     }
 containers-disable-alert-cancel-button = Keep enabled
 
+containers-remove-alert-title = Remove This Container?
+
+# Variables:
+#   $count (Number) - Number of tabs that will be closed.
+containers-remove-alert-msg =
+    { $count ->
+        [one] If you remove this Container now, { $count } container tab will be closed. Are you sure you want to remove this Container?
+       *[other] If you remove this Container now, { $count } container tabs will be closed. Are you sure you want to remove this Container?
+    }
+
+containers-remove-ok-button = Remove this Container
+containers-remove-cancel-button = Don’t remove this Container
+
+
 ## General Section - Language & Appearance
 
 language-and-appearance-header = Language and Appearance
 
 fonts-and-colors-header = Fonts & Colors
 
 default-font = Default font
     .accesskey = D
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -380,16 +380,22 @@ button > hbox > label {
 #trackingProtectionAdvancedSettings {
   margin-inline-start: 15px;
 }
 
 #historyPane {
   margin-top: 4px;
 }
 
+#historyButtons {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}
+
 #acceptCookies {
   margin-top: 1.5em;
 }
 
 /* Collapse the non-active vboxes in decks to use only the height the
    active vbox needs */
 #historyPane:not([selectedIndex="1"]) > #historyDontRememberPane,
 #historyPane:not([selectedIndex="2"]) > #historyCustomPane,
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -120,16 +120,58 @@ public:
 
 private:
   RefPtr<ThreadSafeWorkerRef> mWorkerRef;
   nsACString& mOrigin;
   PrincipalInfo& mPrincipalInfo;
   ErrorResult& mRv;
 };
 
+class BCPostMessageRunnable final : public nsIRunnable,
+                                    public nsICancelableRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  BCPostMessageRunnable(BroadcastChannelChild* aActor,
+                        BroadcastChannelMessage* aData)
+    : mActor(aActor)
+    , mData(aData)
+  {
+    MOZ_ASSERT(mActor);
+  }
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(mActor);
+    if (mActor->IsActorDestroyed()) {
+      return NS_OK;
+    }
+
+    ClonedMessageData message;
+    mData->BuildClonedMessageDataForBackgroundChild(mActor->Manager(), message);
+    mActor->SendPostMessage(message);
+    return NS_OK;
+  }
+
+  nsresult Cancel() override
+  {
+    mActor = nullptr;
+    return NS_OK;
+  }
+
+private:
+  ~BCPostMessageRunnable() {}
+
+  RefPtr<BroadcastChannelChild> mActor;
+  RefPtr<BroadcastChannelMessage> mData;
+};
+
+NS_IMPL_ISUPPORTS(BCPostMessageRunnable, nsICancelableRunnable, nsIRunnable)
+
 class CloseRunnable final : public nsIRunnable,
                             public nsICancelableRunnable
 {
 public:
   NS_DECL_ISUPPORTS
 
   explicit CloseRunnable(BroadcastChannel* aBC)
     : mBC(aBC)
@@ -351,28 +393,45 @@ void
 BroadcastChannel::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                               ErrorResult& aRv)
 {
   if (mState != StateActive) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
+  PostMessageInternal(aCx, aMessage, aRv);
+}
+
+void
+BroadcastChannel::PostMessageInternal(JSContext* aCx,
+                                      JS::Handle<JS::Value> aMessage,
+                                      ErrorResult& aRv)
+{
   RefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
 
   data->Write(aCx, aMessage, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
+  PostMessageData(data);
+}
+
+void
+BroadcastChannel::PostMessageData(BroadcastChannelMessage* aData)
+{
   RemoveDocFromBFCache();
 
-  ClonedMessageData message;
-  data->BuildClonedMessageDataForBackgroundChild(mActor->Manager(), message);
-  mActor->SendPostMessage(message);
+  RefPtr<BCPostMessageRunnable> runnable =
+    new BCPostMessageRunnable(mActor, aData);
+
+  if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+    NS_WARNING("Failed to dispatch to the current thread!");
+  }
 }
 
 void
 BroadcastChannel::Close()
 {
   if (mState != StateActive) {
     return;
   }
--- a/dom/broadcastchannel/tests/test_event_listener_leaks.html
+++ b/dom/broadcastchannel/tests/test_event_listener_leaks.html
@@ -18,20 +18,20 @@
 // exercise the leak condition.
 let count = 0;
 async function useBroadcastChannel(contentWindow) {
   contentWindow.messageCount = 0;
 
   count += 1;
   const name = `test_event_listener_leaks-${count}`;
 
-  let bc = new contentWindow.BroadcastChannel(name);
   let outer = new BroadcastChannel(name);
   outer.postMessage('foo');
 
+  let bc = new contentWindow.BroadcastChannel(name);
   await new Promise(resolve => {
     bc.onmessage = e => {
       contentWindow.messageCount += 1;
       resolve();
     };
   });
 
   is(contentWindow.messageCount, 1, "message should be received");
--- a/image/AnimationFrameBuffer.cpp
+++ b/image/AnimationFrameBuffer.cpp
@@ -12,17 +12,16 @@ namespace image {
 AnimationFrameBuffer::AnimationFrameBuffer()
   : mThreshold(0)
   , mBatch(0)
   , mPending(0)
   , mAdvance(0)
   , mInsertIndex(0)
   , mGetIndex(0)
   , mSizeKnown(false)
-  , mRedecodeError(false)
 { }
 
 void
 AnimationFrameBuffer::Initialize(size_t aThreshold,
                                  size_t aBatch,
                                  size_t aStartFrame)
 {
   MOZ_ASSERT(mThreshold == 0);
@@ -67,26 +66,17 @@ AnimationFrameBuffer::Insert(RawAccessFr
   // We should only insert new frames if we actually asked for them.
   MOZ_ASSERT(mPending > 0);
 
   if (mSizeKnown) {
     // We only insert after the size is known if we are repeating the animation
     // and we did not keep all of the frames. Replace whatever is there
     // (probably an empty frame) with the new frame.
     MOZ_ASSERT(MayDiscard());
-
-    // The first decode produced fewer frames than the redecodes, presumably
-    // because it hit an out-of-memory error which later attempts avoided. Just
-    // stop the animation because we can't tell the image that we have more
-    // frames now.
-    if (mInsertIndex >= mFrames.Length()) {
-      mRedecodeError = true;
-      mPending = 0;
-      return false;
-    }
+    MOZ_ASSERT(mInsertIndex < mFrames.Length());
 
     if (mInsertIndex > 0) {
       MOZ_ASSERT(!mFrames[mInsertIndex]);
       mFrames[mInsertIndex] = Move(aFrame);
     }
   } else if (mInsertIndex == mFrames.Length()) {
     // We are still on the first pass of the animation decoding, so this is
     // the first time we have seen this frame.
@@ -132,33 +122,19 @@ AnimationFrameBuffer::Insert(RawAccessFr
     --mAdvance;
   }
   return continueDecoding;
 }
 
 bool
 AnimationFrameBuffer::MarkComplete()
 {
-  // We may have stopped decoding at a different point in the animation than we
-  // did previously. That means the decoder likely hit a new error, e.g. OOM.
-  // This will prevent us from advancing as well, because we are missing the
-  // required frames to blend.
-  //
-  // XXX(aosmond): In an ideal world, we would be generating full frames, and
-  // the consumer of our data doesn't care about our internal state. It simply
-  // knows about the first frame, the current frame, and how long to display the
-  // current frame.
-  if (NS_WARN_IF(mInsertIndex != mFrames.Length())) {
-    MOZ_ASSERT(mSizeKnown);
-    mRedecodeError = true;
-    mPending = 0;
-  }
-
   // We reached the end of the animation, the next frame we get, if we get
   // another, will be the first frame again.
+  MOZ_ASSERT(mInsertIndex == mFrames.Length());
   mInsertIndex = 0;
 
   // Since we only request advancing when we want to resume at a certain point
   // in the animation, we should never exceed the number of frames.
   MOZ_ASSERT(mAdvance == 0);
 
   if (!mSizeKnown) {
     // We just received the last frame in the animation. Compact the frame array
@@ -250,17 +226,17 @@ AnimationFrameBuffer::AdvanceInternal()
     if (mGetIndex > 1) {
       discard = Move(mFrames[mGetIndex - 1]);
     } else if (mGetIndex == 0) {
       MOZ_ASSERT(mSizeKnown && framesLength > 1);
       discard = Move(mFrames[framesLength - 1]);
     }
   }
 
-  if (!mRedecodeError && (!mSizeKnown || MayDiscard())) {
+  if (!mSizeKnown || MayDiscard()) {
     // Calculate how many frames we have requested ahead of the current frame.
     size_t buffered = mPending;
     if (mGetIndex > mInsertIndex) {
       // It wrapped around and we are decoding the beginning again before the
       // the display has finished the loop.
       MOZ_ASSERT(mSizeKnown);
       buffered += mInsertIndex + framesLength - mGetIndex - 1;
     } else {
@@ -295,30 +271,27 @@ AnimationFrameBuffer::Reset()
       mPending = 1;
     }
 
     // Either the decoder is still running, or we have enough frames already.
     // No need for us to restart it.
     return false;
   }
 
+  // If we are over the threshold, then we know we will have missing frames in
+  // our buffer. The easiest thing to do is to drop everything but the first
+  // frame and go back to the initial state.
+  bool restartDecoder = mPending == 0;
+  mInsertIndex = 0;
+  mPending = 2 * mBatch;
+
   // Discard all frames besides the first, because the decoder always expects
   // that when it re-inserts a frame, it is not present. (It doesn't re-insert
   // the first frame.)
   for (size_t i = 1; i < mFrames.Length(); ++i) {
     RawAccessFrameRef discard = Move(mFrames[i]);
   }
 
-  mInsertIndex = 0;
-
-  // If we hit an error after redecoding, we never want to restart decoding.
-  if (mRedecodeError) {
-    MOZ_ASSERT(mPending == 0);
-    return false;
-  }
-
-  bool restartDecoder = mPending == 0;
-  mPending = 2 * mBatch;
   return restartDecoder;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/AnimationFrameBuffer.h
+++ b/image/AnimationFrameBuffer.h
@@ -125,22 +125,16 @@ public:
   /**
    * @returns True if the frame buffer was ever marked as complete. This implies
    *          that the total number of frames is known and may be gotten from
    *          Frames().Length().
    */
   bool SizeKnown() const { return mSizeKnown; }
 
   /**
-   * @returns True if encountered an error during redecode which should cause
-   *          the caller to stop inserting frames.
-   */
-  bool HasRedecodeError() const { return mRedecodeError; }
-
-  /**
    * @returns The current frame index we have advanced to.
    */
   size_t Displayed() const { return mGetIndex; }
 
   /**
    * @returns Outstanding frames desired from the decoder.
    */
   size_t PendingDecode() const { return mPending; }
@@ -188,17 +182,14 @@ private:
   // The mFrames index in which to insert the next decoded frame.
   size_t mInsertIndex;
 
   // The mFrames index that we have advanced to.
   size_t mGetIndex;
 
   // True if the total number of frames is known.
   bool mSizeKnown;
-
-  // True if we encountered an error while redecoding.
-  bool mRedecodeError;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_AnimationFrameBuffer_h
--- a/image/AnimationSurfaceProvider.cpp
+++ b/image/AnimationSurfaceProvider.cpp
@@ -219,45 +219,40 @@ AnimationSurfaceProvider::Run()
       // failure or truncated data may mean that no new frame got produced.
       // Since we're not sure, rather than call CheckForNewFrameAtYield() here
       // we call CheckForNewFrameAtTerminalState(), which handles both of these
       // possibilities.
       bool continueDecoding = CheckForNewFrameAtTerminalState();
       FinishDecoding();
 
       // Even if it is the last frame, we may not have enough frames buffered
-      // ahead of the current. If we are shutting down, we want to ensure we
-      // release the thread as soon as possible. The animation may advance even
-      // during shutdown, which keeps us decoding, and thus blocking the decode
-      // pool during teardown.
-      if (!mDecoder || !continueDecoding ||
-          DecodePool::Singleton()->IsShuttingDown()) {
-        return;
+      // ahead of the current.
+      if (continueDecoding) {
+        MOZ_ASSERT(mDecoder);
+        continue;
       }
+
+      return;
     }
 
     // Notify for the progress we've made so far.
     if (mImage && mDecoder->HasProgress()) {
       NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
     }
 
     if (result == LexerResult(Yield::NEED_MORE_DATA)) {
       // We can't make any more progress right now. The decoder itself will ensure
       // that we get reenqueued when more data is available; just return for now.
       return;
     }
 
     // There's new output available - a new frame! Grab it. If we don't need any
-    // more for the moment we can break out of the loop. If we are shutting
-    // down, we want to ensure we release the thread as soon as possible. The
-    // animation may advance even during shutdown, which keeps us decoding, and
-    // thus blocking the decode pool during teardown.
+    // more for the moment we can break out of the loop.
     MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
-    if (!CheckForNewFrameAtYield() ||
-        DecodePool::Singleton()->IsShuttingDown()) {
+    if (!CheckForNewFrameAtYield()) {
       return;
     }
   }
 }
 
 bool
 AnimationSurfaceProvider::CheckForNewFrameAtYield()
 {
@@ -294,17 +289,20 @@ AnimationSurfaceProvider::CheckForNewFra
       justGotFirstFrame = true;
     }
   }
 
   if (justGotFirstFrame) {
     AnnounceSurfaceAvailable();
   }
 
-  return continueDecoding;
+  // If we are shutting down, we want to ensure we release the thread as soon
+  // as possible. The animation may advance even during shutdown, which keeps
+  // us decoding, and thus blocking the decode pool during teardown.
+  return continueDecoding && !DecodePool::Singleton()->IsShuttingDown();
 }
 
 bool
 AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
 {
   mDecodingMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mDecoder);
 
@@ -344,17 +342,20 @@ AnimationSurfaceProvider::CheckForNewFra
       justGotFirstFrame = true;
     }
   }
 
   if (justGotFirstFrame) {
     AnnounceSurfaceAvailable();
   }
 
-  return continueDecoding;
+  // If we are shutting down, we want to ensure we release the thread as soon
+  // as possible. The animation may advance even during shutdown, which keeps
+  // us decoding, and thus blocking the decode pool during teardown.
+  return continueDecoding && !DecodePool::Singleton()->IsShuttingDown();
 }
 
 void
 AnimationSurfaceProvider::AnnounceSurfaceAvailable()
 {
   mFramesMutex.AssertNotCurrentThreadOwns();
   MOZ_ASSERT(mImage);
 
@@ -372,25 +373,25 @@ AnimationSurfaceProvider::FinishDecoding
   mDecodingMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mDecoder);
 
   if (mImage) {
     // Send notifications.
     NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
   }
 
-  // Determine if we need to recreate the decoder, in case we are discarding
-  // frames and need to loop back to the beginning.
-  bool recreateDecoder;
+  // Destroy our decoder; we don't need it anymore.
+  bool mayDiscard;
   {
     MutexAutoLock lock(mFramesMutex);
-    recreateDecoder = !mFrames.HasRedecodeError() && mFrames.MayDiscard();
+    mayDiscard = mFrames.MayDiscard();
   }
 
-  if (recreateDecoder) {
+  if (mayDiscard) {
+    // Recreate the decoder so we can regenerate the frames again.
     mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
     MOZ_ASSERT(mDecoder);
   } else {
     mDecoder = nullptr;
   }
 
   // We don't need a reference to our image anymore, either, and we don't want
   // one. We may be stored in the surface cache for a long time after decoding
--- a/image/test/gtest/TestAnimationFrameBuffer.cpp
+++ b/image/test/gtest/TestAnimationFrameBuffer.cpp
@@ -138,17 +138,16 @@ TEST_F(ImageAnimationFrameBuffer, Finish
     EXPECT_FALSE(buffer.SizeKnown());
 
     if (i == 4) {
       EXPECT_EQ(size_t(15), buffer.PendingDecode());
       keepDecoding = buffer.MarkComplete();
       EXPECT_FALSE(keepDecoding);
       EXPECT_TRUE(buffer.SizeKnown());
       EXPECT_EQ(size_t(0), buffer.PendingDecode());
-      EXPECT_FALSE(buffer.HasRedecodeError());
     }
 
     EXPECT_FALSE(buffer.MayDiscard());
 
     DrawableFrameRef gotFrame = buffer.Get(i);
     EXPECT_EQ(frame.get(), gotFrame.get());
     ASSERT_EQ(i + 1, frames.Length());
     EXPECT_EQ(frame.get(), frames[i].get());
@@ -216,17 +215,16 @@ TEST_F(ImageAnimationFrameBuffer, Finish
   // Add the last frame.
   keepDecoding = buffer.Insert(CreateEmptyFrame());
   EXPECT_TRUE(keepDecoding);
   keepDecoding = buffer.MarkComplete();
   EXPECT_FALSE(keepDecoding);
   EXPECT_TRUE(buffer.SizeKnown());
   EXPECT_EQ(size_t(0), buffer.PendingDecode());
   EXPECT_EQ(size_t(5), frames.Length());
-  EXPECT_FALSE(buffer.HasRedecodeError());
 
   // Finish progressing through the animation.
   for ( ; i < frames.Length(); ++i) {
     DrawableFrameRef gotFrame = buffer.Get(i);
     EXPECT_TRUE(gotFrame);
     restartDecoder = buffer.AdvanceTo(i);
     EXPECT_FALSE(restartDecoder);
   }
@@ -327,17 +325,16 @@ TEST_F(ImageAnimationFrameBuffer, MayDis
   // frame will restart at the beginning.
   keepDecoding = buffer.Insert(CreateEmptyFrame());
   EXPECT_TRUE(keepDecoding);
   keepDecoding = buffer.MarkComplete();
   EXPECT_TRUE(keepDecoding);
   EXPECT_TRUE(buffer.SizeKnown());
   EXPECT_EQ(size_t(2), buffer.PendingDecode());
   EXPECT_EQ(size_t(10), frames.Length());
-  EXPECT_FALSE(buffer.HasRedecodeError());
 
   // Use remaining pending room. It shouldn't add new frames, only replace.
   do {
     keepDecoding = buffer.Insert(CreateEmptyFrame());
   } while (keepDecoding);
 
   EXPECT_EQ(size_t(0), buffer.PendingDecode());
   EXPECT_EQ(size_t(10), frames.Length());
@@ -511,164 +508,8 @@ TEST_F(ImageAnimationFrameBuffer, StartA
   // in the real scenario, the decoder thread is still running and it is easier
   // to let it insert its last frame than to coordinate quitting earlier.
   buffer.Reset();
   EXPECT_EQ(size_t(0), buffer.Displayed());
   EXPECT_EQ(size_t(1), buffer.PendingDecode());
   EXPECT_EQ(size_t(0), buffer.PendingAdvance());
 }
 
-TEST_F(ImageAnimationFrameBuffer, RedecodeMoreFrames)
-{
-  const size_t kThreshold = 5;
-  const size_t kBatch = 2;
-  AnimationFrameBuffer buffer;
-  buffer.Initialize(kThreshold, kBatch, 0);
-  const auto& frames = buffer.Frames();
-
-  // Add frames until we exceed the threshold.
-  bool keepDecoding;
-  bool restartDecoder;
-  size_t i = 0;
-  do {
-    keepDecoding = buffer.Insert(CreateEmptyFrame());
-    EXPECT_TRUE(keepDecoding);
-    if (i > 0) {
-      restartDecoder = buffer.AdvanceTo(i);
-      EXPECT_FALSE(restartDecoder);
-    }
-    ++i;
-  } while (!buffer.MayDiscard());
-
-  // Should have threshold + 1 frames, and still not complete.
-  EXPECT_EQ(size_t(6), frames.Length());
-  EXPECT_FALSE(buffer.SizeKnown());
-
-  // Now we lock in at 6 frames.
-  keepDecoding = buffer.MarkComplete();
-  EXPECT_TRUE(keepDecoding);
-  EXPECT_TRUE(buffer.SizeKnown());
-  EXPECT_FALSE(buffer.HasRedecodeError());
-
-  // Reinsert 6 frames first.
-  i = 0;
-  do {
-    keepDecoding = buffer.Insert(CreateEmptyFrame());
-    EXPECT_TRUE(keepDecoding);
-    restartDecoder = buffer.AdvanceTo(i);
-    EXPECT_FALSE(restartDecoder);
-    ++i;
-  } while (i < 6);
-
-  // We should now encounter an error and shutdown further decodes.
-  keepDecoding = buffer.Insert(CreateEmptyFrame());
-  EXPECT_FALSE(keepDecoding);
-  EXPECT_EQ(size_t(0), buffer.PendingDecode());
-  EXPECT_TRUE(buffer.HasRedecodeError());
-}
-
-TEST_F(ImageAnimationFrameBuffer, RedecodeFewerFrames)
-{
-  const size_t kThreshold = 5;
-  const size_t kBatch = 2;
-  AnimationFrameBuffer buffer;
-  buffer.Initialize(kThreshold, kBatch, 0);
-  const auto& frames = buffer.Frames();
-
-  // Add frames until we exceed the threshold.
-  bool keepDecoding;
-  bool restartDecoder;
-  size_t i = 0;
-  do {
-    keepDecoding = buffer.Insert(CreateEmptyFrame());
-    EXPECT_TRUE(keepDecoding);
-    if (i > 0) {
-      restartDecoder = buffer.AdvanceTo(i);
-      EXPECT_FALSE(restartDecoder);
-    }
-    ++i;
-  } while (!buffer.MayDiscard());
-
-  // Should have threshold + 1 frames, and still not complete.
-  EXPECT_EQ(size_t(6), frames.Length());
-  EXPECT_FALSE(buffer.SizeKnown());
-
-  // Now we lock in at 6 frames.
-  keepDecoding = buffer.MarkComplete();
-  EXPECT_TRUE(keepDecoding);
-  EXPECT_TRUE(buffer.SizeKnown());
-  EXPECT_FALSE(buffer.HasRedecodeError());
-
-  // Reinsert 5 frames before marking complete.
-  i = 0;
-  do {
-    keepDecoding = buffer.Insert(CreateEmptyFrame());
-    EXPECT_TRUE(keepDecoding);
-    restartDecoder = buffer.AdvanceTo(i);
-    EXPECT_FALSE(restartDecoder);
-    ++i;
-  } while (i < 5);
-
-  // We should now encounter an error and shutdown further decodes.
-  keepDecoding = buffer.MarkComplete();
-  EXPECT_FALSE(keepDecoding);
-  EXPECT_EQ(size_t(0), buffer.PendingDecode());
-  EXPECT_TRUE(buffer.HasRedecodeError());
-}
-
-TEST_F(ImageAnimationFrameBuffer, RedecodeFewerFramesAndBehindAdvancing)
-{
-  const size_t kThreshold = 5;
-  const size_t kBatch = 2;
-  AnimationFrameBuffer buffer;
-  buffer.Initialize(kThreshold, kBatch, 0);
-  const auto& frames = buffer.Frames();
-
-  // Add frames until we exceed the threshold.
-  bool keepDecoding;
-  bool restartDecoder;
-  size_t i = 0;
-  do {
-    keepDecoding = buffer.Insert(CreateEmptyFrame());
-    EXPECT_TRUE(keepDecoding);
-    if (i > 0) {
-      restartDecoder = buffer.AdvanceTo(i);
-      EXPECT_FALSE(restartDecoder);
-    }
-    ++i;
-  } while (!buffer.MayDiscard());
-
-  // Should have threshold + 1 frames, and still not complete.
-  EXPECT_EQ(size_t(6), frames.Length());
-  EXPECT_FALSE(buffer.SizeKnown());
-
-  // Now we lock in at 6 frames.
-  keepDecoding = buffer.MarkComplete();
-  EXPECT_TRUE(keepDecoding);
-  EXPECT_TRUE(buffer.SizeKnown());
-  EXPECT_FALSE(buffer.HasRedecodeError());
-
-  // Reinsert frames without advancing until we exhaust our pending space. This
-  // should be less than the current buffer length by definition.
-  i = 0;
-  do {
-    keepDecoding = buffer.Insert(CreateEmptyFrame());
-    ++i;
-  } while (keepDecoding);
-
-  EXPECT_EQ(size_t(2), i);
-
-  // We should now encounter an error and shutdown further decodes.
-  keepDecoding = buffer.MarkComplete();
-  EXPECT_FALSE(keepDecoding);
-  EXPECT_EQ(size_t(0), buffer.PendingDecode());
-  EXPECT_TRUE(buffer.HasRedecodeError());
-
-  // We should however be able to continue advancing to the last decoded frame
-  // without it requesting the decoder to restart.
-  i = 0;
-  do {
-    restartDecoder = buffer.AdvanceTo(i);
-    EXPECT_FALSE(restartDecoder);
-    ++i;
-  } while (i < 2);
-}
-
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1453800_container_removal.py
@@ -0,0 +1,56 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import EXTERNAL_ARGUMENT
+from fluent.migrate import COPY
+from fluent.migrate.transforms import PLURALS, REPLACE_IN_TEXT
+
+
+def migrate(ctx):
+    """Bug 1453800 - Migrate Container removal strings to Fluent, part {index}."""
+
+    ctx.add_transforms(
+        'browser/browser/preferences/preferences.ftl',
+        'browser/browser/preferences/preferences.ftl',
+        [
+            FTL.Message(
+                id=FTL.Identifier('containers-remove-alert-title'),
+                value=COPY(
+                    'browser/chrome/browser/preferences/preferences.properties',
+                    'removeContainerAlertTitle',
+                ),
+            ),
+            FTL.Message(
+                id=FTL.Identifier('containers-remove-alert-msg'),
+                value=PLURALS(
+                    'browser/chrome/browser/preferences/preferences.properties',
+                    'removeContainerMsg',
+                    EXTERNAL_ARGUMENT('count'),
+                    lambda text: REPLACE_IN_TEXT(
+                        text,
+                        {
+                            '#S': EXTERNAL_ARGUMENT('count')
+                        }
+                    )
+                )
+            ),
+            FTL.Message(
+                id=FTL.Identifier('containers-remove-ok-button'),
+                value=COPY(
+                    'browser/chrome/browser/preferences/preferences.properties',
+                    'removeContainerOkButton',
+                ),
+            ),
+            FTL.Message(
+                id=FTL.Identifier('containers-remove-cancel-button'),
+                value=COPY(
+                    'browser/chrome/browser/preferences/preferences.properties',
+                    'removeContainerButton2',
+                ),
+            ),
+        ]
+    )