merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 02 Dec 2016 09:23:52 +0100
changeset 325059 f65ad27efe839ce9df0283840a1a40b4bbc9ead0
parent 325031 b1e2d1fe6b070b71661047f94129a1ccd0a7f48f (current diff)
parent 325058 9ff9b1aa382fb49983785d9831b80100a36ecb71 (diff)
child 325060 c784f735d981bcb6eec019fd04eb3e66d413fdf3
child 325078 1451fb639925d5c59389aa5da22781a92bff2e69
child 325087 7156a06172f4f652d7bfc9240bb89891288aeabe
push id31026
push usercbook@mozilla.com
push dateFri, 02 Dec 2016 08:24:04 +0000
treeherdermozilla-central@f65ad27efe83 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone53.0a1
first release with
nightly linux32
f65ad27efe83 / 53.0a1 / 20161202030204 / files
nightly linux64
f65ad27efe83 / 53.0a1 / 20161202030204 / files
nightly mac
f65ad27efe83 / 53.0a1 / 20161202030204 / files
nightly win32
f65ad27efe83 / 53.0a1 / 20161202030204 / files
nightly win64
f65ad27efe83 / 53.0a1 / 20161202030204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
addon-sdk/source/test/leak/test-leak-event-chrome.js
testing/web-platform/meta/FileAPI/reading-data-section/FileReader-multiple-reads.html.ini
testing/web-platform/meta/fetch/api/redirect/redirect-count.html.ini
--- a/addon-sdk/source/lib/sdk/event/chrome.js
+++ b/addon-sdk/source/lib/sdk/event/chrome.js
@@ -52,14 +52,14 @@ function observe(topic) {
   // observerChannel (since third argument is `true`). There for if it
   // will be GC-ed with all it's event listeners once no other references
   // will be held.
   addObserver(observerChannel, topic, true);
 
   // We need to remove any observer added once the add-on is unloaded;
   // otherwise we'll get a "dead object" exception.
   // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833
-  unload(() => removeObserver(observerChannel, topic), { weak: true });
+  unload(() => removeObserver(observerChannel, topic));
 
   return observerChannel;
 }
 
 exports.observe = observe;
--- a/addon-sdk/source/test/leak/jetpack-package.ini
+++ b/addon-sdk/source/test/leak/jetpack-package.ini
@@ -1,8 +1,7 @@
 [DEFAULT]
 support-files =
   leak-utils.js
 
 [test-leak-window-events.js]
 [test-leak-event-dom-closed-window.js]
 [test-leak-tab-events.js]
-[test-leak-event-chrome.js]
--- a/addon-sdk/source/test/leak/leak-utils.js
+++ b/addon-sdk/source/test/leak/leak-utils.js
@@ -26,17 +26,16 @@ function gc() {
           resolve();
         }
       }
     }
 
     Cu.schedulePreciseGC(genGCCallback());
   });
 }
-exports.gc = gc;
 
 // Execute the given test function and verify that we did not leak windows
 // in the process.  The test function must return a promise or be a generator.
 // If the promise is resolved, or generator completes, with an sdk loader
 // object then it will be unloaded after the memory measurements.
 exports.asyncWindowLeakTest = function*(assert, asyncTestFunc) {
 
   // SelfSupportBackend periodically tries to open windows.  This can
deleted file mode 100644
--- a/addon-sdk/source/test/leak/test-leak-event-chrome.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-'use strict';
-
-const { gc } = require("./leak-utils");
-const { Loader } = require("sdk/test/loader");
-const { Cu } = require("chrome");
-const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-
-exports["test sdk/event/chrome does not leak when not referenced"] = function*(assert) {
-  let loader = Loader(module);
-  let { observe } = loader.require("sdk/event/chrome");
-  let { on } = loader.require("sdk/event/core");
-
-  let gotFooEvent = false;
-  on(observe("test-foo"), "data", function(evt) {
-    gotFooEvent = true;
-  });
-
-  let bar = observe("test-bar");
-  let barPromise = new Promise(resolve => {
-    on(bar, "data", function(evt) {
-      assert.ok(!gotFooEvent, "should not have gotten test-foo event");
-      resolve();
-    });
-  });
-
-  // This should clear the test-foo observer channel because we are not
-  // holding a reference to it above.
-  yield gc();
-
-  Services.obs.notifyObservers(null, "test-foo", null);
-  Services.obs.notifyObservers(null, "test-bar", null);
-
-  yield barPromise;
-
-  loader.unload();
-}
-
-require("sdk/test").run(exports);
--- a/browser/base/content/test/general/browser_web_channel.js
+++ b/browser/base/content/test/general/browser_web_channel.js
@@ -32,16 +32,36 @@ var gTests = [
           resolve();
         });
 
         tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?generic");
       });
     }
   },
   {
+    desc: "WebChannel generic message in a private window.",
+    run: function* () {
+      let promiseTestDone = new Promise(function(resolve, reject) {
+        let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null));
+        channel.listen(function(id, message, target) {
+          is(id, "generic");
+          is(message.something.nested, "hello");
+          channel.stopListening();
+          resolve();
+        });
+      });
+
+      const url = HTTP_PATH + HTTP_ENDPOINT + "?generic";
+      let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+      yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, url);
+      yield promiseTestDone;
+      yield BrowserTestUtils.closeWindow(privateWindow);
+    }
+  },
+  {
     desc: "WebChannel two way communication",
     run: function* () {
       return new Promise(function(resolve, reject) {
         let tab;
         let channel = new WebChannel("twoway", Services.io.newURI(HTTP_PATH, null, null));
 
         channel.listen(function(id, message, sender) {
           is(id, "twoway", "bad id");
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -5066,19 +5066,20 @@ nsDocument::UnblockDOMContentLoaded()
   MOZ_ASSERT(mBlockDOMContentLoaded);
   if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
     return;
   }
   mDidFireDOMContentLoaded = true;
 
   MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
   if (!mSynchronousDOMContentLoaded) {
+    MOZ_RELEASE_ASSERT(NS_IsMainThread());
     nsCOMPtr<nsIRunnable> ev =
       NewRunnableMethod(this, &nsDocument::DispatchContentLoadedEvents);
-    NS_DispatchToCurrentThread(ev);
+    Dispatch("DispatchContentLoadedEvents", TaskCategory::Other, ev.forget());
   } else {
     DispatchContentLoadedEvents();
   }
 }
 
 void
 nsDocument::ContentStateChanged(nsIContent* aContent, EventStates aStateMask)
 {
@@ -11839,17 +11840,17 @@ nsDocument::GetVisibilityState() const
   return dom::VisibilityState::Visible;
 }
 
 /* virtual */ void
 nsDocument::PostVisibilityUpdateEvent()
 {
   nsCOMPtr<nsIRunnable> event =
     NewRunnableMethod(this, &nsDocument::UpdateVisibilityState);
-  NS_DispatchToMainThread(event);
+  Dispatch("UpdateVisibility", TaskCategory::Other, event.forget());
 }
 
 void
 nsDocument::MaybeActiveMediaComponents()
 {
   if (mEverInForeground) {
     return;
   }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8667,17 +8667,17 @@ nsGlobalWindow::PostMessageMozOuter(JSCo
   JS::Rooted<JS::Value> message(aCx, aMessage);
   JS::Rooted<JS::Value> transfer(aCx, aTransfer);
 
   event->Write(aCx, message, transfer, JS::CloneDataPolicy(), aError);
   if (NS_WARN_IF(aError.Failed())) {
     return;
   }
 
-  aError = NS_DispatchToCurrentThread(event);
+  aError = Dispatch("PostMessage", TaskCategory::Other, event.forget());
 }
 
 void
 nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                                const nsAString& aTargetOrigin,
                                JS::Handle<JS::Value> aTransfer,
                                nsIPrincipal& aSubjectPrincipal,
                                ErrorResult& aError)
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -12,18 +12,19 @@
 
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "xpcpublic.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIContent.h"
 #include "nsJSUtils.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
-#include "mozilla/dom/Element.h"
 #include "mozilla/dom/SRILogHelper.h"
 #include "nsGkAtoms.h"
 #include "nsNetUtil.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsJSPrincipals.h"
@@ -1713,31 +1714,43 @@ nsScriptLoader::ProcessScriptElement(nsI
 }
 
 namespace {
 
 class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable
 {
   RefPtr<nsScriptLoadRequest> mRequest;
   RefPtr<nsScriptLoader> mLoader;
+  RefPtr<DocGroup> mDocGroup;
   void *mToken;
 
 public:
   NotifyOffThreadScriptLoadCompletedRunnable(nsScriptLoadRequest* aRequest,
                                              nsScriptLoader* aLoader)
-    : mRequest(aRequest), mLoader(aLoader), mToken(nullptr)
-  {}
+    : mRequest(aRequest)
+    , mLoader(aLoader)
+    , mDocGroup(aLoader->GetDocGroup())
+    , mToken(nullptr)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
 
   virtual ~NotifyOffThreadScriptLoadCompletedRunnable();
 
   void SetToken(void* aToken) {
     MOZ_ASSERT(aToken && !mToken);
     mToken = aToken;
   }
 
+  static void Dispatch(already_AddRefed<NotifyOffThreadScriptLoadCompletedRunnable>&& aSelf) {
+    RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> self = aSelf;
+    RefPtr<DocGroup> docGroup = self->mDocGroup;
+    docGroup->Dispatch("OffThreadScriptLoader", TaskCategory::Other, self.forget());
+  }
+
   NS_DECL_NSIRUNNABLE
 };
 
 } /* anonymous namespace */
 
 nsresult
 nsScriptLoader::ProcessOffThreadRequest(nsScriptLoadRequest* aRequest)
 {
@@ -1805,17 +1818,17 @@ NotifyOffThreadScriptLoadCompletedRunnab
 }
 
 static void
 OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
 {
   RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable =
     dont_AddRef(static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
   aRunnable->SetToken(aToken);
-  NS_DispatchToMainThread(aRunnable);
+  NotifyOffThreadScriptLoadCompletedRunnable::Dispatch(aRunnable.forget());
 }
 
 nsresult
 nsScriptLoader::AttemptAsyncScriptCompile(nsScriptLoadRequest* aRequest)
 {
   MOZ_ASSERT_IF(!aRequest->IsModuleRequest(), aRequest->IsReadyToRun());
   MOZ_ASSERT(!aRequest->mWasCompiledOMT);
 
@@ -2204,18 +2217,23 @@ void
 nsScriptLoader::ProcessPendingRequestsAsync()
 {
   if (mParserBlockingRequest ||
       !mXSLTRequests.isEmpty() ||
       !mLoadedAsyncRequests.isEmpty() ||
       !mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
       !mDeferRequests.isEmpty() ||
       !mPendingChildLoaders.IsEmpty()) {
-    NS_DispatchToCurrentThread(NewRunnableMethod(this,
-                                                 &nsScriptLoader::ProcessPendingRequests));
+    nsCOMPtr<nsIRunnable> task = NewRunnableMethod(this,
+                                                   &nsScriptLoader::ProcessPendingRequests);
+    if (mDocument) {
+      mDocument->Dispatch("ScriptLoader", TaskCategory::Other, task.forget());
+    } else {
+      NS_DispatchToCurrentThread(task.forget());
+    }
   }
 }
 
 void
 nsScriptLoader::ProcessPendingRequests()
 {
   RefPtr<nsScriptLoadRequest> request;
 
--- a/dom/base/nsScriptLoader.h
+++ b/dom/base/nsScriptLoader.h
@@ -464,16 +464,21 @@ public:
    * off thread.
    */
   nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest);
 
   bool AddPendingChildLoader(nsScriptLoader* aChild) {
     return mPendingChildLoaders.AppendElement(aChild) != nullptr;
   }
 
+  mozilla::dom::DocGroup* GetDocGroup() const
+  {
+    return mDocument->GetDocGroup();
+  }
+
 private:
   virtual ~nsScriptLoader();
 
   nsScriptLoadRequest* CreateLoadRequest(
     nsScriptKind aKind,
     nsIScriptElement* aElement,
     uint32_t aVersion,
     mozilla::CORSMode aCORSMode,
--- a/dom/file/FileReader.cpp
+++ b/dom/file/FileReader.cpp
@@ -344,31 +344,28 @@ FileReader::DoReadData(uint64_t aCount)
 // Helper methods
 
 void
 FileReader::ReadFileContent(Blob& aBlob,
                             const nsAString &aCharset,
                             eDataFormat aDataFormat,
                             ErrorResult& aRv)
 {
-  //Implicit abort to clear any other activity going on
-  ErrorResult error;
-  Abort(error);
-  error.SuppressException();
-
   if (mReadyState == LOADING) {
-    // A nested ReadAsSomething() as been called during one of the events
-    // dispatched by Abort(). We have to terminate this operation in order to
-    // continue the nested one.
-    aRv.Throw(NS_ERROR_ABORT);
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   mError = nullptr;
+
   SetDOMStringToNull(mResult);
+  mResultArrayBuffer = nullptr;
+
+  mAsyncStream = nullptr;
+
   mTransferred = 0;
   mTotal = 0;
   mReadyState = EMPTY;
   FreeFileData();
 
   mBlob = &aBlob;
   mDataFormat = aDataFormat;
   CopyUTF16toUTF8(aCharset, mCharset);
--- a/dom/file/tests/test_fileapi.html
+++ b/dom/file/tests/test_fileapi.html
@@ -351,28 +351,36 @@ function onFilesOpened(message) {
   r = new FileReader();
   r.onabort = function (event) {
     is(reuseAbortHasRun, false, "abort should only fire once");
     reuseAbortHasRun = true;
     is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
     is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
     is(event.target.result, null, "file data should be null on aborted reads");
   }
-  r.onload = function() { ok(false, "load should not fire for aborted reads") };
+  r.onload = function() { ok(false, "load should fire for nested reads"); };
+
   var abortThrew = false;
   try {
     r.abort();
   } catch(e) {
     abortThrew = true;
   }
   is(abortThrew, true, "abort() must throw if not loading");
   is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
   r.readAsText(asciiFile);
-  r.readAsText(asciiFile);
-  is(reuseAbortHasRun, true, "abort should fire sync");
+
+  var readThrew = false;
+  try {
+    r.readAsText(asciiFile);
+  } catch(e) {
+    readThrew = true;
+  }
+  is(readThrew, true, "readAsText() must throw if loading");
+  is(reuseAbortHasRun, false, "abort should not fire");
   r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
   expectedTestCount++;
 
 
   // Test reading from nonexistent files
   r = new FileReader();
   var didThrow = false;
   r.onerror = function (event) {
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -5653,17 +5653,19 @@ nsresult HTMLMediaElement::DispatchAsync
   // Save events that occur while in the bfcache. These will be dispatched
   // if the page comes out of the bfcache.
   if (mEventDeliveryPaused) {
     mPendingEvents.AppendElement(aName);
     return NS_OK;
   }
 
   nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
-  NS_DispatchToMainThread(event);
+  OwnerDoc()->Dispatch("HTMLMediaElement::DispatchAsyncEvent",
+                       TaskCategory::Other,
+                       event.forget());
 
   if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
     mPlayTime.Start();
     if (IsHidden()) {
       HiddenVideoStart();
     }
   } else if (aName.EqualsLiteral("waiting")) {
     mPlayTime.Pause();
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -561,16 +561,22 @@ MessagePort::Dispatch()
       break;
   }
 
   RefPtr<SharedMessagePortMessage> data = mMessages.ElementAt(0);
   mMessages.RemoveElementAt(0);
 
   mPostMessageRunnable = new PostMessageRunnable(this, data);
 
+  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+  if (NS_IsMainThread() && global) {
+    MOZ_ALWAYS_SUCCEEDS(global->Dispatch("MessagePortMessage", TaskCategory::Other, do_AddRef(mPostMessageRunnable)));
+    return;
+  }
+
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mPostMessageRunnable));
 }
 
 void
 MessagePort::Close()
 {
   CloseInternal(true /* aSoftly */);
 }
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -2751,16 +2751,29 @@ PluginModuleParent::NPP_NewInternal(NPMI
     }
 
     // Release the surrogate reference that was in pdata
     RefPtr<PluginAsyncSurrogate> surrogate(
         dont_AddRef(PluginAsyncSurrogate::Cast(instance)));
     // Now replace it with the instance
     instance->pdata = static_cast<PluginDataResolver*>(parentInstance);
 
+    // Any IPC messages for the PluginInstance actor should be dispatched to the
+    // DocGroup for the plugin's document.
+    RefPtr<nsPluginInstanceOwner> owner = parentInstance->GetOwner();
+    nsCOMPtr<nsIDOMElement> elt;
+    owner->GetDOMElement(getter_AddRefs(elt));
+    if (nsCOMPtr<nsINode> node = do_QueryInterface(elt)) {
+        nsCOMPtr<nsIDocument> doc = node->OwnerDoc();
+        if (doc) {
+            nsCOMPtr<nsIEventTarget> eventTarget = doc->EventTargetFor(dom::TaskCategory::Other);
+            SetEventTargetForActor(parentInstance, eventTarget);
+        }
+    }
+
     if (!SendPPluginInstanceConstructor(parentInstance,
                                         nsDependentCString(pluginType), mode,
                                         names, values)) {
         // |parentInstance| is automatically deleted.
         instance->pdata = nullptr;
         *error = NPERR_GENERIC_ERROR;
         return NS_ERROR_FAILURE;
     }
--- a/dom/smil/nsSMILTimeContainer.cpp
+++ b/dom/smil/nsSMILTimeContainer.cpp
@@ -15,17 +15,19 @@ nsSMILTimeContainer::nsSMILTimeContainer
 :
   mParent(nullptr),
   mCurrentTime(0L),
   mParentOffset(0L),
   mPauseStart(0L),
   mNeedsPauseSample(false),
   mNeedsRewind(false),
   mIsSeeking(false),
+#ifdef DEBUG
   mHoldingEntries(false),
+#endif
   mPauseState(PAUSE_BEGIN)
 {
 }
 
 nsSMILTimeContainer::~nsSMILTimeContainer()
 {
   if (mParent) {
     mParent->RemoveChild(*this);
@@ -211,24 +213,24 @@ nsSMILTimeContainer::SetParent(nsSMILTim
 bool
 nsSMILTimeContainer::AddMilestone(const nsSMILMilestone& aMilestone,
                                   mozilla::dom::SVGAnimationElement& aElement)
 {
   // We record the milestone time and store it along with the element but this
   // time may change (e.g. if attributes are changed on the timed element in
   // between samples). If this happens, then we may do an unecessary sample
   // but that's pretty cheap.
-  MOZ_RELEASE_ASSERT(!mHoldingEntries);
+  MOZ_ASSERT(!mHoldingEntries);
   return mMilestoneEntries.Push(MilestoneEntry(aMilestone, aElement));
 }
 
 void
 nsSMILTimeContainer::ClearMilestones()
 {
-  MOZ_RELEASE_ASSERT(!mHoldingEntries);
+  MOZ_ASSERT(!mHoldingEntries);
   mMilestoneEntries.Clear();
 }
 
 bool
 nsSMILTimeContainer::GetNextMilestoneInParentTime(
     nsSMILMilestone& aNextMilestone) const
 {
   if (mMilestoneEntries.IsEmpty())
@@ -259,46 +261,48 @@ nsSMILTimeContainer::PopMilestoneElement
 
   nsSMILMilestone containerMilestone(containerTime.GetMillis(),
                                      aMilestone.mIsEnd);
 
   MOZ_ASSERT(mMilestoneEntries.Top().mMilestone >= containerMilestone,
              "Trying to pop off earliest times but we have earlier ones that "
              "were overlooked");
 
-  MOZ_RELEASE_ASSERT(!mHoldingEntries);
+  MOZ_ASSERT(!mHoldingEntries);
 
   bool gotOne = false;
   while (!mMilestoneEntries.IsEmpty() &&
       mMilestoneEntries.Top().mMilestone == containerMilestone)
   {
     aMatchedElements.AppendElement(mMilestoneEntries.Pop().mTimebase);
     gotOne = true;
   }
 
   return gotOne;
 }
 
 void
 nsSMILTimeContainer::Traverse(nsCycleCollectionTraversalCallback* aCallback)
 {
+#ifdef DEBUG
   AutoRestore<bool> saveHolding(mHoldingEntries);
   mHoldingEntries = true;
+#endif
   const MilestoneEntry* p = mMilestoneEntries.Elements();
   while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mTimebase");
     aCallback->NoteXPCOMChild(static_cast<nsIContent*>(p->mTimebase.get()));
     ++p;
   }
 }
 
 void
 nsSMILTimeContainer::Unlink()
 {
-  MOZ_RELEASE_ASSERT(!mHoldingEntries);
+  MOZ_ASSERT(!mHoldingEntries);
   mMilestoneEntries.Clear();
 }
 
 void
 nsSMILTimeContainer::UpdateCurrentTime()
 {
   nsSMILTime now = IsPaused() ? mPauseStart : GetParentTime();
   mCurrentTime = now - mParentOffset;
@@ -312,23 +316,30 @@ nsSMILTimeContainer::NotifyTimeChange()
   // time. When this happens time dependencies in other time containers need to
   // re-resolve their times because begin and end times are stored in container
   // time.
   //
   // To get the list of timed elements with dependencies we simply re-use the
   // milestone elements. This is because any timed element with dependents and
   // with significant transitions yet to fire should have their next milestone
   // registered. Other timed elements don't matter.
-  AutoRestore<bool> saveHolding(mHoldingEntries);
-  mHoldingEntries = true;
-  const MilestoneEntry* p = mMilestoneEntries.Elements();
-#if DEBUG
-  uint32_t queueLength = mMilestoneEntries.Length();
+
+  // Copy the timed elements to a separate array before calling
+  // HandleContainerTimeChange on each of them in case doing so mutates
+  // mMilestoneEntries.
+  nsTArray<RefPtr<mozilla::dom::SVGAnimationElement>> elems;
+
+  {
+#ifdef DEBUG
+    AutoRestore<bool> saveHolding(mHoldingEntries);
+    mHoldingEntries = true;
 #endif
-  while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) {
-    mozilla::dom::SVGAnimationElement* elem = p->mTimebase.get();
+    for (const MilestoneEntry* p = mMilestoneEntries.Elements();
+        p < mMilestoneEntries.Elements() + mMilestoneEntries.Length();
+        ++p) {
+      elems.AppendElement(p->mTimebase.get());
+    }
+  }
+
+  for (auto& elem : elems) {
     elem->TimedElement().HandleContainerTimeChange();
-    MOZ_ASSERT(queueLength == mMilestoneEntries.Length(),
-               "Call to HandleContainerTimeChange resulted in a change to the "
-               "queue of milestones");
-    ++p;
   }
 }
--- a/dom/smil/nsSMILTimeContainer.h
+++ b/dom/smil/nsSMILTimeContainer.h
@@ -261,17 +261,20 @@ protected:
   nsSMILTime mPauseStart;
 
   // Whether or not a pause sample is required
   bool mNeedsPauseSample;
 
   bool mNeedsRewind; // Backwards seek performed
   bool mIsSeeking; // Currently in the middle of a seek operation
 
-  bool mHoldingEntries; // True if there's a raw pointer to mMilestoneEntries on the stack.
+#ifdef DEBUG
+  bool mHoldingEntries; // True if there's a raw pointer to mMilestoneEntries
+                        // on the stack.
+#endif
 
   // A bitfield of the pause state for all pause requests
   uint32_t mPauseState;
 
   struct MilestoneEntry
   {
     MilestoneEntry(nsSMILMilestone aMilestone,
                    mozilla::dom::SVGAnimationElement& aElement)
--- a/dom/workers/ServiceWorkerClients.cpp
+++ b/dom/workers/ServiceWorkerClients.cpp
@@ -500,17 +500,17 @@ public:
     MutexAutoLock lock(mPromiseProxy->Lock());
     if (mPromiseProxy->CleanedUp()) {
       return NS_OK;
     }
 
 #ifdef MOZ_WIDGET_ANDROID
     // This fires an intent that will start launching Fennec and foreground it,
     // if necessary.
-    java::GeckoAppShell::OpenWindowForNotification();
+    java::GeckoAppShell::LaunchOrBringToFront();
 #endif
 
     nsCOMPtr<nsPIDOMWindowOuter> window;
     nsresult rv = OpenWindow(getter_AddRefs(window));
     if (NS_SUCCEEDED(rv)) {
       MOZ_ASSERT(window);
 
       rv = nsContentUtils::DispatchFocusChromeEvent(window);
--- a/dom/workers/test/worker_fileReader.js
+++ b/dom/workers/test/worker_fileReader.js
@@ -299,41 +299,43 @@ onmessage = function(message) {
     r.abort();
   } catch(e) {
     abortThrew = true;
   }
   is(abortThrew, true, "abort() must throw if not loading");
   is(abortHasRun, false, "abort() is a no-op unless loading");
   r.readAsText(asciiFile);
   r.abort();
-  is(abortHasRun, true, "abort should fire sync");
+  is(abortHasRun, true, "1 abort should fire sync");
   is(loadEndHasRun, true, "loadend should fire sync");
 
   // Test calling readAsX to cause abort()
   var reuseAbortHasRun = false;
   r = new FileReader();
-  r.onabort = function (event) {
-    is(reuseAbortHasRun, false, "abort should only fire once");
-    reuseAbortHasRun = true;
-    is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
-    is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
-    is(event.target.result, null, "file data should be null on aborted reads");
-  }
-  r.onload = function() { ok(false, "load should not fire for aborted reads") };
+  r.onabort = function (event) { reuseAbortHasRun = true; }
+  r.onload = function() { ok(true, "load should fire for aborted reads") };
   var abortThrew = false;
   try {
     r.abort();
   } catch(e) {
     abortThrew = true;
   }
   is(abortThrew, true, "abort() must throw if not loading");
   is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
   r.readAsText(asciiFile);
+
+  var readThrew = false;
+  try {
   r.readAsText(asciiFile);
-  is(reuseAbortHasRun, true, "abort should fire sync");
+  } catch(e) {
+    readThrew = true;
+  }
+
+  is(readThrew, true, "readAsText() must throw if loading");
+  is(reuseAbortHasRun, false, "2 abort should fire sync");
   r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
   expectedTestCount++;
 
 
   // Test reading from nonexistent files
   r = new FileReader();
   var didThrow = false;
   r.onerror = function (event) {
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -8,16 +8,17 @@
 
 #include <algorithm>
 #ifndef XP_WIN
 #include <unistd.h>
 #endif
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/BlobSet.h"
+#include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/MutableBlobStorage.h"
 #include "mozilla/dom/XMLDocument.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
@@ -3144,16 +3145,25 @@ XMLHttpRequestMainThread::SetTimeout(uin
 
   mTimeoutMilliseconds = aTimeout;
   if (mRequestSentTime) {
     StartTimeoutTimer();
   }
 }
 
 void
+XMLHttpRequestMainThread::SetTimerEventTarget(nsITimer* aTimer)
+{
+  if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
+    nsCOMPtr<nsIEventTarget> target = global->EventTargetFor(TaskCategory::Other);
+    aTimer->SetTarget(target);
+  }
+}
+
+void
 XMLHttpRequestMainThread::StartTimeoutTimer()
 {
   MOZ_ASSERT(mRequestSentTime,
              "StartTimeoutTimer mustn't be called before the request was sent!");
   if (mState == State::done) {
     // do nothing!
     return;
   }
@@ -3163,16 +3173,17 @@ XMLHttpRequestMainThread::StartTimeoutTi
   }
 
   if (!mTimeoutMilliseconds) {
     return;
   }
 
   if (!mTimeoutTimer) {
     mTimeoutTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    SetTimerEventTarget(mTimeoutTimer);
   }
   uint32_t elapsed =
     (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
   mTimeoutTimer->InitWithCallback(
     this,
     mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
     nsITimer::TYPE_ONE_SHOT
   );
@@ -3638,16 +3649,17 @@ XMLHttpRequestMainThread::StopProgressEv
   }
 }
 
 void
 XMLHttpRequestMainThread::StartProgressEventTimer()
 {
   if (!mProgressNotifier) {
     mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID);
+    SetTimerEventTarget(mProgressNotifier);
   }
   if (mProgressNotifier) {
     mProgressTimerIsActive = true;
     mProgressNotifier->Cancel();
     mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
                                         nsITimer::TYPE_ONE_SHOT);
   }
 }
@@ -3664,16 +3676,17 @@ XMLHttpRequestMainThread::MaybeStartSync
 
   // If we are in a beforeunload or a unload event, we must force a timeout.
   TimeDuration diff = (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
   if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
     return eErrorOrExpired;
   }
 
   mSyncTimeoutTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+  SetTimerEventTarget(mSyncTimeoutTimer);
   if (!mSyncTimeoutTimer) {
     return eErrorOrExpired;
   }
 
   uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
   nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
                                                     nsITimer::TYPE_ONE_SHOT);
   return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
@@ -3800,16 +3813,29 @@ XMLHttpRequestMainThread::BlobStoreCompl
   mLoadTotal = mResponseBlob->GetSize(rv);
   if (NS_WARN_IF(rv.Failed())) {
     rv.SuppressException();
   }
 
   ChangeStateToDone();
 }
 
+nsresult
+XMLHttpRequestMainThread::GetName(nsACString& aName)
+{
+  aName.AssignLiteral("XMLHttpRequest");
+  return NS_OK;
+}
+
+nsresult
+XMLHttpRequestMainThread::SetName(const char* aName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 // nsXMLHttpRequestXPCOMifier implementation
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
   NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -159,16 +159,17 @@ class XMLHttpRequestMainThread final : p
                                        public nsIJSXMLHttpRequest,
                                        public nsIStreamListener,
                                        public nsIChannelEventSink,
                                        public nsIProgressEventSink,
                                        public nsIInterfaceRequestor,
                                        public nsSupportsWeakReference,
                                        public nsITimerCallback,
                                        public nsISizeOfEventTarget,
+                                       public nsINamed,
                                        public MutableBlobStorageCallback
 {
   friend class nsXHRParseEndListener;
   friend class nsXMLHttpRequestXPCOMifier;
 
 public:
   enum class ProgressEventType : uint8_t {
     loadstart,
@@ -225,16 +226,19 @@ public:
   NS_DECL_NSIPROGRESSEVENTSINK
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // nsITimerCallback
   NS_DECL_NSITIMERCALLBACK
 
+  // nsINamed
+  NS_DECL_NSINAMED
+
   // nsISizeOfEventTarget
   virtual size_t
     SizeOfEventTargetIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(XMLHttpRequestEventTarget)
 
   // states
   virtual uint16_t ReadyState() const override;
@@ -588,16 +592,18 @@ protected:
 
   void StartProgressEventTimer();
   void StopProgressEventTimer();
 
   void MaybeCreateBlobStorage();
 
   nsresult OnRedirectVerifyCallback(nsresult result);
 
+  void SetTimerEventTarget(nsITimer* aTimer);
+
   already_AddRefed<nsXMLHttpRequestXPCOMifier> EnsureXPCOMifier();
 
   nsCOMPtr<nsISupports> mContext;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIChannel> mChannel;
   nsCString mRequestMethod;
   nsCOMPtr<nsIURI> mRequestURL;
   nsCOMPtr<nsIDocument> mResponseXML;
--- a/gfx/2d/DrawEventRecorder.cpp
+++ b/gfx/2d/DrawEventRecorder.cpp
@@ -31,18 +31,17 @@ DrawEventRecorderPrivate::RecordEvent(co
   WriteElement(*mOutputStream, aEvent.mType);
 
   aEvent.RecordToStream(*mOutputStream);
 
   Flush();
 }
 
 DrawEventRecorderFile::DrawEventRecorderFile(const char *aFilename)
-  : DrawEventRecorderPrivate(nullptr)
-  , mOutputFilename(aFilename)
+  : DrawEventRecorderPrivate(nullptr) 
   , mOutputFile(aFilename, ofstream::binary)
 {
   mOutputStream = &mOutputFile;
 
   WriteHeader();
 }
 
 DrawEventRecorderFile::~DrawEventRecorderFile()
@@ -51,35 +50,16 @@ DrawEventRecorderFile::~DrawEventRecorde
 }
 
 void
 DrawEventRecorderFile::Flush()
 {
   mOutputFile.flush();
 }
 
-void
-DrawEventRecorderFile::OpenAndTruncate()
-{
-  if (mOutputFile.is_open()) {
-    return;
-  }
-
-  mOutputFile.open(mOutputFilename.c_str(), ofstream::binary | ofstream::trunc);
-  WriteHeader();
-}
-
-void
-DrawEventRecorderFile::Close()
-{
-  MOZ_ASSERT(mOutputFile.is_open());
-
-  mOutputFile.close();
-}
-
 DrawEventRecorderMemory::DrawEventRecorderMemory()
   : DrawEventRecorderPrivate(nullptr)
 {
   mOutputStream = &mMemoryStream;
 
   WriteHeader();
 }
 
--- a/gfx/2d/DrawEventRecorder.h
+++ b/gfx/2d/DrawEventRecorder.h
@@ -5,17 +5,16 @@
 
 #ifndef MOZILLA_GFX_DRAWEVENTRECORDER_H_
 #define MOZILLA_GFX_DRAWEVENTRECORDER_H_
 
 #include "2D.h"
 #include "RecordedEvent.h"
 #include <ostream>
 #include <fstream>
-#include <string>
 
 #if defined(_MSC_VER)
 #include <unordered_set>
 #else
 #include <set>
 #endif
 
 namespace mozilla {
@@ -74,34 +73,19 @@ protected:
 
 class DrawEventRecorderFile : public DrawEventRecorderPrivate
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderFile)
   explicit DrawEventRecorderFile(const char *aFilename);
   ~DrawEventRecorderFile();
 
-  /**
-   * Re-opens and truncates the file. The recorder does NOT forget which objects
-   * it has recorded. This can be used with Close, so that a recording can be
-   * processed in chunks. If the file is already open this does nothing.
-   */
-  void OpenAndTruncate();
-
-  /**
-   * Closes the file so that it can be processed. The recorder does NOT forget
-   * which objects it has recorded. This can be used with OpenAndTruncate, so
-   * that a recording can be processed in chunks. The file must be open.
-   */
-  void Close();
-
 private:
   virtual void Flush();
 
-  std::string mOutputFilename;
   std::ofstream mOutputFile;
 };
 
 class DrawEventRecorderMemory final : public DrawEventRecorderPrivate
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderMemory)
 
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -1054,16 +1054,17 @@ RasterImage::RequestDecodeForSize(const 
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mHasSize) {
+    mWantFullDecode = true;
     return NS_OK;
   }
 
   // Decide whether to sync decode images we can decode quickly. Here we are
   // explicitly trading off flashing for responsiveness in the case that we're
   // redecoding an image (see bug 845147).
   bool shouldSyncDecodeIfFast =
     !mHasBeenDecoded && (aFlags & FLAG_SYNC_DECODE_IF_FAST);
new file mode 100644
--- /dev/null
+++ b/js/src/irregexp/RegExpCharacters-inl.h
@@ -0,0 +1,40 @@
+/* Generated by make_unicode.py DO NOT MODIFY */
+/* Unicode version: 9.0.0 */
+#ifndef V8_JSREGEXPCHARACTERS_INL_H_
+#define V8_JSREGEXPCHARACTERS_INL_H_
+
+namespace js {
+
+namespace irregexp {
+
+static inline bool
+RangeContainsLatin1Equivalents(CharacterRange range, bool unicode)
+{
+    if (unicode) {
+        // "LATIN SMALL LETTER LONG S" case folds to "LATIN SMALL LETTER S".
+        if (range.Contains(0x017F))
+            return true;
+        // "LATIN CAPITAL LETTER SHARP S" case folds to "LATIN SMALL LETTER SHARP S".
+        if (range.Contains(0x1E9E))
+            return true;
+        // "KELVIN SIGN" case folds to "LATIN SMALL LETTER K".
+        if (range.Contains(0x212A))
+            return true;
+        // "ANGSTROM SIGN" case folds to "LATIN SMALL LETTER A WITH RING ABOVE".
+        if (range.Contains(0x212B))
+            return true;
+    }
+
+    // "GREEK CAPITAL LETTER MU" case maps to "MICRO SIGN".
+    // "GREEK SMALL LETTER MU" case maps to "MICRO SIGN".
+    if (range.Contains(0x039C) || range.Contains(0x03BC))
+        return true;
+    // "LATIN CAPITAL LETTER Y WITH DIAERESIS" case maps to "LATIN SMALL LETTER Y WITH DIAERESIS".
+    if (range.Contains(0x0178))
+        return true;
+    return false;
+}
+
+} } // namespace js::irregexp
+
+#endif // V8_JSREGEXPCHARACTERS_INL_H_
new file mode 100644
--- /dev/null
+++ b/js/src/irregexp/RegExpCharacters.cpp
@@ -0,0 +1,135 @@
+/* Generated by make_unicode.py DO NOT MODIFY */
+/* Unicode version: 9.0.0 */
+#include "irregexp/RegExpCharacters.h"
+
+#include "mozilla/Assertions.h"
+
+char16_t
+js::irregexp::ConvertNonLatin1ToLatin1(char16_t c, bool unicode)
+{
+    MOZ_ASSERT(c > 0xFF, "Character mustn't be Latin1");
+    if (unicode) {
+        // "LATIN SMALL LETTER LONG S" case folds to "LATIN SMALL LETTER S".
+        if (c == 0x017F)
+            return 0x73;
+        // "LATIN CAPITAL LETTER SHARP S" case folds to "LATIN SMALL LETTER SHARP S".
+        if (c == 0x1E9E)
+            return 0xDF;
+        // "KELVIN SIGN" case folds to "LATIN SMALL LETTER K".
+        if (c == 0x212A)
+            return 0x6B;
+        // "ANGSTROM SIGN" case folds to "LATIN SMALL LETTER A WITH RING ABOVE".
+        if (c == 0x212B)
+            return 0xE5;
+    }
+
+    // "GREEK CAPITAL LETTER MU" case maps to "MICRO SIGN".
+    // "GREEK SMALL LETTER MU" case maps to "MICRO SIGN".
+    if (c == 0x039C || c == 0x03BC)
+        return 0xB5;
+    // "LATIN CAPITAL LETTER Y WITH DIAERESIS" case maps to "LATIN SMALL LETTER Y WITH DIAERESIS".
+    if (c == 0x0178)
+        return 0xFF;
+    return 0;
+}
+
+const int js::irregexp::kSpaceRanges[] = {
+    0x0009, 0x000D + 1, // CHARACTER TABULATION..CARRIAGE RETURN (CR)
+    0x0020, 0x0020 + 1, // SPACE
+    0x00A0, 0x00A0 + 1, // NO-BREAK SPACE
+    0x1680, 0x1680 + 1, // OGHAM SPACE MARK
+    0x2000, 0x200A + 1, // EN QUAD..HAIR SPACE
+    0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR
+    0x202F, 0x202F + 1, // NARROW NO-BREAK SPACE
+    0x205F, 0x205F + 1, // MEDIUM MATHEMATICAL SPACE
+    0x3000, 0x3000 + 1, // IDEOGRAPHIC SPACE
+    0xFEFF, 0xFEFF + 1, // ZERO WIDTH NO-BREAK SPACE
+    0xFFFF + 1
+};
+const int js::irregexp::kSpaceRangeCount = 21;
+
+const int js::irregexp::kSpaceAndSurrogateRanges[] = {
+    0x0009, 0x000D + 1, // CHARACTER TABULATION..CARRIAGE RETURN (CR)
+    0x0020, 0x0020 + 1, // SPACE
+    0x00A0, 0x00A0 + 1, // NO-BREAK SPACE
+    0x1680, 0x1680 + 1, // OGHAM SPACE MARK
+    0x2000, 0x200A + 1, // EN QUAD..HAIR SPACE
+    0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR
+    0x202F, 0x202F + 1, // NARROW NO-BREAK SPACE
+    0x205F, 0x205F + 1, // MEDIUM MATHEMATICAL SPACE
+    0x3000, 0x3000 + 1, // IDEOGRAPHIC SPACE
+    0xD800, 0xDFFF + 1, // <Lead Surrogate Min>..<Trail Surrogate Max>
+    0xFEFF, 0xFEFF + 1, // ZERO WIDTH NO-BREAK SPACE
+    0xFFFF + 1
+};
+const int js::irregexp::kSpaceAndSurrogateRangeCount = 23;
+
+const int js::irregexp::kWordRanges[] = {
+    0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE
+    0x0041, 0x005A + 1, // LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+    0x005F, 0x005F + 1, // LOW LINE
+    0x0061, 0x007A + 1, // LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+    0xFFFF + 1
+};
+const int js::irregexp::kWordRangeCount = 9;
+
+const int js::irregexp::kIgnoreCaseWordRanges[] = {
+    0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE
+    0x0041, 0x005A + 1, // LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+    0x005F, 0x005F + 1, // LOW LINE
+    0x0061, 0x007A + 1, // LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+    0x017F, 0x017F + 1, // LATIN SMALL LETTER LONG S
+    0x212A, 0x212A + 1, // KELVIN SIGN
+    0xFFFF + 1
+};
+const int js::irregexp::kIgnoreCaseWordRangeCount = 13;
+
+const int js::irregexp::kWordAndSurrogateRanges[] = {
+    0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE
+    0x0041, 0x005A + 1, // LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+    0x005F, 0x005F + 1, // LOW LINE
+    0x0061, 0x007A + 1, // LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+    0xD800, 0xDFFF + 1, // <Lead Surrogate Min>..<Trail Surrogate Max>
+    0xFFFF + 1
+};
+const int js::irregexp::kWordAndSurrogateRangeCount = 11;
+
+const int js::irregexp::kNegatedIgnoreCaseWordAndSurrogateRanges[] = {
+    0x0000, 0x002F + 1, // NULL..SOLIDUS
+    0x003A, 0x0040 + 1, // COLON..COMMERCIAL AT
+    0x005B, 0x005E + 1, // LEFT SQUARE BRACKET..CIRCUMFLEX ACCENT
+    0x0060, 0x0060 + 1, // GRAVE ACCENT
+    0x007B, 0x017E + 1, // LEFT CURLY BRACKET..LATIN SMALL LETTER Z WITH CARON
+    0x0180, 0x2129 + 1, // LATIN SMALL LETTER B WITH STROKE..TURNED GREEK SMALL LETTER IOTA
+    0x212B, 0xD7FF + 1, // ANGSTROM SIGN..<Unused>
+    0xE000, 0xFFFF + 1, // Private Use..<Unused>
+    0xFFFF + 1
+};
+const int js::irregexp::kNegatedIgnoreCaseWordAndSurrogateRangeCount = 17;
+
+const int js::irregexp::kDigitRanges[] = {
+    0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE
+    0xFFFF + 1
+};
+const int js::irregexp::kDigitRangeCount = 3;
+
+const int js::irregexp::kDigitAndSurrogateRanges[] = {
+    0x0030, 0x0039 + 1, // DIGIT ZERO..DIGIT NINE
+    0xD800, 0xDFFF + 1, // <Lead Surrogate Min>..<Trail Surrogate Max>
+    0xFFFF + 1
+};
+const int js::irregexp::kDigitAndSurrogateRangeCount = 5;
+
+const int js::irregexp::kSurrogateRanges[] = {
+    0xD800, 0xDFFF + 1, // <Lead Surrogate Min>..<Trail Surrogate Max>
+    0xFFFF + 1
+};
+const int js::irregexp::kSurrogateRangeCount = 3;
+
+const int js::irregexp::kLineTerminatorRanges[] = {
+    0x000A, 0x000A + 1, // LINE FEED (LF)
+    0x000D, 0x000D + 1, // CARRIAGE RETURN (CR)
+    0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR
+    0xFFFF + 1
+};
+const int js::irregexp::kLineTerminatorRangeCount = 7;
new file mode 100644
--- /dev/null
+++ b/js/src/irregexp/RegExpCharacters.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99: */
+
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef V8_JSREGEXPCHARACTERS_H_
+#define V8_JSREGEXPCHARACTERS_H_
+
+namespace js {
+
+namespace irregexp {
+
+char16_t
+ConvertNonLatin1ToLatin1(char16_t c, bool unicode);
+
+// -------------------------------------------------------------------
+// CharacterRange
+
+// The ranges have inclusive from and exclusive to.
+
+// This covers \s as defined in ES2016, 21.2.2.12 CharacterClassEscape,
+// which includes WhiteSpace (11.2) and LineTerminator (11.3) values.
+extern const int kSpaceRanges[];
+extern const int kSpaceRangeCount;
+
+// Characters in \s and additionally all surrogate characters.
+extern const int kSpaceAndSurrogateRanges[];
+extern const int kSpaceAndSurrogateRangeCount;
+
+// This covers \w as defined in ES2016, 21.2.2.12 CharacterClassEscape.
+extern const int kWordRanges[];
+extern const int kWordRangeCount;
+
+// Characters which case-fold to characters in \w.
+extern const int kIgnoreCaseWordRanges[];
+extern const int kIgnoreCaseWordRangeCount;
+
+// Characters in \w and additionally all surrogate characters.
+extern const int kWordAndSurrogateRanges[];
+extern const int kWordAndSurrogateRangeCount;
+
+// All characters excluding those which case-fold to \w and excluding all
+// surrogate characters.
+extern const int kNegatedIgnoreCaseWordAndSurrogateRanges[];
+extern const int kNegatedIgnoreCaseWordAndSurrogateRangeCount;
+
+// This covers \d as defined in ES2016, 21.2.2.12 CharacterClassEscape.
+extern const int kDigitRanges[];
+extern const int kDigitRangeCount;
+
+// Characters in \d and additionally all surrogate characters.
+extern const int kDigitAndSurrogateRanges[];
+extern const int kDigitAndSurrogateRangeCount;
+
+// The range of all surrogate characters.
+extern const int kSurrogateRanges[];
+extern const int kSurrogateRangeCount;
+
+// Line terminators as defined in ES2016, 11.3 LineTerminator.
+extern const int kLineTerminatorRanges[];
+extern const int kLineTerminatorRangeCount;
+
+} } // namespace js::irregexp
+
+#endif // V8_JSREGEXPCHARACTERS_H_
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -26,19 +26,22 @@
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "irregexp/RegExpEngine.h"
 
 #include "irregexp/NativeRegExpMacroAssembler.h"
+#include "irregexp/RegExpCharacters.h"
 #include "irregexp/RegExpMacroAssembler.h"
 #include "jit/JitCommon.h"
 
+#include "irregexp/RegExpCharacters-inl.h"
+
 using namespace js;
 using namespace js::irregexp;
 
 using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 using mozilla::Maybe;
 
 #define DEFINE_ACCEPT(Type)                                          \
@@ -55,71 +58,16 @@ void LoopChoiceNode::Accept(NodeVisitor*
 static const int kMaxLookaheadForBoyerMoore = 8;
 
 RegExpNode::RegExpNode(LifoAlloc* alloc)
   : replacement_(nullptr), trace_count_(0), alloc_(alloc)
 {
     bm_info_[0] = bm_info_[1] = nullptr;
 }
 
-// -------------------------------------------------------------------
-// CharacterRange
-
-// The '2' variant has inclusive from and exclusive to.
-// This covers \s as defined in ES2016, 21.2.2.12 CharacterClassEscape,
-// which include WhiteSpace (11.2) or LineTerminator (11.3) values.
-static const int kSpaceRanges[] = { '\t', '\r' + 1, ' ', ' ' + 1,
-    0x00A0, 0x00A1, 0x1680, 0x1681, 0x2000, 0x200B,
-    0x2028, 0x202A, 0x202F, 0x2030, 0x205F, 0x2060, 0x3000, 0x3001,
-    0xFEFF, 0xFF00, 0x10000 };
-static const int kSpaceRangeCount = ArrayLength(kSpaceRanges);
-
-static const int kSpaceAndSurrogateRanges[] = { '\t', '\r' + 1, ' ', ' ' + 1,
-    0x00A0, 0x00A1, 0x1680, 0x1681, 0x2000, 0x200B,
-    0x2028, 0x202A, 0x202F, 0x2030, 0x205F, 0x2060, 0x3000, 0x3001,
-    unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1,
-    0xFEFF, 0xFF00, 0x10000 };
-static const int kSpaceAndSurrogateRangeCount = ArrayLength(kSpaceAndSurrogateRanges);
-static const int kWordRanges[] = {
-    '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1, 0x10000 };
-static const int kWordRangeCount = ArrayLength(kWordRanges);
-static const int kIgnoreCaseWordRanges[] = {
-    '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1,
-    0x017F, 0x017F + 1, 0x212A, 0x212A + 1,
-    0x10000 };
-static const int kIgnoreCaseWordCount = ArrayLength(kIgnoreCaseWordRanges);
-static const int kWordAndSurrogateRanges[] = {
-    '0', '9' + 1, 'A', 'Z' + 1, '_', '_' + 1, 'a', 'z' + 1,
-    unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1,
-    0x10000 };
-static const int kWordAndSurrogateRangeCount = ArrayLength(kWordAndSurrogateRanges);
-static const int kNegatedIgnoreCaseWordAndSurrogateRanges[] = {
-    0, '0', '9' + 1, 'A',
-    'Z' + 1, '_', '_' + 1, 'a',
-    'z' + 1, 0x017F,
-    0x017F + 1, 0x212A,
-    0x212A + 1, unicode::LeadSurrogateMin,
-    unicode::TrailSurrogateMax + 1, 0x10000,
-    0x10000 };
-static const int kNegatedIgnoreCaseWordAndSurrogateRangeCount =
-    ArrayLength(kNegatedIgnoreCaseWordAndSurrogateRanges);
-static const int kDigitRanges[] = { '0', '9' + 1, 0x10000 };
-static const int kDigitRangeCount = ArrayLength(kDigitRanges);
-static const int kDigitAndSurrogateRanges[] = {
-    '0', '9' + 1,
-    unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1,
-    0x10000 };
-static const int kDigitAndSurrogateRangeCount = ArrayLength(kDigitAndSurrogateRanges);
-static const int kSurrogateRanges[] = {
-    unicode::LeadSurrogateMin, unicode::TrailSurrogateMax + 1,
-    0x10000 };
-static const int kSurrogateRangeCount = ArrayLength(kSurrogateRanges);
-static const int kLineTerminatorRanges[] = { 0x000A, 0x000B, 0x000D, 0x000E,
-    0x2028, 0x202A, 0x10000 };
-static const int kLineTerminatorRangeCount = ArrayLength(kLineTerminatorRanges);
 static const int kMaxOneByteCharCode = 0xff;
 static const int kMaxUtf16CodeUnit = 0xffff;
 
 static char16_t
 MaximumCharacter(bool ascii)
 {
     return ascii ? kMaxOneByteCharCode : kMaxUtf16CodeUnit;
 }
@@ -207,17 +155,17 @@ CharacterRange::AddClassEscapeUnicode(Li
       case 'd':
         return AddClassEscape(alloc, type, ranges);
         break;
       case 'S':
         AddClassNegated(kSpaceAndSurrogateRanges, kSpaceAndSurrogateRangeCount, ranges);
         break;
       case 'w':
         if (ignore_case)
-            AddClass(kIgnoreCaseWordRanges, kIgnoreCaseWordCount, ranges);
+            AddClass(kIgnoreCaseWordRanges, kIgnoreCaseWordRangeCount, ranges);
         else
             AddClassEscape(alloc, type, ranges);
         break;
       case 'W':
         if (ignore_case) {
             AddClass(kNegatedIgnoreCaseWordAndSurrogateRanges,
                      kNegatedIgnoreCaseWordAndSurrogateRangeCount, ranges);
         } else {
@@ -227,43 +175,16 @@ CharacterRange::AddClassEscapeUnicode(Li
       case 'D':
         AddClassNegated(kDigitAndSurrogateRanges, kDigitAndSurrogateRangeCount, ranges);
         break;
       default:
         MOZ_CRASH("Bad type!");
     }
 }
 
-#define FOR_EACH_NON_ASCII_TO_ASCII_FOLDING(macro)      \
-    /* LATIN CAPITAL LETTER Y WITH DIAERESIS */         \
-    macro(0x0178, 0x00FF)                               \
-    /* LATIN SMALL LETTER LONG S */                     \
-    macro(0x017F, 0x0073)                               \
-    /* LATIN CAPITAL LETTER SHARP S */                  \
-    macro(0x1E9E, 0x00DF)                               \
-    /* KELVIN SIGN */                                   \
-    macro(0x212A, 0x006B)                               \
-    /* ANGSTROM SIGN */                                 \
-    macro(0x212B, 0x00E5)
-
-// We need to check for the following characters: 0x39c 0x3bc 0x178.
-static inline bool
-RangeContainsLatin1Equivalents(CharacterRange range, bool unicode)
-{
-    /* TODO(dcarney): this could be a lot more efficient. */
-    if (unicode) {
-#define CHECK_RANGE(C, F) \
-        if (range.Contains(C)) return true;
-FOR_EACH_NON_ASCII_TO_ASCII_FOLDING(CHECK_RANGE)
-#undef CHECK_RANGE
-    }
-
-    return range.Contains(0x39c) || range.Contains(0x3bc) || range.Contains(0x178);
-}
-
 static bool
 RangesContainLatin1Equivalents(const CharacterRangeVector& ranges, bool unicode)
 {
     for (size_t i = 0; i < ranges.length(); i++) {
         // TODO(dcarney): this could be a lot more efficient.
         if (RangeContainsLatin1Equivalents(ranges[i], unicode))
             return true;
     }
@@ -330,17 +251,17 @@ GetCaseIndependentLetters(char16_t chara
     char16_t other1 = others.other1();
     char16_t other2 = others.other2();
     char16_t other3 = others.other3();
 
     // ES 2017 draft 996af87b7072b3c3dd2b1def856c66f456102215 21.2.4.2
     // step 3.g.
     // The standard requires that non-ASCII characters cannot have ASCII
     // character codes in their equivalence class, even though this
-    // situation occurs multiple times in the unicode tables.
+    // situation occurs multiple times in the Unicode tables.
     static const unsigned kMaxAsciiCharCode = 127;
     if (upper <= kMaxAsciiCharCode) {
         if (character > kMaxAsciiCharCode) {
             // If Canonicalize(character) == character, all other characters
             // should be ignored.
             return GetCaseIndependentLetters(character, ascii_subject, unicode,
                                              &character, 1, letters);
         }
@@ -359,41 +280,16 @@ GetCaseIndependentLetters(char16_t chara
         other1,
         other2,
         other3
     };
     return GetCaseIndependentLetters(character, ascii_subject, unicode,
                                      choices, ArrayLength(choices), letters);
 }
 
-static char16_t
-ConvertNonLatin1ToLatin1(char16_t c, bool unicode)
-{
-    MOZ_ASSERT(c > kMaxOneByteCharCode);
-    if (unicode) {
-        switch (c) {
-#define CONVERT(C, F) case C: return F;
-FOR_EACH_NON_ASCII_TO_ASCII_FOLDING(CONVERT)
-#undef CONVERT
-        }
-    }
-
-    switch (c) {
-      // This are equivalent characters in unicode.
-      case 0x39c:
-      case 0x3bc:
-        return 0xb5;
-      // This is an uppercase of a Latin-1 character
-      // outside of Latin-1.
-      case 0x178:
-        return 0xff;
-    }
-    return 0;
-}
-
 void
 CharacterRange::AddCaseEquivalents(bool is_ascii, bool unicode, CharacterRangeVector* ranges)
 {
     char16_t bottom = from();
     char16_t top = to();
 
     if (is_ascii && !RangeContainsLatin1Equivalents(*this, unicode)) {
         if (bottom > kMaxOneByteCharCode)
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -192,16 +192,17 @@ UNIFIED_SOURCES += [
     'gc/Nursery.cpp',
     'gc/RootMarking.cpp',
     'gc/Statistics.cpp',
     'gc/Tracer.cpp',
     'gc/Verifier.cpp',
     'gc/Zone.cpp',
     'irregexp/NativeRegExpMacroAssembler.cpp',
     'irregexp/RegExpAST.cpp',
+    'irregexp/RegExpCharacters.cpp',
     'irregexp/RegExpEngine.cpp',
     'irregexp/RegExpInterpreter.cpp',
     'irregexp/RegExpMacroAssembler.cpp',
     'irregexp/RegExpParser.cpp',
     'irregexp/RegExpStack.cpp',
     'jit/AliasAnalysis.cpp',
     'jit/AliasAnalysisShared.cpp',
     'jit/AlignmentMaskAnalysis.cpp',
--- a/js/src/vm/make_unicode.py
+++ b/js/src/vm/make_unicode.py
@@ -128,16 +128,27 @@ def read_derived_core_properties(derived
         char_property = row[1].strip()
         if '..' not in char_range:
             yield (int(char_range, 16), char_property)
         else:
             [start, end] = char_range.split('..')
             for char in range(int(start, 16), int(end, 16) + 1):
                 yield (char, char_property)
 
+def int_ranges(ints):
+    """ Yields consecutive ranges (inclusive) from integer values. """
+    from itertools import tee, izip_longest
+
+    (a, b) = tee(sorted(ints))
+    start = next(b)
+    for (curr, succ) in izip_longest(a, b):
+        if curr + 1 != succ:
+            yield (start, curr)
+            start = succ
+
 def utf16_encode(code):
     NonBMPMin = 0x10000
     LeadSurrogateMin = 0xD800
     TrailSurrogateMin = 0xDC00
 
     lead = (code - NonBMPMin) / 1024 + LeadSurrogateMin
     trail = ((code - NonBMPMin) % 1024) + TrailSurrogateMin
 
@@ -853,16 +864,214 @@ def splitbins(t):
     dump(t1, t2, shift, bytes)
 
     # exhaustively verify that the decomposition is correct
     mask = 2**shift - 1
     for i in range(len(t)):
         assert t[i] == t2[(t1[i >> shift] << shift) + (i & mask)]
     return best
 
+def make_irregexp_tables(version,
+                         table, index,
+                         folding_table, folding_index,
+                         test_table):
+    import string
+    from functools import partial
+    from itertools import chain, ifilter, imap
+
+    MAX_ASCII = 0x7F
+    MAX_LATIN1 = 0xFF
+    LEAD_SURROGATE_MIN = 0xD800
+    TRAIL_SURROGATE_MAX = 0xDFFF
+
+    def hex2(n):
+        assert 0 <= n and n < 16**2
+        return '0x{:02X}'.format(n)
+
+    def hex4(n):
+        assert 0 <= n and n < 16**4
+        return '0x{:04X}'.format(n)
+
+    def uhex4(n):
+        assert 0 <= n and n < 16**4
+        return 'U+{:04X}'.format(n)
+
+    def case_info(code):
+        assert 0 <= code and code <= MAX_BMP
+        (upper, lower, flags) = table[index[code]]
+        return ((code + upper) & 0xffff, (code + lower) & 0xffff, flags)
+
+    def is_space(code):
+        (_, _, flags) = case_info(code)
+        return bool(flags & FLAG_SPACE)
+
+    def to_upper(code):
+        (upper, _, _) = case_info(code)
+        return upper
+
+    def casefold(code):
+        assert 0 <= code and code <= MAX_BMP
+        (folding, _, _, _) = folding_table[folding_index[code]]
+        return (code + folding) & 0xffff
+
+    def casefolds_to_ascii(code):
+        return casefold(code) <= MAX_ASCII
+
+    def casefolds_to_latin1(code):
+        return casefold(code) <= MAX_LATIN1
+
+    def casemaps_to_nonlatin1(code):
+        upper = to_upper(code)
+        return upper > MAX_LATIN1
+
+    def char_name(code):
+        assert 0 <= code and code <= MAX_BMP
+        if code not in test_table:
+            return '<Unused>'
+        if code == LEAD_SURROGATE_MIN:
+            return '<Lead Surrogate Min>'
+        if code == TRAIL_SURROGATE_MAX:
+            return '<Trail Surrogate Max>'
+        (_, _, name, alias) = test_table[code]
+        return name if not name.startswith('<') else alias
+
+    def write_character_range(println, name, characters):
+        char_ranges = list(int_ranges(characters))
+        println('')
+        println('const int js::irregexp::k{}Ranges[] = {{'.format(name))
+        for (start, end) in char_ranges:
+            s_name = char_name(start)
+            e_name = char_name(end)
+            println('    {}, {} + 1, // {}'.format(hex4(start), hex4(end),
+                                                               '{}..{}'.format(s_name, e_name)
+                                                               if start != end else s_name))
+        println('    {} + 1'.format(hex4(MAX_BMP)))
+        println('};')
+        println('const int js::irregexp::k{}RangeCount = {};'.format(name,
+                                                                     len(char_ranges) * 2 + 1))
+
+    def write_character_test(println, test, consequent, default):
+        # Latin1 characters which, when case-mapped through
+        # String.prototype.toUpperCase(), canonicalize to a non-Latin1 character.
+        # ES2017, §21.2.2.8.2 Runtime Semantics: Canonicalize
+        casemapped_to_nonlatin1 = ifilter(casemaps_to_nonlatin1, xrange(0, MAX_LATIN1 + 1))
+
+        def casemap_closure(ch):
+            upper = to_upper(ch)
+            return (ch, [c for c in xrange(MAX_LATIN1 + 1, MAX_BMP + 1) if upper == to_upper(c)])
+
+        # Mapping from Latin1 characters to the list of case map equivalent
+        # non-Latin1 characters.
+        casemap_for_latin1 = dict(chain(imap(casemap_closure, casemapped_to_nonlatin1)))
+
+        # Non-latin1 characters which, when Unicode case-folded, canonicalize to
+        # a Latin1 character.
+        # ES2017, §21.2.2.8.2 Runtime Semantics: Canonicalize
+        casefolded_to_latin1 = ifilter(casefolds_to_latin1, xrange(MAX_LATIN1 + 1, MAX_BMP + 1))
+
+        println('    if (unicode) {')
+        for ch in casefolded_to_latin1:
+            casefolded = casefold(ch)
+            # Skip if also handled below for case mapping.
+            if casefolded in casemap_for_latin1 and ch in casemap_for_latin1[casefolded]:
+                continue
+            println('        // "{}" case folds to "{}".'.format(char_name(ch),
+                                                                 char_name(casefolded)))
+            println('        if ({})'.format(test(ch)))
+            println('            return {};'.format(consequent(casefolded)))
+        println('    }')
+        println('')
+        for (ch, casemapped_chars) in casemap_for_latin1.iteritems():
+            for casemapped in casemapped_chars:
+                println('    // "{}" case maps to "{}".'.format(char_name(casemapped),
+                                                                char_name(ch)))
+            println('    if ({})'.format(' || '.join(imap(test, casemapped_chars))))
+            println('        return {};'.format(consequent(ch)))
+        println('    return {};'.format(default))
+
+    with io.open('../irregexp/RegExpCharacters-inl.h', 'wb') as chars_file:
+        write = partial(print, file=chars_file, sep='', end='')
+        println = partial(write, end='\n')
+
+        write(warning_message)
+        write(unicode_version_message.format(version))
+
+        println('#ifndef V8_JSREGEXPCHARACTERS_INL_H_')
+        println('#define V8_JSREGEXPCHARACTERS_INL_H_')
+        println('')
+        println('namespace js {')
+        println('')
+        println('namespace irregexp {')
+        println('')
+
+        println('static inline bool')
+        println('RangeContainsLatin1Equivalents(CharacterRange range, bool unicode)')
+        println('{')
+        write_character_test(println, lambda ch: 'range.Contains({})'.format(hex4(ch)),
+                             lambda _: 'true', 'false')
+        println('}')
+
+        println('')
+        println('} } // namespace js::irregexp')
+        println('')
+        println('#endif // V8_JSREGEXPCHARACTERS_INL_H_')
+
+    with io.open('../irregexp/RegExpCharacters.cpp', 'wb') as chars_file:
+        write = partial(print, file=chars_file, sep='', end='')
+        println = partial(write, end='\n')
+        character_range = partial(write_character_range, println)
+
+        # Characters in \s, 21.2.2.12 CharacterClassEscape.
+        space_chars = filter(is_space, xrange(0, MAX_BMP + 1))
+
+        # Characters in \d, 21.2.2.12 CharacterClassEscape.
+        digit_chars = map(ord, string.digits)
+        assert all(ch <= MAX_ASCII for ch in digit_chars)
+
+        # Characters in \w, 21.2.2.12 CharacterClassEscape.
+        word_chars = map(ord, string.digits + string.ascii_letters + '_')
+        assert all(ch <= MAX_ASCII for ch in word_chars)
+
+        # Characters which case-fold to characters in \w.
+        ignorecase_word_chars = (word_chars +
+                                filter(casefolds_to_ascii, xrange(MAX_ASCII + 1, MAX_BMP + 1)))
+
+        # Surrogate characters.
+        surrogate_chars = range(LEAD_SURROGATE_MIN, TRAIL_SURROGATE_MAX + 1)
+
+        write(warning_message)
+        write(unicode_version_message.format(version))
+        println('#include "irregexp/RegExpCharacters.h"')
+        println('')
+        println('#include "mozilla/Assertions.h"')
+        println('')
+
+        println('char16_t')
+        println('js::irregexp::ConvertNonLatin1ToLatin1(char16_t c, bool unicode)')
+        println('{')
+        println('    MOZ_ASSERT(c > {}, "Character mustn\'t be Latin1");'.format(hex2(MAX_LATIN1)))
+        write_character_test(println, lambda ch: 'c == {}'.format(hex4(ch)), hex2, '0')
+        println('}')
+
+        character_range('Space', space_chars)
+        character_range('SpaceAndSurrogate', space_chars + surrogate_chars)
+
+        character_range('Word', word_chars)
+        character_range('IgnoreCaseWord', ignorecase_word_chars)
+        character_range('WordAndSurrogate', word_chars + surrogate_chars)
+        character_range('NegatedIgnoreCaseWordAndSurrogate',
+                        set(xrange(0, MAX_BMP + 1)) - set(ignorecase_word_chars + surrogate_chars))
+
+        character_range('Digit', digit_chars)
+        character_range('DigitAndSurrogate', digit_chars + surrogate_chars)
+
+        character_range('Surrogate', surrogate_chars)
+
+        character_range('LineTerminator', line_terminator)
+
 def update_unicode(args):
     import urllib2
 
     version = args.version
     if version is not None:
         baseurl = 'http://unicode.org/Public'
         if version == 'UNIDATA':
             url = '%s/%s' % (baseurl, version)
@@ -924,16 +1133,20 @@ def update_unicode(args):
                       table, index,
                       same_upper_table, same_upper_index,
                       folding_table, folding_index,
                       non_bmp_space_set,
                       non_bmp_id_start_set, non_bmp_id_cont_set)
     make_non_bmp_file(unicode_version,
                       non_bmp_lower_map, non_bmp_upper_map,
                       non_bmp_folding_map, non_bmp_rev_folding_map)
+    make_irregexp_tables(unicode_version,
+                         table, index,
+                         folding_table, folding_index,
+                         test_table)
 
     make_bmp_mapping_test(unicode_version, test_table)
     make_non_bmp_mapping_test(unicode_version, non_bmp_upper_map, non_bmp_lower_map)
     make_space_test(unicode_version, test_space_table)
     make_regexp_space_test(unicode_version, test_space_table)
     make_icase_test(unicode_version, folding_tests)
 
 if __name__ == '__main__':
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -2204,17 +2204,17 @@ class BaseCompiler
             masm.freeStack(adjustment);
 
         if (call.reloadMachineStateAfter) {
             loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));
             masm.loadWasmPinnedRegsFromTls();
         }
     }
 
-    // TODO / OPTIMIZE (Bug 1316820): This is expensive; let's roll the iterator
+    // TODO / OPTIMIZE (Bug 1316821): This is expensive; let's roll the iterator
     // walking into the walking done for passArg.  See comments in passArg.
 
     size_t stackArgAreaSize(const ValTypeVector& args) {
         ABIArgIter<const ValTypeVector> i(args);
         while (!i.done())
             i++;
         return AlignBytes(i.stackBytesConsumedSoFar(), 16u);
     }
@@ -2227,17 +2227,17 @@ class BaseCompiler
         if (adjustment)
             masm.reserveStack(adjustment);
     }
 
     const ABIArg reservePointerArgument(FunctionCall& call) {
         return call.abi.next(MIRType::Pointer);
     }
 
-    // TODO / OPTIMIZE (Bug 1316820): Note passArg is used only in one place.
+    // TODO / OPTIMIZE (Bug 1316821): Note passArg is used only in one place.
     // (Or it was, until Luke wandered through, but that can be fixed again.)
     // I'm not saying we should manually inline it, but we could hoist the
     // dispatch into the caller and have type-specific implementations of
     // passArg: passArgI32(), etc.  Then those might be inlined, at least in PGO
     // builds.
     //
     // The bulk of the work here (60%) is in the next() call, though.
     //
--- a/layout/printing/ipc/PRemotePrintJob.ipdl
+++ b/layout/printing/ipc/PRemotePrintJob.ipdl
@@ -19,17 +19,18 @@ both:
 
 parent:
   // Initialize the real print device with the given information.
   async InitializePrint(nsString aDocumentTitle, nsString aPrintToFile,
                         int32_t aStartPage, int32_t aEndPage);
 
   // Translate the stored page recording and play back the events to the real
   // print device.
-  async ProcessPage(nsCString aPageFileName);
+  // This will always deallocate the shared memory.
+  async ProcessPage(Shmem aStoredPage);
 
   // This informs the real print device that we've finished, so it can trigger
   // the actual print.
   async FinalizePrint();
 
   // Report a state change to listeners in the parent process.
   async StateChange(long aStateFlags,
                     nsresult aStatus);
--- a/layout/printing/ipc/RemotePrintJobChild.cpp
+++ b/layout/printing/ipc/RemotePrintJobChild.cpp
@@ -42,22 +42,22 @@ mozilla::ipc::IPCResult
 RemotePrintJobChild::RecvPrintInitializationResult(const nsresult& aRv)
 {
   mPrintInitialized = true;
   mInitializationResult = aRv;
   return IPC_OK();
 }
 
 void
-RemotePrintJobChild::ProcessPage(const nsCString& aPageFileName)
+RemotePrintJobChild::ProcessPage(Shmem& aStoredPage)
 {
   MOZ_ASSERT(mPagePrintTimer);
 
   mPagePrintTimer->WaitForRemotePrint();
-  Unused << SendProcessPage(aPageFileName);
+  Unused << SendProcessPage(aStoredPage);
 }
 
 mozilla::ipc::IPCResult
 RemotePrintJobChild::RecvPageProcessed()
 {
   MOZ_ASSERT(mPagePrintTimer);
 
   mPagePrintTimer->RemotePrintFinished();
--- a/layout/printing/ipc/RemotePrintJobChild.h
+++ b/layout/printing/ipc/RemotePrintJobChild.h
@@ -31,17 +31,17 @@ public:
 
   nsresult InitializePrint(const nsString& aDocumentTitle,
                            const nsString& aPrintToFile,
                            const int32_t& aStartPage,
                            const int32_t& aEndPage);
 
   mozilla::ipc::IPCResult RecvPrintInitializationResult(const nsresult& aRv) final;
 
-  void ProcessPage(const nsCString& aPageFileName);
+  void ProcessPage(Shmem& aStoredPage);
 
   mozilla::ipc::IPCResult RecvPageProcessed() final;
 
   mozilla::ipc::IPCResult RecvAbortPrint(const nsresult& aRv) final;
 
   void SetPagePrintTimer(nsPagePrintTimer* aPagePrintTimer);
 
   void SetPrintEngine(nsPrintEngine* aPrintEngine);
--- a/layout/printing/ipc/RemotePrintJobParent.cpp
+++ b/layout/printing/ipc/RemotePrintJobParent.cpp
@@ -1,24 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RemotePrintJobParent.h"
 
-#include <fstream>
+#include <istream>
 
 #include "gfxContext.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Unused.h"
-#include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
-#include "nsDirectoryServiceUtils.h"
 #include "nsDeviceContext.h"
 #include "nsIDeviceContextSpec.h"
 #include "nsIPrintSettings.h"
 #include "nsIWebProgressListener.h"
 #include "PrintTranslator.h"
 
 namespace mozilla {
 namespace layout {
@@ -78,73 +76,56 @@ RemotePrintJobParent::InitializePrintDev
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
-RemotePrintJobParent::RecvProcessPage(const nsCString& aPageFileName)
+RemotePrintJobParent::RecvProcessPage(Shmem&& aStoredPage)
 {
-  nsresult rv = PrintPage(aPageFileName);
+  nsresult rv = PrintPage(aStoredPage);
+
+  // Always deallocate the shared memory no matter what the result.
+  if (!DeallocShmem(aStoredPage)) {
+    NS_WARNING("Failed to deallocated shared memory, remote print will abort.");
+    rv = NS_ERROR_FAILURE;
+  }
 
   if (NS_FAILED(rv)) {
     Unused << SendAbortPrint(rv);
   } else {
     Unused << SendPageProcessed();
   }
 
   return IPC_OK();
 }
 
 nsresult
-RemotePrintJobParent::PrintPage(const nsCString& aPageFileName)
+RemotePrintJobParent::PrintPage(const Shmem& aStoredPage)
 {
   MOZ_ASSERT(mPrintDeviceContext);
 
   nsresult rv = mPrintDeviceContext->BeginPage();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsCOMPtr<nsIFile> recordingFile;
-  rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
-                              getter_AddRefs(recordingFile));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = recordingFile->AppendNative(aPageFileName);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsAutoCString recordingPath;
-  rv = recordingFile->GetNativePath(recordingPath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  std::ifstream recording(recordingPath.get(), std::ifstream::binary);
+  std::istringstream recording(std::string(aStoredPage.get<char>(),
+                                           aStoredPage.Size<char>()));
   if (!mPrintTranslator->TranslateRecording(recording)) {
     return NS_ERROR_FAILURE;
   }
 
   rv = mPrintDeviceContext->EndPage();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  recording.close();
-  rv = recordingFile->Remove(/* recursive= */ false);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
 RemotePrintJobParent::RecvFinalizePrint()
 {
   // EndDocument is sometimes called in the child even when BeginDocument has
   // not been called. See bug 1223332.
--- a/layout/printing/ipc/RemotePrintJobParent.h
+++ b/layout/printing/ipc/RemotePrintJobParent.h
@@ -29,17 +29,17 @@ public:
 
   void ActorDestroy(ActorDestroyReason aWhy) final;
 
   mozilla::ipc::IPCResult RecvInitializePrint(const nsString& aDocumentTitle,
                                               const nsString& aPrintToFile,
                                               const int32_t& aStartPage,
                                               const int32_t& aEndPage) final;
 
-  mozilla::ipc::IPCResult RecvProcessPage(const nsCString& aPageFileName) final;
+  mozilla::ipc::IPCResult RecvProcessPage(Shmem&& aStoredPage) final;
 
   mozilla::ipc::IPCResult RecvFinalizePrint() final;
 
   mozilla::ipc::IPCResult RecvAbortPrint(const nsresult& aRv) final;
 
   mozilla::ipc::IPCResult RecvStateChange(const long& aStateFlags,
                                           const nsresult& aStatus) final;
 
@@ -65,17 +65,17 @@ public:
 private:
   ~RemotePrintJobParent() final;
 
   nsresult InitializePrintDevice(const nsString& aDocumentTitle,
                                  const nsString& aPrintToFile,
                                  const int32_t& aStartPage,
                                  const int32_t& aEndPage);
 
-  nsresult PrintPage(const nsCString& aPageFileName);
+  nsresult PrintPage(const Shmem& aStoredPage);
 
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   RefPtr<nsDeviceContext> mPrintDeviceContext;
   UniquePtr<PrintTranslator> mPrintTranslator;
   nsCOMArray<nsIWebProgressListener> mPrintProgressListeners;
 };
 
 } // namespace layout
--- a/layout/reftests/forms/fieldset/fieldset-min-width-2-ref.html
+++ b/layout/reftests/forms/fieldset/fieldset-min-width-2-ref.html
@@ -1,2 +1,2 @@
 <!DOCTYPE html>
-<fieldset style="width: -moz-fit-content">&zwnj;<!-- To give us the right height --></fieldset>
+<fieldset style="width: -moz-fit-content">&ZeroWidthSpace;<!-- To give us the right height --></fieldset>
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1321357-1.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<body onload="document.getElementById('containerA').pauseAnimations()">
+  <svg id="containerA">
+    <animate id="ia" end="50s"></animate>
+    <animate begin="60s" end="ic.end"></animate>
+  </svg>
+  <svg>
+    <animate id="ic" end="ia.end"></animate>
+  </svg>
+</body>
+</html>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -159,8 +159,9 @@ pref(dom.animations-api.core.enabled,tru
 pref(dom.animations-api.core.enabled,true) load 1290994-2.html
 pref(dom.animations-api.core.enabled,true) load 1290994-3.html
 load 1290994-4.html
 load 1314531.html
 load 1315889-1.html
 load 1315894-1.html
 load 1319072-1.html
 HTTP load 1320423-1.html
+load 1321357-1.html
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -202,16 +202,18 @@ public abstract class GeckoApp
     private final HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
     protected boolean mLastSessionCrashed;
     protected boolean mShouldRestore;
     private boolean mSessionRestoreParsingFinished = false;
 
     private int lastSelectedTabId = -1;
 
+    private boolean foregrounded = false;
+
     private static final class LastSessionParser extends SessionParser {
         private JSONArray tabs;
         private JSONObject windowObject;
         private boolean isExternalURL;
 
         private boolean selectNextTab;
         private boolean tabsWereSkipped;
         private boolean tabsWereProcessed;
@@ -2066,25 +2068,32 @@ public abstract class GeckoApp
         return intent.getDataString();
     }
 
     protected int getOrientation() {
         return GeckoScreenOrientation.getInstance().getAndroidOrientation();
     }
 
     @Override
+    public boolean isForegrounded() {
+        return foregrounded;
+    }
+
+    @Override
     public void onResume()
     {
         // After an onPause, the activity is back in the foreground.
         // Undo whatever we did in onPause.
         super.onResume();
         if (mIsAbortingAppLaunch) {
             return;
         }
 
+        foregrounded = true;
+
         GeckoAppShell.setGeckoInterface(this);
 
         if (lastSelectedTabId >= 0 && (lastActiveGeckoApp == null || lastActiveGeckoApp.get() != this)) {
             Tabs.getInstance().selectTab(lastSelectedTabId);
         }
 
         int newOrientation = getResources().getConfiguration().orientation;
         if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
@@ -2155,16 +2164,18 @@ public abstract class GeckoApp
     @Override
     public void onPause()
     {
         if (mIsAbortingAppLaunch) {
             super.onPause();
             return;
         }
 
+        foregrounded = false;
+
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         if (selectedTab != null) {
             lastSelectedTabId = selectedTab.getId();
         }
         lastActiveGeckoApp = new WeakReference<GeckoApp>(this);
 
         final HealthRecorder rec = mHealthRecorder;
         final Context context = this;
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -103,16 +103,17 @@ public class Tabs implements GeckoEventL
         }
     };
 
     private Tabs() {
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             "Tab:Added",
             "Tab:Close",
             "Tab:Select",
+            "Tab:SelectAndForeground",
             "Content:LocationChange",
             "Content:SecurityChange",
             "Content:StateChange",
             "Content:LoadError",
             "Content:PageShow",
             "DOMTitleChanged",
             "Link:Favicon",
             "Link:Touchicon",
@@ -519,16 +520,19 @@ public class Tabs implements GeckoEventL
             // Tab was already closed; abort
             if (tab == null)
                 return;
 
             if (event.equals("Tab:Close")) {
                 closeTab(tab);
             } else if (event.equals("Tab:Select")) {
                 selectTab(tab.getId());
+            } else if (event.equals("Tab:SelectAndForeground")) {
+                GeckoAppShell.launchOrBringToFront();
+                selectTab(tab.getId());
             } else if (event.equals("Content:LocationChange")) {
                 tab.handleLocationChange(message);
             } else if (event.equals("Content:SecurityChange")) {
                 tab.updateIdentityData(message.getJSONObject("identity"));
                 notifyListeners(tab, TabEvents.SECURITY_CHANGE);
             } else if (event.equals("Content:StateChange")) {
                 int state = message.getInt("state");
                 if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -3526,16 +3526,17 @@ Tab.prototype = {
     this.browser.addEventListener("DOMAudioPlaybackStopped", this, true);
     this.browser.addEventListener("DOMWindowClose", this, true);
     this.browser.addEventListener("DOMWillOpenModalDialog", this, true);
     this.browser.addEventListener("DOMAutoComplete", this, true);
     this.browser.addEventListener("blur", this, true);
     this.browser.addEventListener("pageshow", this, true);
     this.browser.addEventListener("MozApplicationManifest", this, true);
     this.browser.addEventListener("TabPreZombify", this, true);
+    this.browser.addEventListener("DOMServiceWorkerFocusClient", this, true);
 
     // Note that the XBL binding is untrusted
     this.browser.addEventListener("PluginBindingAttached", this, true, true);
     this.browser.addEventListener("VideoBindingAttached", this, true, true);
     this.browser.addEventListener("VideoBindingCast", this, true, true);
 
     Services.obs.addObserver(this, "before-first-paint", false);
     Services.obs.addObserver(this, "media-playback", false);
@@ -3638,16 +3639,17 @@ Tab.prototype = {
     this.browser.removeEventListener("DOMAudioPlaybackStopped", this, true);
     this.browser.removeEventListener("DOMWindowClose", this, true);
     this.browser.removeEventListener("DOMWillOpenModalDialog", this, true);
     this.browser.removeEventListener("DOMAutoComplete", this, true);
     this.browser.removeEventListener("blur", this, true);
     this.browser.removeEventListener("pageshow", this, true);
     this.browser.removeEventListener("MozApplicationManifest", this, true);
     this.browser.removeEventListener("TabPreZombify", this, true);
+    this.browser.removeEventListener("DOMServiceWorkerFocusClient", this, true);
 
     this.browser.removeEventListener("PluginBindingAttached", this, true, true);
     this.browser.removeEventListener("VideoBindingAttached", this, true, true);
     this.browser.removeEventListener("VideoBindingCast", this, true, true);
 
     Services.obs.removeObserver(this, "before-first-paint");
     Services.obs.removeObserver(this, "media-playback", false);
     Services.obs.removeObserver(this, "media-playback-resumed", false);
@@ -4075,16 +4077,24 @@ Tab.prototype = {
         break;
       }
 
       case "MozApplicationManifest": {
         OfflineApps.offlineAppRequested(aEvent.originalTarget.defaultView);
         break;
       }
 
+      case "DOMServiceWorkerFocusClient": {
+        Messaging.sendRequest({
+          type: "Tab:SelectAndForeground",
+          tabID: this.id
+        });
+        break;
+      }
+
       case "pageshow": {
         LoginManagerContent.onPageShow(aEvent, this.browser.contentWindow);
 
         // The rest of this only handles pageshow for the top-level document.
         if (aEvent.originalTarget.defaultView != this.browser.contentWindow)
           return;
 
         let target = aEvent.originalTarget;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
@@ -161,9 +161,14 @@ public class BaseGeckoInterface implemen
         return new String[] {};
     }
 
     @Override
     public String getDefaultChromeURI() {
         // By default, use the GeckoView-specific chrome URI.
         return "chrome://browser/content/geckoview.xul";
     }
+
+    @Override
+    public boolean isForegrounded() {
+        return false;
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -318,22 +318,25 @@ public class GeckoAppShell
     }
 
     @WrapForJNI(exceptionMode = "ignore")
     private static void handleUncaughtException(Throwable e) {
         CRASH_HANDLER.uncaughtException(null, e);
     }
 
     @WrapForJNI
-    public static void openWindowForNotification() {
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-        intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
+    public static void launchOrBringToFront() {
+        GeckoInterface gi = getGeckoInterface();
+        if (gi == null || !gi.isForegrounded()) {
+            Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+            intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
 
-        getApplicationContext().startActivity(intent);
+            getApplicationContext().startActivity(intent);
+        }
     }
 
     private static float getLocationAccuracy(Location location) {
         float radius = location.getAccuracy();
         return (location.hasAccuracy() && radius > 0) ? radius : 1001;
     }
 
     @SuppressLint("MissingPermission") // Permissions are explicitly checked for in enableLocation()
@@ -1731,16 +1734,17 @@ public class GeckoAppShell
         public void disableOrientationListener();
         public void addAppStateListener(AppStateListener listener);
         public void removeAppStateListener(AppStateListener listener);
         public void notifyWakeLockChanged(String topic, String state);
         public boolean areTabsShown();
         public AbsoluteLayout getPluginContainer();
         public void notifyCheckUpdateResult(String result);
         public void invalidateOptionsMenu();
+        public boolean isForegrounded();
 
         /**
          * Create a shortcut -- generally a home-screen icon -- linking the given title to the given URI.
          * <p>
          * This method is always invoked on the Gecko thread.
          *
          * @param title of URI to link to.
          * @param URI to link to.
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -1316,17 +1316,20 @@ nsHtml5StreamParser::FlushTreeOpsAndDisa
     // a mutex
     mFlushTimer->Cancel();
     mFlushTimerArmed = false;
   }
   if (mMode == VIEW_SOURCE_HTML || mMode == VIEW_SOURCE_XML) {
     mTokenizer->FlushViewSource();
   }
   mTreeBuilder->Flush();
-  if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
+  nsCOMPtr<nsIRunnable> runnable(mExecutorFlusher);
+  if (NS_FAILED(mExecutor->GetDocument()->Dispatch("FlushTreeOpsAndDisarmTimer",
+                                                   dom::TaskCategory::Other,
+                                                   runnable.forget()))) {
     NS_WARNING("failed to dispatch executor flush event");
   }
 }
 
 void
 nsHtml5StreamParser::ParseAvailableData()
 {
   NS_ASSERTION(IsParserThread(), "Wrong thread!");
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -254,17 +254,19 @@ FlushTimerCallback(nsITimer* aTimer, voi
   }
 }
 
 void
 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
 {
   if (!mDocument || !mDocument->IsInBackgroundWindow()) {
     nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);  
-    if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
+    if (NS_FAILED(mDocument->Dispatch("ContinueInterruptedParsingAsync",
+                                      dom::TaskCategory::Other,
+                                      flusher.forget()))) {
       NS_WARNING("failed to dispatch executor flush event");
     }
   } else {
     if (!gBackgroundFlushList) {
       gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>();
     }
     if (!isInList()) {
       gBackgroundFlushList->insertBack(this);
deleted file mode 100644
--- a/testing/web-platform/meta/FileAPI/reading-data-section/FileReader-multiple-reads.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[FileReader-multiple-reads.html]
-  type: testharness
-  [test FileReader InvalidStateError exception for readAsText]
-    expected: FAIL
-
-  [test FileReader InvalidStateError exception for readAsDataURL]
-    expected: FAIL
-
-  [test FileReader InvalidStateError exception for readAsArrayBuffer]
-    expected: FAIL
-
-  [test FileReader InvalidStateError exception in onloadstart event for readAsArrayBuffer]
-    expected: FAIL
-
-  [test FileReader no InvalidStateError exception in onloadstart event for readAsArrayBuffer]
-    expected: FAIL
-
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -39511,16 +39511,23 @@
         ],
         "fetch/api/redirect/redirect-count-worker.html": [
           {
             "path": "fetch/api/redirect/redirect-count-worker.html",
             "timeout": "long",
             "url": "/fetch/api/redirect/redirect-count-worker.html"
           }
         ],
+        "fetch/api/redirect/redirect-count.html": [
+          {
+            "path": "fetch/api/redirect/redirect-count.html",
+            "timeout": "long",
+            "url": "/fetch/api/redirect/redirect-count.html"
+          }
+        ],
         "fetch/api/redirect/redirect-referrer-worker.html": [
           {
             "path": "fetch/api/redirect/redirect-referrer-worker.html",
             "url": "/fetch/api/redirect/redirect-referrer-worker.html"
           }
         ],
         "fetch/api/redirect/redirect-referrer.html": [
           {
deleted file mode 100644
--- a/testing/web-platform/meta/fetch/api/redirect/redirect-count.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[redirect-count.html]
-  type: testharness
-  expected:
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): TIMEOUT
-  [Redirect 308 21 times]
-    expected:
-      if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): TIMEOUT
-      if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-
--- a/testing/web-platform/mozilla/meta/fetch/api/redirect/redirect-referrer.https.html.ini
+++ b/testing/web-platform/mozilla/meta/fetch/api/redirect/redirect-referrer.https.html.ini
@@ -1,3 +1,3 @@
 [redirect-referrer.https.html]
   type: testharness
-  prefs: [security.mixed_content.block_active_content:false, security.mixed_content.block_display_content:false]
+  prefs: [security.mixed_content.block_active_content:false, security.mixed_content.block_display_content:false, security.mixed_content.send_hsts_priming:false, security.mixed_content.use_hsts:false]
--- a/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-multiple-reads.html
+++ b/testing/web-platform/tests/FileAPI/reading-data-section/FileReader-multiple-reads.html
@@ -57,17 +57,17 @@ async_test(function() {
   assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
 }, 'test FileReader InvalidStateError exception in onloadstart event for readAsArrayBuffer');
 
 async_test(function() {
   var blob_1 = new Blob(['TEST000000001'])
   var blob_2 = new Blob(['TEST000000002'])
   var reader = new FileReader();
   reader.onloadend = this.step_func_done(function() {
-    assert_equals(reader.readyState, FileReader.LOADING,
-                  "readyState must be LOADING")
+    assert_equals(reader.readyState, FileReader.DONE,
+                  "readyState must be DONE")
     reader.readAsArrayBuffer(blob_2)
     assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
   });
   reader.readAsArrayBuffer(blob_1)
   assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
 }, 'test FileReader no InvalidStateError exception in onloadstart event for readAsArrayBuffer');
 </script>
--- a/testing/web-platform/tests/fetch/api/redirect/redirect-count.html
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-count.html
@@ -1,16 +1,17 @@
 <!doctype html>
 <html>
   <head>
     <meta charset="utf-8">
     <title>Fetch: redirection loop</title>
+    <meta name="timeout" content="long">
     <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
     <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
   </head>
   <body>
     <script src="/common/utils.js"></script>
     <script src="../resources/utils.js"></script>
     <script src="redirect-count.js"></script>
   </body>
-</html>
\ No newline at end of file
+</html>
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -254,20 +254,18 @@ PopupNotifications.prototype = {
    * @param browser
    *        The browser whose notifications should be searched. If null, the
    *        currently selected browser's notifications will be searched.
    *
    * @returns the corresponding Notification object, or null if no such
    *          notification exists.
    */
   getNotification: function PopupNotifications_getNotification(id, browser) {
-    let n = null;
     let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
-    notifications.some(x => x.id == id && (n = x));
-    return n;
+    return notifications.find(x => x.id == id) || null;
   },
 
   /**
    * Adds a new popup notification.
    * @param browser
    *        The <xul:browser> element associated with the notification. Must not
    *        be null.
    * @param id
--- a/toolkit/modules/WebChannel.jsm
+++ b/toolkit/modules/WebChannel.jsm
@@ -171,29 +171,29 @@ this.WebChannel = function(id, originOrP
   this.id = id;
   // originOrPermission can be either an nsIURI or a string representing a
   // permission name.
   if (typeof originOrPermission == "string") {
     this._originCheckCallback = requestPrincipal => {
       // The permission manager operates on domain names rather than true
       // origins (bug 1066517).  To mitigate that, we explicitly check that
       // the scheme is https://.
-      let uri = Services.io.newURI(requestPrincipal.origin, null, null);
+      let uri = Services.io.newURI(requestPrincipal.originNoSuffix, null, null);
       if (uri.scheme != "https") {
         return false;
       }
       // OK - we have https - now we can check the permission.
       let perm = Services.perms.testExactPermissionFromPrincipal(requestPrincipal,
                                                                  originOrPermission);
       return perm == Ci.nsIPermissionManager.ALLOW_ACTION;
     }
   } else {
     // a simple URI, so just check for an exact match.
     this._originCheckCallback = requestPrincipal => {
-      return originOrPermission.prePath === requestPrincipal.origin;
+      return originOrPermission.prePath === requestPrincipal.originNoSuffix;
     }
   }
   this._originOrPermission = originOrPermission;
 };
 
 this.WebChannel.prototype = {
 
   /**
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -717,18 +717,19 @@ HttpObserverManager = {
           mergeStatus(data, channel, kind);
         }
 
         try {
           let result = callback(data);
 
           if (result && typeof result === "object" && opts.blocking
               && !AddonManagerPermissions.isHostPermitted(uri.host)
-              && loadInfo && loadInfo.loadingPrincipal && loadInfo.loadingPrincipal.URI
-              && !AddonManagerPermissions.isHostPermitted(loadInfo.loadingPrincipal.URI.host)) {
+              && (!loadInfo || !loadInfo.loadingPrincipal
+                  || !loadInfo.loadingPrincipal.URI
+                  || !AddonManagerPermissions.isHostPermitted(loadInfo.loadingPrincipal.URI.host))) {
             handlerResults.push({opts, result});
           }
         } catch (e) {
           Cu.reportError(e);
         }
       }
     } catch (e) {
       Cu.reportError(e);
--- a/toolkit/modules/addons/WebRequestUpload.jsm
+++ b/toolkit/modules/addons/WebRequestUpload.jsm
@@ -8,314 +8,525 @@ const EXPORTED_SYMBOLS = ["WebRequestUpl
 
 /* exported WebRequestUpload */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
+Cu.importGlobalProperties(["TextEncoder"]);
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
+const {
+  DefaultMap,
+} = ExtensionUtils;
+
+XPCOMUtils.defineLazyServiceGetter(this, "mimeHeader", "@mozilla.org/network/mime-hdrparam;1",
+                                   "nsIMIMEHeaderParam");
+
+const BinaryInputStream = Components.Constructor(
+  "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream",
+  "setInputStream");
+const ConverterInputStream = Components.Constructor(
+  "@mozilla.org/intl/converter-input-stream;1", "nsIConverterInputStream",
+  "init");
+
 var WebRequestUpload;
 
-function rewind(stream) {
-  try {
-    if (stream instanceof Ci.nsISeekableStream) {
-      stream.seek(0, 0);
+/**
+ * Parses the given raw header block, and stores the value of each
+ * lower-cased header name in the resulting map.
+ */
+class Headers extends Map {
+  constructor(headerText) {
+    super();
+
+    if (headerText) {
+      this.parseHeaders(headerText);
+    }
+  }
+
+  parseHeaders(headerText) {
+    let lines = headerText.split("\r\n");
+
+    let lastHeader;
+    for (let line of lines) {
+      // The first empty line indicates the end of the header block.
+      if (line === "") {
+        return;
+      }
+
+      // Lines starting with whitespace are appended to the previous
+      // header.
+      if (/^\s/.test(line)) {
+        if (lastHeader) {
+          let val = this.get(lastHeader);
+          this.set(lastHeader, `${val}\r\n${line}`);
+        }
+        continue;
+      }
+
+      let match = /^(.*?)\s*:\s+(.*)/.exec(line);
+      if (match) {
+        lastHeader = match[1].toLowerCase();
+        this.set(lastHeader, match[2]);
+      }
     }
-  } catch (e) {
-    // It might be already closed, e.g. because of a previous error.
+  }
+
+  /**
+   * If the given header exists, and contains the given parameter,
+   * returns the value of that parameter.
+   *
+   * @param {string} name
+   *        The lower-cased header name.
+   * @param {string} paramName
+   *        The name of the parameter to retrieve, or empty to retrieve
+   *        the first (possibly unnamed) parameter.
+   * @returns {string | null}
+   */
+  getParam(name, paramName) {
+    return Headers.getParam(this.get(name), paramName);
+  }
+
+  /**
+   * If the given header value is non-null, and contains the given
+   * parameter, returns the value of that parameter.
+   *
+   * @param {string | null} header
+   *        The text of the header from which to retrieve the param.
+   * @param {string} paramName
+   *        The name of the parameter to retrieve, or empty to retrieve
+   *        the first (possibly unnamed) parameter.
+   * @returns {string | null}
+   */
+  static getParam(header, paramName) {
+    if (header) {
+      // The service expects this to be a raw byte string, so convert to
+      // UTF-8.
+      let bytes = new TextEncoder().encode(header);
+      let binHeader = String.fromCharCode(...bytes);
+
+      return mimeHeader.getParameterHTTP(binHeader, paramName, null,
+                                         false, {});
+    }
+
+    return null;
   }
 }
 
-function parseFormData(stream, channel, lenient = false) {
-  const BUFFER_SIZE = 8192; // Empirically it seemed a good compromise.
+/**
+ * Creates a new Object with a corresponding property for every
+ * key-value pair in the given Map.
+ *
+ * @param {Map} map
+ *        The map to convert.
+ * @returns {Object}
+ */
+function mapToObject(map) {
+  let result = {};
+  for (let [key, value] of map) {
+    result[key] = value;
+  }
+  return result;
+}
 
-  let mimeStream = null;
+/**
+ * Rewinds the given seekable input stream to its beginning, and catches
+ * any resulting errors.
+ *
+ * @param {nsISeekableStream} stream
+ *        The stream to rewind.
+ */
+function rewind(stream) {
+  // Do this outside the try-catch so that we throw if the stream is not
+  // actually seekable.
+  stream.QueryInterface(Ci.nsISeekableStream);
+
+  try {
+    stream.seek(0, 0);
+  } catch (e) {
+    // It might be already closed, e.g. because of a previous error.
+    Cu.reportError(e);
+  }
+}
 
-  if (stream instanceof Ci.nsIMIMEInputStream && stream.data) {
-    mimeStream = stream;
-    stream = stream.data;
+/**
+ * Iterates over all of the sub-streams that make up the given stream,
+ * or yields the stream itself if it is not a multi-part stream.
+ *
+ * @param {nsIIMultiplexInputStream|nsIStreamBufferAccess<nsIMultiplexInputStream>|nsIInputStream} outerStream
+ *        The outer stream over which to iterate.
+ */
+function* getStreams(outerStream) {
+  // If this is a multi-part stream, we need to iterate over its sub-streams,
+  // rather than treating it as a simple input stream. Since it may be wrapped
+  // in a buffered input stream, unwrap it before we do any checks.
+  let unbuffered = outerStream;
+  if (outerStream instanceof Ci.nsIStreamBufferAccess) {
+    unbuffered = outerStream.unbufferedStream;
   }
-  let multiplexStream = null;
-  if (stream instanceof Ci.nsIMultiplexInputStream) {
-    multiplexStream = stream;
+
+  if (unbuffered instanceof Ci.nsIMultiplexInputStream) {
+    let count = unbuffered.count;
+    for (let i = 0; i < count; i++) {
+      yield unbuffered.getStream(i);
+    }
+  } else {
+    yield outerStream;
   }
+}
+
+/**
+ * Parses the form data of the given stream as either multipart/form-data or
+ * x-www-form-urlencoded, and returns a map of its fields.
+ *
+ * @param {nsIInputStream} stream
+ *        The input stream from which to parse the form data.
+ * @param {nsIHttpChannel} channel
+ *        The channel to which the stream belongs.
+ * @param {boolean} [lenient = false]
+ *        If true, the operation will succeed even if there are UTF-8
+ *        decoding errors.
+ *
+ * @returns {Map<string, Array<string>> | null}
+ */
+function parseFormData(stream, channel, lenient = false) {
+  const BUFFER_SIZE = 8192;
 
   let touchedStreams = new Set();
 
+  /**
+   * Creates a converter input stream from the given raw input stream,
+   * and adds it to the list of streams to be rewound at the end of
+   * parsing.
+   *
+   * Returns null if the given raw stream cannot be rewound.
+   *
+   * @param {nsIInputStream} stream
+   *        The base stream from which to create a converter.
+   * @returns {ConverterInputStream | null}
+   */
   function createTextStream(stream) {
-    let textStream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
-    textStream.init(stream, "UTF-8", 0, lenient ? textStream.DEFAULT_REPLACEMENT_CHARACTER : 0);
-    if (stream instanceof Ci.nsISeekableStream) {
-      touchedStreams.add(stream);
+    if (!(stream instanceof Ci.nsISeekableStream)) {
+      return null;
     }
-    return textStream;
-  }
 
-  let streamIdx = 0;
-  function nextTextStream() {
-    for (; streamIdx < multiplexStream.count;) {
-      let currentStream = multiplexStream.getStream(streamIdx++);
-      if (currentStream instanceof Ci.nsIStringInputStream) {
-        touchedStreams.add(multiplexStream);
-        return createTextStream(currentStream);
-      }
-    }
-    return null;
-  }
-
-  let textStream;
-  if (multiplexStream) {
-    textStream = nextTextStream();
-  } else {
-    textStream = createTextStream(mimeStream || stream);
-  }
-
-  if (!textStream) {
-    return null;
+    touchedStreams.add(stream);
+    return ConverterInputStream(
+      stream, "UTF-8", 0,
+      lenient ? Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER
+              : 0);
   }
 
-  function readString() {
-    if (textStream) {
-      let textBuffer = {};
-      textStream.readString(BUFFER_SIZE, textBuffer);
-      return textBuffer.value;
-    }
-    return "";
+  /**
+   * Reads a string of no more than the given length from the given text
+   * stream.
+   *
+   * @param {ConverterInputStream} stream
+   *        The stream to read.
+   * @param {integer} [length = BUFFER_SIZE]
+   *        The maximum length of data to read.
+   * @returns {string}
+   */
+  function readString(stream, length = BUFFER_SIZE) {
+    let data = {};
+    stream.readString(length, data);
+    return data.value;
   }
 
-  function multiplexRead() {
-    let str = readString();
-    if (!str) {
-      textStream = nextTextStream();
-      if (textStream) {
-        str = multiplexRead();
+  /**
+   * Iterates over all of the sub-streams of the given (possibly multi-part)
+   * input stream, and yields a ConverterInputStream for each
+   * nsIStringInputStream among them.
+   *
+   * @param {nsIInputStream|nsIMultiplexInputStream} outerStream
+   *        The multi-part stream over which to iterate.
+   */
+  function* getTextStreams(outerStream) {
+    for (let stream of getStreams(outerStream)) {
+      if (stream instanceof Ci.nsIStringInputStream) {
+        touchedStreams.add(outerStream);
+        yield createTextStream(stream);
       }
     }
-    return str;
   }
 
-  let readChunk;
-  if (multiplexStream) {
-    readChunk = multiplexRead;
-  } else {
-    readChunk = readString;
-  }
-
-  function appendFormData(formData, name, value) {
-    if (name in formData) {
-      formData[name].push(value);
-    } else {
-      formData[name] = [value];
+  /**
+   * Iterates over all of the string streams of the given (possibly
+   * multi-part) input stream, and yields all of the available data in each as
+   * chunked strings, each no more than BUFFER_SIZE in length.
+   *
+   * @param {nsIInputStream|nsIMultiplexInputStream} outerStream
+   *        The multi-part stream over which to iterate.
+   */
+  function* readAllStrings(outerStream) {
+    for (let textStream of getTextStreams(outerStream)) {
+      let str;
+      while ((str = readString(textStream))) {
+        yield str;
+      }
     }
   }
 
-  function parseMultiPart(firstChunk, boundary = "") {
-    let formData = Object.create(null);
+  /**
+   * Iterates over the text contents of all of the string streams in the given
+   * (possibly multi-part) input stream, splits them at occurrences of the
+   * given boundary string, and yields each part.
+   *
+   * @param {nsIInputStream|nsIMultiplexInputStream} stream
+   *        The multi-part stream over which to iterate.
+   * @param {string} boundary
+   *        The boundary at which to split the parts.
+   * @param {string} [tail = ""]
+   *        Any initial data to prepend to the start of the stream data.
+   */
+  function* getParts(stream, boundary, tail = "") {
+    for (let chunk of readAllStrings(stream)) {
+      chunk = tail + chunk;
 
-    if (!boundary) {
-      let match = firstChunk.match(/^--\S+/);
-      if (!match) {
-        return null;
-      }
-      boundary = match[0];
+      let parts = chunk.split(boundary);
+      tail = parts.pop();
+
+      yield* parts;
     }
 
-    let unslash = (s) => s.replace(/\\"/g, '"');
-    let tail = "";
-    for (let chunk = firstChunk;
-         chunk || tail;
-         chunk = readChunk()) {
-      let parts;
-      if (chunk) {
-        chunk = tail + chunk;
-        parts = chunk.split(boundary);
-        tail = parts.pop();
-      } else {
-        parts = [tail];
-        tail = "";
+    if (tail) {
+      yield tail;
+    }
+  }
+
+  /**
+   * Parses the given stream as multipart/form-data and returns a map of its fields.
+   *
+   * @param {nsIMultiplexInputStream|nsIInputStream} stream
+   *        The (possibly multi-part) stream to parse.
+   * @param {string} boundary
+   *        The boundary at which to split the parts.
+   * @returns {Map<string, Array<string>>}
+   */
+  function parseMultiPart(stream, boundary) {
+    let formData = new DefaultMap(() => []);
+
+    for (let part of getParts(stream, boundary, "\r\n")) {
+      if (part === "") {
+        // The first part will always be empty.
+        continue;
+      }
+      if (part === "--\r\n") {
+        // This indicates the end of the stream.
+        break;
       }
 
-      for (let part of parts) {
-        let match = part.match(/^\r\nContent-Disposition: form-data; name="(.*)"\r\n(?:Content-Type: (\S+))?.*\r\n/i);
-        if (!match) {
-          continue;
-        }
-        let [header, name, contentType] = match;
-        if (contentType) {
-          let fileName;
-          // Since escaping inside Content-Disposition subfields is still poorly defined and buggy (see Bug 136676),
-          // currently we always consider backslash-prefixed quotes as escaped even if that's not generally true
-          // (i.e. in a field whose value actually ends with a backslash).
-          // Therefore in this edge case we may end coalescing name and filename, which is marginally better than
-          // potentially truncating the name field at the wrong point, at least from a XSS filter POV.
-          match = name.match(/^(.*[^\\])"; filename="(.*)/);
-          if (match) {
-            [, name, fileName] = match;
-          }
-          appendFormData(formData, unslash(name), fileName ? unslash(fileName) : "");
-        } else {
-          appendFormData(formData, unslash(name), part.slice(header.length, -2));
-        }
+      let end = part.indexOf("\r\n\r\n");
+
+      // All valid parts must begin with \r\n, and we can't process form
+      // fields without any header block.
+      if (!part.startsWith("\r\n") || end <= 0) {
+        throw new Error("Invalid MIME stream");
       }
+
+      let content = part.slice(end + 4);
+      let headerText = part.slice(2, end);
+      let headers = new Headers(headerText);
+
+      let name = headers.getParam("content-disposition", "name");
+      if (!name || headers.getParam("content-disposition", "") !== "form-data") {
+        throw new Error("Invalid MIME stream: No valid Content-Disposition header");
+      }
+
+      if (headers.has("content-type")) {
+        // For file upload fields, we return the filename, rather than the
+        // file data.
+        let filename = headers.getParam("content-disposition", "filename");
+        content = filename || "";
+      }
+      formData.get(name).push(content);
     }
 
     return formData;
   }
 
-  function parseUrlEncoded(firstChunk) {
-    let formData = Object.create(null);
+  /**
+   * Parses the given stream as x-www-form-urlencoded, and returns a map of its fields.
+   *
+   * @param {nsIInputStream} stream
+   *        The stream to parse.
+   * @returns {Map<string, Array<string>>}
+   */
+  function parseUrlEncoded(stream) {
+    let formData = new DefaultMap(() => []);
 
-    let tail = "";
-    for (let chunk = firstChunk;
-         chunk || tail;
-         chunk = readChunk()) {
-      let pairs;
-      if (chunk) {
-        chunk = tail + chunk.trim();
-        pairs = chunk.split("&");
-        tail = pairs.pop();
-      } else {
-        chunk = tail;
-        tail = "";
-        pairs = [chunk];
-      }
-      for (let pair of pairs) {
-        let [name, value] = pair.replace(/\+/g, " ").split("=").map(decodeURIComponent);
-        appendFormData(formData, name, value);
-      }
+    for (let part of getParts(stream, "&")) {
+      let [name, value] = part.replace(/\+/g, " ").split("=").map(decodeURIComponent);
+      formData.get(name).push(value);
     }
 
     return formData;
   }
 
   try {
-    let chunk = readChunk();
+    let headers;
+    if (stream instanceof Ci.nsIMIMEInputStream && stream.data) {
+      // MIME input streams encode additional headers as a block at the
+      // beginning of their stream. The actual request data comes from a
+      // sub-stream, which is accessible via their `data` member. The
+      // difference in available bytes between the outer stream and the
+      // inner data stream tells us the size of that header block.
+      //
+      // Since we need to know at least the value of the Content-Type
+      // header to properly parse the request body, we need to read and
+      // parse the header block in order to extract it.
 
-    if (multiplexStream) {
-      touchedStreams.add(multiplexStream);
-      return parseMultiPart(chunk);
-    }
-    let contentType;
-    if (/^Content-Type:/i.test(chunk)) {
-      contentType = chunk.replace(/^Content-Type:\s*/i, "");
-      chunk = chunk.slice(chunk.indexOf("\r\n\r\n") + 4);
-    } else {
-      try {
-        contentType = channel.getRequestHeader("Content-Type");
-      } catch (e) {
-        Cu.reportError(e);
-        return null;
-      }
+      headers = readString(createTextStream(stream),
+                           stream.available() - stream.data.available());
+
+      rewind(stream);
+      stream = stream.data;
     }
 
-    let match = contentType.match(/^(?:multipart\/form-data;\s*boundary=(\S*)|application\/x-www-form-urlencoded\s)/i);
-    if (match) {
-      let boundary = match[1];
-      if (boundary) {
-        return parseMultiPart(chunk, boundary);
-      }
-      return parseUrlEncoded(chunk);
+    let contentType;
+    try {
+      contentType = channel.getRequestHeader("Content-Type");
+    } catch (e) {
+      contentType = new Headers(headers).get("content-type");
+    }
+
+    switch (Headers.getParam(contentType, "")) {
+      case "multipart/form-data":
+        let boundary = Headers.getParam(contentType, "boundary");
+        return parseMultiPart(stream, `\r\n--${boundary}`);
+
+      case "application/x-www-form-urlencoded":
+        return parseUrlEncoded(stream);
     }
   } finally {
     for (let stream of touchedStreams) {
       rewind(stream);
     }
   }
 
   return null;
 }
 
-function createFormData(stream, channel) {
+/**
+ * Parses the form data of the given stream as either multipart/form-data or
+ * x-www-form-urlencoded, and returns a map of its fields.
+ *
+ * Returns null if the stream is not seekable.
+ *
+ * @param {nsIMultiplexInputStream|nsIInputStream} stream
+ *        The (possibly multi-part) stream from which to create the form data.
+ * @param {nsIChannel} channel
+ *        The channel to which the stream belongs.
+ * @param {boolean} [lenient = false]
+ *        If true, the operation will succeed even if there are UTF-8
+ *        decoding errors.
+ * @returns {Map<string, Array<string>> | null}
+ */
+function createFormData(stream, channel, lenient) {
+  if (!(stream instanceof Ci.nsISeekableStream)) {
+    return null;
+  }
+
   try {
-    rewind(stream);
-    return parseFormData(stream, channel);
+    let formData = parseFormData(stream, channel, lenient);
+    if (formData) {
+      return mapToObject(formData);
+    }
   } catch (e) {
     Cu.reportError(e);
   } finally {
     rewind(stream);
   }
   return null;
 }
 
-function convertRawData(outerStream) {
-  let raw = [];
-  let totalBytes = 0;
-
-  // Here we read the stream up to WebRequestUpload.MAX_RAW_BYTES, returning false if we had to truncate the result.
-  function readAll(stream) {
-    let unbuffered = stream.unbufferedStream || stream;
-    if (unbuffered instanceof Ci.nsIFileInputStream) {
-      raw.push({file: "<file>"}); // Full paths not supported yet for naked files (follow up bug)
-      return true;
+/**
+ * Iterates over all of the sub-streams of the given (possibly multi-part)
+ * input stream, and yields an object containing the data for each chunk, up
+ * to a total of `maxRead` bytes.
+ *
+ * @param {nsIMultiplexInputStream|nsIInputStream} outerStream
+ *        The stream for which to return data.
+ * @param {integer} [maxRead = WebRequestUpload.MAX_RAW_BYTES]
+ *        The maximum total bytes to read.
+ */
+function* getRawDataChunked(outerStream, maxRead = WebRequestUpload.MAX_RAW_BYTES) {
+  for (let stream of getStreams(outerStream)) {
+    // We need to inspect the stream to make sure it's not a file input
+    // stream. If it's wrapped in a buffered input stream, unwrap it first,
+    // so we can inspect the inner stream directly.
+    let unbuffered = stream;
+    if (stream instanceof Ci.nsIStreamBufferAccess) {
+      unbuffered = stream.unbufferedStream;
     }
-    rewind(stream);
 
-    let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
-    binaryStream.setInputStream(stream);
-    const MAX_BYTES = WebRequestUpload.MAX_RAW_BYTES;
+    // For file fields, we return an object containing the full path of
+    // the file, rather than its data.
+    if (unbuffered instanceof Ci.nsIFileInputStream) {
+      // But this is not actually supported yet.
+      yield {file: "<file>"};
+      continue;
+    }
+
     try {
-      for (let available; (available = binaryStream.available());) {
-        let size = Math.min(MAX_BYTES - totalBytes, available);
-        let bytes = new ArrayBuffer(size);
-        binaryStream.readArrayBuffer(size, bytes);
-        let chunk = {bytes};
-        raw.push(chunk);
-        totalBytes += size;
+      let binaryStream = BinaryInputStream(stream);
+      let available;
+      while ((available = binaryStream.available())) {
+        let buffer = new ArrayBuffer(Math.min(maxRead, available));
+        binaryStream.readArrayBuffer(buffer.byteLength, buffer);
+
+        maxRead -= buffer.byteLength;
+
+        let chunk = {bytes: buffer};
 
-        if (totalBytes >= MAX_BYTES) {
-          if (size < available) {
-            chunk.truncated = true;
-            chunk.originalSize = available;
-            return false;
-          }
-          break;
+        if (buffer.byteLength < available) {
+          chunk.truncated = true;
+          chunk.originalSize = available;
+        }
+
+        yield chunk;
+
+        if (maxRead <= 0) {
+          return;
         }
       }
     } finally {
       rewind(stream);
     }
-    return true;
   }
-
-  let unbuffered = outerStream;
-  if (outerStream instanceof Ci.nsIStreamBufferAccess) {
-    unbuffered = outerStream.unbufferedStream;
-  }
-
-  if (unbuffered instanceof Ci.nsIMultiplexInputStream) {
-    for (let i = 0, count = unbuffered.count; i < count; i++) {
-      if (!readAll(unbuffered.getStream(i))) {
-        break;
-      }
-    }
-  } else {
-    readAll(outerStream);
-  }
-
-  return raw;
 }
 
 WebRequestUpload = {
   createRequestBody(channel) {
-    let requestBody = null;
     if (channel instanceof Ci.nsIUploadChannel && channel.uploadStream) {
       try {
-        let stream = channel.uploadStream.QueryInterface(Ci.nsISeekableStream);
+        let stream = channel.uploadStream;
+
         let formData = createFormData(stream, channel);
         if (formData) {
-          requestBody = {formData};
-        } else {
-          requestBody = {raw: convertRawData(stream), lenientFormData: createFormData(stream, channel, true)};
+          return {formData};
         }
+
+        // If we failed to parse the stream as form data, return it as a
+        // sequence of raw data chunks, along with a leniently-parsed form
+        // data object, which ignores encoding errors.
+        return {
+          raw: Array.from(getRawDataChunked(stream)),
+          lenientFormData: createFormData(stream, channel, true),
+        };
       } catch (e) {
         Cu.reportError(e);
-        requestBody = {error: e.message || String(e)};
+        return {error: e.message || String(e)};
       }
-      requestBody = Object.freeze(requestBody);
     }
-    return requestBody;
+
+    return null;
   },
 };
 
-XPCOMUtils.defineLazyPreferenceGetter(WebRequestUpload, "MAX_RAW_BYTES", "webextensions.webRequest.requestBodyMaxRawBytes");
+XPCOMUtils.defineLazyPreferenceGetter(WebRequestUpload, "MAX_RAW_BYTES",
+                                      "webextensions.webRequest.requestBodyMaxRawBytes");
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -522,16 +522,24 @@ auto GeckoAppShell::IsTablet() -> bool
 constexpr char GeckoAppShell::KillAnyZombies_t::name[];
 constexpr char GeckoAppShell::KillAnyZombies_t::signature[];
 
 auto GeckoAppShell::KillAnyZombies() -> void
 {
     return mozilla::jni::Method<KillAnyZombies_t>::Call(GeckoAppShell::Context(), nullptr);
 }
 
+constexpr char GeckoAppShell::LaunchOrBringToFront_t::name[];
+constexpr char GeckoAppShell::LaunchOrBringToFront_t::signature[];
+
+auto GeckoAppShell::LaunchOrBringToFront() -> void
+{
+    return mozilla::jni::Method<LaunchOrBringToFront_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
 constexpr char GeckoAppShell::LoadPluginClass_t::name[];
 constexpr char GeckoAppShell::LoadPluginClass_t::signature[];
 
 auto GeckoAppShell::LoadPluginClass(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> mozilla::jni::Class::LocalRef
 {
     return mozilla::jni::Method<LoadPluginClass_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
 }
 
@@ -588,24 +596,16 @@ constexpr char GeckoAppShell::OnSensorCh
 constexpr char GeckoAppShell::OpenUriExternal_t::name[];
 constexpr char GeckoAppShell::OpenUriExternal_t::signature[];
 
 auto GeckoAppShell::OpenUriExternal(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3, mozilla::jni::String::Param a4, mozilla::jni::String::Param a5) -> bool
 {
     return mozilla::jni::Method<OpenUriExternal_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1, a2, a3, a4, a5);
 }
 
-constexpr char GeckoAppShell::OpenWindowForNotification_t::name[];
-constexpr char GeckoAppShell::OpenWindowForNotification_t::signature[];
-
-auto GeckoAppShell::OpenWindowForNotification() -> void
-{
-    return mozilla::jni::Method<OpenWindowForNotification_t>::Call(GeckoAppShell::Context(), nullptr);
-}
-
 constexpr char GeckoAppShell::PerformHapticFeedback_t::name[];
 constexpr char GeckoAppShell::PerformHapticFeedback_t::signature[];
 
 auto GeckoAppShell::PerformHapticFeedback(bool a0) -> void
 {
     return mozilla::jni::Method<PerformHapticFeedback_t>::Call(GeckoAppShell::Context(), nullptr, a0);
 }
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1421,16 +1421,35 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     static auto KillAnyZombies() -> void;
 
+    struct LaunchOrBringToFront_t {
+        typedef GeckoAppShell Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "launchOrBringToFront";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto LaunchOrBringToFront() -> void;
+
     struct LoadPluginClass_t {
         typedef GeckoAppShell Owner;
         typedef mozilla::jni::Class::LocalRef ReturnType;
         typedef mozilla::jni::Class::Param SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
                 mozilla::jni::String::Param> Args;
         static constexpr char name[] = "loadPluginClass";
@@ -1670,35 +1689,16 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     static auto OpenUriExternal(mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) -> bool;
 
-    struct OpenWindowForNotification_t {
-        typedef GeckoAppShell Owner;
-        typedef void ReturnType;
-        typedef void SetterType;
-        typedef mozilla::jni::Args<> Args;
-        static constexpr char name[] = "openWindowForNotification";
-        static constexpr char signature[] =
-                "()V";
-        static const bool isStatic = true;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-        static const mozilla::jni::CallingThread callingThread =
-                mozilla::jni::CallingThread::ANY;
-        static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::CURRENT;
-    };
-
-    static auto OpenWindowForNotification() -> void;
-
     struct PerformHapticFeedback_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 bool> Args;
         static constexpr char name[] = "performHapticFeedback";
         static constexpr char signature[] =
--- a/widget/nsDeviceContextSpecProxy.cpp
+++ b/widget/nsDeviceContextSpecProxy.cpp
@@ -9,21 +9,18 @@
 #include "gfxASurface.h"
 #include "gfxPlatform.h"
 #include "mozilla/gfx/DrawEventRecorder.h"
 #include "mozilla/gfx/PrintTargetThebes.h"
 #include "mozilla/layout/RemotePrintJobChild.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Unused.h"
 #include "nsComponentManagerUtils.h"
-#include "nsAppDirectoryServiceDefs.h"
-#include "nsDirectoryServiceUtils.h"
 #include "nsIPrintSession.h"
 #include "nsIPrintSettings.h"
-#include "nsIUUIDGenerator.h"
 
 using mozilla::Unused;
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 NS_IMPL_ISUPPORTS(nsDeviceContextSpecProxy, nsIDeviceContextSpec)
 
@@ -132,47 +129,17 @@ nsDeviceContextSpecProxy::GetPrintingSca
   return mRealDeviceContextSpec->GetPrintingScale();
 }
 
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::BeginDocument(const nsAString& aTitle,
                                         const nsAString& aPrintToFileName,
                                         int32_t aStartPage, int32_t aEndPage)
 {
-  nsCOMPtr<nsIFile> recordingFile;
-  nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
-                                       getter_AddRefs(recordingFile));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsCOMPtr<nsIUUIDGenerator> uuidgen =
-    do_GetService("@mozilla.org/uuid-generator;1", &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsID uuid;
-  rv = uuidgen->GenerateUUIDInPlace(&uuid);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  char uuidChars[NSID_LENGTH];
-  uuid.ToProvidedString(uuidChars);
-  mRecorderFile.AssignASCII(uuidChars);
-  rv = recordingFile->AppendNative(mRecorderFile);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsAutoCString recordingPath;
-  rv = recordingFile->GetNativePath(recordingPath);
-
-  mRecorder = new mozilla::gfx::DrawEventRecorderFile(recordingPath.get());
+  mRecorder = new mozilla::gfx::DrawEventRecorderMemory();
   return mRemotePrintJob->InitializePrint(nsString(aTitle),
                                           nsString(aPrintToFileName),
                                           aStartPage, aEndPage);
 }
 
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::EndDocument()
 {
@@ -185,23 +152,39 @@ nsDeviceContextSpecProxy::AbortDocument(
 {
   Unused << mRemotePrintJob->SendAbortPrint(NS_OK);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::BeginPage()
 {
-  // Reopen the file, if necessary, ready for the next page.
-  mRecorder->OpenAndTruncate();
-
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::EndPage()
 {
+  // Save the current page recording to shared memory.
+  mozilla::ipc::Shmem storedPage;
+  size_t recordingSize = mRecorder->RecordingSize();
+  if (!mRemotePrintJob->AllocShmem(recordingSize,
+                                   mozilla::ipc::SharedMemory::TYPE_BASIC,
+                                   &storedPage)) {
+    NS_WARNING("Failed to create shared memory for remote printing.");
+    return NS_ERROR_FAILURE;
+  }
+
+  bool success = mRecorder->CopyRecording(storedPage.get<char>(), recordingSize);
+  if (!success) {
+    NS_WARNING("Copying recording to shared memory was not succesful.");
+    return NS_ERROR_FAILURE;
+  }
+
+  // Wipe the recording to free memory. The recorder does not forget which data
+  // backed objects that it has stored.
+  mRecorder->WipeRecording();
+
   // Send the page recording to the parent.
-  mRecorder->Close();
-  mRemotePrintJob->ProcessPage(mRecorderFile);
+  mRemotePrintJob->ProcessPage(storedPage);
 
   return NS_OK;
 }
--- a/widget/nsDeviceContextSpecProxy.h
+++ b/widget/nsDeviceContextSpecProxy.h
@@ -4,23 +4,22 @@
  * 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 nsDeviceContextSpecProxy_h
 #define nsDeviceContextSpecProxy_h
 
 #include "nsIDeviceContextSpec.h"
 #include "nsCOMPtr.h"
-#include "nsString.h"
 
 class nsIPrintSession;
 
 namespace mozilla {
 namespace gfx {
-class DrawEventRecorderFile;
+class DrawEventRecorderMemory;
 }
 
 namespace layout {
 class RemotePrintJobChild;
 }
 }
 
 class nsDeviceContextSpecProxy final : public nsIDeviceContextSpec
@@ -53,13 +52,12 @@ public:
 
 private:
   ~nsDeviceContextSpecProxy() {}
 
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   nsCOMPtr<nsIPrintSession> mPrintSession;
   nsCOMPtr<nsIDeviceContextSpec> mRealDeviceContextSpec;
   RefPtr<mozilla::layout::RemotePrintJobChild> mRemotePrintJob;
-  RefPtr<mozilla::gfx::DrawEventRecorderFile> mRecorder;
-  nsCString mRecorderFile;
+  RefPtr<mozilla::gfx::DrawEventRecorderMemory> mRecorder;
 };
 
 #endif // nsDeviceContextSpecProxy_h
--- a/xpcom/io/nsAppDirectoryServiceDefs.h
+++ b/xpcom/io/nsAppDirectoryServiceDefs.h
@@ -105,14 +105,11 @@
 //
 // New code should avoid writing to the filesystem from the content process
 // and should instead proxy through the parent process whenever possible.
 //
 // At present, all sandboxed content processes use the same directory for
 // NS_APP_CONTENT_PROCESS_TEMP_DIR, but that should not be relied upon.
 //
 #define NS_APP_CONTENT_PROCESS_TEMP_DIR         "ContentTmpD"
-#else
-// Otherwise NS_APP_CONTENT_PROCESS_TEMP_DIR must match NS_OS_TEMP_DIR.
-#define NS_APP_CONTENT_PROCESS_TEMP_DIR         "TmpD"
 #endif // (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
 
 #endif // nsAppDirectoryServiceDefs_h___