merge mozilla-inbound to mozilla-central a=merge FIREFOX_AURORA_47_BASE
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 07 Mar 2016 11:34:06 +0100
changeset 287009 68d3781deda0d4d58ec9877862830db89669b3a5
parent 286984 a75d87f4cf527815e6271932676800138c27da70 (current diff)
parent 287008 4b9a16cc88c2e2689dc2c9c47be8fc01ea5439da (diff)
child 287010 552ef9bae23386aa976f28e0d66ed2ae92bc2194
child 287017 087905ffec78fb4902e3f7dee90c3be2899ec0e7
child 287080 4ac661e25c72acd51c8c4fad4da64aa96e4fef11
push id30060
push usercbook@mozilla.com
push dateMon, 07 Mar 2016 10:34:14 +0000
treeherdermozilla-central@68d3781deda0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
first release with
nightly linux32
68d3781deda0 / 47.0a1 / 20160307030208 / files
nightly linux64
68d3781deda0 / 47.0a1 / 20160307030208 / files
nightly mac
68d3781deda0 / 47.0a1 / 20160307030208 / files
nightly win32
68d3781deda0 / 47.0a1 / 20160307030208 / files
nightly win64
68d3781deda0 / 47.0a1 / 20160307030208 / 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
layout/reftests/fonts/sil/Charis-license.txt
layout/reftests/fonts/sil/CharisSIL-R.ttf
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -22,16 +22,18 @@
 #include "nsError.h"
 #include "nsIDocument.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsMimeTypes.h"
 #include "nsProxyRelease.h"
 #include "nsTArray.h"
 #include "GeckoProfiler.h"
+#include "nsContentTypeParser.h"
+#include "nsCharSeparatedTokenizer.h"
 
 #ifdef LOG
 #undef LOG
 #endif
 
 mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
 #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
 
@@ -966,16 +968,21 @@ MediaRecorder::Constructor(const GlobalO
                            ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
   if (!ownerWindow) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
+  if (!IsTypeSupported(aInitDict.mMimeType)) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
   RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
   object->SetOptions(aInitDict);
   return object.forget();
 }
 
 /* static */ already_AddRefed<MediaRecorder>
 MediaRecorder::Constructor(const GlobalObject& aGlobal,
                            AudioNode& aSrcAudioNode,
@@ -1000,16 +1007,21 @@ MediaRecorder::Constructor(const GlobalO
 
   // aSrcOutput doesn't matter to destination node because it has no output.
   if (aSrcAudioNode.NumberOfOutputs() > 0 &&
        aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
+  if (!IsTypeSupported(aInitDict.mMimeType)) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
   RefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
                                                      aSrcOutput,
                                                      ownerWindow);
   object->SetOptions(aInitDict);
   return object.forget();
 }
 
 void
@@ -1030,16 +1042,117 @@ MediaRecorder::SetOptions(const MediaRec
   //
   // Until dynamic changes are supported, I prefer to be safe and err
   // slightly high
   if (aInitDict.mBitsPerSecond.WasPassed() && !aInitDict.mVideoBitsPerSecond.WasPassed()) {
     mVideoBitsPerSecond = mBitsPerSecond;
   }
 }
 
+static char const *const gWebMAudioEncoderCodecs[3] = {
+  "vorbis",
+  "opus",
+  // no VP9 yet
+  nullptr,
+};
+static char const *const gWebMVideoEncoderCodecs[5] = {
+  "vorbis",
+  "opus",
+  "vp8",
+  "vp8.0",
+  // no VP9 yet
+  nullptr,
+};
+static char const *const gOggAudioEncoderCodecs[2] = {
+  "opus",
+  // we could support vorbis here too, but don't
+  nullptr,
+};
+
+template <class String>
+static bool
+CodecListContains(char const *const * aCodecs, const String& aCodec)
+{
+  for (int32_t i = 0; aCodecs[i]; ++i) {
+    if (aCodec.EqualsASCII(aCodecs[i]))
+      return true;
+  }
+  return false;
+}
+
+/* static */
+bool
+MediaRecorder::IsTypeSupported(GlobalObject& aGlobal, const nsAString& aMIMEType)
+{
+  return IsTypeSupported(aMIMEType);
+}
+
+/* static */
+bool
+MediaRecorder::IsTypeSupported(const nsAString& aMIMEType)
+{
+  char const* const* codeclist = nullptr;
+
+  if (aMIMEType.IsEmpty()) {
+    return true;
+  }
+
+  nsContentTypeParser parser(aMIMEType);
+  nsAutoString mimeType;
+  nsresult rv = parser.GetType(mimeType);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  // effectively a 'switch (mimeType) {'
+  if (mimeType.EqualsLiteral(AUDIO_OGG)) {
+    if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) {
+      codeclist = gOggAudioEncoderCodecs;
+    }
+  }
+#ifdef MOZ_WEBM_ENCODER
+  else if (mimeType.EqualsLiteral(VIDEO_WEBM) &&
+           MediaEncoder::IsWebMEncoderEnabled()) {
+    codeclist = gWebMVideoEncoderCodecs;
+  }
+#endif
+#ifdef MOZ_OMX_ENCODER
+    // We're working on MP4 encoder support for desktop
+  else if (mimeType.EqualsLiteral(VIDEO_MP4) ||
+           mimeType.EqualsLiteral(AUDIO_3GPP) ||
+           mimeType.EqualsLiteral(AUDIO_3GPP2)) {
+    if (MediaEncoder::IsOMXEncoderEnabled()) {
+      // XXX check codecs for MP4/3GPP
+      return true;
+    }
+  }
+#endif
+
+  // codecs don't matter if we don't support the container
+  if (!codeclist) {
+    return false;
+  }
+  // now filter on codecs, and if needed rescind support
+  nsAutoString codecstring;
+  rv = parser.GetParameter("codecs", codecstring);
+
+  nsTArray<nsString> codecs;
+  if (!ParseCodecsString(codecstring, codecs)) {
+    return false;
+  }
+  for (const nsString& codec : codecs) {
+    if (!CodecListContains(codeclist, codec)) {
+      // Totally unsupported codec
+      return false;
+    }
+  }
+
+  return true;
+}
+
 nsresult
 MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   if (!CheckPrincipal()) {
     // Media is not same-origin, don't allow the data out.
     RefPtr<nsIDOMBlob> blob = aBlob;
     return NS_ERROR_DOM_SECURITY_ERR;
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -17,16 +17,17 @@
 namespace mozilla {
 
 class AudioNodeStream;
 class DOMMediaStream;
 class ErrorResult;
 class MediaInputPort;
 struct MediaRecorderOptions;
 class MediaStream;
+class GlobalObject;
 
 namespace dom {
 
 class AudioNode;
 
 /**
  * Implementation of https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/MediaRecorder.html
  * The MediaRecorder accepts a mediaStream as input source passed from UA. When recorder starts,
@@ -73,16 +74,19 @@ public:
   void RequestData(ErrorResult& aResult);
   // Return the The DOMMediaStream passed from UA.
   DOMMediaStream* Stream() const { return mDOMStream; }
   // The current state of the MediaRecorder object.
   RecordingState State() const { return mState; }
   // Return the current encoding MIME type selected by the MediaEncoder.
   void GetMimeType(nsString &aMimeType);
 
+  static bool IsTypeSupported(GlobalObject& aGlobal, const nsAString& aType);
+  static bool IsTypeSupported(const nsAString& aType);
+
   // Construct a recorder with a DOM media stream object as its source.
   static already_AddRefed<MediaRecorder>
   Constructor(const GlobalObject& aGlobal,
               DOMMediaStream& aStream,
               const MediaRecorderOptions& aInitDict,
               ErrorResult& aRv);
   // Construct a recorder with a Web Audio destination node as its source.
   static already_AddRefed<MediaRecorder>
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -1282,25 +1282,16 @@ nsresult FileMediaResource::Open(nsIStre
       NS_ENSURE_SUCCESS(rv, rv);
 
       rv = NS_NewLocalFileInputStream(
         getter_AddRefs(mInput), file, -1, -1, nsIFileInputStream::SHARE_DELETE);
     } else if (IsBlobURI(mURI)) {
       rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
     }
   } else {
-
-#ifdef DEBUG
-    {
-      nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
-      MOZ_ASSERT((loadInfo->GetSecurityMode() &
-                 nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) == 0,
-                 "can not enforce CORS when calling Open2()");
-    }
-#endif
     rv = mChannel->Open2(getter_AddRefs(mInput));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   mSeekable = do_QueryInterface(mInput);
   if (!mSeekable) {
     // XXX The file may just be a .url or similar
     // shortcut that points to a Web site. We need to fix this by
@@ -1355,32 +1346,28 @@ already_AddRefed<MediaResource> FileMedi
   dom::HTMLMediaElement* element = owner->GetMediaElement();
   if (!element) {
     // The decoder is being shut down, so we can't clone
     return nullptr;
   }
   nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
   NS_ENSURE_TRUE(loadGroup, nullptr);
 
-  nsSecurityFlags securityFlags = element->ShouldCheckAllowOrigin()
-                                  ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
-                                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
-
   MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
   nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
     nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
 
   nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
 
   nsCOMPtr<nsIChannel> channel;
   nsresult rv =
     NS_NewChannel(getter_AddRefs(channel),
                   mURI,
                   element,
-                  securityFlags,
+                  nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
                   contentPolicyType,
                   loadGroup,
                   nullptr,  // aCallbacks
                   loadFlags);
 
   if (NS_FAILED(rv))
     return nullptr;
 
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -727,16 +727,22 @@ tags=msg
 [test_mediarecorder_record_stopms.html]
 tags=msg
 [test_mediarecorder_record_timeslice.html]
 tags=msg capturestream
 [test_mediarecorder_reload_crash.html]
 tags=msg capturestream
 [test_mediarecorder_unsupported_src.html]
 tags=msg
+[test_mediarecorder_webm_support.html]
+skip-if = os == 'android' || arch == 'arm' || arch == 'arm64'
+tags=msg
+[test_mediarecorder_mp4_support.html]
+skip-if = toolkit != 'gonk' || android_version < '17' # Android/Gonk before SDK version 17 does not have the OMX Encoder API.
+tags=msg
 [test_mediarecorder_record_getdata_afterstart.html]
 tags=msg capturestream
 [test_mediatrack_consuming_mediaresource.html]
 [test_mediatrack_consuming_mediastream.html]
 tags=msg
 [test_mediatrack_events.html]
 skip-if = toolkit == 'gonk' && debug # bug 1065924
 [test_mediatrack_parsing_ogg.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_mp4_support.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media Recording - test mp4 MIME support</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ok(MediaRecorder.isTypeSupported('video/mp4'), 'Should support video/mp4');
+
+</script>
+</head>
+</html>
--- a/dom/media/test/test_mediarecorder_unsupported_src.html
+++ b/dom/media/test/test_mediarecorder_unsupported_src.html
@@ -5,17 +5,25 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957439">Mozilla Bug 957439</a>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
+
 function startTest() {
+  // also do general checks on mimetype support for audio-only
+  ok(MediaRecorder.isTypeSupported("audio/ogg"), 'Should support audio/ogg');
+  ok(MediaRecorder.isTypeSupported('audio/ogg; codecs="opus"'), 'Should support audio/ogg+opus');
+  ok(!MediaRecorder.isTypeSupported('audio/ogg; codecs="foobar"'), 'Should not support audio/ogg + unknown_codec');
+  ok(!MediaRecorder.isTypeSupported("video/webm"), 'Should not support video/webm');
+  ok(!MediaRecorder.isTypeSupported("video/mp4"), 'Should not support video/mp4');
+
   navigator.mozGetUserMedia({audio: false, video: true, fake: true},
     function(stream) {
 
       // Expected callback sequence should be:
       // 1. onerror (from start)
       // 2. onerror (from pause)
       // 3. ondataavailable
       // 4. onstop
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_webm_support.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media Recording - test WebM MIME support</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ok(MediaRecorder.isTypeSupported('video/webm'), 'Should support video/webm');
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8, vorbis"'), 'Should support video/webm + vp8/vorbis');
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, vorbis"'), 'Should not support video/webm + vp9/vorbis');
+</script>
+</head>
+</html>
--- a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -146,16 +146,17 @@ SpeechTaskCallback::OnError(uint32_t aIn
   mTask->DispatchError(GetTimeDurationFromStart(), aIndex);
 }
 
 void
 SpeechTaskCallback::OnDidFinishSpeaking()
 {
   mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
   // no longer needed
+  [mSpeechSynthesizer setDelegate:nil];
   mTask = nullptr;
 }
 
 @interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
 {
 @private
   SpeechTaskCallback* mCallback;
 }
--- a/dom/webidl/MediaRecorder.webidl
+++ b/dom/webidl/MediaRecorder.webidl
@@ -42,16 +42,18 @@ interface MediaRecorder : EventTarget {
   [Throws]
   void pause();
 
   [Throws]
   void resume();
 
   [Throws]
   void requestData();
+
+  static boolean isTypeSupported(DOMString type);
 };
 
 dictionary MediaRecorderOptions {
   DOMString mimeType = ""; // Default encoding mimeType.
   unsigned long audioBitsPerSecond;
   unsigned long videoBitsPerSecond;
   unsigned long bitsPerSecond;
 };
--- a/image/SVGDocumentWrapper.cpp
+++ b/image/SVGDocumentWrapper.cpp
@@ -110,16 +110,22 @@ SVGDocumentWrapper::FlushImageTransformI
   svgElem->FlushImageTransformInvalidation();
   FlushLayout();
   mIgnoreInvalidation = false;
 }
 
 bool
 SVGDocumentWrapper::IsAnimated()
 {
+  // Can be called for animated images during shutdown, after we've
+  // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
+  if (!mViewer) {
+    return false;
+  }
+
   nsIDocument* doc = mViewer->GetDocument();
   if (!doc) {
     return false;
   }
   if (doc->Timeline()->HasAnimations()) {
     // CSS animations (technically HasAnimations() also checks for CSS
     // transitions and Web animations but since SVG-as-an-image doesn't run
     // script they will never run in the document that we wrap).
new file mode 100644
--- /dev/null
+++ b/image/test/crashtests/1253362-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+
+<div style="content: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22background%3A%20url%28data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%253C%252Fsvg%253E%29%22%3E%3C%2Fsvg%3E%0D%0A)"></div>
+
+</body>
+</html>
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -16,16 +16,17 @@ load 1205923-1.html
 load 1212954-1.svg
 load 1235605.gif
 load 1241728-1.html
 load 1241729-1.html
 load 1242093-1.html
 load 1242778-1.png
 load 1249576-1.png
 load 1251091-1.html
+load 1253362-1.html
 load colormap-range.gif
 HTTP load delayedframe.sjs # A 3-frame animated GIF with an inordinate delay between the second and third frame
 
 # Animated gifs with a very large canvas, but tiny actual content.
 skip-if(B2G) load delaytest.html?523528-1.gif
 skip-if(B2G) load delaytest.html?523528-2.gif
 
 # Bug 1160801 - Ensure that we handle invalid disposal types.
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -276,39 +276,219 @@ private:
     MessageChannel& mThat;
 
     // Disable harmful methods.
     CxxStackFrame() = delete;
     CxxStackFrame(const CxxStackFrame&) = delete;
     CxxStackFrame& operator=(const CxxStackFrame&) = delete;
 };
 
+class AutoEnterTransaction
+{
+public:
+    explicit AutoEnterTransaction(MessageChannel *aChan,
+                                  int32_t aMsgSeqno,
+                                  int32_t aTransactionID,
+                                  int aPriority)
+      : mChan(aChan),
+        mActive(true),
+        mOutgoing(true),
+        mPriority(aPriority),
+        mSeqno(aMsgSeqno),
+        mTransaction(aTransactionID),
+        mNext(mChan->mTransactionStack)
+    {
+        mChan->mMonitor->AssertCurrentThreadOwns();
+        mChan->mTransactionStack = this;
+    }
+
+    explicit AutoEnterTransaction(MessageChannel *aChan, const IPC::Message &aMessage)
+      : mChan(aChan),
+        mActive(true),
+        mOutgoing(false),
+        mPriority(aMessage.priority()),
+        mSeqno(aMessage.seqno()),
+        mTransaction(aMessage.transaction_id()),
+        mNext(mChan->mTransactionStack)
+    {
+        mChan->mMonitor->AssertCurrentThreadOwns();
+
+        if (!aMessage.is_sync()) {
+            mActive = false;
+            return;
+        }
+
+        mChan->mTransactionStack = this;
+    }
+
+    ~AutoEnterTransaction() {
+        mChan->mMonitor->AssertCurrentThreadOwns();
+        if (mActive) {
+            mChan->mTransactionStack = mNext;
+        }
+    }
+
+    void Cancel() {
+        AutoEnterTransaction *cur = mChan->mTransactionStack;
+        MOZ_RELEASE_ASSERT(cur == this);
+        while (cur && cur->mPriority != IPC::Message::PRIORITY_NORMAL) {
+            // Note that, in the following situation, we will cancel multiple
+            // transactions:
+            // 1. Parent sends high prio message P1 to child.
+            // 2. Child sends high prio message C1 to child.
+            // 3. Child dispatches P1, parent blocks.
+            // 4. Child cancels.
+            // In this case, both P1 and C1 are cancelled. The parent will
+            // remove C1 from its queue when it gets the cancellation message.
+            MOZ_RELEASE_ASSERT(cur->mActive);
+            cur->mActive = false;
+            cur = cur->mNext;
+        }
+
+        mChan->mTransactionStack = cur;
+
+        MOZ_RELEASE_ASSERT(IsComplete());
+    }
+
+    bool AwaitingSyncReply() const {
+        MOZ_RELEASE_ASSERT(mActive);
+        if (mOutgoing) {
+            return true;
+        }
+        return mNext ? mNext->AwaitingSyncReply() : false;
+    }
+
+    int AwaitingSyncReplyPriority() const {
+        MOZ_RELEASE_ASSERT(mActive);
+        if (mOutgoing) {
+            return mPriority;
+        }
+        return mNext ? mNext->AwaitingSyncReplyPriority() : 0;
+    }
+
+    bool DispatchingSyncMessage() const {
+        MOZ_RELEASE_ASSERT(mActive);
+        if (!mOutgoing) {
+            return true;
+        }
+        return mNext ? mNext->DispatchingSyncMessage() : false;
+    }
+
+    int DispatchingSyncMessagePriority() const {
+        MOZ_RELEASE_ASSERT(mActive);
+        if (!mOutgoing) {
+            return mPriority;
+        }
+        return mNext ? mNext->DispatchingSyncMessagePriority() : 0;
+    }
+
+    int Priority() const {
+        MOZ_RELEASE_ASSERT(mActive);
+        return mPriority;
+    }
+
+    int32_t SequenceNumber() const {
+        MOZ_RELEASE_ASSERT(mActive);
+        return mSeqno;
+    }
+
+    int32_t TransactionID() const {
+        MOZ_RELEASE_ASSERT(mActive);
+        return mTransaction;
+    }
+
+    void ReceivedReply(const IPC::Message& aMessage) {
+        MOZ_RELEASE_ASSERT(aMessage.seqno() == mSeqno);
+        MOZ_RELEASE_ASSERT(aMessage.transaction_id() == mTransaction);
+        MOZ_RELEASE_ASSERT(!mReply);
+        IPC_LOG("Reply received on worker thread: seqno=%d", mSeqno);
+        mReply = new IPC::Message(aMessage);
+        MOZ_RELEASE_ASSERT(IsComplete());
+    }
+
+    void HandleReply(const IPC::Message& aMessage) {
+        AutoEnterTransaction *cur = mChan->mTransactionStack;
+        MOZ_RELEASE_ASSERT(cur == this);
+        while (cur) {
+            MOZ_RELEASE_ASSERT(cur->mActive);
+            if (aMessage.seqno() == cur->mSeqno) {
+                cur->ReceivedReply(aMessage);
+                break;
+            }
+            cur = cur->mNext;
+            MOZ_RELEASE_ASSERT(cur);
+        }
+    }
+
+    bool IsComplete() {
+        return !mActive || mReply;
+    }
+
+    bool IsOutgoing() {
+        return mOutgoing;
+    }
+
+    bool IsCanceled() {
+        return !mActive;
+    }
+
+    bool IsBottom() const {
+        return !mNext;
+    }
+
+    bool IsError() {
+        MOZ_RELEASE_ASSERT(mReply);
+        return mReply->is_reply_error();
+    }
+
+    nsAutoPtr<IPC::Message> GetReply() {
+        return Move(mReply);
+    }
+
+private:
+    MessageChannel *mChan;
+
+    // Active is true if this transaction is on the mChan->mTransactionStack
+    // stack. Generally we're not on the stack if the transaction was canceled
+    // or if it was for a message that doesn't require transactions (an async
+    // message).
+    bool mActive;
+
+    // Is this stack frame for an outgoing message?
+    bool mOutgoing;
+
+    // Properties of the message being sent/received.
+    int mPriority;
+    int32_t mSeqno;
+    int32_t mTransaction;
+
+    // Next item in mChan->mTransactionStack.
+    AutoEnterTransaction *mNext;
+
+    // Pointer the a reply received for this message, if one was received.
+    nsAutoPtr<IPC::Message> mReply;
+};
+
 MessageChannel::MessageChannel(MessageListener *aListener)
   : mListener(aListener),
     mChannelState(ChannelClosed),
     mSide(UnknownSide),
     mLink(nullptr),
     mWorkerLoop(nullptr),
     mChannelErrorTask(nullptr),
     mWorkerLoopID(-1),
     mTimeoutMs(kNoTimeout),
     mInTimeoutSecondHalf(false),
     mNextSeqno(0),
     mLastSendError(SyncSendError::SendSuccess),
-    mAwaitingSyncReply(false),
-    mAwaitingSyncReplyPriority(0),
-    mDispatchingSyncMessage(false),
-    mDispatchingSyncMessagePriority(0),
     mDispatchingAsyncMessage(false),
     mDispatchingAsyncMessagePriority(0),
-    mCurrentTransaction(0),
-    mPendingSendPriorities(0),
+    mTransactionStack(nullptr),
     mTimedOutMessageSeqno(0),
     mTimedOutMessagePriority(0),
-    mRecvdErrors(0),
     mRemoteStackDepthGuess(false),
     mSawInterruptOutMsg(false),
     mIsWaitingForIncoming(false),
     mAbortOnError(false),
     mFlags(REQUIRE_DEFAULT),
     mPeerPidSet(false),
     mPeerPid(-1)
 {
@@ -339,16 +519,61 @@ MessageChannel::~MessageChannel()
     IPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors");
 #ifdef OS_WIN
     BOOL ok = CloseHandle(mEvent);
     MOZ_RELEASE_ASSERT(ok);
 #endif
     Clear();
 }
 
+// This function returns the current transaction ID. Since the notion of a
+// "current transaction" can be hard to define when messages race with each
+// other and one gets canceled and the other doesn't, we require that this
+// function is only called when the current transaction is known to be for a
+// high priority message. In that case, we know for sure what the caller is
+// looking for.
+int32_t
+MessageChannel::CurrentHighPriorityTransaction() const
+{
+    mMonitor->AssertCurrentThreadOwns();
+    if (!mTransactionStack) {
+        return 0;
+    }
+    MOZ_RELEASE_ASSERT(mTransactionStack->Priority() == IPC::Message::PRIORITY_HIGH);
+    return mTransactionStack->TransactionID();
+}
+
+bool
+MessageChannel::AwaitingSyncReply() const
+{
+    mMonitor->AssertCurrentThreadOwns();
+    return mTransactionStack ? mTransactionStack->AwaitingSyncReply() : false;
+}
+
+int
+MessageChannel::AwaitingSyncReplyPriority() const
+{
+    mMonitor->AssertCurrentThreadOwns();
+    return mTransactionStack ? mTransactionStack->AwaitingSyncReplyPriority() : 0;
+}
+
+bool
+MessageChannel::DispatchingSyncMessage() const
+{
+    mMonitor->AssertCurrentThreadOwns();
+    return mTransactionStack ? mTransactionStack->DispatchingSyncMessage() : false;
+}
+
+int
+MessageChannel::DispatchingSyncMessagePriority() const
+{
+    mMonitor->AssertCurrentThreadOwns();
+    return mTransactionStack ? mTransactionStack->DispatchingSyncMessagePriority() : 0;
+}
+
 static void
 PrintErrorMessage(Side side, const char* channelName, const char* msg)
 {
     const char *from = (side == ChildSide)
                        ? "Child"
                        : ((side == ParentSide) ? "Parent" : "Unknown");
     printf_stderr("\n###!!! [%s][%s] Error: %s\n\n", from, channelName, msg);
 }
@@ -400,17 +625,16 @@ MessageChannel::Clear()
 
     if (mChannelErrorTask) {
         mChannelErrorTask->Cancel();
         mChannelErrorTask = nullptr;
     }
 
     // Free up any memory used by pending messages.
     mPending.clear();
-    mRecvd = nullptr;
     mOutOfTurnReplies.clear();
     while (!mDeferred.empty()) {
         mDeferred.pop();
     }
 }
 
 bool
 MessageChannel::Open(Transport* aTransport, MessageLoop* aIOLoop, Side aSide)
@@ -617,17 +841,17 @@ MessageChannel::ShouldDeferMessage(const
     // race by dispatching in the child and deferring the incoming message in
     // the parent. However, the parent still needs to dispatch nested sync
     // messages.
     //
     // Deferring in the parent only sort of breaks message ordering. When the
     // child's message comes in, we can pretend the child hasn't quite
     // finished sending it yet. Since the message is sync, we know that the
     // child hasn't moved on yet.
-    return mSide == ParentSide && aMsg.transaction_id() != mCurrentTransaction;
+    return mSide == ParentSide && aMsg.transaction_id() != CurrentHighPriorityTransaction();
 }
 
 // Predicate that is true for messages that should be consolidated if 'compress' is set.
 class MatchingKinds {
     typedef IPC::Message Message;
     Message::msgid_t mType;
     int32_t mRoutingId;
 public:
@@ -654,43 +878,27 @@ MessageChannel::OnMessageReceivedFromLin
 
         if (aMsg.seqno() == mTimedOutMessageSeqno) {
             // Drop the message, but allow future sync messages to be sent.
             IPC_LOG("Received reply to timedout message; igoring; xid=%d", mTimedOutMessageSeqno);
             EndTimeout();
             return;
         }
 
-        MOZ_RELEASE_ASSERT(aMsg.transaction_id() == mCurrentTransaction);
         MOZ_RELEASE_ASSERT(AwaitingSyncReply());
-        MOZ_RELEASE_ASSERT(!mRecvd);
         MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
 
-        // Rather than storing errors in mRecvd, we mark them in
-        // mRecvdErrors. We need a counter because multiple replies can arrive
-        // when a timeout happens, as in the following example. Imagine the
-        // child is running slowly. The parent sends a sync message P1. It times
-        // out. The child eventually sends a sync message C1. While waiting for
-        // the C1 response, the child dispatches P1. In doing so, it sends sync
-        // message C2. At that point, it's valid for the parent to send error
-        // responses for both C1 and C2.
-        if (aMsg.is_reply_error()) {
-            mRecvdErrors++;
-            NotifyWorkerThread();
-            return;
-        }
-
-        mRecvd = new Message(aMsg);
+        mTransactionStack->HandleReply(aMsg);
         NotifyWorkerThread();
         return;
     }
 
     // Prioritized messages cannot be compressed.
     MOZ_RELEASE_ASSERT(aMsg.compress_type() == IPC::Message::COMPRESSION_NONE ||
-                          aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
+                       aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
 
     bool compress = false;
     if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
         compress = (!mPending.empty() &&
                     mPending.back().type() == aMsg.type() &&
                     mPending.back().routing_id() == aMsg.routing_id());
         if (compress) {
             // This message type has compression enabled, and the back of the
@@ -763,87 +971,71 @@ MessageChannel::OnMessageReceivedFromLin
             // If we compressed away the previous message, we'll re-use
             // its pending task.
             mWorkerLoop->PostTask(FROM_HERE, new DequeueTask(mDequeueOneTask));
         }
     }
 }
 
 void
-MessageChannel::ProcessPendingRequests(int seqno, int transaction)
+MessageChannel::ProcessPendingRequests(AutoEnterTransaction& aTransaction)
 {
+    int32_t seqno = aTransaction.SequenceNumber();
+    int32_t transaction = aTransaction.TransactionID();
+
     IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d", seqno, transaction);
 
     // Loop until there aren't any more priority messages to process.
     for (;;) {
         // If we canceled during ProcessPendingRequest, then we need to leave
         // immediately because the results of ShouldDeferMessage will be
         // operating with weird state (as if no Send is in progress). That could
         // cause even normal priority sync messages to be processed (but not
         // normal priority async messages), which would break message ordering.
-        if (WasTransactionCanceled(transaction)) {
+        if (aTransaction.IsCanceled()) {
             return;
         }
 
         mozilla::Vector<Message> toProcess;
 
         for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
             Message &msg = *it;
 
-            MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction,
-                                  "Calling ShouldDeferMessage when cancelled");
+            MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
+                               "Calling ShouldDeferMessage when cancelled");
             bool defer = ShouldDeferMessage(msg);
 
             // Only log the interesting messages.
             if (msg.is_sync() || msg.priority() == IPC::Message::PRIORITY_URGENT) {
                 IPC_LOG("ShouldDeferMessage(seqno=%d) = %d", msg.seqno(), defer);
             }
 
             if (!defer) {
                 if (!toProcess.append(Move(msg)))
                     MOZ_CRASH();
                 it = mPending.erase(it);
                 continue;
             }
             it++;
         }
 
-        if (toProcess.empty())
+        if (toProcess.empty()) {
             break;
+        }
 
         // Processing these messages could result in more messages, so we
         // loop around to check for more afterwards.
 
-        for (auto it = toProcess.begin(); it != toProcess.end(); it++)
+        for (auto it = toProcess.begin(); it != toProcess.end(); it++) {
             ProcessPendingRequest(*it);
+        }
     }
 }
 
 bool
-MessageChannel::WasTransactionCanceled(int transaction)
-{
-    if (transaction != mCurrentTransaction) {
-        // Imagine this scenario:
-        // 1. Child sends high prio sync message H1.
-        // 2. Parent sends reply to H1.
-        // 3. Parent sends high prio sync message H2.
-        // 4. Child's link thread receives H1 reply and H2 before worker wakes up.
-        // 5. Child dispatches H2 while still waiting for H1 reply.
-        // 6. Child cancels H2.
-        //
-        // In this case H1 will also be considered cancelled. However, its
-        // reply is still sitting in mRecvd, which can trip up later Sends. So
-        // we null it out here.
-        mRecvd = nullptr;
-        return true;
-    }
-    return false;
-}
-
-bool
 MessageChannel::Send(Message* aMsg, Message* aReply)
 {
     nsAutoPtr<Message> msg(aMsg);
 
     // Sanity checks.
     AssertWorkerThread();
     mMonitor->AssertNotCurrentThreadOwns();
 
@@ -861,180 +1053,173 @@ MessageChannel::Send(Message* aMsg, Mess
         // and we haven't received a reply for it. Once the original timed-out
         // message receives a reply, we'll be able to send more sync messages
         // again.
         IPC_LOG("Send() failed due to previous timeout");
         mLastSendError = SyncSendError::PreviousTimeout;
         return false;
     }
 
-    if (mCurrentTransaction &&
-        DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
+    if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
         msg->priority() > IPC::Message::PRIORITY_NORMAL)
     {
         // Don't allow sending CPOWs while we're dispatching a sync message.
         // If you want to do that, use sendRpcMessage instead.
         IPC_LOG("Prio forbids send");
         mLastSendError = SyncSendError::SendingCPOWWhileDispatchingSync;
         return false;
     }
 
-    if (mCurrentTransaction &&
-        (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
-         DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT))
+    if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
+        DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
     {
         // Generally only the parent dispatches urgent messages. And the only
         // sync messages it can send are high-priority. Mainly we want to ensure
         // here that we don't return false for non-CPOW messages.
         MOZ_RELEASE_ASSERT(msg->priority() == IPC::Message::PRIORITY_HIGH);
         IPC_LOG("Sending while dispatching urgent message");
         mLastSendError = SyncSendError::SendingCPOWWhileDispatchingUrgent;
         return false;
     }
 
-    if (mCurrentTransaction &&
-        (msg->priority() < DispatchingSyncMessagePriority() ||
-         msg->priority() < AwaitingSyncReplyPriority()))
+    if (msg->priority() < DispatchingSyncMessagePriority() ||
+        msg->priority() < AwaitingSyncReplyPriority())
     {
         MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
         IPC_LOG("Cancel from Send");
-        CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
-        CancelTransaction(mCurrentTransaction);
+        CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
+        CancelTransaction(CurrentHighPriorityTransaction());
         mLink->SendMessage(cancel);
     }
 
     IPC_ASSERT(msg->is_sync(), "can only Send() sync messages here");
 
-    if (mCurrentTransaction) {
-        IPC_ASSERT(msg->priority() >= DispatchingSyncMessagePriority(),
-                   "can't send sync message of a lesser priority than what's being dispatched");
-        IPC_ASSERT(AwaitingSyncReplyPriority() <= msg->priority(),
-                   "nested sync message sends must be of increasing priority");
-        IPC_ASSERT(DispatchingSyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
-                   "not allowed to send messages while dispatching urgent messages");
-    }
+    IPC_ASSERT(msg->priority() >= DispatchingSyncMessagePriority(),
+               "can't send sync message of a lesser priority than what's being dispatched");
+    IPC_ASSERT(AwaitingSyncReplyPriority() <= msg->priority(),
+               "nested sync message sends must be of increasing priority");
+    IPC_ASSERT(DispatchingSyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
+               "not allowed to send messages while dispatching urgent messages");
 
     IPC_ASSERT(DispatchingAsyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
                "not allowed to send messages while dispatching urgent messages");
 
     if (!Connected()) {
         ReportConnectionError("MessageChannel::SendAndWait", msg);
         mLastSendError = SyncSendError::NotConnectedBeforeSend;
         return false;
     }
 
     msg->set_seqno(NextSeqno());
 
     int32_t seqno = msg->seqno();
     int prio = msg->priority();
     msgid_t replyType = msg->type() + 1;
 
-    AutoSetValue<bool> replies(mAwaitingSyncReply, true);
-    AutoSetValue<int> prioSet(mAwaitingSyncReplyPriority, prio);
-    AutoEnterTransaction transact(this, seqno);
+    AutoEnterTransaction *stackTop = mTransactionStack;
 
-    int prios = mPendingSendPriorities | (1 << prio);
-    AutoSetValue<int> priosSet(mPendingSendPriorities, prios);
-
-    int32_t transaction = mCurrentTransaction;
+    // If the most recent message on the stack is high priority, then our
+    // message should nest inside that and we use the same transaction
+    // ID. Otherwise we need a new transaction ID (so we use the seqno of the
+    // message we're sending).
+    bool nest = stackTop && stackTop->Priority() == IPC::Message::PRIORITY_HIGH;
+    int32_t transaction = nest ? stackTop->TransactionID() : seqno;
     msg->set_transaction_id(transaction);
 
-    IPC_LOG("Send seqno=%d, xid=%d, pending=%d", seqno, transaction, prios);
+    bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
+    AutoEnterTransaction transact(this, seqno, transaction, prio);
 
-    bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
+    IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction);
+
     mLink->SendMessage(msg.forget());
 
     while (true) {
-        ProcessPendingRequests(seqno, transaction);
-        if (WasTransactionCanceled(transaction)) {
-            IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
-            mLastSendError = SyncSendError::CancelledAfterSend;
-            return false;
+        MOZ_RELEASE_ASSERT(!transact.IsCanceled());
+        ProcessPendingRequests(transact);
+        if (transact.IsComplete()) {
+            break;
         }
         if (!Connected()) {
             ReportConnectionError("MessageChannel::Send");
             mLastSendError = SyncSendError::DisconnectedDuringSend;
             return false;
         }
 
-        // See if we've received a reply.
-        if (mRecvdErrors) {
-            IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
-            mRecvdErrors--;
-            mLastSendError = SyncSendError::ReplyError;
-            return false;
-        }
+        MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
+        MOZ_RELEASE_ASSERT(!transact.IsComplete());
+        MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
 
-        if (mRecvd) {
-            IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
-            break;
-        }
+        bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
 
-        MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
-
-        MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
-        bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
         if (mListener->NeedArtificialSleep()) {
             MonitorAutoUnlock unlock(*mMonitor);
             mListener->ArtificialSleep();
         }
 
         if (!Connected()) {
             ReportConnectionError("MessageChannel::SendAndWait");
             mLastSendError = SyncSendError::DisconnectedDuringSend;
             return false;
         }
 
-        if (WasTransactionCanceled(transaction)) {
-            IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
-            mLastSendError = SyncSendError::CancelledAfterSend;
-            return false;
+        if (transact.IsCanceled()) {
+            break;
         }
 
+        MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
+
         // We only time out a message if it initiated a new transaction (i.e.,
         // if neither side has any other message Sends on the stack).
-        bool canTimeOut = transaction == seqno;
+        bool canTimeOut = transact.IsBottom();
         if (maybeTimedOut && canTimeOut && !ShouldContinueFromTimeout()) {
             // Since ShouldContinueFromTimeout drops the lock, we need to
             // re-check all our conditions here. We shouldn't time out if any of
             // these things happen because there won't be a reply to the timed
             // out message in these cases.
-            if (WasTransactionCanceled(transaction)) {
-                IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
-                mLastSendError = SyncSendError::CancelledAfterSend;
-                return false;
-            }
-            if (mRecvdErrors) {
-                mRecvdErrors--;
-                mLastSendError = SyncSendError::ReplyError;
-                return false;
-            }
-            if (mRecvd) {
+            if (transact.IsComplete()) {
                 break;
             }
 
             IPC_LOG("Timing out Send: xid=%d", transaction);
 
             mTimedOutMessageSeqno = seqno;
             mTimedOutMessagePriority = prio;
             mLastSendError = SyncSendError::TimedOut;
             return false;
         }
+
+        if (transact.IsCanceled()) {
+            break;
+        }
+    }
+
+    if (transact.IsCanceled()) {
+        IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
+        mLastSendError = SyncSendError::CancelledAfterSend;
+        return false;
     }
 
-    MOZ_RELEASE_ASSERT(mRecvd);
-    MOZ_RELEASE_ASSERT(mRecvd->is_reply(), "expected reply");
-    MOZ_RELEASE_ASSERT(!mRecvd->is_reply_error());
-    MOZ_RELEASE_ASSERT(mRecvd->seqno() == seqno);
-    MOZ_RELEASE_ASSERT(mRecvd->type() == replyType, "wrong reply type");
-    MOZ_RELEASE_ASSERT(mRecvd->is_sync());
+    if (transact.IsError()) {
+        IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
+        mLastSendError = SyncSendError::ReplyError;
+        return false;
+    }
+
+    IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
 
-    *aReply = Move(*mRecvd);
-    mRecvd = nullptr;
-    mLastSendError = SyncSendError::SendSuccess;
+    nsAutoPtr<Message> reply = transact.GetReply();
+
+    MOZ_RELEASE_ASSERT(reply);
+    MOZ_RELEASE_ASSERT(reply->is_reply(), "expected reply");
+    MOZ_RELEASE_ASSERT(!reply->is_reply_error());
+    MOZ_RELEASE_ASSERT(reply->seqno() == seqno);
+    MOZ_RELEASE_ASSERT(reply->type() == replyType, "wrong reply type");
+    MOZ_RELEASE_ASSERT(reply->is_sync());
+
+    *aReply = Move(*reply);
     return true;
 }
 
 bool
 MessageChannel::Call(Message* aMsg, Message* aReply)
 {
     nsAutoPtr<Message> msg(aMsg);
     AssertWorkerThread();
@@ -1247,41 +1432,24 @@ MessageChannel::InterruptEventOccurred()
 }
 
 bool
 MessageChannel::ProcessPendingRequest(const Message &aUrgent)
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
 
-    // Note that it is possible we could have sent a sync message at
-    // the same time the parent process sent an urgent message, and
-    // therefore mPendingUrgentRequest is set *and* mRecvd is set as
-    // well, because the link thread received both before the worker
-    // thread woke up.
-    //
-    // In this case, we process the urgent message first, but we need
-    // to save the reply.
-    nsAutoPtr<Message> savedReply(mRecvd.forget());
-
     IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent.seqno(), aUrgent.transaction_id());
 
     DispatchMessage(aUrgent);
     if (!Connected()) {
         ReportConnectionError("MessageChannel::ProcessPendingRequest");
         return false;
     }
 
-    // In between having dispatched our reply to the parent process, and
-    // re-acquiring the monitor, the parent process could have already
-    // processed that reply and sent the reply to our sync message. If so,
-    // our saved reply should be empty.
-    IPC_ASSERT(!mRecvd || !savedReply, "unknown reply");
-    if (!mRecvd)
-        mRecvd = savedReply.forget();
     return true;
 }
 
 bool
 MessageChannel::DequeueOne(Message *recvd)
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
@@ -1347,18 +1515,16 @@ MessageChannel::OnMaybeDequeueOne()
 
     if (IsOnCxxStack() && recvd.is_interrupt() && recvd.is_reply()) {
         // We probably just received a reply in a nested loop for an
         // Interrupt call sent before entering that loop.
         mOutOfTurnReplies[recvd.seqno()] = Move(recvd);
         return false;
     }
 
-    // We should not be in a transaction yet if we're not blocked.
-    MOZ_RELEASE_ASSERT(mCurrentTransaction == 0);
     DispatchMessage(recvd);
 
     return true;
 }
 
 void
 MessageChannel::DispatchMessage(const Message &aMsg)
 {
@@ -1369,17 +1535,17 @@ MessageChannel::DispatchMessage(const Me
     nsAutoPtr<Message> reply;
 
     IPC_LOG("DispatchMessage: seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id());
 
     {
         AutoEnterTransaction transaction(this, aMsg);
 
         int id = aMsg.transaction_id();
-        MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == mCurrentTransaction);
+        MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == transaction.TransactionID());
 
         {
             MonitorAutoUnlock unlock(*mMonitor);
             CxxStackFrame frame(*this, IN_MESSAGE, &aMsg);
 
             mListener->ArtificialSleep();
 
             if (aMsg.is_sync())
@@ -1387,17 +1553,17 @@ MessageChannel::DispatchMessage(const Me
             else if (aMsg.is_interrupt())
                 DispatchInterruptMessage(aMsg, 0);
             else
                 DispatchAsyncMessage(aMsg);
 
             mListener->ArtificialSleep();
         }
 
-        if (mCurrentTransaction != id) {
+        if (reply && transaction.IsCanceled()) {
             // The transaction has been canceled. Don't send a reply.
             IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d", aMsg.seqno(), id);
             reply = nullptr;
         }
     }
 
     if (reply && ChannelConnected == mChannelState) {
         IPC_LOG("Sending reply seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id());
@@ -1415,18 +1581,16 @@ MessageChannel::DispatchSyncMessage(cons
     MOZ_RELEASE_ASSERT(prio == IPC::Message::PRIORITY_NORMAL || NS_IsMainThread());
 
     MessageChannel* dummy;
     MessageChannel*& blockingVar = mSide == ChildSide && NS_IsMainThread() ? gParentProcessBlocker : dummy;
 
     Result rv;
     {
         AutoSetValue<MessageChannel*> blocked(blockingVar, this);
-        AutoSetValue<bool> sync(mDispatchingSyncMessage, true);
-        AutoSetValue<int> prioSet(mDispatchingSyncMessagePriority, prio);
         rv = mListener->OnMessageReceived(aMsg, aReply);
     }
 
     if (!MaybeHandleError(rv, aMsg, "DispatchSyncMessage")) {
         aReply = new Message();
         aReply->set_sync();
         aReply->set_priority(aMsg.priority());
         aReply->set_reply();
@@ -2118,124 +2282,82 @@ MessageChannel::CancelTransaction(int tr
     // canceled. Consequently, we have to update the state variables below.
     //
     // We also need to ensure that when any IPC functions on the stack return,
     // they don't reset these values using an RAII class like AutoSetValue. To
     // avoid that, these RAII classes check if the variable they set has been
     // tampered with (by us). If so, they don't reset the variable to the old
     // value.
 
-    IPC_LOG("CancelTransaction: xid=%d prios=%d", transaction, mPendingSendPriorities);
-
-    if (mPendingSendPriorities & (1 << IPC::Message::PRIORITY_NORMAL)) {
-        // This isn't an assert so much as an intentional crash because we're in
-        // a situation that we don't know how to recover from: The child is
-        // awaiting a reply to a normal-priority sync message. The transaction
-        // that this message initiated has now been canceled. That could only
-        // happen if a CPOW raced with the sync message and was dispatched by
-        // the child while the child was awaiting the sync reply; at some point
-        // while dispatching the CPOW, the transaction was canceled.
-        //
-        // Notes:
-        //
-        // 1. We don't want to cancel the normal-priority sync message along
-        // with the CPOWs because the browser relies on these messages working
-        // reliably.
-        //
-        // 2. Ideally we would like to avoid dispatching CPOWs while awaiting a
-        // sync response. This isn't possible though. To avoid deadlock, the
-        // parent would have to dispatch the sync message while waiting for the
-        // CPOW response. However, it wouldn't have dispatched async messages at
-        // that time, so we would have a message ordering bug. Dispatching the
-        // async messages first causes other hard-to-handle situations (what if
-        // they send CPOWs?).
-        //
-        // 3. We would like to be able to cancel the CPOWs but not the sync
-        // message. However, that would leave both the parent and the child
-        // running code at the same time, all while the sync message is still
-        // outstanding. That can cause a problem where message replies are
-        // received out of order.
-        mListener->IntentionalCrash();
-    }
+    IPC_LOG("CancelTransaction: xid=%d", transaction);
 
     // An unusual case: We timed out a transaction which the other side then
     // cancelled. In this case we just leave the timedout state and try to
     // forget this ever happened.
     if (transaction == mTimedOutMessageSeqno) {
         IPC_LOG("Cancelled timed out message %d", mTimedOutMessageSeqno);
         EndTimeout();
 
         // Normally mCurrentTransaction == 0 here. But it can be non-zero if:
         // 1. Parent sends hi prio message H.
         // 2. Parent times out H.
         // 3. Child dispatches H and sends nested message H' (same transaction).
         // 4. Parent dispatches H' and cancels.
-        MOZ_RELEASE_ASSERT(!mCurrentTransaction || mCurrentTransaction == transaction);
-        mCurrentTransaction = 0;
-
-        // During a timeout Send should always fail.
-        MOZ_RELEASE_ASSERT(!mAwaitingSyncReply);
+        MOZ_RELEASE_ASSERT(!mTransactionStack || mTransactionStack->TransactionID() == transaction);
+        if (mTransactionStack) {
+            mTransactionStack->Cancel();
+        }
     } else {
-        MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
-        mCurrentTransaction = 0;
-
-        mAwaitingSyncReply = false;
-        mAwaitingSyncReplyPriority = 0;
+        MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction);
+        mTransactionStack->Cancel();
     }
 
     bool foundSync = false;
     for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
         Message &msg = *it;
 
         // If there was a race between the parent and the child, then we may
         // have a queued sync message. We want to drop this message from the
-        // queue since it will get cancelled along with the transaction being
-        // cancelled. We don't bother doing this for normal priority messages
-        // because the child is just going to crash in that case, and we want to
-        // avoid processing messages out of order in the short time before it
-        // crashes.
+        // queue since if will get cancelled along with the transaction being
+        // cancelled. This happens if the message in the queue is high priority.
         if (msg.is_sync() && msg.priority() != IPC::Message::PRIORITY_NORMAL) {
             MOZ_RELEASE_ASSERT(!foundSync);
             MOZ_RELEASE_ASSERT(msg.transaction_id() != transaction);
             IPC_LOG("Removing msg from queue seqno=%d xid=%d", msg.seqno(), msg.transaction_id());
             foundSync = true;
             it = mPending.erase(it);
             continue;
         }
 
-        // There may be messages in the queue that we expected to process from
-        // ProcessPendingRequests. However, Send will no longer call that
-        // function once it's been canceled. So we may need to process these
-        // messages in the normal event loop instead.
-        mWorkerLoop->PostTask(FROM_HERE, new DequeueTask(mDequeueOneTask));
-
         it++;
     }
+}
 
-    // We could also zero out mDispatchingSyncMessage here. However, that would
-    // cause a race because mDispatchingSyncMessage is a worker-thread-only
-    // field and we can be called on the I/O thread. Luckily, we can check to
-    // see if mCurrentTransaction is 0 before examining DispatchSyncMessage.
+bool
+MessageChannel::IsInTransaction() const
+{
+    MonitorAutoLock lock(*mMonitor);
+    return !!mTransactionStack;
 }
 
 void
 MessageChannel::CancelCurrentTransaction()
 {
     MonitorAutoLock lock(*mMonitor);
-    if (mCurrentTransaction) {
+    if (DispatchingSyncMessagePriority() >= IPC::Message::PRIORITY_HIGH) {
         if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
             DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
         {
             mListener->IntentionalCrash();
         }
 
-        IPC_LOG("Cancel requested: current xid=%d", mCurrentTransaction);
+        IPC_LOG("Cancel requested: current xid=%d", CurrentHighPriorityTransaction());
         MOZ_RELEASE_ASSERT(DispatchingSyncMessage());
-        CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
-        CancelTransaction(mCurrentTransaction);
+        CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
+        CancelTransaction(CurrentHighPriorityTransaction());
         mLink->SendMessage(cancel);
     }
 }
 
 void
 CancelCPOWs()
 {
     if (gParentProcessBlocker) {
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -52,16 +52,18 @@ enum class SyncSendError {
     NotConnectedBeforeSend,
     DisconnectedDuringSend,
     CancelledBeforeSend,
     CancelledAfterSend,
     TimedOut,
     ReplyError,
 };
 
+class AutoEnterTransaction;
+
 class MessageChannel : HasResultCodes
 {
     friend class ProcessLink;
     friend class ThreadLink;
 
     class CxxStackFrame;
     class InterruptFrame;
 
@@ -151,17 +153,17 @@ class MessageChannel : HasResultCodes
     }
 
     void SetReplyTimeoutMs(int32_t aTimeoutMs);
 
     bool IsOnCxxStack() const {
         return !mCxxStackFrames.empty();
     }
 
-    bool IsInTransaction() const { return mCurrentTransaction != 0; }
+    bool IsInTransaction() const;
     void CancelCurrentTransaction();
 
     /**
      * This function is used by hang annotation code to determine which IPDL
      * actor is highest in the call stack at the time of the hang. It should
      * be called from the main thread when a sync or intr message is about to
      * be sent.
      */
@@ -258,17 +260,17 @@ class MessageChannel : HasResultCodes
     void Clear();
 
     // Send OnChannelConnected notification to listeners.
     void DispatchOnChannelConnected();
 
     bool InterruptEventOccurred();
     bool HasPendingEvents();
 
-    void ProcessPendingRequests(int seqno, int transaction);
+    void ProcessPendingRequests(AutoEnterTransaction& aTransaction);
     bool ProcessPendingRequest(const Message &aUrgent);
 
     void MaybeUndeferIncall();
     void EnqueuePendingMessages();
 
     // Executed on the worker thread. Dequeues one pending message.
     bool OnMaybeDequeueOne();
     bool DequeueOne(Message *recvd);
@@ -361,25 +363,16 @@ class MessageChannel : HasResultCodes
 
   private:
     // Called from both threads
     size_t InterruptStackDepth() const {
         mMonitor->AssertCurrentThreadOwns();
         return mInterruptStack.size();
     }
 
-    // Returns true if we're blocking waiting for a reply.
-    bool AwaitingSyncReply() const {
-        mMonitor->AssertCurrentThreadOwns();
-        return mAwaitingSyncReply;
-    }
-    int AwaitingSyncReplyPriority() const {
-        mMonitor->AssertCurrentThreadOwns();
-        return mAwaitingSyncReplyPriority;
-    }
     bool AwaitingInterruptReply() const {
         mMonitor->AssertCurrentThreadOwns();
         return !mInterruptStack.empty();
     }
     bool AwaitingIncomingMessage() const {
         mMonitor->AssertCurrentThreadOwns();
         return mIsWaitingForIncoming;
     }
@@ -399,27 +392,17 @@ class MessageChannel : HasResultCodes
             mChannel.mIsWaitingForIncoming = false;
         }
 
     private:
         MessageChannel& mChannel;
     };
     friend class AutoEnterWaitForIncoming;
 
-    // Returns true if we're dispatching a sync message's callback.
-    bool DispatchingSyncMessage() const {
-        AssertWorkerThread();
-        return mDispatchingSyncMessage;
-    }
-
-    int DispatchingSyncMessagePriority() const {
-        AssertWorkerThread();
-        return mDispatchingSyncMessagePriority;
-    }
-
+    // Returns true if we're dispatching an async message's callback.
     bool DispatchingAsyncMessage() const {
         AssertWorkerThread();
         return mDispatchingAsyncMessage;
     }
 
     int DispatchingAsyncMessagePriority() const {
         AssertWorkerThread();
         return mDispatchingAsyncMessagePriority;
@@ -555,25 +538,16 @@ class MessageChannel : HasResultCodes
             }
         }
       private:
         T& mVar;
         T mPrev;
         T mNew;
     };
 
-    // Worker thread only.
-    bool mAwaitingSyncReply;
-    int mAwaitingSyncReplyPriority;
-
-    // Set while we are dispatching a synchronous message. Only for use on the
-    // worker thread.
-    bool mDispatchingSyncMessage;
-    int mDispatchingSyncMessagePriority;
-
     bool mDispatchingAsyncMessage;
     int mDispatchingAsyncMessagePriority;
 
     // When we send an urgent request from the parent process, we could race
     // with an RPC message that was issued by the child beforehand. In this
     // case, if the parent were to wake up while waiting for the urgent reply,
     // and process the RPC, it could send an additional urgent message. The
     // child would wake up to process the urgent message (as it always will),
@@ -585,66 +559,26 @@ class MessageChannel : HasResultCodes
     // chain of RPC/urgent messages, it allocates a new transaction ID. Any
     // messages the parent receives, not apart of this transaction, are
     // deferred. When issuing RPC/urgent requests on top of a started
     // transaction, the initiating transaction ID is used.
     //
     // To ensure IDs are unique, we use sequence numbers for transaction IDs,
     // which grow in opposite directions from child to parent.
 
-    // The current transaction ID.
-    int32_t mCurrentTransaction;
+    friend class AutoEnterTransaction;
+    AutoEnterTransaction *mTransactionStack;
 
-    // This field describes the priorities of the sync Send calls that are
-    // currently on stack. If a Send call for a message with priority P is on
-    // the C stack, then mPendingSendPriorities & (1 << P) will be
-    // non-zero. Note that cancelled Send calls are not removed from this
-    // bitfield (until they return).
-    int mPendingSendPriorities;
+    int32_t CurrentHighPriorityTransaction() const;
 
-    class AutoEnterTransaction
-    {
-     public:
-       explicit AutoEnterTransaction(MessageChannel *aChan, int32_t aMsgSeqno)
-        : mChan(aChan),
-          mNewTransaction(INT32_MAX),
-          mOldTransaction(mChan->mCurrentTransaction)
-       {
-           mChan->mMonitor->AssertCurrentThreadOwns();
-           if (mChan->mCurrentTransaction == 0) {
-               mNewTransaction = aMsgSeqno;
-               mChan->mCurrentTransaction = aMsgSeqno;
-           }
-       }
-       explicit AutoEnterTransaction(MessageChannel *aChan, const Message &aMessage)
-        : mChan(aChan),
-          mNewTransaction(aMessage.transaction_id()),
-          mOldTransaction(mChan->mCurrentTransaction)
-       {
-           mChan->mMonitor->AssertCurrentThreadOwns();
+    bool AwaitingSyncReply() const;
+    int AwaitingSyncReplyPriority() const;
 
-           if (!aMessage.is_sync())
-               return;
-
-           MOZ_DIAGNOSTIC_ASSERT(
-               !(mChan->mSide == ParentSide && mOldTransaction != aMessage.transaction_id()) ||
-               !mOldTransaction || aMessage.priority() > mChan->AwaitingSyncReplyPriority());
-           mChan->mCurrentTransaction = aMessage.transaction_id();
-       }
-       ~AutoEnterTransaction() {
-           mChan->mMonitor->AssertCurrentThreadOwns();
-           if (mChan->mCurrentTransaction == mNewTransaction) {
-               mChan->mCurrentTransaction = mOldTransaction;
-           }
-       }
-
-      private:
-       MessageChannel *mChan;
-       int32_t mNewTransaction, mOldTransaction;
-    };
+    bool DispatchingSyncMessage() const;
+    int DispatchingSyncMessagePriority() const;
 
     // If a sync message times out, we store its sequence number here. Any
     // future sync messages will fail immediately. Once the reply for original
     // sync message is received, we allow sync messages again.
     //
     // When a message times out, nothing is done to inform the other side. The
     // other side will eventually dispatch the message and send a reply. Our
     // side is responsible for replying to all sync messages sent by the other
@@ -652,24 +586,16 @@ class MessageChannel : HasResultCodes
     // error.
     //
     // A message is only timed out if it initiated a transaction. This avoids
     // hitting a lot of corner cases with message nesting that we don't really
     // care about.
     int32_t mTimedOutMessageSeqno;
     int mTimedOutMessagePriority;
 
-    // If waiting for the reply to a sync out-message, it will be saved here
-    // on the I/O thread and then read and cleared by the worker thread.
-    nsAutoPtr<Message> mRecvd;
-
-    // If a sync message reply that is an error arrives, we increment this
-    // counter rather than storing it in mRecvd.
-    size_t mRecvdErrors;
-
     // Queue of all incoming messages, except for replies to sync and urgent
     // messages, which are delivered directly to mRecvd, and any pending urgent
     // incall, which is stored in mPendingUrgentRequest.
     //
     // If both this side and the other side are functioning correctly, the queue
     // can only be in certain configurations.  Let
     //
     //   |A<| be an async in-message,
--- a/ipc/ipdl/test/cxx/TestDemon.cpp
+++ b/ipc/ipdl/test/cxx/TestDemon.cpp
@@ -202,27 +202,41 @@ TestDemonParent::RunLimitedSequence(int 
       gStackHeight--;
       return;
     }
   }
 
   gStackHeight--;
 }
 
+static bool
+AllowAsync(int outgoing, int incoming)
+{
+  return incoming >= outgoing - 5;
+}
+
 bool
 TestDemonParent::DoAction(int flags)
 {
   if (flags & ASYNC_ONLY) {
-    DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
-    return SendAsyncMessage(mOutgoing[0]++);
+    if (AllowAsync(mOutgoing[0], mIncoming[0])) {
+      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+      return SendAsyncMessage(mOutgoing[0]++);
+    } else {
+      return true;
+    }
   } else {
 	switch (Choose(3)) {
      case 0:
-      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
-      return SendAsyncMessage(mOutgoing[0]++);
+      if (AllowAsync(mOutgoing[0], mIncoming[0])) {
+        DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+        return SendAsyncMessage(mOutgoing[0]++);
+      } else {
+        return true;
+      }
 
      case 1: {
        DEMON_LOG("Start SendHiPrioSyncMessage");
        bool r = SendHiPrioSyncMessage();
        DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
        return r;
      }
 
@@ -334,18 +348,22 @@ TestDemonChild::RunLimitedSequence()
   gStackHeight--;
 }
 
 bool
 TestDemonChild::DoAction()
 {
   switch (Choose(6)) {
    case 0:
-	DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
-	return SendAsyncMessage(mOutgoing[0]++);
+    if (AllowAsync(mOutgoing[0], mIncoming[0])) {
+      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+      return SendAsyncMessage(mOutgoing[0]++);
+    } else {
+      return true;
+    }
 
    case 1: {
      DEMON_LOG("Start SendHiPrioSyncMessage");
      bool r = SendHiPrioSyncMessage();
      DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
      return r;
    }
 
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2645,17 +2645,17 @@ class MOZ_STACK_CLASS FunctionValidator
 
     bool init(PropertyName* name, unsigned line) {
         if (!locals_.init() || !breakLabels_.init() || !continueLabels_.init())
             return false;
 
         if (!m_.mg().startFuncDef(line, &fg_))
             return false;
 
-        encoder_.emplace(fg_.bytecode());
+        encoder_.emplace(fg_.bytes());
         return true;
     }
 
     bool finish(uint32_t funcIndex, unsigned generateTime) {
         return m_.mg().finishFuncDef(funcIndex, generateTime, &fg_);
     }
 
     bool fail(ParseNode* pn, const char* str) {
@@ -3527,23 +3527,18 @@ CheckVariables(FunctionValidator& f, Par
         for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) {
             if (!CheckVariable(f, var, &types, &inits))
                 return false;
         }
     }
 
     MOZ_ASSERT(f.encoder().empty());
 
-    if (!f.encoder().writeVarU32(types.length()))
-        return false;
-
-    for (ValType v : types) {
-        if (!f.encoder().writeValType(v))
-            return false;
-    }
+    if (!EncodeLocalEntries(f.encoder(), types))
+        return false;
 
     for (uint32_t i = 0; i < inits.length(); i++) {
         NumLit lit = inits[i];
         if (lit.isZeroBits())
             continue;
         if (!f.encoder().writeExpr(Expr::SetLocal))
             return false;
         if (!f.encoder().writeVarU32(firstVar + i))
@@ -6418,17 +6413,17 @@ CheckSwitchRange(FunctionValidator& f, P
         if (!CheckCaseExpr(f, CaseExpr(stmt), &i))
             return false;
 
         *low = Min(*low, i);
         *high = Max(*high, i);
     }
 
     int64_t i64 = (int64_t(*high) - int64_t(*low)) + 1;
-    if (i64 > 4 * 1024 * 1024)
+    if (i64 > MaxBrTableElems)
         return f.fail(initialStmt, "all switch statements generate tables; this table would be too big");
 
     *tableLength = uint32_t(i64);
     return true;
 }
 
 static bool
 CheckSwitchExpr(FunctionValidator& f, ParseNode* switchExpr)
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -139,68 +139,46 @@ static bool
 CheckType(FunctionDecoder& f, ExprType actual, ExprType expected)
 {
     MOZ_ASSERT(expected != AnyType);
     return expected == ExprType::Void ||
            CheckType(f, actual, NonVoidToValType(expected));
 }
 
 static bool
-DecodeExpr(FunctionDecoder& f, ExprType* type);
-
-static bool
-DecodeValType(JSContext* cx, Decoder& d, ValType *type)
+CheckValType(JSContext* cx, Decoder& d, ValType type)
 {
-    if (!d.readValType(type))
-        return Fail(cx, d, "bad value type");
-
-    switch (*type) {
+    switch (type) {
       case ValType::I32:
       case ValType::F32:
       case ValType::F64:
         return true;
       case ValType::I64:
 #ifndef JS_CPU_X64
         return Fail(cx, d, "i64 NYI on this platform");
 #endif
         return true;
       default:
         // Note: it's important not to remove this default since readValType()
         // can return ValType values for which there is no enumerator.
         break;
     }
 
-    return Fail(cx, "bad value type");
+    return Fail(cx, d, "bad value type");
 }
 
 static bool
-DecodeExprType(JSContext* cx, Decoder& d, ExprType *type)
+CheckExprType(JSContext* cx, Decoder& d, ExprType type)
 {
-    if (!d.readExprType(type))
-        return Fail(cx, d, "bad expression type");
+    return type == ExprType::Void ||
+           CheckValType(cx, d, NonVoidToValType(type));
+}
 
-    switch (*type) {
-      case ExprType::I32:
-      case ExprType::F32:
-      case ExprType::F64:
-      case ExprType::Void:
-        return true;
-      case ExprType::I64:
-#ifndef JS_CPU_X64
-        return Fail(cx, d, "i64 NYI on this platform");
-#endif
-        return true;
-      default:
-        // Note: it's important not to remove this default since readExprType()
-        // can return ExprType values for which there is no enumerator.
-        break;
-    }
-
-    return Fail(cx, "bad expression type");
-}
+static bool
+DecodeExpr(FunctionDecoder& f, ExprType* type);
 
 static bool
 DecodeNop(FunctionDecoder& f, ExprType* type)
 {
     *type = ExprType::Void;
     return true;
 }
 
@@ -264,27 +242,29 @@ DecodeCallIndirect(FunctionDecoder& f, E
         return false;
 
     return DecodeCallWithSig(f, f.mg().sig(sigIndex), type);
 }
 
 static bool
 DecodeConstI32(FunctionDecoder& f, ExprType* type)
 {
-    if (!f.d().readVarU32())
+    uint32_t _;
+    if (!f.d().readVarU32(&_))
         return f.fail("unable to read i32.const immediate");
 
     *type = ExprType::I32;
     return true;
 }
 
 static bool
 DecodeConstI64(FunctionDecoder& f, ExprType* type)
 {
-    if (!f.d().readVarU64())
+    uint64_t _;
+    if (!f.d().readVarU64(&_))
         return f.fail("unable to read i64.const immediate");
 
     *type = ExprType::I64;
     return true;
 }
 
 static bool
 DecodeConstF32(FunctionDecoder& f, ExprType* type)
@@ -851,34 +831,16 @@ DecodeExpr(FunctionDecoder& f, ExprType*
         // can return Expr values for which there is no enumerator.
         break;
     }
 
     return f.fail("bad expression code");
 }
 
 /*****************************************************************************/
-// dynamic link data
-
-struct ImportName
-{
-    UniqueChars module;
-    UniqueChars func;
-
-    ImportName(UniqueChars module, UniqueChars func)
-      : module(Move(module)), func(Move(func))
-    {}
-    ImportName(ImportName&& rhs)
-      : module(Move(rhs.module)), func(Move(rhs.func))
-    {}
-};
-
-typedef Vector<ImportName, 0, SystemAllocPolicy> ImportNameVector;
-
-/*****************************************************************************/
 // wasm decoding and generation
 
 typedef HashSet<const DeclaredSig*, SigHashPolicy> SigSet;
 
 static bool
 DecodeSignatures(JSContext* cx, Decoder& d, ModuleGeneratorData* init)
 {
     uint32_t sectionStart;
@@ -907,25 +869,31 @@ DecodeSignatures(JSContext* cx, Decoder&
         uint32_t numArgs;
         if (!d.readVarU32(&numArgs))
             return Fail(cx, d, "bad number of signature args");
 
         if (numArgs > MaxArgsPerFunc)
             return Fail(cx, d, "too many arguments in signature");
 
         ExprType result;
-        if (!DecodeExprType(cx, d, &result))
+        if (!d.readExprType(&result))
+            return Fail(cx, d, "bad expression type");
+
+        if (!CheckExprType(cx, d, result))
             return false;
 
         ValTypeVector args;
         if (!args.resize(numArgs))
             return false;
 
         for (uint32_t i = 0; i < numArgs; i++) {
-            if (!DecodeValType(cx, d, &args[i]))
+            if (!d.readValType(&args[i]))
+                return Fail(cx, d, "bad value type");
+
+            if (!CheckValType(cx, d, args[i]))
                 return false;
         }
 
         init->sigs[sigIndex] = Sig(Move(args), result);
 
         SigSet::AddPtr p = dupSet.lookupForAdd(init->sigs[sigIndex]);
         if (p)
             return Fail(cx, d, "duplicate signature");
@@ -1053,38 +1021,53 @@ CheckTypeForJS(JSContext* cx, Decoder& d
     }
 
     if (sig.ret() == ExprType::I64)
         return Fail(cx, d, "cannot import/export i64 return type");
 
     return true;
 }
 
+struct ImportName
+{
+    Bytes module;
+    Bytes func;
+
+    ImportName(Bytes&& module, Bytes&& func)
+      : module(Move(module)), func(Move(func))
+    {}
+    ImportName(ImportName&& rhs)
+      : module(Move(rhs.module)), func(Move(rhs.func))
+    {}
+};
+
+typedef Vector<ImportName, 0, SystemAllocPolicy> ImportNameVector;
+
 static bool
 DecodeImport(JSContext* cx, Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
 {
     const DeclaredSig* sig;
     if (!DecodeSignatureIndex(cx, d, *init, &sig))
         return false;
 
     if (!init->imports.emplaceBack(sig))
         return false;
 
     if (!CheckTypeForJS(cx, d, *sig))
         return false;
 
-    UniqueChars moduleName = d.readCString();
-    if (!moduleName)
+    Bytes moduleName;
+    if (!d.readBytes(&moduleName))
         return Fail(cx, d, "expected import module name");
 
-    if (!*moduleName.get())
+    if (moduleName.empty())
         return Fail(cx, d, "module name cannot be empty");
 
-    UniqueChars funcName = d.readCString();
-    if (!funcName)
+    Bytes funcName;
+    if (!d.readBytes(&funcName))
         return Fail(cx, d, "expected import func name");
 
     return importNames->emplaceBack(Move(moduleName), Move(funcName));
 }
 
 static bool
 DecodeImportTable(JSContext* cx, Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
 {
@@ -1159,24 +1142,36 @@ DecodeMemory(JSContext* cx, Decoder& d, 
 
     mg.initHeapUsage(HeapUsage::Unshared);
     return true;
 }
 
 typedef HashSet<const char*, CStringHasher> CStringSet;
 
 static UniqueChars
-DecodeFieldName(JSContext* cx, Decoder& d, CStringSet* dupSet)
+DecodeExportName(JSContext* cx, Decoder& d, CStringSet* dupSet)
 {
-    UniqueChars fieldName = d.readCString();
-    if (!fieldName) {
-        Fail(cx, d, "expected export external name string");
+    Bytes fieldBytes;
+    if (!d.readBytes(&fieldBytes)) {
+        Fail(cx, d, "expected export name");
         return nullptr;
     }
 
+    if (memchr(fieldBytes.begin(), 0, fieldBytes.length())) {
+        Fail(cx, d, "null in export names not yet supported");
+        return nullptr;
+    }
+
+    if (!fieldBytes.append(0))
+        return nullptr;
+
+    UniqueChars fieldName((char*)fieldBytes.extractRawBuffer());
+    if (!fieldName)
+        return nullptr;
+
     CStringSet::AddPtr p = dupSet->lookupForAdd(fieldName.get());
     if (p) {
         Fail(cx, d, "duplicate export");
         return nullptr;
     }
 
     if (!dupSet->add(p, fieldName.get()))
         return nullptr;
@@ -1192,17 +1187,17 @@ DecodeFunctionExport(JSContext* cx, Deco
         return Fail(cx, d, "expected export internal index");
 
     if (funcIndex >= mg.numFuncSigs())
         return Fail(cx, d, "export function index out of range");
 
     if (!CheckTypeForJS(cx, d, mg.funcSig(funcIndex)))
         return false;
 
-    UniqueChars fieldName = DecodeFieldName(cx, d, dupSet);
+    UniqueChars fieldName = DecodeExportName(cx, d, dupSet);
     if (!fieldName)
         return false;
 
     return mg.declareExport(Move(fieldName), funcIndex);
 }
 
 static bool
 DecodeExportTable(JSContext* cx, Decoder& d, ModuleGenerator& mg)
@@ -1253,25 +1248,21 @@ DecodeFunctionBody(JSContext* cx, Decode
     FunctionGenerator fg;
     if (!mg.startFuncDef(d.currentOffset(), &fg))
         return false;
 
     ValTypeVector locals;
     if (!locals.appendAll(mg.funcSig(funcIndex).args()))
         return false;
 
-    uint32_t numVars;
-    if (!d.readVarU32(&numVars))
-        return Fail(cx, d, "expected number of local vars");
+    if (!DecodeLocalEntries(d, &locals))
+        return Fail(cx, d, "failed decoding local entries");
 
-    for (uint32_t i = 0; i < numVars; i++) {
-        ValType type;
-        if (!DecodeValType(cx, d, &type))
-            return false;
-        if (!locals.append(type))
+    for (ValType type : locals) {
+        if (!CheckValType(cx, d, type))
             return false;
     }
 
     FunctionDecoder f(cx, d, mg, fg, funcIndex, locals);
 
     ExprType type = ExprType::Void;
 
     while (d.currentPosition() < bodyEnd) {
@@ -1280,20 +1271,20 @@ DecodeFunctionBody(JSContext* cx, Decode
     }
 
     if (!CheckType(f, type, f.sig().ret()))
         return false;
 
     if (d.currentPosition() != bodyEnd)
         return Fail(cx, d, "function body length mismatch");
 
-    if (!fg.bytecode().resize(bodySize))
+    if (!fg.bytes().resize(bodySize))
         return false;
 
-    memcpy(fg.bytecode().begin(), bodyBegin, bodySize);
+    memcpy(fg.bytes().begin(), bodyBegin, bodySize);
 
     int64_t after = PRMJ_Now();
     unsigned generateTime = (after - before) / PRMJ_USEC_PER_MSEC;
 
     return mg.finishFuncDef(funcIndex, generateTime, &fg);
 }
 
 static bool
@@ -1355,17 +1346,17 @@ DecodeDataSegments(JSContext* cx, Decode
         uint32_t numBytes;
         if (!d.readVarU32(&numBytes))
             return Fail(cx, d, "expected segment size");
 
         if (dstOffset > heapLength || heapLength - dstOffset < numBytes)
             return Fail(cx, d, "data segment does not fit in memory");
 
         const uint8_t* src;
-        if (!d.readRawData(numBytes, &src))
+        if (!d.readBytesRaw(numBytes, &src))
             return Fail(cx, d, "data segment shorter than declared");
 
         memcpy(heapBase + dstOffset, src, numBytes);
         prevEnd = dstOffset + numBytes;
     }
 
     if (!d.finishSection(sectionStart))
         return Fail(cx, d, "data section byte size mismatch");
@@ -1467,44 +1458,44 @@ CheckCompilerSupport(JSContext* cx)
 #endif
         JS_ReportError(cx, "WebAssembly is not supported on the current device.");
         return false;
     }
     return true;
 }
 
 static bool
-GetProperty(JSContext* cx, HandleObject obj, const char* utf8Chars, MutableHandleValue v)
+GetProperty(JSContext* cx, HandleObject obj, const Bytes& bytes, MutableHandleValue v)
 {
-    JSAtom* atom = AtomizeUTF8Chars(cx, utf8Chars, strlen(utf8Chars));
+    JSAtom* atom = AtomizeUTF8Chars(cx, (char*)bytes.begin(), bytes.length());
     if (!atom)
         return false;
 
     RootedId id(cx, AtomToId(atom));
     return GetProperty(cx, obj, obj, id, v);
 }
 
 static bool
 ImportFunctions(JSContext* cx, HandleObject importObj, const ImportNameVector& importNames,
                 MutableHandle<FunctionVector> imports)
 {
     if (!importNames.empty() && !importObj)
         return Fail(cx, "no import object given");
 
     for (const ImportName& name : importNames) {
         RootedValue v(cx);
-        if (!GetProperty(cx, importObj, name.module.get(), &v))
+        if (!GetProperty(cx, importObj, name.module, &v))
             return false;
 
-        if (*name.func.get()) {
+        if (!name.func.empty()) {
             if (!v.isObject())
                 return Fail(cx, "import object field is not an Object");
 
             RootedObject obj(cx, &v.toObject());
-            if (!GetProperty(cx, obj, name.func.get(), &v))
+            if (!GetProperty(cx, obj, name.func, &v))
                 return false;
         }
 
         if (!IsFunctionObject(v))
             return Fail(cx, "import object field is not a Function");
 
         if (!imports.append(&v.toObject().as<JSFunction>()))
             return false;
@@ -1539,16 +1530,17 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
     UniqueChars file;
     if (!DescribeScriptedCaller(cx, &file))
         return false;
 
     ImportNameVector importNames;
     UniqueExportMap exportMap;
     Rooted<ArrayBufferObject*> heap(cx);
     Rooted<WasmModuleObject*> moduleObj(cx);
+
     if (!DecodeModule(cx, Move(file), bytes, length, &importNames, &exportMap, &heap, &moduleObj)) {
         if (!cx->isExceptionPending())
             ReportOutOfMemory(cx);
         return false;
     }
 
     Rooted<FunctionVector> imports(cx, FunctionVector(cx));
     if (!ImportFunctions(cx, importObj, importNames, &imports))
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBinary.cpp
@@ -0,0 +1,87 @@
+/* -*- 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 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "asmjs/WasmBinary.h"
+
+#include "asmjs/WasmTypes.h"
+
+using namespace js;
+using namespace js::wasm;
+
+bool
+wasm::EncodeLocalEntries(Encoder& e, const ValTypeVector& locals)
+{
+    uint32_t numLocalEntries = 0;
+    ValType prev = ValType::Limit;
+    for (ValType t : locals) {
+        if (t != prev) {
+            numLocalEntries++;
+            prev = t;
+        }
+    }
+
+    if (!e.writeVarU32(numLocalEntries))
+        return false;
+
+    if (numLocalEntries) {
+        prev = locals[0];
+        uint32_t count = 1;
+        for (uint32_t i = 1; i < locals.length(); i++, count++) {
+            if (prev != locals[i]) {
+                if (!e.writeVarU32(count))
+                    return false;
+                if (!e.writeValType(prev))
+                    return false;
+                prev = locals[i];
+                count = 0;
+            }
+        }
+        if (!e.writeVarU32(count))
+            return false;
+        if (!e.writeValType(prev))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+wasm::DecodeLocalEntries(Decoder& d, ValTypeVector* locals)
+{
+    uint32_t numLocalEntries;
+    if (!d.readVarU32(&numLocalEntries))
+        return false;
+
+    for (uint32_t i = 0; i < numLocalEntries; i++) {
+        uint32_t count;
+        if (!d.readVarU32(&count))
+            return false;
+
+        if (MaxLocals - locals->length() < count)
+            return false;
+
+        ValType type;
+        if (!d.readValType(&type))
+            return false;
+
+        if (!locals->appendN(type, count))
+            return false;
+    }
+
+    return true;
+}
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -14,25 +14,21 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef wasm_binary_h
 #define wasm_binary_h
 
-#include "mozilla/DebugOnly.h"
-
 #include "builtin/SIMD.h"
 
 namespace js {
 namespace wasm {
 
-using mozilla::DebugOnly;
-
 static const uint32_t MagicNumber        = 0x6d736100; // "\0asm"
 static const uint32_t EncodingVersion    = 0xa;
 
 static const char SignaturesId[]         = "signatures";
 static const char ImportTableId[]        = "import_table";
 static const char FunctionSignaturesId[] = "function_signatures";
 static const char FunctionTableId[]      = "function_table";
 static const char MemoryId[]             = "memory";
@@ -323,40 +319,39 @@ enum class ExprType
     F32x4 = uint8_t(ValType::F32x4),
     B32x4 = uint8_t(ValType::B32x4),
 
     Limit
 };
 
 typedef int32_t I32x4[4];
 typedef float F32x4[4];
-typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytecode;
-typedef UniquePtr<Bytecode> UniqueBytecode;
+typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
 
-// The Encoder class appends bytes to the Bytecode object it is given during
-// construction. The client is responsible for the Bytecode's lifetime and must
-// keep the Bytecode alive as long as the Encoder is used.
+// The Encoder class appends bytes to the Bytes object it is given during
+// construction. The client is responsible for the Bytes's lifetime and must
+// keep the Bytes alive as long as the Encoder is used.
 
 class Encoder
 {
-    Bytecode& bytecode_;
+    Bytes& bytes_;
 
     template <class T>
     MOZ_WARN_UNUSED_RESULT bool write(const T& v) {
-        return bytecode_.append(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
+        return bytes_.append(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
     }
 
     template <typename UInt>
     MOZ_WARN_UNUSED_RESULT bool writeVarU(UInt i) {
         do {
             uint8_t byte = i & 0x7F;
             i >>= 7;
             if (i != 0)
                 byte |= 0x80;
-            if (!bytecode_.append(byte))
+            if (!bytes_.append(byte))
                 return false;
         } while(i != 0);
         return true;
     }
 
     template <class T>
     MOZ_WARN_UNUSED_RESULT bool writeEnum(T v) {
         static_assert(uint32_t(T::Limit) <= UINT32_MAX, "fits");
@@ -369,52 +364,49 @@ class Encoder
             uint8_t assertByte = assertBits & 0x7f;
             uint8_t patchByte = patchBits & 0x7f;
             assertBits >>= 7;
             patchBits >>= 7;
             if (assertBits != 0) {
                 assertByte |= 0x80;
                 patchByte |= 0x80;
             }
-            MOZ_ASSERT(assertByte == bytecode_[offset]);
-            bytecode_[offset] = patchByte;
+            MOZ_ASSERT(assertByte == bytes_[offset]);
+            bytes_[offset] = patchByte;
             offset++;
         } while(assertBits != 0);
     }
 
     uint32_t varU32ByteLength(size_t offset) const {
         size_t start = offset;
-        while (bytecode_[offset] & 0x80)
+        while (bytes_[offset] & 0x80)
             offset++;
         return offset - start + 1;
     }
-    static const uint32_t EnumSentinel = 0x3fff;
 
     template <class T>
     MOZ_WARN_UNUSED_RESULT bool writePatchableEnum(size_t* offset) {
-        static_assert(uint32_t(T::Limit) <= EnumSentinel, "reserve enough bits");
-        *offset = bytecode_.length();
-        return writeVarU32(EnumSentinel);
+        *offset = bytes_.length();
+        return writeVarU32(uint32_t(T::Limit));
     }
 
     template <class T>
     void patchEnum(size_t offset, T v) {
-        static_assert(uint32_t(T::Limit) <= UINT32_MAX, "fits");
         MOZ_ASSERT(uint32_t(v) < uint32_t(T::Limit));
-        return patchVarU32(offset, uint32_t(v), EnumSentinel);
+        return patchVarU32(offset, uint32_t(v), uint32_t(T::Limit));
     }
 
   public:
-    explicit Encoder(Bytecode& bytecode)
-      : bytecode_(bytecode)
+    explicit Encoder(Bytes& bytes)
+      : bytes_(bytes)
     {
         MOZ_ASSERT(empty());
     }
 
-    size_t currentOffset() const { return bytecode_.length(); }
+    size_t currentOffset() const { return bytes_.length(); }
     bool empty() const { return currentOffset() == 0; }
 
     // Fixed-size encoding operations simply copy the literal bytes (without
     // attempting to align).
 
     MOZ_WARN_UNUSED_RESULT bool writeFixedU32(uint32_t i) {
         return write<uint32_t>(i);
     }
@@ -447,80 +439,78 @@ class Encoder
     }
     MOZ_WARN_UNUSED_RESULT bool writeExprType(ExprType type) {
         return writeEnum(type);
     }
 
     // Variable-length encodings that allow back-patching.
 
     MOZ_WARN_UNUSED_RESULT bool writePatchableVarU32(size_t* offset) {
-        *offset = bytecode_.length();
+        *offset = bytes_.length();
         return writeVarU32(UINT32_MAX);
     }
     void patchVarU32(size_t offset, uint32_t patchBits) {
         return patchVarU32(offset, patchBits, UINT32_MAX);
     }
 
     MOZ_WARN_UNUSED_RESULT bool writePatchableVarU8(size_t* offset) {
-        *offset = bytecode_.length();
+        *offset = bytes_.length();
         return writeU8(UINT8_MAX);
     }
     void patchVarU8(size_t offset, uint8_t patchBits) {
         MOZ_ASSERT(patchBits < 0x80);
         return patchU8(offset, patchBits);
     }
 
     MOZ_WARN_UNUSED_RESULT bool writePatchableExpr(size_t* offset) {
         return writePatchableEnum<Expr>(offset);
     }
     void patchExpr(size_t offset, Expr expr) {
         patchEnum(offset, expr);
     }
 
-    // C-strings are written in UTF8 and null-terminated while raw data can
-    // contain nulls and instead has an explicit byte length.
+    // Byte ranges start with an LEB128 length followed by an arbitrary sequence
+    // of bytes. When used for strings, bytes are to be interpreted as utf8.
 
-    MOZ_WARN_UNUSED_RESULT bool writeCString(const char* cstr) {
-        return bytecode_.append(reinterpret_cast<const uint8_t*>(cstr), strlen(cstr) + 1);
-    }
-    MOZ_WARN_UNUSED_RESULT bool writeRawData(const uint8_t* bytes, uint32_t numBytes) {
-        return bytecode_.append(bytes, numBytes);
+    MOZ_WARN_UNUSED_RESULT bool writeBytes(const void* bytes, uint32_t numBytes) {
+        return writeVarU32(numBytes) &&
+               bytes_.append(reinterpret_cast<const uint8_t*>(bytes), numBytes);
     }
 
     // A "section" is a contiguous range of bytes that stores its own size so
     // that it may be trivially skipped without examining the contents. Sections
     // require backpatching since the size of the section is only known at the
-    // end while the size's uint32 must be stored at the beginning. Immediately
+    // end while the size's varU32 must be stored at the beginning. Immediately
     // after the section length is the string id of the section.
 
     template <size_t IdSizeWith0>
     MOZ_WARN_UNUSED_RESULT bool startSection(const char (&id)[IdSizeWith0], size_t* offset) {
         static const size_t IdSize = IdSizeWith0 - 1;
         MOZ_ASSERT(id[IdSize] == '\0');
         return writePatchableVarU32(offset) &&
                writeVarU32(IdSize) &&
-               writeRawData(reinterpret_cast<const uint8_t*>(id), IdSize);
+               bytes_.append(reinterpret_cast<const uint8_t*>(id), IdSize);
     }
     void finishSection(size_t offset) {
-        return patchVarU32(offset, bytecode_.length() - offset - varU32ByteLength(offset));
+        return patchVarU32(offset, bytes_.length() - offset - varU32ByteLength(offset));
     }
 
     // Temporary encoding forms which should be removed as part of the
     // conversion to wasm:
 
     MOZ_WARN_UNUSED_RESULT bool writeU8(uint8_t i) {
         return write<uint8_t>(i);
     }
     MOZ_WARN_UNUSED_RESULT bool writePatchableU8(size_t* offset) {
-        *offset = bytecode_.length();
-        return bytecode_.append(0xff);
+        *offset = bytes_.length();
+        return bytes_.append(0xff);
     }
     void patchU8(size_t offset, uint8_t i) {
-        MOZ_ASSERT(bytecode_[offset] == 0xff);
-        bytecode_[offset] = i;
+        MOZ_ASSERT(bytes_[offset] == 0xff);
+        bytes_[offset] = i;
     }
 };
 
 // The Decoder class decodes the bytes in the range it is given during
 // construction. The client is responsible for keeping the byte range alive as
 // long as the Decoder is used.
 
 class Decoder
@@ -528,30 +518,28 @@ class Decoder
     const uint8_t* const beg_;
     const uint8_t* const end_;
     const uint8_t* cur_;
 
     template <class T>
     MOZ_WARN_UNUSED_RESULT bool read(T* out) {
         if (bytesRemain() < sizeof(T))
             return false;
-        if (out)
-            memcpy((void*)out, cur_, sizeof(T));
+        memcpy((void*)out, cur_, sizeof(T));
         cur_ += sizeof(T);
         return true;
     }
 
     template <class T>
     MOZ_WARN_UNUSED_RESULT bool readEnum(T* out) {
         static_assert(uint32_t(T::Limit) <= UINT32_MAX, "fits");
         uint32_t u32;
         if (!readVarU32(&u32) || u32 >= uint32_t(T::Limit))
             return false;
-        if (out)
-            *out = T(u32);
+        *out = T(u32);
         return true;
     }
 
     template <class T>
     T uncheckedRead() {
         MOZ_ASSERT(bytesRemain() >= sizeof(T));
         T ret;
         memcpy(&ret, cur_, sizeof(T));
@@ -561,53 +549,51 @@ class Decoder
 
     template <class T>
     T uncheckedReadEnum() {
         static_assert(uint32_t(T::Limit) <= UINT32_MAX, "fits");
         return (T)uncheckedReadVarU32();
     }
 
     template <typename UInt>
-    MOZ_WARN_UNUSED_RESULT bool readVarU(UInt* out = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readVarU(UInt* out) {
         const unsigned numBits = sizeof(UInt) * CHAR_BIT;
         const unsigned remainderBits = numBits % 7;
         const unsigned numBitsInSevens = numBits - remainderBits;
         UInt u = 0;
         uint8_t byte;
         UInt shift = 0;
         do {
             if (!readFixedU8(&byte))
                 return false;
             if (!(byte & 0x80)) {
-                if (out)
-                    *out = u | UInt(byte) << shift;
+                *out = u | UInt(byte) << shift;
                 return true;
             }
             u |= UInt(byte & 0x7F) << shift;
             shift += 7;
         } while (shift != numBitsInSevens);
         if (!readFixedU8(&byte) || (byte & (unsigned(-1) << remainderBits)))
             return false;
-        if (out)
-            *out = u | UInt(byte) << numBitsInSevens;
+        *out = u | UInt(byte) << numBitsInSevens;
         return true;
     }
 
   public:
     Decoder(const uint8_t* begin, const uint8_t* end)
       : beg_(begin),
         end_(end),
         cur_(begin)
     {
         MOZ_ASSERT(begin <= end);
     }
-    explicit Decoder(const Bytecode& bytecode)
-      : beg_(bytecode.begin()),
-        end_(bytecode.end()),
-        cur_(bytecode.begin())
+    explicit Decoder(const Bytes& bytes)
+      : beg_(bytes.begin()),
+        end_(bytes.end()),
+        cur_(bytes.begin())
     {}
 
     bool done() const {
         MOZ_ASSERT(cur_ <= end_);
         return cur_ == end_;
     }
 
     uintptr_t bytesRemain() const {
@@ -615,71 +601,69 @@ class Decoder
         return uintptr_t(end_ - cur_);
     }
     const uint8_t* currentPosition() const {
         return cur_;
     }
     size_t currentOffset() const {
         return cur_ - beg_;
     }
-    void assertCurrentIs(const DebugOnly<size_t> offset) const {
-        MOZ_ASSERT(currentOffset() == offset);
-    }
 
     // Fixed-size encoding operations simply copy the literal bytes (without
     // attempting to align).
 
-    MOZ_WARN_UNUSED_RESULT bool readFixedU32(uint32_t* u = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readFixedU32(uint32_t* u) {
         return read<uint32_t>(u);
     }
-    MOZ_WARN_UNUSED_RESULT bool readFixedF32(float* f = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readFixedF32(float* f) {
         return read<float>(f);
     }
-    MOZ_WARN_UNUSED_RESULT bool readFixedF64(double* d = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readFixedF64(double* d) {
         return read<double>(d);
     }
-    MOZ_WARN_UNUSED_RESULT bool readFixedI32x4(I32x4* i32x4 = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readFixedI32x4(I32x4* i32x4) {
         return read<I32x4>(i32x4);
     }
-    MOZ_WARN_UNUSED_RESULT bool readFixedF32x4(F32x4* f32x4 = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readFixedF32x4(F32x4* f32x4) {
         return read<F32x4>(f32x4);
     }
 
     // Variable-length encodings that all use LEB128.
 
-    MOZ_WARN_UNUSED_RESULT bool readVarU32(uint32_t* out = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readVarU32(uint32_t* out) {
         return readVarU<uint32_t>(out);
     }
-    MOZ_WARN_UNUSED_RESULT bool readVarU64(uint64_t* out = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readVarU64(uint64_t* out) {
         return readVarU<uint64_t>(out);
     }
-    MOZ_WARN_UNUSED_RESULT bool readExpr(Expr* expr = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readExpr(Expr* expr) {
         return readEnum(expr);
     }
     MOZ_WARN_UNUSED_RESULT bool readValType(ValType* type) {
         return readEnum(type);
     }
     MOZ_WARN_UNUSED_RESULT bool readExprType(ExprType* type) {
         return readEnum(type);
     }
 
-    // C-strings are written in UTF8 and null-terminated while raw data can
-    // contain nulls and instead has an explicit byte length.
+    // See writeBytes comment.
 
-    MOZ_WARN_UNUSED_RESULT UniqueChars readCString() {
-        const char* begin = reinterpret_cast<const char*>(cur_);
-        for (; cur_ != end_; cur_++) {
-            if (!*cur_) {
-                cur_++;
-                return UniqueChars(DuplicateString(begin));
-            }
-        }
-        return nullptr;
+    MOZ_WARN_UNUSED_RESULT bool readBytes(Bytes* bytes) {
+        uint32_t numBytes;
+        if (!readVarU32(&numBytes))
+            return false;
+        if (bytesRemain() < numBytes)
+            return false;
+        if (!bytes->resize(numBytes))
+            return false;
+        memcpy(bytes->begin(), cur_, numBytes);
+        cur_ += numBytes;
+        return true;
     }
-    MOZ_WARN_UNUSED_RESULT bool readRawData(uint32_t numBytes, const uint8_t** bytes = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readBytesRaw(uint32_t numBytes, const uint8_t** bytes) {
         if (bytes)
             *bytes = cur_;
         if (bytesRemain() < numBytes)
             return false;
         cur_ += numBytes;
         return true;
     }
 
@@ -701,17 +685,17 @@ class Decoder
         if (!readVarU32(&idSize))
             goto backup;
         if (bytesRemain() < idSize)
             return false;
         if (idSize != IdSize || !!memcmp(cur_, id, IdSize))
             goto backup;
         cur_ += IdSize;
         *startOffset = before - beg_;
-        return  true;
+        return true;
       backup:
         cur_ = before;
         *startOffset = NotStarted;
         return true;
     }
     MOZ_WARN_UNUSED_RESULT bool finishSection(uint32_t startOffset) {
         uint32_t currentOffset = cur_ - beg_;
         cur_ = beg_ + startOffset;
@@ -730,17 +714,17 @@ class Decoder
             return false;
         if (uint32_t(cur_ - begin) > size)
             return false;
         cur_ = begin + size;
         return true;
     }
 
     // The infallible "unchecked" decoding functions can be used when we are
-    // sure that the bytecode is well-formed (by construction or due to previous
+    // sure that the bytes are well-formed (by construction or due to previous
     // validation).
 
     uint32_t uncheckedReadFixedU32() {
         return uncheckedRead<uint32_t>();
     }
     float uncheckedReadFixedF32() {
         return uncheckedRead<float>();
     }
@@ -782,23 +766,31 @@ class Decoder
     }
     ValType uncheckedReadValType() {
         return uncheckedReadEnum<ValType>();
     }
 
     // Temporary encoding forms which should be removed as part of the
     // conversion to wasm:
 
-    MOZ_WARN_UNUSED_RESULT bool readFixedU8(uint8_t* i = nullptr) {
+    MOZ_WARN_UNUSED_RESULT bool readFixedU8(uint8_t* i) {
         return read<uint8_t>(i);
     }
-    MOZ_WARN_UNUSED_RESULT bool readFixedI32(int32_t* i = nullptr) {
-        return read<int32_t>(i);
-    }
     uint8_t uncheckedReadFixedU8() {
         return uncheckedRead<uint8_t>();
     }
 };
 
+// Reusable macro encoding/decoding functions reused by both the two
+// encoders (AsmJS/WasmText) and decoders (Wasm/WasmIonCompile).
+
+typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
+
+bool
+EncodeLocalEntries(Encoder& d, const ValTypeVector& locals);
+
+bool
+DecodeLocalEntries(Decoder& d, ValTypeVector* locals);
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_binary_h
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -296,17 +296,17 @@ ModuleGenerator::convertOutOfRangeBranch
     masm_.clearJumpSites();
 
     return true;
 }
 
 bool
 ModuleGenerator::finishTask(IonCompileTask* task)
 {
-    const FuncBytecode& func = task->func();
+    const FuncBytes& func = task->func();
     FuncCompileResults& results = task->results();
 
     // Before merging in the new function's code, if jumps/calls in a previous
     // function's body might go out of range, patch these to thunks which have
     // full range.
     if ((masm_.size() - startOfUnpatchedBranches_) + results.masm().size() > JumpRange()) {
         startOfUnpatchedBranches_ = masm_.size();
         if (!convertOutOfRangeBranchesToThunks())
@@ -720,17 +720,17 @@ ModuleGenerator::numExports() const
 {
     return module_->exports.length();
 }
 
 bool
 ModuleGenerator::addMemoryExport(UniqueChars fieldName)
 {
     return exportMap_->fieldNames.append(Move(fieldName)) &&
-           exportMap_->fieldsToExports.append(ExportMap::MemoryExport);
+           exportMap_->fieldsToExports.append(MemoryExport);
 }
 
 bool
 ModuleGenerator::startFuncDefs()
 {
     MOZ_ASSERT(!startedFuncDefs());
     threadView_ = MakeUnique<ModuleGeneratorThreadView>(*shared_);
     if (!threadView_)
@@ -777,44 +777,36 @@ ModuleGenerator::startFuncDef(uint32_t l
     MOZ_ASSERT(!activeFunc_);
     MOZ_ASSERT(!finishedFuncs_);
 
     if (freeTasks_.empty() && !finishOutstandingTask())
         return false;
 
     IonCompileTask* task = freeTasks_.popCopy();
 
-    task->reset(&fg->bytecode_);
-    if (fg->bytecode_) {
-        fg->bytecode_->clear();
-    } else {
-        fg->bytecode_ = MakeUnique<Bytecode>();
-        if (!fg->bytecode_)
-            return false;
-    }
-
+    task->reset(&fg->bytes_);
+    fg->bytes_.clear();
     fg->lineOrBytecode_ = lineOrBytecode;
     fg->m_ = this;
     fg->task_ = task;
     activeFunc_ = fg;
     return true;
 }
 
 bool
 ModuleGenerator::finishFuncDef(uint32_t funcIndex, unsigned generateTime, FunctionGenerator* fg)
 {
     MOZ_ASSERT(activeFunc_ == fg);
 
-    UniqueFuncBytecode func =
-        js::MakeUnique<FuncBytecode>(funcIndex,
-                                     funcSig(funcIndex),
-                                     Move(fg->bytecode_),
-                                     fg->lineOrBytecode_,
-                                     Move(fg->callSiteLineNums_),
-                                     generateTime);
+    auto func = js::MakeUnique<FuncBytes>(Move(fg->bytes_),
+                                          funcIndex,
+                                          funcSig(funcIndex),
+                                          fg->lineOrBytecode_,
+                                          Move(fg->callSiteLineNums_),
+                                          generateTime);
     if (!func)
         return false;
 
     fg->task_->init(Move(func));
 
     if (parallel_) {
         if (!StartOffThreadWasmCompile(cx_, fg->task_))
             return false;
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -46,17 +46,17 @@ struct SlowFunction
 };
 typedef Vector<SlowFunction> SlowFunctionVector;
 
 // The ModuleGeneratorData holds all the state shared between the
 // ModuleGenerator and ModuleGeneratorThreadView. The ModuleGeneratorData
 // is encapsulated by ModuleGenerator/ModuleGeneratorThreadView classes which
 // present a race-free interface to the code in each thread assuming any given
 // element is initialized by the ModuleGenerator thread before an index to that
-// element is written to Bytecode sent to a ModuleGeneratorThreadView thread.
+// element is written to Bytes sent to a ModuleGeneratorThreadView thread.
 // Once created, the Vectors are never resized.
 
 struct TableModuleGeneratorData
 {
     uint32_t globalDataOffset;
     uint32_t numElems;
     Uint32Vector elemFuncIndices;
 
@@ -197,18 +197,18 @@ class MOZ_STACK_CLASS ModuleGenerator
     // Parallel compilation
     bool                            parallel_;
     uint32_t                        outstanding_;
     UniqueModuleGeneratorThreadView threadView_;
     Vector<IonCompileTask>          tasks_;
     Vector<IonCompileTask*>         freeTasks_;
 
     // Assertions
-    DebugOnly<FunctionGenerator*>   activeFunc_;
-    DebugOnly<bool>                 finishedFuncs_;
+    FunctionGenerator*              activeFunc_;
+    bool                            finishedFuncs_;
 
     bool finishOutstandingTask();
     bool funcIsDefined(uint32_t funcIndex) const;
     uint32_t funcEntry(uint32_t funcIndex) const;
     bool convertOutOfRangeBranchesToThunks();
     bool finishTask(IonCompileTask* task);
     bool finishCodegen(StaticLinkData* link);
     bool finishStaticLinkData(uint8_t* code, uint32_t codeBytes, StaticLinkData* link);
@@ -283,33 +283,33 @@ class MOZ_STACK_CLASS ModuleGenerator
 // anything else. After the body is complete, ModuleGenerator::finishFunc must
 // be called before the FunctionGenerator is destroyed and the next function is
 // started.
 
 class MOZ_STACK_CLASS FunctionGenerator
 {
     friend class ModuleGenerator;
 
-    ModuleGenerator*   m_;
-    IonCompileTask*    task_;
+    ModuleGenerator* m_;
+    IonCompileTask*  task_;
 
     // Data created during function generation, then handed over to the
-    // FuncBytecode in ModuleGenerator::finishFunc().
-    UniqueBytecode     bytecode_;
-    Uint32Vector       callSiteLineNums_;
+    // FuncBytes in ModuleGenerator::finishFunc().
+    Bytes            bytes_;
+    Uint32Vector     callSiteLineNums_;
 
     uint32_t lineOrBytecode_;
 
   public:
     FunctionGenerator()
       : m_(nullptr), task_(nullptr), lineOrBytecode_(0)
     {}
 
-    Bytecode& bytecode() const {
-        return *bytecode_;
+    Bytes& bytes() {
+        return bytes_;
     }
     bool addCallSiteLineNum(uint32_t lineno) {
         return callSiteLineNums_.append(lineno);
     }
 };
 
 } // namespace wasm
 } // namespace js
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -35,17 +35,17 @@ typedef Vector<MBasicBlock*, 8, SystemAl
 // MIR graph.
 class FunctionCompiler
 {
   private:
     typedef Vector<BlockVector, 0, SystemAllocPolicy> BlocksVector;
 
     ModuleGeneratorThreadView& mg_;
     Decoder&                   decoder_;
-    const FuncBytecode&        func_;
+    const FuncBytes&           func_;
     const ValTypeVector&       locals_;
     size_t                     lastReadCallSite_;
 
     TempAllocator&             alloc_;
     MIRGraph&                  graph_;
     const CompileInfo&         info_;
     MIRGenerator&              mirGen_;
 
@@ -55,17 +55,17 @@ class FunctionCompiler
     uint32_t                   blockDepth_;
     BlocksVector               targets_;
 
     FuncCompileResults&        compileResults_;
 
   public:
     FunctionCompiler(ModuleGeneratorThreadView& mg,
                      Decoder& decoder,
-                     const FuncBytecode& func,
+                     const FuncBytes& func,
                      const ValTypeVector& locals,
                      MIRGenerator& mirGen,
                      FuncCompileResults& compileResults)
       : mg_(mg),
         decoder_(decoder),
         func_(func),
         locals_(locals),
         lastReadCallSite_(0),
@@ -148,17 +148,17 @@ class FunctionCompiler
         MOZ_ASSERT(loopDepth_ == 0);
         MOZ_ASSERT(blockDepth_ == 0);
 #ifdef DEBUG
         for (BlockVector& vec : targets_) {
             MOZ_ASSERT(vec.empty());
         }
 #endif
         MOZ_ASSERT(inDeadCode());
-        MOZ_ASSERT(decoder_.done(), "all bytecode must be consumed");
+        MOZ_ASSERT(decoder_.done(), "all bytes must be consumed");
         MOZ_ASSERT(func_.callSiteLineNums().length() == lastReadCallSite_);
     }
 
     /************************* Read-only interface (after local scope setup) */
 
     MIRGenerator&       mirGen() const     { return mirGen_; }
     MIRGraph&           mirGraph() const   { return graph_; }
     const CompileInfo&  info() const       { return info_; }
@@ -3043,32 +3043,28 @@ EmitExpr(FunctionCompiler& f, MDefinitio
     MOZ_CRASH("unexpected wasm opcode");
 }
 
 bool
 wasm::IonCompileFunction(IonCompileTask* task)
 {
     int64_t before = PRMJ_Now();
 
-    const FuncBytecode& func = task->func();
+    const FuncBytes& func = task->func();
     FuncCompileResults& results = task->results();
 
-    // Read in the variable types to build the local types vector.
-
-    Decoder d(func.bytecode());
+    Decoder d(func.bytes());
+
+    // Build the local types vector.
 
     ValTypeVector locals;
     if (!locals.appendAll(func.sig().args()))
         return false;
-
-    uint32_t numVars = d.uncheckedReadVarU32();
-    for (uint32_t i = 0; i < numVars; i++) {
-        if (!locals.append(d.uncheckedReadValType()))
-            return false;
-    }
+    if (!DecodeLocalEntries(d, &locals))
+        return false;
 
     // Set up for Ion compilation.
 
     JitContext jitContext(CompileRuntime::get(task->runtime()), &results.alloc());
     const JitCompileOptions options;
     MIRGraph graph(&results.alloc());
     CompileInfo compileInfo(locals.length());
     MIRGenerator mir(nullptr, options, &results.alloc(), &graph, &compileInfo,
--- a/js/src/asmjs/WasmIonCompile.h
+++ b/js/src/asmjs/WasmIonCompile.h
@@ -26,62 +26,58 @@ namespace js {
 namespace wasm {
 
 class ModuleGeneratorThreadView;
 
 typedef Vector<jit::MIRType, 8, SystemAllocPolicy> MIRTypeVector;
 typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
 typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
 
-// The FuncBytecode class contains the intermediate representation of a
-// parsed/decoded and validated asm.js/WebAssembly function. The FuncBytecode
-// lives only until it is fully compiled.
+// The FuncBytes class represents a single, concurrently-compilable function.
+// A FuncBytes object is composed of the wasm function body bytes along with the
+// ambient metadata describing the function necessary to compile it.
 
-class FuncBytecode
+class FuncBytes
 {
-    // Function metadata
+    Bytes              bytes_;
+    uint32_t           index_;
     const DeclaredSig& sig_;
-    uint32_t lineOrBytecode_;
-    Uint32Vector callSiteLineNums_;
-
-    // Compilation bookkeeping
-    uint32_t index_;
-    unsigned generateTime_;
-
-    UniqueBytecode bytecode_;
+    uint32_t           lineOrBytecode_;
+    Uint32Vector       callSiteLineNums_;
+    unsigned           generateTime_;
 
   public:
-    FuncBytecode(uint32_t index,
-                 const DeclaredSig& sig,
-                 UniqueBytecode bytecode,
-                 uint32_t lineOrBytecode,
-                 Uint32Vector&& callSiteLineNums,
-                 unsigned generateTime)
-      : sig_(sig),
+    FuncBytes(Bytes&& bytes,
+              uint32_t index,
+              const DeclaredSig& sig,
+              uint32_t lineOrBytecode,
+              Uint32Vector&& callSiteLineNums,
+              unsigned generateTime)
+      : bytes_(Move(bytes)),
+        index_(index),
+        sig_(sig),
         lineOrBytecode_(lineOrBytecode),
         callSiteLineNums_(Move(callSiteLineNums)),
-        index_(index),
-        generateTime_(generateTime),
-        bytecode_(Move(bytecode))
+        generateTime_(generateTime)
     {}
 
-    UniqueBytecode recycleBytecode() { return Move(bytecode_); }
-
+    Bytes& bytes() { return bytes_; }
+    const Bytes& bytes() const { return bytes_; }
+    uint32_t index() const { return index_; }
+    const DeclaredSig& sig() const { return sig_; }
     uint32_t lineOrBytecode() const { return lineOrBytecode_; }
     const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
-    uint32_t index() const { return index_; }
-    const DeclaredSig& sig() const { return sig_; }
-    const Bytecode& bytecode() const { return *bytecode_; }
     unsigned generateTime() const { return generateTime_; }
 };
 
-typedef UniquePtr<FuncBytecode> UniqueFuncBytecode;
+typedef UniquePtr<FuncBytes> UniqueFuncBytes;
 
-// The FuncCompileResults contains the results of compiling a single function
-// body, ready to be merged into the whole-module MacroAssembler.
+// The FuncCompileResults class contains the results of compiling a single
+// function body, ready to be merged into the whole-module MacroAssembler.
+
 class FuncCompileResults
 {
     jit::TempAllocator alloc_;
     jit::MacroAssembler masm_;
     FuncOffsets offsets_;
     unsigned compileTime_;
 
     FuncCompileResults(const FuncCompileResults&) = delete;
@@ -103,23 +99,24 @@ class FuncCompileResults
 };
 
 // An IonCompileTask represents the task of compiling a single function body. An
 // IonCompileTask is filled with the wasm code to be compiled on the main
 // validation thread, sent off to an Ion compilation helper thread which creates
 // the FuncCompileResults, and finally sent back to the validation thread. To
 // save time allocating and freeing memory, IonCompileTasks are reset() and
 // reused.
+
 class IonCompileTask
 {
-    JSRuntime* const runtime_;
+    JSRuntime* const           runtime_;
     ModuleGeneratorThreadView& mg_;
-    LifoAlloc lifo_;
-    UniqueFuncBytecode func_;
-    mozilla::Maybe<FuncCompileResults> results_;
+    LifoAlloc                  lifo_;
+    UniqueFuncBytes            func_;
+    Maybe<FuncCompileResults>  results_;
 
     IonCompileTask(const IonCompileTask&) = delete;
     IonCompileTask& operator=(const IonCompileTask&) = delete;
 
   public:
     IonCompileTask(JSRuntime* rt, ModuleGeneratorThreadView& mg, size_t defaultChunkSize)
       : runtime_(rt), mg_(mg), lifo_(defaultChunkSize), func_(nullptr)
     {}
@@ -127,31 +124,31 @@ class IonCompileTask
         return runtime_;
     }
     LifoAlloc& lifo() {
         return lifo_;
     }
     ModuleGeneratorThreadView& mg() const {
         return mg_;
     }
-    void init(UniqueFuncBytecode func) {
+    void init(UniqueFuncBytes func) {
         MOZ_ASSERT(!func_);
-        func_ = mozilla::Move(func);
+        func_ = Move(func);
         results_.emplace(lifo_);
     }
-    const FuncBytecode& func() const {
+    const FuncBytes& func() const {
         MOZ_ASSERT(func_);
         return *func_;
     }
     FuncCompileResults& results() {
         return *results_;
     }
-    void reset(UniqueBytecode* recycled) {
+    void reset(Bytes* recycled) {
         if (func_)
-            *recycled = func_->recycleBytecode();
+            *recycled = Move(func_->bytes());
         func_.reset(nullptr);
         results_.reset();
         lifo_.releaseAll();
     }
 };
 
 bool
 IonCompileFunction(IonCompileTask* task);
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -49,18 +49,16 @@ using namespace js::jit;
 using namespace js::wasm;
 using mozilla::BinarySearch;
 using mozilla::MakeEnumeratedRange;
 using mozilla::PodCopy;
 using mozilla::PodZero;
 using mozilla::Swap;
 using JS::GenericNaN;
 
-const uint32_t ExportMap::MemoryExport;
-
 UniqueCodePtr
 wasm::AllocateCode(ExclusiveContext* cx, size_t bytes)
 {
     // On most platforms, this will allocate RWX memory. On iOS, or when
     // --non-writable-jitcode is used, this will allocate RW memory. In this
     // case, DynamicallyLinkModule will reprotect the code as RX.
     unsigned permissions =
         ExecutableAllocator::initialProtectionFlags(ExecutableAllocator::Writable);
@@ -1125,17 +1123,17 @@ CreateExportObject(JSContext* cx,
     MOZ_ASSERT(exportMap.exportFuncIndices.length() == exports.length());
     MOZ_ASSERT(exportMap.fieldNames.length() == exportMap.fieldsToExports.length());
 
     for (size_t fieldIndex = 0; fieldIndex < exportMap.fieldNames.length(); fieldIndex++) {
         const char* fieldName = exportMap.fieldNames[fieldIndex].get();
         if (!*fieldName) {
             MOZ_ASSERT(!exportObj);
             uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
-            if (exportIndex == ExportMap::MemoryExport) {
+            if (exportIndex == MemoryExport) {
                 MOZ_ASSERT(heap);
                 exportObj.set(heap);
             } else {
                 exportObj.set(NewExportedFunction(cx, moduleObj, exportMap, exportIndex));
                 if (!exportObj)
                     return false;
             }
             break;
@@ -1162,17 +1160,17 @@ CreateExportObject(JSContext* cx,
 
         JSAtom* atom = AtomizeUTF8Chars(cx, fieldName, strlen(fieldName));
         if (!atom)
             return false;
 
         RootedId id(cx, AtomToId(atom));
         RootedValue val(cx);
         uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
-        if (exportIndex == ExportMap::MemoryExport)
+        if (exportIndex == MemoryExport)
             val = ObjectValue(*heap);
         else
             val = vals[exportIndex];
 
         if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE))
             return false;
     }
 
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -331,20 +331,20 @@ typedef Vector<CacheableChars, 0, System
 // The 'fieldNames' vector provides the list of names of the module's exports.
 // For each field in fieldNames, 'fieldsToExports' provides either:
 //  - the sentinel value MemoryExport indicating an export of linear memory; or
 //  - the index of an export (both into the module's ExportVector and the
 //    ExportMap's exportFuncIndices vector).
 // Lastly, the 'exportFuncIndices' vector provides, for each exported function,
 // the internal index of the function.
 
+static const uint32_t MemoryExport = UINT32_MAX;
+
 struct ExportMap
 {
-    static const uint32_t MemoryExport = UINT32_MAX;
-
     CacheableCharsVector fieldNames;
     Uint32Vector fieldsToExports;
     Uint32Vector exportFuncIndices;
 
     WASM_DECLARE_SERIALIZABLE(ExportMap)
 };
 
 typedef UniquePtr<ExportMap> UniqueExportMap;
--- a/js/src/asmjs/WasmText.cpp
+++ b/js/src/asmjs/WasmText.cpp
@@ -779,16 +779,17 @@ class WasmToken
         GetLocal,
         If,
         IfElse,
         Import,
         Index,
         UnsignedInteger,
         SignedInteger,
         Memory,
+        NegativeZero,
         Load,
         Local,
         Loop,
         Module,
         Name,
         Nop,
         Offset,
         OpenParen,
@@ -1266,16 +1267,18 @@ WasmTokenStream::literal(const char16_t*
             if (!u.isValid())
                 return LexHexFloatLiteral(begin, end_, &cur_);
 
             cur_++;
         } while (cur_ != end_);
 
         if (*begin == '-') {
             uint64_t value = u.value();
+            if (value == 0)
+                return WasmToken(WasmToken::NegativeZero, begin, cur_);
             if (value > uint64_t(INT64_MIN))
                 return LexHexFloatLiteral(begin, end_, &cur_);
 
             value = -value;
             return WasmToken(int64_t(value), begin, cur_);
         }
     } else {
         while (cur_ != end_) {
@@ -1290,16 +1293,18 @@ WasmTokenStream::literal(const char16_t*
             if (!u.isValid())
                 return LexDecFloatLiteral(begin, end_, &cur_);
 
             cur_++;
         }
 
         if (*begin == '-') {
             uint64_t value = u.value();
+            if (value == 0)
+                return WasmToken(WasmToken::NegativeZero, begin, cur_);
             if (value > uint64_t(INT64_MIN))
                 return LexDecFloatLiteral(begin, end_, &cur_);
 
             value = -value;
             return WasmToken(int64_t(value), begin, cur_);
         }
     }
 
@@ -2265,16 +2270,19 @@ ParseFloatLiteral(WasmParseContext& c, W
         *result = token.index();
         return true;
       case WasmToken::UnsignedInteger:
         *result = token.uint();
         return true;
       case WasmToken::SignedInteger:
         *result = token.sint();
         return true;
+      case WasmToken::NegativeZero:
+        *result = -0.0;
+        return true;
       case WasmToken::Float:
         break;
       default:
         c.ts.generateError(token, c.error);
         return false;
     }
 
     const char16_t* begin = token.begin();
@@ -2340,29 +2348,33 @@ ParseConst(WasmParseContext& c, WasmToke
           case WasmToken::Index:
             return new(c.lifo) WasmAstConst(Val(val.index()));
           case WasmToken::SignedInteger: {
             CheckedInt<int32_t> sint = val.sint();
             if (!sint.isValid())
                 break;
             return new(c.lifo) WasmAstConst(Val(uint32_t(sint.value())));
           }
+          case WasmToken::NegativeZero:
+            return new(c.lifo) WasmAstConst(Val(uint32_t(0)));
           default:
             break;
         }
         break;
       }
       case ValType::I64: {
         switch (val.kind()) {
           case WasmToken::Index:
             return new(c.lifo) WasmAstConst(Val(uint64_t(val.index())));
           case WasmToken::UnsignedInteger:
             return new(c.lifo) WasmAstConst(Val(val.uint()));
           case WasmToken::SignedInteger:
             return new(c.lifo) WasmAstConst(Val(uint64_t(val.sint())));
+          case WasmToken::NegativeZero:
+            return new(c.lifo) WasmAstConst(Val(uint32_t(0)));
           default:
             break;
         }
         break;
       }
       case ValType::F32: {
         float result;
         if (!ParseFloatLiteral(c, val, &result))
@@ -3765,33 +3777,33 @@ EncodeFunctionSignatures(Encoder& e, Was
             return false;
     }
 
     e.finishSection(offset);
     return true;
 }
 
 static bool
-EncodeCString(Encoder& e, WasmName wasmName)
+EncodeBytes(Encoder& e, WasmName wasmName)
 {
     TwoByteChars range(wasmName.begin(), wasmName.length());
     UniqueChars utf8(JS::CharsToNewUTF8CharsZ(nullptr, range).c_str());
-    return utf8 && e.writeCString(utf8.get());
+    return utf8 && e.writeBytes(utf8.get(), strlen(utf8.get()));
 }
 
 static bool
 EncodeImport(Encoder& e, WasmAstImport& imp)
 {
     if (!e.writeVarU32(imp.sigIndex()))
         return false;
 
-    if (!EncodeCString(e, imp.module()))
+    if (!EncodeBytes(e, imp.module()))
         return false;
 
-    if (!EncodeCString(e, imp.func()))
+    if (!EncodeBytes(e, imp.func()))
         return false;
 
     return true;
 }
 
 static bool
 EncodeImportTable(Encoder& e, WasmAstModule& module)
 {
@@ -3849,17 +3861,17 @@ EncodeMemory(Encoder& e, WasmAstModule& 
 }
 
 static bool
 EncodeFunctionExport(Encoder& e, WasmAstExport& exp)
 {
     if (!e.writeVarU32(exp.func().index()))
         return false;
 
-    if (!EncodeCString(e, exp.name()))
+    if (!EncodeBytes(e, exp.name()))
         return false;
 
     return true;
 }
 
 static bool
 EncodeExportTable(Encoder& e, WasmAstModule& module)
 {
@@ -3920,23 +3932,21 @@ static bool
 EncodeFunctionBody(Encoder& e, WasmAstFunc& func)
 {
     size_t bodySizeAt;
     if (!e.writePatchableVarU32(&bodySizeAt))
         return false;
 
     size_t beforeBody = e.currentOffset();
 
-    if (!e.writeVarU32(func.vars().length()))
+    ValTypeVector varTypes;
+    if (!varTypes.appendAll(func.vars()))
         return false;
-
-    for (ValType type : func.vars()) {
-        if (!e.writeValType(type))
-            return false;
-    }
+    if (!EncodeLocalEntries(e, varTypes))
+        return false;
 
     for (WasmAstExpr* expr : func.body()) {
         if (!EncodeExpr(e, *expr))
             return false;
     }
 
     e.patchVarU32(bodySizeAt, e.currentOffset() - beforeBody);
     return true;
@@ -3976,20 +3986,17 @@ EncodeDataSegment(Encoder& e, WasmAstSeg
     const char16_t* cur = text.begin();
     const char16_t* end = text.end();
     while (cur != end) {
         uint8_t byte;
         MOZ_ALWAYS_TRUE(ConsumeTextByte(&cur, end, &byte));
         bytes.infallibleAppend(byte);
     }
 
-    if (!e.writeVarU32(bytes.length()))
-        return false;
-
-    if (!e.writeRawData(bytes.begin(), bytes.length()))
+    if (!e.writeBytes(bytes.begin(), bytes.length()))
         return false;
 
     return true;
 }
 
 static bool
 EncodeDataSegments(Encoder& e, WasmAstModule& module)
 {
@@ -4009,65 +4016,61 @@ EncodeDataSegments(Encoder& e, WasmAstMo
         if (!EncodeDataSegment(e, *segment))
             return false;
     }
 
     e.finishSection(offset);
     return true;
 }
 
-static UniqueBytecode
-EncodeModule(WasmAstModule& module)
+static bool
+EncodeModule(WasmAstModule& module, Bytes* bytes)
 {
-    UniqueBytecode bytecode = MakeUnique<Bytecode>();
-    if (!bytecode)
-        return nullptr;
-
-    Encoder e(*bytecode);
+    Encoder e(*bytes);
 
     if (!e.writeFixedU32(MagicNumber))
-        return nullptr;
+        return false;
 
     if (!e.writeFixedU32(EncodingVersion))
-        return nullptr;
+        return false;
 
     if (!EncodeSignatures(e, module))
-        return nullptr;
+        return false;
 
     if (!EncodeImportTable(e, module))
-        return nullptr;
+        return false;
 
     if (!EncodeFunctionSignatures(e, module))
-        return nullptr;
+        return false;
 
     if (!EncodeFunctionTable(e, module))
-        return nullptr;
+        return false;
 
     if (!EncodeMemory(e, module))
-        return nullptr;
+        return false;
 
     if (!EncodeExportTable(e, module))
-        return nullptr;
+        return false;
 
     if (!EncodeFunctionBodies(e, module))
-        return nullptr;
+        return false;
 
     if (!EncodeDataSegments(e, module))
-        return nullptr;
-
-    return Move(bytecode);
+        return false;
+
+    return true;
 }
 
 /*****************************************************************************/
 
-UniqueBytecode
-wasm::TextToBinary(const char16_t* text, UniqueChars* error)
+bool
+wasm::TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error)
 {
     LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
     WasmAstModule* module = ParseModule(text, lifo, error);
     if (!module)
-        return nullptr;
+        return false;
 
     if (!ResolveModule(lifo, module, error))
-        return nullptr;
-
-    return EncodeModule(*module);
+        return false;
+
+    return EncodeModule(*module, bytes);
 }
--- a/js/src/asmjs/WasmText.h
+++ b/js/src/asmjs/WasmText.h
@@ -21,18 +21,18 @@
 
 #include "asmjs/WasmBinary.h"
 #include "js/Utility.h"
 
 namespace js {
 namespace wasm {
 
 // Translate the textual representation of a wasm module (given by a
-// null-terminated char16_t array) into a Bytecode object. If there is an error
+// null-terminated char16_t array) into serialized bytes. If there is an error
 // other than out-of-memory an error message string will be stored in 'error'.
 
-extern UniqueBytecode
-TextToBinary(const char16_t* text, UniqueChars* error);
+extern bool
+TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error);
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_text_h
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -16,16 +16,17 @@
  * limitations under the License.
  */
 
 #ifndef wasm_types_h
 #define wasm_types_h
 
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 
 #include "NamespaceImports.h"
 
 #include "asmjs/WasmBinary.h"
 #include "ds/LifoAlloc.h"
 #include "jit/IonTypes.h"
 #include "js/UniquePtr.h"
@@ -34,21 +35,21 @@
 
 namespace js {
 
 class PropertyName;
 
 namespace wasm {
 
 using mozilla::EnumeratedArray;
+using mozilla::Maybe;
 using mozilla::Move;
 using mozilla::MallocSizeOf;
 
 typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
-typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
 
 // ValType/ExprType utilities
 
 static inline bool
 IsVoid(ExprType et)
 {
     return et == ExprType::Void;
 }
@@ -589,20 +590,21 @@ enum ModuleKind
 // Constants:
 
 static const unsigned ActivationGlobalDataOffset = 0;
 static const unsigned HeapGlobalDataOffset       = ActivationGlobalDataOffset + sizeof(void*);
 static const unsigned NaN64GlobalDataOffset      = HeapGlobalDataOffset + sizeof(void*);
 static const unsigned NaN32GlobalDataOffset      = NaN64GlobalDataOffset + sizeof(double);
 static const unsigned InitialGlobalDataBytes     = NaN32GlobalDataOffset + sizeof(float);
 
-static const unsigned MaxSigs                    =   4 * 1024;
-static const unsigned MaxFuncs                   = 512 * 1024;
-static const unsigned MaxImports                 =   4 * 1024;
-static const unsigned MaxExports                 =   4 * 1024;
-static const unsigned MaxTableElems              = 128 * 1024;
-static const unsigned MaxArgsPerFunc             =   4 * 1024;
-static const unsigned MaxBrTableElems            =   4 * 1024;
+static const unsigned MaxSigs                    =        4 * 1024;
+static const unsigned MaxFuncs                   =      512 * 1024;
+static const unsigned MaxLocals                  =       64 * 1024;
+static const unsigned MaxImports                 =       64 * 1024;
+static const unsigned MaxExports                 =       64 * 1024;
+static const unsigned MaxTableElems              =      128 * 1024;
+static const unsigned MaxArgsPerFunc             =        4 * 1024;
+static const unsigned MaxBrTableElems            = 4 * 1024 * 1024;
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_types_h
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -673,24 +673,144 @@ js::obj_getOwnPropertyDescriptor(JSConte
         return false;
 
     // Steps 5-7.
     Rooted<PropertyDescriptor> desc(cx);
     return GetOwnPropertyDescriptor(cx, obj, id, &desc) &&
            FromPropertyDescriptor(cx, desc, args.rval());
 }
 
-// ES6 draft rev27 (2014/08/24) 19.1.2.14 Object.keys(O)
+enum EnumerableOwnPropertiesKind {
+    Keys,
+    Values,
+    KeysAndValues
+};
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#EnumerableOwnProperties
+static bool
+EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPropertiesKind kind)
+{
+    // Step 1. (Step 1 of Object.{keys,values,entries}, really.)
+    RootedObject obj(cx, ToObject(cx, args.get(0)));
+    if (!obj)
+        return false;
+
+    // Step 2.
+    AutoIdVector ids(cx);
+    if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids))
+        return false;
+
+    // Step 3.
+    AutoValueVector properties(cx);
+    size_t len = ids.length();
+    if (!properties.resize(len))
+        return false;
+
+    RootedId id(cx);
+    RootedValue key(cx);
+    RootedValue value(cx);
+    RootedNativeObject nobj(cx);
+    if (obj->is<NativeObject>())
+        nobj = &obj->as<NativeObject>();
+    RootedShape shape(cx);
+    Rooted<PropertyDescriptor> desc(cx);
+
+    // Step 4.
+    size_t out = 0;
+    for (size_t i = 0; i < len; i++) {
+        id = ids[i];
+
+        // Step 4.a. (Symbols were filtered out in step 2.)
+        MOZ_ASSERT(!JSID_IS_SYMBOL(id));
+
+        if (kind != Values) {
+            if (!IdToStringOrSymbol(cx, id, &key))
+                return false;
+        }
+
+        // Step 4.a.i.
+        if (nobj) {
+            if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) {
+                value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id));
+            } else {
+                shape = nobj->lookup(cx, id);
+                if (!shape || !(GetShapeAttributes(nobj, shape) & JSPROP_ENUMERATE))
+                    continue;
+                if (!shape->isAccessorShape()) {
+                    if (!NativeGetExistingProperty(cx, nobj, nobj, shape, &value))
+                        return false;
+                } else if (!GetProperty(cx, obj, obj, id, &value)) {
+                    return false;
+                }
+            }
+        } else {
+            if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
+                return false;
+
+            // Step 4.a.ii. (inverted.)
+            if (!desc.object() || !desc.enumerable())
+                continue;
+
+            // Step 4.a.ii.1.
+            // (Omitted because Object.keys doesn't use this implementation.)
+
+            // Step 4.a.ii.2.a.
+            if (obj->isNative() && desc.hasValue())
+                value = desc.value();
+            else if (!GetProperty(cx, obj, obj, id, &value))
+                return false;
+        }
+
+        // Steps 4.a.ii.2.b-c.
+        if (kind == Values)
+            properties[out++].set(value);
+        else if (!NewValuePair(cx, key, value, properties[out++]))
+            return false;
+    }
+
+    // Step 5.
+    // (Implemented in step 2.)
+
+    // Step 3 of Object.{keys,values,entries}
+    JSObject* aobj = NewDenseCopiedArray(cx, out, properties.begin());
+    if (!aobj)
+        return false;
+
+    args.rval().setObject(*aobj);
+    return true;
+}
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.keys
 static bool
 obj_keys(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY);
 }
 
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.values
+static bool
+obj_values(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return EnumerableOwnProperties(cx, args, Values);
+}
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.entries
+static bool
+obj_entries(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return EnumerableOwnProperties(cx, args, KeysAndValues);
+}
+
 /* ES6 draft 15.2.3.16 */
 static bool
 obj_is(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     bool same;
     if (!SameValue(cx, args.get(0), args.get(1), &same))
@@ -971,58 +1091,56 @@ ProtoSetter(JSContext* cx, unsigned argc
     return true;
 }
 
 static const JSFunctionSpec object_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str,             obj_toSource,                0,0),
 #endif
     JS_FN(js_toString_str,             obj_toString,                0,0),
-    JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0,JSPROP_DEFINE_LATE),
+    JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0),
     JS_FN(js_valueOf_str,              obj_valueOf,                 0,0),
 #if JS_HAS_OBJ_WATCHPOINT
     JS_FN(js_watch_str,                obj_watch,                   2,0),
     JS_FN(js_unwatch_str,              obj_unwatch,                 1,0),
 #endif
     JS_FN(js_hasOwnProperty_str,       obj_hasOwnProperty,          1,0),
     JS_FN(js_isPrototypeOf_str,        obj_isPrototypeOf,           1,0),
     JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable,    1,0),
 #if JS_OLD_GETTER_SETTER_METHODS
-    JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter",    2,JSPROP_DEFINE_LATE),
-    JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter",    2,JSPROP_DEFINE_LATE),
-    JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter",    1,JSPROP_DEFINE_LATE),
-    JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter",    1,JSPROP_DEFINE_LATE),
+    JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter",    2,0),
+    JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter",    2,0),
+    JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter",    1,0),
+    JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter",    1,0),
 #endif
     JS_FS_END
 };
 
 static const JSPropertySpec object_properties[] = {
 #if JS_HAS_OBJ_PROTO_PROP
     JS_PSGS("__proto__", ProtoGetter, ProtoSetter, 0),
 #endif
     JS_PS_END
 };
 
 static const JSFunctionSpec object_static_methods[] = {
-    JS_SELF_HOSTED_FN("assign",        "ObjectStaticAssign",        2, JSPROP_DEFINE_LATE),
-    JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf",     1, JSPROP_DEFINE_LATE),
+    JS_SELF_HOSTED_FN("assign",        "ObjectStaticAssign",        2, 0),
+    JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf",     1, 0),
     JS_FN("setPrototypeOf",            obj_setPrototypeOf,          2, 0),
     JS_FN("getOwnPropertyDescriptor",  obj_getOwnPropertyDescriptor,2, 0),
     JS_FN("keys",                      obj_keys,                    1, 0),
-#ifndef RELEASE_BUILD
-    JS_SELF_HOSTED_FN("values",        "ObjectValues",              1, JSPROP_DEFINE_LATE),
-    JS_SELF_HOSTED_FN("entries",       "ObjectEntries",             1, JSPROP_DEFINE_LATE),
-#endif
+    JS_FN("values",                    obj_values,                  1, 0),
+    JS_FN("entries",                   obj_entries,                 1, 0),
     JS_FN("is",                        obj_is,                      2, 0),
     JS_FN("defineProperty",            obj_defineProperty,          3, 0),
     JS_FN("defineProperties",          obj_defineProperties,        2, 0),
     JS_INLINABLE_FN("create",          obj_create,                  2, 0, ObjectCreate),
     JS_FN("getOwnPropertyNames",       obj_getOwnPropertyNames,     1, 0),
     JS_FN("getOwnPropertySymbols",     obj_getOwnPropertySymbols,   1, 0),
-    JS_SELF_HOSTED_FN("isExtensible",  "ObjectIsExtensible",        1, JSPROP_DEFINE_LATE),
+    JS_SELF_HOSTED_FN("isExtensible",  "ObjectIsExtensible",        1, 0),
     JS_FN("preventExtensions",         obj_preventExtensions,       1, 0),
     JS_FN("freeze",                    obj_freeze,                  1, 0),
     JS_FN("isFrozen",                  obj_isFrozen,                1, 0),
     JS_FN("seal",                      obj_seal,                    1, 0),
     JS_FN("isSealed",                  obj_isSealed,                1, 0),
     JS_FS_END
 };
 
@@ -1084,32 +1202,16 @@ FinishObjectClassInit(JSContext* cx, JS:
         return false;
     global->setOriginalEval(evalobj);
 
     Rooted<NativeObject*> holder(cx, GlobalObject::getIntrinsicsHolder(cx, global));
     if (!holder)
         return false;
 
     /*
-     * Define self-hosted functions on Object and Function after setting the
-     * intrinsics holder (which is needed to define self-hosted functions).
-     */
-    if (!cx->runtime()->isSelfHostingGlobal(global)) {
-        if (!JS_DefineFunctions(cx, ctor, object_static_methods, OnlyDefineLateProperties))
-            return false;
-        if (!JS_DefineFunctions(cx, proto, object_methods, OnlyDefineLateProperties))
-            return false;
-        RootedObject funProto(cx, global->getOrCreateFunctionPrototype(cx));
-        if (!funProto)
-            return false;
-        if (!JS_DefineFunctions(cx, funProto, function_methods, OnlyDefineLateProperties))
-            return false;
-    }
-
-    /*
      * The global object should have |Object.prototype| as its [[Prototype]].
      * Eventually we'd like to have standard classes be there from the start,
      * and thus we would know we were always setting what had previously been a
      * null [[Prototype]], but right now some code assumes it can set the
      * [[Prototype]] before standard classes have been initialized.  For now,
      * only set the [[Prototype]] if it hasn't already been set.
      */
     Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
--- a/js/src/builtin/Object.js
+++ b/js/src/builtin/Object.js
@@ -134,54 +134,8 @@ function ObjectLookupGetter(name) {
         if (desc) {
             if (callFunction(std_Object_hasOwnProperty, desc, "get"))
                 return desc.get;
             return undefined;
         }
         object = std_Reflect_getPrototypeOf(object);
     } while (object !== null);
 }
-
-// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.values
-function ObjectValues(O) {
-    // Steps 1-2.
-    var object = ToObject(O);
-
-    // Steps 3-4.
-    // EnumerableOwnProperties is inlined here.
-    var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN);
-    var values = [];
-    var valuesCount = 0;
-    for (var i = 0; i < keys.length; i++) {
-        var key = keys[i];
-        if (!callFunction(std_Object_propertyIsEnumerable, object, key))
-            continue;
-
-        var value = object[key];
-        _DefineDataProperty(values, valuesCount++, value);
-    }
-
-    // Step 5.
-    return values;
-}
-
-// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.entries
-function ObjectEntries(O) {
-    // Steps 1-2.
-    var object = ToObject(O);
-
-    // Steps 3-4.
-    // EnumerableOwnProperties is inlined here.
-    var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN);
-    var entries = [];
-    var entriesCount = 0;
-    for (var i = 0; i < keys.length; i++) {
-        var key = keys[i];
-        if (!callFunction(std_Object_propertyIsEnumerable, object, key))
-            continue;
-
-        var value = object[key];
-        _DefineDataProperty(entries, entriesCount++, [key, value]);
-    }
-
-    // Step 5.
-    return entries;
-}
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -520,29 +520,29 @@ WasmTextToBinary(JSContext* cx, unsigned
         ReportUsageError(cx, callee, "First argument must be a String");
         return false;
     }
 
     AutoStableStringChars twoByteChars(cx);
     if (!twoByteChars.initTwoByte(cx, args[0].toString()))
         return false;
 
+    wasm::Bytes bytes;
     UniqueChars error;
-    wasm::UniqueBytecode bytes = wasm::TextToBinary(twoByteChars.twoByteChars(), &error);
-    if (!bytes) {
+    if (!wasm::TextToBinary(twoByteChars.twoByteChars(), &bytes, &error)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
                              error.get() ? error.get() : "out of memory");
         return false;
     }
 
-    RootedObject obj(cx, JS_NewUint8Array(cx, bytes->length()));
+    RootedObject obj(cx, JS_NewUint8Array(cx, bytes.length()));
     if (!obj)
         return false;
 
-    memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes->begin(), bytes->length());
+    memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes.begin(), bytes.length());
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
 IsLazyFunction(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -309,18 +309,18 @@ class StoreBuffer
                    (start <= otherEnd && otherEnd <= end);
         }
 
         // Destructively make this SlotsEdge range the union of the other
         // SlotsEdge range and this one. A precondition is that the ranges must
         // overlap.
         void merge(const SlotsEdge& other) {
             MOZ_ASSERT(overlaps(other));
-            auto end = std::max(start_ + count_, other.start_ + other.count_);
-            start_ = std::min(start_, other.start_);
+            auto end = Max(start_ + count_, other.start_ + other.count_);
+            start_ = Min(start_, other.start_);
             count_ = end - start_;
         }
 
         bool maybeInRememberedSet(const Nursery& n) const {
             return !IsInsideNursery(reinterpret_cast<Cell*>(object()));
         }
 
         void trace(TenuringTracer& mover) const;
--- a/js/src/jit-test/tests/wasm/basic-const.js
+++ b/js/src/jit-test/tests/wasm/basic-const.js
@@ -36,19 +36,21 @@ testConst('i32', '0xffffffff', -1);
 //testConst('i64', '18446744073709551615', -1); // TODO: NYI
 //testConst('i64', '-9223372036854775808', -9223372036854775808); // TODO: NYI
 //testConst('i64', '0x7fffffffffffffff', 9223372036854775807); // TODO: NYI
 //testConst('i64', '0x8000000000000000', -9223372036854775808); // TODO: NYI
 //testConst('i64', '-0x8000000000000000', -9223372036854775808); // TODO: NYI
 //testConst('i64', '0xffffffffffffffff', -1); // TODO: NYI
 
 testConst('f32', '0.0', 0.0);
+testConst('f32', '-0', -0.0);
 testConst('f32', '-0.0', -0.0);
 testConst('f32', '0x0.0', 0.0);
 testConst('f32', '-0x0.0', -0.0);
+testConst('f32', '-0x0', -0.0);
 testConst('f32', '0x0.0p0', 0.0);
 testConst('f32', '-0x0.0p0', -0.0);
 testConst('f32', 'infinity', Infinity);
 testConst('f32', '-infinity', -Infinity);
 testConst('f32', '+infinity', Infinity);
 testConst('f32', 'nan', NaN);
 //testConst('f32', '-nan', NaN); // TODO: NYI
 testConst('f32', '+nan', NaN);
@@ -121,18 +123,20 @@ testConst('f32', '0x0434.234p0', 1076.13
 testConst('f32', '-0x3434.234p0', -13364.1376953125);
 testConst('f32', '0x4.22342p0', 4.133607864379883);
 testConst('f32', '0x30000p-20', 1.875000e-01);
 testConst('f32', '0x0.533fcccp-125', 7.645233588931088e-39);
 testConst('f32', '0', 0);
 
 testConst('f64', '0.0', 0.0);
 testConst('f64', '-0.0', -0.0);
+testConst('f64', '-0', -0.0);
 testConst('f64', '0x0.0', 0.0);
 testConst('f64', '-0x0.0', -0.0);
+testConst('f64', '-0x0', -0.0);
 testConst('f64', '0x0.0p0', 0.0);
 testConst('f64', '-0x0.0p0', -0.0);
 testConst('f64', 'infinity', Infinity);
 testConst('f64', '-infinity', -Infinity);
 testConst('f64', '+infinity', Infinity);
 testConst('f64', 'nan', NaN);
 //testConst('f64', '-nan', NaN); // TODO: NYI
 testConst('f64', '+nan', NaN);
--- a/js/src/jit-test/tests/wasm/basic-memory.js
+++ b/js/src/jit-test/tests/wasm/basic-memory.js
@@ -1,13 +1,10 @@
 load(libdir + "wasm.js");
 
-if (!wasmIsSupported())
-    quit();
-
 function testLoad(type, ext, base, offset, align, expect) {
   assertEq(wasmEvalText(
     '(module' +
     '  (memory 1' +
     '    (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")' +
     '    (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")' +
     '  )' +
     '  (func (param i32) (result ' + type + ')' +
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -74,26 +74,30 @@ assertErrorMessage(() => wasmEval(toU8(m
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1))), TypeError, sectionError);
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0))), TypeError, unknownSectionError);
 
 function cstring(name) {
     return (name + '\0').split('').map(c => c.charCodeAt(0));
 }
 
 function string(name) {
-    return name.split('').map(c => c.charCodeAt(0));
+    var nameBytes = name.split('').map(c => {
+        var code = c.charCodeAt(0);
+        assertEq(code < 128, true); // TODO
+        return code
+    });
+    return varU32(nameBytes.length).concat(nameBytes);
 }
 
 function moduleWithSections(sectionArray) {
     var bytes = moduleHeaderThen();
     for (let section of sectionArray) {
-        var nameLength = varU32(section.name.length);
-        bytes.push(...varU32(nameLength.length + section.name.length + section.body.length));
-        bytes.push(...nameLength);
-        bytes.push(...string(section.name));
+        var sectionName = string(section.name);
+        bytes.push(...varU32(sectionName.length + section.body.length));
+        bytes.push(...sectionName);
         bytes.push(...section.body);
     }
     return toU8(bytes);
 }
 
 function sigSection(sigs) {
     var body = [];
     body.push(...varU32(sigs.length));
@@ -128,18 +132,18 @@ function bodySection(bodies) {
     return { name: functionBodiesId, body };
 }
 
 function importSection(imports) {
     var body = [];
     body.push(...varU32(imports.length));
     for (let imp of imports) {
         body.push(...varU32(imp.sigIndex));
-        body.push(...cstring(imp.module));
-        body.push(...cstring(imp.func));
+        body.push(...string(imp.module));
+        body.push(...string(imp.func));
     }
     return { name: importId, body };
 }
 
 function tableSection(elems) {
     var body = [];
     body.push(...varU32(elems.length));
     for (let i of elems)
@@ -158,18 +162,18 @@ assertErrorMessage(() => wasmEval(module
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: sigId, body: [1]}])), TypeError);
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: sigId, body: [1, 1, 0]}])), TypeError);
 
 wasmEval(moduleWithSections([sigSection([])]));
 wasmEval(moduleWithSections([sigSection([v2vSig])]));
 wasmEval(moduleWithSections([sigSection([i2vSig])]));
 wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])]));
 
-assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /bad expression type/);
-assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), TypeError, /bad value type/);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /expression type/);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), TypeError, /value type/);
 
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([]), declSection([0])])), TypeError, /signature index out of range/);
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([1])])), TypeError, /signature index out of range/);
 assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0])])), TypeError, /expected function bodies/);
 wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([v2vBody])]));
 
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), {name: importId, body:[]}])), TypeError);
 assertErrorMessage(() => wasmEval(moduleWithSections([importSection([{sigIndex:0, module:"a", func:"b"}])])), TypeError, /signature index out of range/);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/memory-aliasing.js
@@ -0,0 +1,26 @@
+load(libdir + "wasm.js");
+
+var i = wasmEvalText(
+`(module
+   (memory 1 (segment 0 "\\01\\02\\03\\04\\05\\06\\07\\08"))
+   (func $off1 (param $base i32) (result i32)
+     (i32.add
+       (i32.load8_u (get_local $base))
+       (i32.load8_u offset=1 (get_local $base)))
+   )
+   (export "off1" $off1)
+   (func $off2 (param $base i32) (result i32)
+     (i32.add
+       (i32.load8_u offset=1 (get_local $base))
+       (i32.load8_u offset=2 (get_local $base)))
+   )
+   (export "off2" $off2)
+)`);
+assertEq(i.off1(0), 3);
+assertEq(i.off1(1), 5);
+assertEq(i.off1(2), 7);
+assertEq(i.off1(3), 9);
+assertEq(i.off2(0), 5);
+assertEq(i.off2(1), 7);
+assertEq(i.off2(2), 9);
+assertEq(i.off2(3), 11);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4550,28 +4550,31 @@ MAsmJSLoadHeap::mightAlias(const MDefini
 {
     if (def->isAsmJSStoreHeap()) {
         const MAsmJSStoreHeap* store = def->toAsmJSStoreHeap();
         if (store->accessType() != accessType())
             return true;
         if (!base()->isConstant() || !store->base()->isConstant())
             return true;
         const MConstant* otherBase = store->base()->toConstant();
-        return base()->toConstant()->equals(otherBase);
+        return base()->toConstant()->equals(otherBase) &&
+               offset() == store->offset();
     }
     return true;
 }
 
 bool
 MAsmJSLoadHeap::congruentTo(const MDefinition* ins) const
 {
     if (!ins->isAsmJSLoadHeap())
         return false;
     const MAsmJSLoadHeap* load = ins->toAsmJSLoadHeap();
-    return load->accessType() == accessType() && congruentIfOperandsEqual(load);
+    return load->accessType() == accessType() &&
+           load->offset() == offset() &&
+           congruentIfOperandsEqual(load);
 }
 
 bool
 MAsmJSLoadGlobalVar::mightAlias(const MDefinition* def) const
 {
     if (def->isAsmJSStoreGlobalVar()) {
         const MAsmJSStoreGlobalVar* store = def->toAsmJSStoreGlobalVar();
         return store->globalDataOffset() == globalDataOffset_;
--- a/js/src/jsapi-tests/testWasmLEB128.cpp
+++ b/js/src/jsapi-tests/testWasmLEB128.cpp
@@ -38,73 +38,73 @@ static bool WriteValidBytes(js::wasm::En
     return true;
 }
 
 BEGIN_TEST(testWasmLEB128_encoding)
 {
     using namespace js;
     using namespace wasm;
 
-    Bytecode bc;
-    Encoder encoder(bc);
+    Bytes bytes;
+    Encoder encoder(bytes);
 
     bool passed;
     if (!WriteValidBytes(encoder, &passed))
         return false;
     CHECK(passed);
 
     size_t i = 0;
-    CHECK(bc[i++] == 0x0);
-    CHECK(bc[i++] == 0x1);
-    CHECK(bc[i++] == 0x42);
+    CHECK(bytes[i++] == 0x0);
+    CHECK(bytes[i++] == 0x1);
+    CHECK(bytes[i++] == 0x42);
 
-    CHECK(bc[i++] == 0x80);
-    CHECK(bc[i++] == 0x01);
+    CHECK(bytes[i++] == 0x80);
+    CHECK(bytes[i++] == 0x01);
 
-    CHECK(bc[i++] == 0x80);
-    CHECK(bc[i++] == 0x03);
+    CHECK(bytes[i++] == 0x80);
+    CHECK(bytes[i++] == 0x03);
 
-    if (i + 1 < bc.length())
-        CHECK(bc[i++] == 0x00);
+    if (i + 1 < bytes.length())
+        CHECK(bytes[i++] == 0x00);
     return true;
 }
 END_TEST(testWasmLEB128_encoding)
 
 BEGIN_TEST(testWasmLEB128_valid_decoding)
 {
     using namespace js;
     using namespace wasm;
 
-    Bytecode bc;
-    if (!bc.append(0x0) || !bc.append(0x1) || !bc.append(0x42))
+    Bytes bytes;
+    if (!bytes.append(0x0) || !bytes.append(0x1) || !bytes.append(0x42))
         return false;
 
-    if (!bc.append(0x80) || !bc.append(0x01))
+    if (!bytes.append(0x80) || !bytes.append(0x01))
         return false;
 
-    if (!bc.append(0x80) || !bc.append(0x03))
+    if (!bytes.append(0x80) || !bytes.append(0x03))
         return false;
 
     {
         // Fallible decoding
-        Decoder decoder(bc);
+        Decoder decoder(bytes);
         uint32_t value;
 
         CHECK(decoder.readVarU32(&value) && value == 0x0);
         CHECK(decoder.readVarU32(&value) && value == 0x1);
         CHECK(decoder.readVarU32(&value) && value == 0x42);
         CHECK(decoder.readVarU32(&value) && value == 0x80);
         CHECK(decoder.readVarU32(&value) && value == 0x180);
 
         CHECK(decoder.done());
     }
 
     {
         // Infallible decoding
-        Decoder decoder(bc);
+        Decoder decoder(bytes);
         uint32_t value;
 
         value = decoder.uncheckedReadVarU32();
         CHECK(value == 0x0);
         value = decoder.uncheckedReadVarU32();
         CHECK(value == 0x1);
         value = decoder.uncheckedReadVarU32();
         CHECK(value == 0x42);
@@ -119,48 +119,48 @@ BEGIN_TEST(testWasmLEB128_valid_decoding
 }
 END_TEST(testWasmLEB128_valid_decoding)
 
 BEGIN_TEST(testWasmLEB128_invalid_decoding)
 {
     using namespace js;
     using namespace wasm;
 
-    Bytecode bc;
+    Bytes bytes;
     // Fill bits as per 28 encoded bits
-    if (!bc.append(0x80) || !bc.append(0x80) || !bc.append(0x80) || !bc.append(0x80))
+    if (!bytes.append(0x80) || !bytes.append(0x80) || !bytes.append(0x80) || !bytes.append(0x80))
         return false;
 
     // Test last valid values
-    if (!bc.append(0x00))
+    if (!bytes.append(0x00))
         return false;
 
     for (uint8_t i = 0; i < 0x0F; i++) {
-        bc[4] = i;
+        bytes[4] = i;
 
         {
-            Decoder decoder(bc);
+            Decoder decoder(bytes);
             uint32_t value;
             CHECK(decoder.readVarU32(&value));
             CHECK(value == uint32_t(i << 28));
             CHECK(decoder.done());
         }
 
         {
-            Decoder decoder(bc);
+            Decoder decoder(bytes);
             uint32_t value = decoder.uncheckedReadVarU32();
             CHECK(value == uint32_t(i << 28));
             CHECK(decoder.done());
         }
     }
 
     // Test all invalid values of the same size
     for (uint8_t i = 0x10; i < 0xF0; i++) {
-        bc[4] = i;
+        bytes[4] = i;
 
-        Decoder decoder(bc);
+        Decoder decoder(bytes);
         uint32_t value;
         CHECK(!decoder.readVarU32(&value));
     }
 
     return true;
 }
 END_TEST(testWasmLEB128_invalid_decoding)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3664,25 +3664,24 @@ JS_IsNativeFunction(JSObject* funobj, JS
 
 extern JS_PUBLIC_API(bool)
 JS_IsConstructor(JSFunction* fun)
 {
     return fun->isConstructor();
 }
 
 JS_PUBLIC_API(bool)
-JS_DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
-                   PropertyDefinitionBehavior behavior)
+JS_DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
 
-    return DefineFunctions(cx, obj, fs, NotIntrinsic, behavior);
+    return DefineFunctions(cx, obj, fs, NotIntrinsic);
 }
 
 JS_PUBLIC_API(JSFunction*)
 JS_DefineFunction(JSContext* cx, HandleObject obj, const char* name, JSNative call,
                   unsigned nargs, unsigned attrs)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -829,20 +829,17 @@ class MOZ_STACK_CLASS SourceBufferHolder
 #define JSPROP_GETTER           0x10    /* property holds getter function */
 #define JSPROP_SETTER           0x20    /* property holds setter function */
 #define JSPROP_SHARED           0x40    /* don't allocate a value slot for this
                                            property; don't copy the property on
                                            set of the same-named property in an
                                            object that delegates to a prototype
                                            containing this property */
 #define JSPROP_INTERNAL_USE_BIT 0x80    /* internal JS engine use only */
-#define JSPROP_DEFINE_LATE     0x100    /* Don't define property when initially creating
-                                           the constructor. Some objects like Function/Object
-                                           have self-hosted functions that can only be defined
-                                           after the initialization is already finished. */
+//                             0x100    /* Unused */
 #define JSFUN_STUB_GSOPS       0x200    /* use JS_PropertyStub getter/setter
                                            instead of defaulting to class gsops
                                            for property holding function */
 
 #define JSFUN_CONSTRUCTOR      0x400    /* native that can be called as a ctor */
 
 /*
  * Specify a generic native prototype methods, i.e., methods of a class
@@ -3588,30 +3585,18 @@ JS_ObjectIsFunction(JSContext* cx, JSObj
 
 extern JS_PUBLIC_API(bool)
 JS_IsNativeFunction(JSObject* funobj, JSNative call);
 
 /** Return whether the given function is a valid constructor. */
 extern JS_PUBLIC_API(bool)
 JS_IsConstructor(JSFunction* fun);
 
-/**
- * This enum is used to select if properties with JSPROP_DEFINE_LATE flag
- * should be defined on the object.
- * Normal JSAPI consumers probably always want DefineAllProperties here.
- */
-enum PropertyDefinitionBehavior {
-    DefineAllProperties,
-    OnlyDefineLateProperties,
-    DontDefineLateProperties
-};
-
-extern JS_PUBLIC_API(bool)
-JS_DefineFunctions(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* fs,
-                   PropertyDefinitionBehavior behavior = DefineAllProperties);
+extern JS_PUBLIC_API(bool)
+JS_DefineFunctions(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* fs);
 
 extern JS_PUBLIC_API(JSFunction*)
 JS_DefineFunction(JSContext* cx, JS::Handle<JSObject*> obj, const char* name, JSNative call,
                   unsigned nargs, unsigned attrs);
 
 extern JS_PUBLIC_API(JSFunction*)
 JS_DefineUCFunction(JSContext* cx, JS::Handle<JSObject*> obj,
                     const char16_t* name, size_t namelen, JSNative call,
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3674,16 +3674,30 @@ js::NewCopiedArrayForCallingAllocationSi
                                            HandleObject proto /* = nullptr */)
 {
     RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto));
     if (!group)
         return nullptr;
     return NewCopiedArrayTryUseGroup(cx, group, vp, length);
 }
 
+bool
+js::NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval)
+{
+    JS::AutoValueArray<2> vec(cx);
+    vec[0].set(val1);
+    vec[1].set(val2);
+
+    JSObject* aobj = js::NewDenseCopiedArray(cx, 2, vec.begin());
+    if (!aobj)
+        return false;
+    rval.setObject(*aobj);
+    return true;
+}
+
 #ifdef DEBUG
 bool
 js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JSObject* obj;
 
     for (unsigned i = 0; i < args.length(); i++) {
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -114,16 +114,19 @@ NewCopiedArrayTryUseGroup(ExclusiveConte
                           const Value* vp, size_t length,
                           NewObjectKind newKind = GenericObject,
                           ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
 
 extern JSObject*
 NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
                                        HandleObject proto = nullptr);
 
+extern bool
+NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval);
+
 /*
  * Determines whether a write to the given element on |obj| should fail because
  * |obj| is an Array with a non-writable length, and writing that element would
  * increase the length of the array.
  */
 extern bool
 WouldDefinePastNonwritableLength(HandleNativeObject obj, uint32_t index);
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1590,17 +1590,17 @@ OnBadFormal(JSContext* cx)
 const JSFunctionSpec js::function_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str,   fun_toSource,   0,0),
 #endif
     JS_FN(js_toString_str,   fun_toString,   0,0),
     JS_FN(js_apply_str,      fun_apply,      2,0),
     JS_FN(js_call_str,       fun_call,       1,0),
     JS_FN("isGenerator",     fun_isGenerator,0,0),
-    JS_SELF_HOSTED_FN("bind", "FunctionBind", 2,JSPROP_DEFINE_LATE|JSFUN_HAS_REST),
+    JS_SELF_HOSTED_FN("bind", "FunctionBind", 2,JSFUN_HAS_REST),
     JS_FS_END
 };
 
 static bool
 FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -79,25 +79,17 @@ struct IdHashPolicy {
     }
 };
 
 typedef HashSet<jsid, IdHashPolicy> IdSet;
 
 static inline bool
 NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval)
 {
-    JS::AutoValueArray<2> vec(cx);
-    vec[0].set(IdToValue(id));
-    vec[1].set(val);
-
-    JSObject* aobj = NewDenseCopiedArray(cx, 2, vec.begin());
-    if (!aobj)
-        return false;
-    rval.setObject(*aobj);
-    return true;
+    return NewValuePair(cx, IdToValue(id), val, rval);
 }
 
 static inline bool
 Enumerate(JSContext* cx, HandleObject pobj, jsid id,
           bool enumerable, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props)
 {
     // Allow duplicate properties from Proxy's [[OwnPropertyKeys]].
     bool proxyOwnProperty = pobj->is<ProxyObject>() && (flags & JSITER_OWNONLY);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2902,34 +2902,20 @@ DefineFunctionFromSpec(JSContext* cx, Ha
         fun->setIsIntrinsic();
 
     RootedValue funVal(cx, ObjectValue(*fun));
     return DefineProperty(cx, obj, id, funVal, gop, sop, flags & ~JSFUN_FLAGS_MASK);
 }
 
 bool
 js::DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
-                    DefineAsIntrinsic intrinsic, PropertyDefinitionBehavior behavior)
+                    DefineAsIntrinsic intrinsic)
 {
     for (; fs->name; fs++) {
-        unsigned flags = fs->flags;
-        switch (behavior) {
-          case DefineAllProperties:
-            break;
-          case OnlyDefineLateProperties:
-            if (!(flags & JSPROP_DEFINE_LATE))
-                continue;
-            break;
-          default:
-            MOZ_ASSERT(behavior == DontDefineLateProperties);
-            if (flags & JSPROP_DEFINE_LATE)
-                continue;
-        }
-
-        if (!DefineFunctionFromSpec(cx, obj, fs, flags & ~JSPROP_DEFINE_LATE, intrinsic))
+        if (!DefineFunctionFromSpec(cx, obj, fs, fs->flags, intrinsic))
             return false;
     }
     return true;
 }
 
 
 /*** ToPrimitive *************************************************************/
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -987,18 +987,17 @@ HasOwnProperty(JSContext* cx, HandleObje
  */
 enum DefineAsIntrinsic {
     NotIntrinsic,
     AsIntrinsic
 };
 
 extern bool
 DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
-                DefineAsIntrinsic intrinsic,
-                PropertyDefinitionBehavior behavior = DefineAllProperties);
+                DefineAsIntrinsic intrinsic);
 
 /*
  * Set a watchpoint: a synchronous callback when the given property of the
  * given object is set.
  *
  * Watchpoints are nonstandard and do not fit in well with the way ES6
  * specifies [[Set]]. They are also insufficient for implementing
  * Object.observe.
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -142,16 +142,17 @@ EXPORTS.js += [
     '../public/Value.h',
     '../public/Vector.h',
     '../public/WeakMapPtr.h',
 ]
 
 UNIFIED_SOURCES += [
     'asmjs/AsmJS.cpp',
     'asmjs/Wasm.cpp',
+    'asmjs/WasmBinary.cpp',
     'asmjs/WasmFrameIterator.cpp',
     'asmjs/WasmGenerator.cpp',
     'asmjs/WasmIonCompile.cpp',
     'asmjs/WasmModule.cpp',
     'asmjs/WasmSignalHandlers.cpp',
     'asmjs/WasmStubs.cpp',
     'asmjs/WasmText.cpp',
     'asmjs/WasmTypes.cpp',
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -215,28 +215,30 @@ GlobalObject::resolveConstructor(JSConte
         if (!global->addDataProperty(cx, id, constructorPropertySlot(key), 0))
             return false;
     }
 
     global->setConstructor(key, ObjectValue(*ctor));
     global->setConstructorPropertySlot(key, ObjectValue(*ctor));
 
     // Define any specified functions and properties, unless we're a dependent
-    // standard class (in which case they live on the prototype).
-    if (!StandardClassIsDependent(key)) {
+    // standard class (in which case they live on the prototype), or we're
+    // operating on the self-hosting global, in which case we don't want any
+    // functions and properties on the builtins and their prototypes.
+    if (!StandardClassIsDependent(key) && !cx->runtime()->isSelfHostingGlobal(global)) {
         if (const JSFunctionSpec* funs = clasp->spec.prototypeFunctions()) {
-            if (!JS_DefineFunctions(cx, proto, funs, DontDefineLateProperties))
+            if (!JS_DefineFunctions(cx, proto, funs))
                 return false;
         }
         if (const JSPropertySpec* props = clasp->spec.prototypeProperties()) {
             if (!JS_DefineProperties(cx, proto, props))
                 return false;
         }
         if (const JSFunctionSpec* funs = clasp->spec.constructorFunctions()) {
-            if (!JS_DefineFunctions(cx, ctor, funs, DontDefineLateProperties))
+            if (!JS_DefineFunctions(cx, ctor, funs))
                 return false;
         }
         if (const JSPropertySpec* props = clasp->spec.constructorProperties()) {
             if (!JS_DefineProperties(cx, ctor, props))
                 return false;
         }
     }
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -132,16 +132,69 @@ intrinsic_IsCallable(JSContext* cx, unsi
 static bool
 intrinsic_IsConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(IsConstructor(args[0]));
     return true;
 }
 
+/**
+ * Intrinsic for calling a wrapped self-hosted function without invoking the
+ * wrapper's security checks.
+ *
+ * Takes a wrapped function as the first and the receiver object as the
+ * second argument. Any additional arguments are passed on to the unwrapped
+ * function.
+ *
+ * Xray wrappers prevent lower-privileged code from passing objects to wrapped
+ * functions from higher-privileged realms. In some cases, this check is too
+ * strict, so this intrinsic allows getting around it.
+ *
+ * Note that it's not possible to replace all usages with dedicated intrinsics
+ * as the function in question might be an inner function that closes over
+ * state relevant to its execution.
+ *
+ * Right now, this is used for the Promise implementation to enable creating
+ * resolution functions for xrayed Promises in the privileged realm and then
+ * creating the Promise instance in the non-privileged one. The callbacks have
+ * to be called by non-privileged code in various places, in many cases
+ * passing objects as arguments.
+ */
+static bool
+intrinsic_UnsafeCallWrappedFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() >= 2);
+    MOZ_ASSERT(IsCallable(args[0]));
+    MOZ_ASSERT(IsWrapper(&args[0].toObject()));
+    MOZ_ASSERT(args[1].isObject() || args[1].isUndefined());
+
+    MOZ_RELEASE_ASSERT(args[0].isObject());
+    RootedObject wrappedFun(cx, &args[0].toObject());
+    RootedObject fun(cx, UncheckedUnwrap(wrappedFun));
+    MOZ_RELEASE_ASSERT(fun->is<JSFunction>());
+    MOZ_RELEASE_ASSERT(fun->as<JSFunction>().isSelfHostedBuiltin());
+
+    InvokeArgs args2(cx);
+    if (!args2.init(args.length() - 2))
+        return false;
+
+    args2.setThis(args[1]);
+
+    for (size_t i = 0; i < args2.length(); i++)
+        args2[i].set(args[i + 2]);
+
+    AutoWaivePolicy waivePolicy(cx, wrappedFun, JSID_VOIDHANDLE, BaseProxyHandler::CALL);
+    if (!CrossCompartmentWrapper::singleton.call(cx, wrappedFun, args2))
+        return false;
+    args.rval().set(args2.rval());
+    return true;
+}
+
 template<typename T>
 static bool
 intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     MOZ_ASSERT(args[0].isObject());
 
@@ -1837,16 +1890,18 @@ static const JSFunctionSpec intrinsic_fu
                     IntrinsicUnsafeGetObjectFromReservedSlot),
     JS_INLINABLE_FN("UnsafeGetInt32FromReservedSlot",   intrinsic_UnsafeGetInt32FromReservedSlot,  2,0,
                     IntrinsicUnsafeGetInt32FromReservedSlot),
     JS_INLINABLE_FN("UnsafeGetStringFromReservedSlot",  intrinsic_UnsafeGetStringFromReservedSlot, 2,0,
                     IntrinsicUnsafeGetStringFromReservedSlot),
     JS_INLINABLE_FN("UnsafeGetBooleanFromReservedSlot", intrinsic_UnsafeGetBooleanFromReservedSlot,2,0,
                     IntrinsicUnsafeGetBooleanFromReservedSlot),
 
+    JS_FN("UnsafeCallWrappedFunction", intrinsic_UnsafeCallWrappedFunction,2,0),
+
     JS_FN("IsPackedArray",           intrinsic_IsPackedArray,           1,0),
 
     JS_FN("GetIteratorPrototype",    intrinsic_GetIteratorPrototype,    0,0),
 
     JS_FN("NewArrayIterator",        intrinsic_NewArrayIterator,        0,0),
     JS_FN("CallArrayIteratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<ArrayIteratorObject>>,      2,0),
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -9300,50 +9300,62 @@ PresShell::Observe(nsISupports* aSubject
           --mChangeNestCount;
         }
       }
     }
     return NS_OK;
   }
 #endif
 
-  if (!nsCRT::strcmp(aTopic, "agent-sheet-added") && mStyleSet) {
-    AddAgentSheet(aSubject);
+  if (!nsCRT::strcmp(aTopic, "agent-sheet-added")) {
+    if (mStyleSet) {
+      AddAgentSheet(aSubject);
+    }
     return NS_OK;
   }
 
-  if (!nsCRT::strcmp(aTopic, "user-sheet-added") && mStyleSet) {
-    AddUserSheet(aSubject);
+  if (!nsCRT::strcmp(aTopic, "user-sheet-added")) {
+    if (mStyleSet) {
+      AddUserSheet(aSubject);
+    }
     return NS_OK;
   }
 
-  if (!nsCRT::strcmp(aTopic, "author-sheet-added") && mStyleSet) {
-    AddAuthorSheet(aSubject);
+  if (!nsCRT::strcmp(aTopic, "author-sheet-added")) {
+    if (mStyleSet) {
+      AddAuthorSheet(aSubject);
+    }
     return NS_OK;
   }
 
-  if (!nsCRT::strcmp(aTopic, "agent-sheet-removed") && mStyleSet) {
-    RemoveSheet(SheetType::Agent, aSubject);
+  if (!nsCRT::strcmp(aTopic, "agent-sheet-removed")) {
+    if (mStyleSet) {
+      RemoveSheet(SheetType::Agent, aSubject);
+    }
     return NS_OK;
   }
 
-  if (!nsCRT::strcmp(aTopic, "user-sheet-removed") && mStyleSet) {
-    RemoveSheet(SheetType::User, aSubject);
+  if (!nsCRT::strcmp(aTopic, "user-sheet-removed")) {
+    if (mStyleSet) {
+      RemoveSheet(SheetType::User, aSubject);
+    }
     return NS_OK;
   }
 
-  if (!nsCRT::strcmp(aTopic, "author-sheet-removed") && mStyleSet) {
-    RemoveSheet(SheetType::Doc, aSubject);
+  if (!nsCRT::strcmp(aTopic, "author-sheet-removed")) {
+    if (mStyleSet) {
+      RemoveSheet(SheetType::Doc, aSubject);
+    }
     return NS_OK;
   }
 
-  if (!nsCRT::strcmp(aTopic, "memory-pressure") &&
-      !AssumeAllImagesVisible() &&
-      mPresContext->IsRootContentDocument()) {
-    DoUpdateImageVisibility(/* aRemoveOnly = */ true);
+  if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+    if (!AssumeAllImagesVisible() && mPresContext->IsRootContentDocument()) {
+      DoUpdateImageVisibility(/* aRemoveOnly = */ true);
+    }
     return NS_OK;
   }
 
   NS_WARNING("unrecognized topic in PresShell::Observe");
   return NS_ERROR_FAILURE;
 }
 
 bool
old mode 100755
new mode 100644
old mode 100755
new mode 100644
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -377,17 +377,16 @@ class Options
     // other blocks. This mode disables sampling.
     Scan
   };
 
   char* mDMDEnvVar;   // a saved copy, for later printing
 
   Mode mMode;
   NumOption<size_t> mSampleBelowSize;
-  NumOption<uint32_t> mMaxFrames;
   bool mShowDumpStats;
 
   void BadArg(const char* aArg);
   static const char* ValueIfMatch(const char* aArg, const char* aOptionName);
   static bool GetLong(const char* aArg, const char* aOptionName,
                       long aMin, long aMax, long* aValue);
   static bool GetBool(const char* aArg, const char* aOptionName, bool* aValue);
 
@@ -399,17 +398,16 @@ public:
   bool IsCumulativeMode() const { return mMode == Cumulative; }
   bool IsScanMode()       const { return mMode == Scan; }
 
   const char* ModeString() const;
 
   const char* DMDEnvVar() const { return mDMDEnvVar; }
 
   size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
-  size_t MaxFrames()       const { return mMaxFrames.mActual; }
   size_t ShowDumpStats()   const { return mShowDumpStats; }
 };
 
 static Options *gOptions;
 
 //---------------------------------------------------------------------------
 // The global lock
 //---------------------------------------------------------------------------
@@ -690,19 +688,17 @@ typedef CodeAddressService<StringTable, 
 
 class StackTrace
 {
 public:
   static const uint32_t MaxFrames = 24;
 
 private:
   uint32_t mLength;             // The number of PCs.
-  const void* mPcs[MaxFrames];  // The PCs themselves.  If --max-frames is less
-                                // than 24, this array is bigger than
-                                // necessary, but that case is unusual.
+  const void* mPcs[MaxFrames];  // The PCs themselves.
 
 public:
   StackTrace() : mLength(0) {}
 
   uint32_t Length() const { return mLength; }
   const void* Pc(uint32_t i) const
   {
     MOZ_ASSERT(i < mLength);
@@ -774,17 +770,17 @@ StackTrace::Get(Thread* aT)
   // https://bugzilla.mozilla.org/show_bug.cgi?id=374829#c8
   // On Linux, something similar can happen;  see bug 824340.
   // So let's just release it on all platforms.
   StackTrace tmp;
   {
     AutoUnlockState unlock;
     uint32_t skipFrames = 2;
     if (MozStackWalk(StackWalkCallback, skipFrames,
-                      gOptions->MaxFrames(), &tmp, 0, nullptr)) {
+                     MaxFrames, &tmp, 0, nullptr)) {
       // Handle the common case first.  All is ok.  Nothing to do.
     } else {
       tmp.mLength = 0;
     }
   }
 
   StackTraceTable::AddPtr p = gStackTraceTable->lookupForAdd(&tmp);
   if (!p) {
@@ -1425,17 +1421,16 @@ Options::GetBool(const char* aArg, const
 //   values, because jemalloc always rounds up requests sizes.  In contrast, a
 //   prime size will explore all possible values of the alloc counter.
 //
 Options::Options(const char* aDMDEnvVar)
   : mDMDEnvVar(aDMDEnvVar ? InfallibleAllocPolicy::strdup_(aDMDEnvVar)
                           : nullptr)
   , mMode(DarkMatter)
   , mSampleBelowSize(4093, 100 * 100 * 1000)
-  , mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames)
   , mShowDumpStats(false)
 {
   // It's no longer necessary to set the DMD env var to "1" if you want default
   // options (you can leave it undefined) but we still accept "1" for
   // backwards compatibility.
   char* e = mDMDEnvVar;
   if (e && strcmp(e, "1") != 0) {
     bool isEnd = false;
@@ -1468,19 +1463,16 @@ Options::Options(const char* aDMDEnvVar)
         mMode = Options::Cumulative;
       } else if (strcmp(arg, "--mode=scan") == 0) {
         mMode = Options::Scan;
 
       } else if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax,
                  &myLong)) {
         mSampleBelowSize.mActual = myLong;
 
-      } else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) {
-        mMaxFrames.mActual = myLong;
-
       } else if (GetBool(arg, "--show-dump-stats", &myBool)) {
         mShowDumpStats = myBool;
 
       } else if (strcmp(arg, "") == 0) {
         // This can only happen if there is trailing whitespace.  Ignore.
         MOZ_ASSERT(isEnd);
 
       } else {
@@ -1497,32 +1489,17 @@ Options::Options(const char* aDMDEnvVar)
   }
 }
 
 void
 Options::BadArg(const char* aArg)
 {
   StatusMsg("\n");
   StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
-  StatusMsg("\n");
-  StatusMsg("$DMD must be a whitespace-separated list of |--option=val|\n");
-  StatusMsg("entries.\n");
-  StatusMsg("\n");
-  StatusMsg("The following options are allowed;  defaults are shown in [].\n");
-  StatusMsg("  --mode=<mode>                Profiling mode [dark-matter]\n");
-  StatusMsg("      where <mode> is one of: live, dark-matter, cumulative\n");
-  StatusMsg("  --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
-            int(mSampleBelowSize.mMax),
-            int(mSampleBelowSize.mDefault));
-  StatusMsg("                               (prime numbers are recommended)\n");
-  StatusMsg("  --max-frames=<1..%d>         Max. depth of stack traces [%d]\n",
-            int(mMaxFrames.mMax),
-            int(mMaxFrames.mDefault));
-  StatusMsg("  --show-dump-stats=<yes|no>   Show stats about dumps? [no]\n");
-  StatusMsg("\n");
+  StatusMsg("See the output of |mach help run| for the allowed options.\n");
   exit(1);
 }
 
 const char*
 Options::ModeString() const
 {
   switch (mMode) {
   case Live:
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5157,16 +5157,26 @@ pref("reader.font_type", "sans-serif");
 
 // Whether or not the user has interacted with the reader mode toolbar.
 // This is used to show a first-launch tip in reader mode.
 pref("reader.has_used_toolbar", false);
 
 // Whether to use a vertical or horizontal toolbar.
 pref("reader.toolbar.vertical", true);
 
+#if !defined(ANDROID)
+pref("narrate.enabled", true);
+#else
+pref("narrate.enabled", false);
+#endif
+
+pref("narrate.test", false);
+pref("narrate.rate", 0);
+pref("narrate.voice", "automatic");
+
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
 // Whether to allow, on a Linux system that doesn't support the necessary sandboxing
 // features, loading Gecko Media Plugins unsandboxed.  However, EME CDMs will not be
 // loaded without sandboxing even if this pref is changed.
 pref("media.gmp.insecure.allow", false);
 #endif
 
 pref("dom.audiochannel.mutedByDefault", false);
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1113,23 +1113,20 @@ class RunProgram(MachCommandBase):
 
     @CommandArgumentGroup('DMD')
     @CommandArgument('--dmd', action='store_true', group='DMD',
         help='Enable DMD. The following arguments have no effect without this.')
     @CommandArgument('--mode', choices=['live', 'dark-matter', 'cumulative', 'scan'], group='DMD',
          help='Profiling mode. The default is \'dark-matter\'.')
     @CommandArgument('--sample-below', default=None, type=str, group='DMD',
         help='Sample blocks smaller than this. Use 1 for no sampling. The default is 4093.')
-    @CommandArgument('--max-frames', default=None, type=str, group='DMD',
-        help='The maximum depth of stack traces. The default and maximum is 24.')
     @CommandArgument('--show-dump-stats', action='store_true', group='DMD',
         help='Show stats when doing dumps.')
     def run(self, params, remote, background, noprofile, debug, debugger,
-        debugparams, slowscript, dmd, mode, sample_below, max_frames,
-        show_dump_stats):
+        debugparams, slowscript, dmd, mode, sample_below, show_dump_stats):
 
         if conditions.is_android(self):
             # Running Firefox for Android is completely different
             if dmd:
                 print("DMD is not supported for Firefox for Android")
                 return 1
             from mozrunner.devices.android_device import verify_android_device, run_firefox_for_android
             if not (debug or debugger or debugparams):
@@ -1202,18 +1199,16 @@ class RunProgram(MachCommandBase):
 
         if dmd:
             dmd_params = []
 
             if mode:
                 dmd_params.append('--mode=' + mode)
             if sample_below:
                 dmd_params.append('--sample-below=' + sample_below)
-            if max_frames:
-                dmd_params.append('--max-frames=' + max_frames)
             if show_dump_stats:
                 dmd_params.append('--show-dump-stats=yes')
 
             bin_dir = os.path.dirname(binpath)
             lib_name = self.substs['DLL_PREFIX'] + 'dmd' + self.substs['DLL_SUFFIX']
             dmd_lib = os.path.join(bin_dir, lib_name)
             if not os.path.exists(dmd_lib):
                 print("Please build with |--enable-dmd| to use DMD.")
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -48,17 +48,17 @@ function WebRequestEventManager(context,
 
       // Fills in tabId typically.
       let result = {};
       extensions.emit("fill-browser-data", data.browser, data2, result);
       if (result.cancel) {
         return;
       }
 
-      let optional = ["requestHeaders", "responseHeaders", "statusCode", "redirectUrl"];
+      let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "redirectUrl"];
       for (let opt of optional) {
         if (opt in data) {
           data2[opt] = data[opt];
         }
       }
 
       return runSafeSync(context, callback, data2);
     };
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html
@@ -91,16 +91,30 @@ function compareLists(list1, list2, kind
 function backgroundScript() {
   let checkCompleted = true;
   let savedTabId = -1;
 
   function shouldRecord(url) {
     return url.startsWith(BASE) || /^data:.*\bwebRequestTest\b/.test(url);
   }
 
+  let statuses = [
+    {url: /_script_good\b/, code: 200, line: /^HTTP\/1.1 200 OK\b/i},
+    {url: /\bredirection\b/, code: 302, line: /^HTTP\/1.1 302\b/},
+    {url: /\bnonexistent_script_/, code: 404, line: /^HTTP\/1.1 404 Not Found\b/i},
+  ];
+  function checkStatus(details) {
+    for (let {url, code, line} of statuses) {
+      if (url.test(details.url)) {
+        browser.test.assertTrue(code === details.statusCode, `HTTP status code ${code} for ${details.url} (found ${details.statusCode})`);
+        browser.test.assertTrue(line.test(details.statusLine), `HTTP status line ${line} for ${details.url} (found ${details.statusLine})`);
+      }
+    }
+  }
+
   function checkType(details) {
     let expected_type = "???";
     if (details.url.indexOf("style") != -1) {
       expected_type = "stylesheet";
     } else if (details.url.indexOf("image") != -1) {
       expected_type = "image";
     } else if (details.url.indexOf("script") != -1) {
       expected_type = "script";
@@ -297,16 +311,17 @@ function backgroundScript() {
     browser.test.log(`onBeforeRedirect ${details.url} -> ${details.redirectUrl}`);
     checkRequestId(details, "redirect");
     checkResourceType(details.type);
     if (shouldRecord(details.url)) {
       recorded.beforeRedirect.push(details.url);
 
       browser.test.assertEq(details.tabId, savedTabId, "correct tab ID");
       checkType(details);
+      checkStatus(details);
 
       let id = frameIDs.get(details.url);
       browser.test.assertEq(id, details.frameId, "frame ID same in onBeforeRedirect as onBeforeRequest");
       frameIDs.set(details.redirectUrl, details.frameId);
     }
     if (details.url.indexOf("_redirect.") != -1) {
       let expectedUrl = details.url.replace("_redirect.", "_good.");
       browser.test.assertEq(details.redirectUrl, expectedUrl, "correct redirectUrl value");
@@ -339,16 +354,17 @@ function backgroundScript() {
     }
     if (checkCompleted && !completedUrls[kind].has(details.url)) {
       // We can only tell IPs for HTTP requests.
       if (/^https?:/.test(details.url)) {
         browser.test.assertEq(details.ip, "127.0.0.1", "correct ip");
       }
       completedUrls[kind].add(details.url);
     }
+    checkStatus(details);
   }
 
   function onHeadersReceived(details) {
     checkIpAndRecord("headersReceived", details);
     processHeaders("response", details);
     browser.test.log(`After processing response headers: ${details.responseHeaders.toSource()}`);
     return {responseHeaders: details.responseHeaders};
   }
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -29,16 +29,17 @@ DIRS += [
     'filepicker',
     'filewatcher',
     'finalizationwitness',
     'formautofill',
     'find',
     'gfx',
     'jsdownloads',
     'lz4',
+    'narrate',
     'mediasniffer',
     'microformats',
     'osfile',
     'parentalcontrols',
     'passwordmgr',
     'perf',
     'perfmonitoring',
     'places',
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/.eslintrc
@@ -0,0 +1,93 @@
+{
+  "extends": [
+    "../../.eslintrc"
+  ],
+
+  "globals": {
+    "Components": true,
+    "dump": true,
+    "Iterator": true
+  },
+
+  "env": { "browser": true },
+
+  "rules": {
+    // Mozilla stuff
+    "mozilla/no-aArgs": 1,
+    "mozilla/reject-importGlobalProperties": 1,
+    "mozilla/var-only-at-top-level": 1,
+
+    "block-scoped-var": 2,
+    "brace-style": [1, "1tbs", {"allowSingleLine": false}],
+    "camelcase": 1,
+    "comma-dangle": 1,
+    "comma-spacing": [1, {"before": false, "after": true}],
+    "comma-style": [1, "last"],
+    "complexity": 1,
+    "consistent-return": 2,
+    "curly": 2,
+    "dot-location": [1, "property"],
+    "dot-notation": 2,
+    "eol-last": 2,
+    "generator-star-spacing": [1, "after"],
+    "indent": [1, 2, {"SwitchCase": 1}],
+    "key-spacing": [1, {"beforeColon": false, "afterColon": true}],
+    "max-len": [1, 80, 2, {"ignoreUrls": true}],
+    "max-nested-callbacks": [2, 3],
+    "new-cap": [2, {"capIsNew": false}],
+    "new-parens": 2,
+    "no-array-constructor": 2,
+    "no-cond-assign": 2,
+    "no-control-regex": 2,
+    "no-debugger": 2,
+    "no-delete-var": 2,
+    "no-dupe-args": 2,
+    "no-dupe-keys": 2,
+    "no-duplicate-case": 2,
+    "no-else-return": 2,
+    "no-eval": 2,
+    "no-extend-native": 2,
+    "no-extra-bind": 2,
+    "no-extra-boolean-cast": 2,
+    "no-extra-semi": 1,
+    "no-fallthrough": 2,
+    "no-inline-comments": 1,
+    "no-lonely-if": 2,
+    "no-mixed-spaces-and-tabs": 2,
+    "no-multi-spaces": 1,
+    "no-multi-str": 1,
+    "no-multiple-empty-lines": [1, {"max": 1}],
+    "no-native-reassign": 2,
+    "no-nested-ternary": 2,
+    "no-redeclare": 2,
+    "no-return-assign": 2,
+    "no-self-compare": 2,
+    "no-sequences": 2,
+    "no-shadow": 1,
+    "no-shadow-restricted-names": 2,
+    "no-spaced-func": 1,
+    "no-throw-literal": 2,
+    "no-trailing-spaces": 2,
+    "no-undef": 2,
+    "no-unneeded-ternary": 2,
+    "no-unreachable": 2,
+    "no-unused-vars": 2,
+    "no-with": 2,
+    "padded-blocks": [1, "never"],
+    "quotes": [1, "double", "avoid-escape"],
+    "semi": [1, "always"],
+    "semi-spacing": [1, {"before": false, "after": true}],
+    "space-after-keywords": [1, "always"],
+    "space-before-blocks": [1, "always"],
+    "space-before-function-paren": [1, "never"],
+    "space-in-parens": [1, "never"],
+    "space-infix-ops": [1, {"int32Hint": true}],
+    "space-return-throw-case": 1,
+    "space-unary-ops": [1, { "words": true, "nonwords": false }],
+    "spaced-comment": [1, "always"],
+    "strict": [2, "global"],
+    "use-isnan": 2,
+    "valid-typeof": 2,
+    "yoda": 2
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/NarrateControls.jsm
@@ -0,0 +1,244 @@
+/* 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 Cu = Components.utils;
+
+Cu.import("resource://gre/modules/narrate/VoiceSelect.jsm");
+Cu.import("resource://gre/modules/narrate/Narrator.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = ["NarrateControls"];
+
+var gStrings = Services.strings.createBundle("chrome://global/locale/narrate.properties");
+
+function NarrateControls(mm, win) {
+  this._mm = mm;
+  this._winRef = Cu.getWeakReference(win);
+
+  // Append content style sheet in document head
+  let style = win.document.createElement("link");
+  style.rel = "stylesheet";
+  style.href = "chrome://global/skin/narrate.css";
+  win.document.head.appendChild(style);
+
+  function localize(pieces, ...substitutions) {
+    let result = pieces[0];
+    for (let i = 0; i < substitutions.length; ++i) {
+      result += gStrings.GetStringFromName(substitutions[i]) + pieces[i + 1];
+    }
+    return result;
+  }
+
+  let dropdown = win.document.createElement("ul");
+  dropdown.className = "dropdown";
+  dropdown.id = "narrate-dropdown";
+  dropdown.innerHTML =
+    localize`<style scoped>
+      @import url("chrome://global/skin/narrateControls.css");
+    </style>
+    <li>
+       <button class="dropdown-toggle button"
+               id="narrate-toggle" title="${"narrate"}"></button>
+    </li>
+    <li class="dropdown-popup">
+      <div id="narrate-control" class="narrate-row">
+        <button disabled id="narrate-skip-previous"
+                title="${"back"}"></button>
+        <button id="narrate-start-stop" title="${"start"}"></button>
+        <button disabled id="narrate-skip-next"
+                title="${"forward"}"></button>
+      </div>
+      <div id="narrate-rate" class="narrate-row">
+        <input id="narrate-rate-input" value="0" title="${"speed"}"
+               step="25" max="400" min="-400" type="range">
+      </div>
+      <div id="narrate-voices" class="narrate-row"></div>
+      <div class="dropdown-arrow"></div>
+    </li>`;
+
+  this.narrator = new Narrator(win);
+
+  let selectLabel = gStrings.GetStringFromName("selectvoicelabel");
+  let comparer = win.Intl ?
+    (new Intl.Collator()).compare : (a, b) => a.localeCompare(b);
+  let options = this.narrator.getVoiceOptions().map(v => {
+    return {
+      label: this._createVoiceLabel(v),
+      value: v.voiceURI
+    };
+  }).sort((a, b) => comparer(a.label, b.label));
+  options.unshift({
+    label: gStrings.GetStringFromName("defaultvoice"),
+    value: "automatic"
+  });
+  this.voiceSelect = new VoiceSelect(win, selectLabel, options);
+  this.voiceSelect.element.addEventListener("change", this);
+  this.voiceSelect.element.id = "voice-select";
+  dropdown.querySelector("#narrate-voices").appendChild(
+    this.voiceSelect.element);
+
+  dropdown.addEventListener("click", this, true);
+
+  let rateRange = dropdown.querySelector("#narrate-rate > input");
+  rateRange.addEventListener("input", this);
+  rateRange.addEventListener("mousedown", this);
+  rateRange.addEventListener("mouseup", this);
+
+  let branch = Services.prefs.getBranch("narrate.");
+  this.voiceSelect.value = branch.getCharPref("voice");
+  // The rate is stored as an integer.
+  rateRange.value = branch.getIntPref("rate");
+
+  let tb = win.document.getElementById("reader-toolbar");
+  tb.appendChild(dropdown);
+}
+
+NarrateControls.prototype = {
+  handleEvent: function(evt) {
+    switch (evt.type) {
+      case "mousedown":
+        this._rateMousedown = true;
+        break;
+      case "mouseup":
+        this._rateMousedown = false;
+        break;
+      case "input":
+        this._onRateInput(evt);
+        break;
+      case "change":
+        this._onVoiceChange();
+        break;
+      case "click":
+        this._onButtonClick(evt);
+        break;
+    }
+  },
+
+  _onRateInput: function(evt) {
+    if (!this._rateMousedown) {
+      this._mm.sendAsyncMessage("Reader:SetIntPref",
+        { name: "narrate.rate", value: evt.target.value });
+      this.narrator.setRate(this._convertRate(evt.target.value));
+    }
+  },
+
+  _onVoiceChange: function() {
+    let voice = this.voice;
+    this._mm.sendAsyncMessage("Reader:SetCharPref",
+      { name: "narrate.voice", value: voice });
+    this.narrator.setVoice(voice);
+  },
+
+  _onButtonClick: function(evt) {
+    switch (evt.target.id) {
+      case "narrate-skip-previous":
+        this.narrator.skipPrevious();
+        break;
+      case "narrate-skip-next":
+        this.narrator.skipNext();
+        break;
+      case "narrate-start-stop":
+        if (this.narrator.speaking) {
+          this.narrator.stop();
+        } else {
+          this._updateSpeechControls(true);
+          let options = { rate: this.rate, voice: this.voice };
+          this.narrator.start(options).then(() => {
+            this._updateSpeechControls(false);
+          });
+        }
+        break;
+      case "narrate-toggle":
+        let dropdown = this._doc.getElementById("narrate-dropdown");
+        if (dropdown.classList.contains("open")) {
+          if (this.narrator.speaking) {
+            this.narrator.stop();
+          }
+
+          // We need to remove "keep-open" class here so that AboutReader
+          // closes this dropdown properly. This class is eventually removed in
+          // _updateSpeechControls which gets called after narration stops,
+          // but that happend asynchronously and is too late.
+          dropdown.classList.remove("keep-open");
+        }
+        break;
+    }
+  },
+
+  _updateSpeechControls: function(speaking) {
+    let dropdown = this._doc.getElementById("narrate-dropdown");
+    dropdown.classList.toggle("keep-open", speaking);
+
+    let startStopButton = this._doc.getElementById("narrate-start-stop");
+    startStopButton.classList.toggle("speaking", speaking);
+    startStopButton.title =
+      gStrings.GetStringFromName(speaking ? "start" : "stop");
+
+    this._doc.getElementById("narrate-skip-previous").disabled = !speaking;
+    this._doc.getElementById("narrate-skip-next").disabled = !speaking;
+  },
+
+  _createVoiceLabel: function(voice) {
+    // This is a highly imperfect method of making human-readable labels
+    // for system voices. Because each platform has a different naming scheme
+    // for voices, we use a different method for each platform.
+    switch (Services.appinfo.OS) {
+      case "WINNT":
+        // On windows the language is included in the name, so just use the name
+        return voice.name;
+      case "Linux":
+        // On Linux, the name is usually the unlocalized language name.
+        // Use a localized language name, and have the language tag in
+        // parenthisis. This is to avoid six languages called "English".
+        return gStrings.formatStringFromName("voiceLabel",
+          [this._getLanguageName(voice.lang) || voice.name, voice.lang], 2);
+      default:
+        // On Mac the language is not included in the name, find a localized
+        // language name or show the tag if none exists.
+        // This is the ideal naming scheme so it is also the "default".
+        return gStrings.formatStringFromName("voiceLabel",
+          [voice.name, this._getLanguageName(voice.lang) || voice.lang], 2);
+    }
+  },
+
+  _getLanguageName: function(lang) {
+    if (!this._langStrings) {
+      this._langStrings = Services.strings.createBundle(
+        "chrome://global/locale/languageNames.properties ");
+    }
+
+    try {
+      // language tags will be lower case ascii between 2 and 3 characters long.
+      return this._langStrings.GetStringFromName(lang.match(/^[a-z]{2,3}/)[0]);
+    } catch (e) {
+      return "";
+    }
+  },
+
+  _convertRate: function(rate) {
+    // We need to convert a relative percentage value to a fraction rate value.
+    // eg. -100 is half the speed, 100 is twice the speed in percentage,
+    // 0.5 is half the speed and 2 is twice the speed in fractions.
+    return Math.pow(Math.abs(rate / 100) + 1, rate < 0 ? -1 : 1);
+  },
+
+  get _win() {
+    return this._winRef.get();
+  },
+
+  get _doc() {
+    return this._win.document;
+  },
+
+  get rate() {
+    return this._convertRate(
+      this._doc.getElementById("narrate-rate-input").value);
+  },
+
+  get voice() {
+    return this.voiceSelect.value;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/Narrator.jsm
@@ -0,0 +1,219 @@
+/* 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 { interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
+  "resource:///modules/translation/LanguageDetector.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = [ "Narrator" ];
+
+// Maximum time into paragraph when pressing "skip previous" will go
+// to previous paragraph and not the start of current one.
+const PREV_THRESHOLD = 2000;
+
+function Narrator(win) {
+  this._winRef = Cu.getWeakReference(win);
+  this._inTest = Services.prefs.getBoolPref("narrate.test");
+  this._speechOptions = {};
+  this._startTime = 0;
+  this._stopped = false;
+}
+
+Narrator.prototype = {
+  get _doc() {
+    return this._winRef.get().document;
+  },
+
+  get _win() {
+    return this._winRef.get();
+  },
+
+  get _voiceMap() {
+    if (!this._voiceMapInner) {
+      this._voiceMapInner = new Map();
+      for (let voice of this._win.speechSynthesis.getVoices()) {
+        this._voiceMapInner.set(voice.voiceURI, voice);
+      }
+    }
+
+    return this._voiceMapInner;
+  },
+
+  get _paragraphs() {
+    if (!this._paragraphsInner) {
+      let wu = this._win.QueryInterface(
+        Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+      let queryString = "#reader-header > *:not(style):not(:empty), " +
+        "#moz-reader-content > .page > * > *:not(style):not(:empty)";
+      // filter out zero sized paragraphs.
+      let paragraphs = Array.from(this._doc.querySelectorAll(queryString));
+      paragraphs = paragraphs.filter(p => {
+        let bb = wu.getBoundsWithoutFlushing(p);
+        return bb.width && bb.height;
+      });
+
+      this._paragraphsInner = paragraphs.map(Cu.getWeakReference);
+    }
+
+    return this._paragraphsInner;
+  },
+
+  get _timeIntoParagraph() {
+    let rv = Date.now() - this._startTime;
+    return rv;
+  },
+
+  get speaking() {
+    return this._win.speechSynthesis.speaking ||
+      this._win.speechSynthesis.pending;
+  },
+
+  _getParagraphAt: function(index) {
+    let paragraph = this._paragraphsInner[index];
+    return paragraph ? paragraph.get() : null;
+  },
+
+  _isParagraphInView: function(paragraphRef) {
+    let paragraph = paragraphRef && paragraphRef.get && paragraphRef.get();
+    if (!paragraph) {
+      return false;
+    }
+
+    let bb = paragraph.getBoundingClientRect();
+    return bb.top >= 0 && bb.top < this._win.innerHeight;
+  },
+
+  _detectLanguage: function() {
+    if (this._speechOptions.lang || this._speechOptions.voice) {
+      return Promise.resolve();
+    }
+
+    let sampleText = this._doc.getElementById(
+      "moz-reader-content").textContent.substring(0, 60 * 1024);
+    return LanguageDetector.detectLanguage(sampleText).then(result => {
+      if (result.confident) {
+        this._speechOptions.lang = result.language;
+      }
+    });
+  },
+
+  _sendTestEvent: function(eventType, detail) {
+    let win = this._win;
+    win.dispatchEvent(new win.CustomEvent(eventType,
+      { detail: Cu.cloneInto(detail, win.document) }));
+  },
+
+  _speakInner: function() {
+    this._win.speechSynthesis.cancel();
+    let paragraph = this._getParagraphAt(this._index);
+    let utterance = new this._win.SpeechSynthesisUtterance(
+      paragraph.textContent);
+    utterance.rate = this._speechOptions.rate;
+    if (this._speechOptions.voice) {
+      utterance.voice = this._speechOptions.voice;
+    } else {
+      utterance.lang = this._speechOptions.lang;
+    }
+
+    this._startTime = Date.now();
+
+    return new Promise(resolve => {
+      utterance.addEventListener("start", () => {
+        paragraph.classList.add("narrating");
+        let bb = paragraph.getBoundingClientRect();
+        if (bb.top < 0 || bb.bottom > this._win.innerHeight) {
+          paragraph.scrollIntoView({ behavior: "smooth", block: "start"});
+        }
+
+        if (this._inTest) {
+          this._sendTestEvent("paragraphstart", {
+            voice: utterance.chosenVoiceURI,
+            rate: utterance.rate,
+            paragraph: this._index
+          });
+        }
+      });
+
+      utterance.addEventListener("end", () => {
+        if (!this._win) {
+          // page got unloaded, don't do anything.
+          return;
+        }
+
+        paragraph.classList.remove("narrating");
+        this._startTime = 0;
+        if (this._inTest) {
+          this._sendTestEvent("paragraphend", {});
+        }
+
+        if (this._index + 1 >= this._paragraphs.length || this._stopped) {
+          // We reached the end of the document, or the user pressed stopped.
+          resolve();
+        } else {
+          this._index++;
+          this._speakInner().then(resolve);
+        }
+      });
+
+      this._win.speechSynthesis.speak(utterance);
+    });
+  },
+
+  getVoiceOptions: function() {
+    return Array.from(this._voiceMap.values());
+  },
+
+  start: function(speechOptions) {
+    this._speechOptions = {
+      rate: speechOptions.rate,
+      voice: this._voiceMap.get(speechOptions.voice)
+    };
+
+    this._stopped = false;
+    return this._detectLanguage().then(() => {
+      if (!this._isParagraphInView(this._paragraphs[this._index])) {
+        this._index = this._paragraphs.findIndex(
+          this._isParagraphInView.bind(this));
+      }
+
+      return this._speakInner();
+    });
+  },
+
+  stop: function() {
+    this._stopped = true;
+    this._win.speechSynthesis.cancel();
+  },
+
+  skipNext: function() {
+    this._win.speechSynthesis.cancel();
+  },
+
+  skipPrevious: function() {
+    this._index -=
+      this._index > 0 && this._timeIntoParagraph < PREV_THRESHOLD ? 2 : 1;
+    this._win.speechSynthesis.cancel();
+  },
+
+  setRate: function(rate) {
+    this._speechOptions.rate = rate;
+    /* repeat current paragraph */
+    this._index--;
+    this._win.speechSynthesis.cancel();
+  },
+
+  setVoice: function(voice) {
+    this._speechOptions.voice = this._voiceMap.get(voice);
+    /* repeat current paragraph */
+    this._index--;
+    this._win.speechSynthesis.cancel();
+  }
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/VoiceSelect.jsm
@@ -0,0 +1,291 @@
+/* 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 Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ["VoiceSelect"];
+
+function VoiceSelect(win, label, options = []) {
+  this._winRef = Cu.getWeakReference(win);
+
+  let element = win.document.createElement("div");
+  element.classList.add("voiceselect");
+  element.innerHTML =
+   `<button class="select-toggle" aria-controls="voice-options">
+      <span class="label">${label}</span> <span class="current-voice"></span>
+    </button>
+    <div class="options" id="voice-options" role="listbox"></div>`;
+
+  this._elementRef = Cu.getWeakReference(element);
+
+  let button = this.selectToggle;
+  button.addEventListener("click", this);
+  button.addEventListener("keypress", this);
+
+  let listbox = this.listbox;
+  listbox.addEventListener("click", this);
+  listbox.addEventListener("mousemove", this);
+  listbox.addEventListener("keypress", this);
+  listbox.addEventListener("wheel", this, true);
+
+  win.addEventListener("resize", () => {
+    this._updateDropdownHeight();
+  });
+
+  for (let option of options) {
+    this.add(option.label, option.value);
+  }
+
+  this.selectedIndex = 0;
+}
+
+VoiceSelect.prototype = {
+  add: function(label, value) {
+    let option = this._doc.createElement("button");
+    option.dataset.value = value;
+    option.classList.add("option");
+    option.tabIndex = "-1";
+    option.setAttribute("role", "option");
+    option.textContent = label;
+    this.listbox.appendChild(option);
+  },
+
+  toggleList: function(force, focus = true) {
+    if (this.element.classList.toggle("open", force)) {
+      if (focus) {
+        (this.selected || this.options[0]).focus();
+      }
+
+      this._updateDropdownHeight(true);
+      this.listbox.setAttribute("aria-expanded", true);
+      this._win.addEventListener("focus", this, true);
+    } else {
+      if (focus) {
+        this.element.querySelector(".select-toggle").focus();
+      }
+
+      this.listbox.setAttribute("aria-expanded", false);
+      this._win.removeEventListener("focus", this, true);
+    }
+  },
+
+  handleEvent: function(evt) {
+    let target = evt.target;
+
+    switch (evt.type) {
+      case "click":
+        if (target.classList.contains("option")) {
+          if (!target.classList.contains("selected")) {
+            this.selected = target;
+          }
+
+          this.toggleList(false);
+        } else if (target.classList.contains("select-toggle")) {
+          this.toggleList();
+        }
+        break;
+
+      case "mousemove":
+        this.listbox.classList.add("hovering");
+        break;
+
+      case "keypress":
+        if (target.classList.contains("select-toggle")) {
+          if (evt.altKey) {
+            this.toggleList(true);
+          } else {
+            this._keyPressedButton(evt);
+          }
+        } else {
+          this.listbox.classList.remove("hovering");
+          this._keyPressedInBox(evt);
+        }
+        break;
+
+      case "wheel":
+        // Don't let wheel events bubble to document. It will scroll the page
+        // and close the entire narrate dialog.
+        evt.stopPropagation();
+        break;
+
+      case "focus":
+        this._win.console.log(evt);
+        if (!evt.target.closest('.options')) {
+          this.toggleList(false, false);
+        }
+        break;
+    }
+  },
+
+  _getPagedOption: function(option, up) {
+    let height = elem => elem.getBoundingClientRect().height;
+    let listboxHeight = height(this.listbox);
+
+    let next = option;
+    for (let delta = 0; delta < listboxHeight; delta += height(next)) {
+      let sibling = up ? next.previousElementSibling : next.nextElementSibling;
+      if (!sibling) {
+        break;
+      }
+
+      next = sibling;
+    }
+
+    return next;
+  },
+
+  _keyPressedButton: function(evt) {
+    if (evt.altKey && (evt.key === "ArrowUp" || evt.key === "ArrowUp")) {
+      this.toggleList(true);
+      return;
+    }
+
+    let toSelect;
+    switch (evt.key) {
+      case "PageUp":
+      case "ArrowUp":
+        toSelect = this.selected.previousElementSibling;
+        break;
+      case "PageDown":
+      case "ArrowDown":
+        toSelect = this.selected.nextElementSibling;
+        break;
+      case "Home":
+        toSelect = this.selected.parentNode.firstElementChild;
+        break;
+      case "End":
+        toSelect = this.selected.parentNode.lastElementChild;
+        break;
+    }
+
+    if (toSelect && toSelect.classList.contains("option")) {
+      evt.preventDefault();
+      this.selected = toSelect;
+    }
+  },
+
+  _keyPressedInBox: function(evt) {
+    let toFocus;
+    let cur = this._doc.activeElement;
+
+    switch (evt.key) {
+      case "ArrowUp":
+        toFocus = cur.previousElementSibling || this.listbox.lastElementChild;
+        break;
+      case "ArrowDown":
+        toFocus = cur.nextElementSibling || this.listbox.firstElementChild;
+        break;
+      case "PageUp":
+        toFocus = this._getPagedOption(cur, true);
+        break;
+      case "PageDown":
+        toFocus = this._getPagedOption(cur, false);
+        break;
+      case "Home":
+        toFocus = cur.parentNode.firstElementChild;
+        break;
+      case "End":
+        toFocus = cur.parentNode.lastElementChild;
+        break;
+      case "Escape":
+        this.toggleList(false);
+        break;
+    }
+
+    if (toFocus && toFocus.classList.contains("option")) {
+      evt.preventDefault();
+      toFocus.focus();
+    }
+  },
+
+  _select: function(option) {
+    let oldSelected = this.selected;
+    if (oldSelected) {
+      oldSelected.removeAttribute("aria-selected");
+      oldSelected.classList.remove("selected");
+    }
+
+    if (option) {
+      option.setAttribute("aria-selected", true);
+      option.classList.add("selected");
+      this.element.querySelector(".current-voice").textContent =
+        option.textContent;
+    }
+
+    let evt = this.element.ownerDocument.createEvent("Event");
+    evt.initEvent("change", true, true);
+    this.element.dispatchEvent(evt);
+  },
+
+  _updateDropdownHeight: function(now) {
+    let updateInner = () => {
+      let winHeight = this._win.innerHeight;
+      let listbox = this.listbox;
+      let listboxTop = listbox.getBoundingClientRect().top;
+      listbox.style.maxHeight = (winHeight - listboxTop - 10) + "px";
+    };
+
+    if (now) {
+      updateInner();
+    } else if (!this._pendingDropdownUpdate) {
+      this._pendingDropdownUpdate = true;
+      this._win.requestAnimationFrame(() => {
+        updateInner();
+        delete this._pendingDropdownUpdate;
+      });
+    }
+  },
+
+  get element() {
+    return this._elementRef.get();
+  },
+
+  get listbox() {
+    return this._elementRef.get().querySelector(".options");
+  },
+
+  get selectToggle() {
+    return this._elementRef.get().querySelector(".select-toggle");
+  },
+
+  get _win() {
+    return this._winRef.get();
+  },
+
+  get _doc() {
+    return this._win.document;
+  },
+
+  set selected(option) {
+    this._select(option);
+  },
+
+  get selected() {
+    return this.element.querySelector(".options > .option.selected");
+  },
+
+  get options() {
+    return this.element.querySelectorAll(".options > .option");
+  },
+
+  set selectedIndex(index) {
+    this._select(this.options[index]);
+  },
+
+  get selectedIndex() {
+    return Array.from(this.options).indexOf(this.selected);
+  },
+
+  set value(value) {
+    let option = Array.from(this.options).find(o => o.dataset.value === value);
+    this._select(option);
+  },
+
+  get value() {
+    let selected = this.selected;
+    return selected ? selected.dataset.value : "";
+  }
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_JS_MODULES.narrate = [
+  'NarrateControls.jsm',
+  'Narrator.jsm',
+  'VoiceSelect.jsm'
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/test/.eslintrc
@@ -0,0 +1,21 @@
+{
+  "extends": [
+    "../.eslintrc"
+  ],
+
+  "globals": {
+    "is": true,
+    "isnot": true,
+    "ok": true,
+    "NarrateTestUtils": true,
+    "content": true,
+    "ContentTaskUtils": true,
+    "ContentTask": true,
+    "BrowserTestUtils": true,
+    "gBrowser": true,
+  },
+
+  "rules": {
+    "mozilla/import-headjs-globals": 1
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/test/NarrateTestUtils.jsm
@@ -0,0 +1,114 @@
+/* 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";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = [ "NarrateTestUtils" ];
+
+this.NarrateTestUtils = {
+  TOGGLE: "#narrate-toggle",
+  POPUP: "#narrate-dropdown .dropdown-popup",
+  VOICE_SELECT: "#narrate-voices .select-toggle",
+  VOICE_OPTIONS: "#narrate-voices .options",
+  VOICE_SELECTED: "#narrate-voices .options .option.selected",
+  VOICE_SELECT_LABEL: "#narrate-voices .select-toggle .current-voice",
+  RATE: "#narrate-rate-input",
+  START: "#narrate-start-stop:not(.speaking)",
+  STOP: "#narrate-start-stop.speaking",
+  BACK: "#narrate-skip-previous",
+  FORWARD: "#narrate-skip-next",
+
+  isVisible: function(element) {
+    let style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+    if (style.display == "none") {
+      return false;
+    } else if (style.visibility != "visible") {
+      return false;
+    } else if (style.display == "-moz-popup" && element.state != "open") {
+      return false;
+    }
+
+    // Hiding a parent element will hide all its children
+    if (element.parentNode != element.ownerDocument) {
+      return this.isVisible(element.parentNode);
+    }
+
+    return true;
+  },
+
+  isStoppedState: function(window, ok) {
+    let $ = window.document.querySelector.bind(window.document);
+    ok($(this.BACK).disabled, "back button is disabled");
+    ok($(this.FORWARD).disabled, "forward button is disabled");
+    ok(!!$(this.START), "start button is showing");
+    ok(!$(this.STOP), "stop button is hidden");
+  },
+
+  isStartedState: function(window, ok) {
+    let $ = window.document.querySelector.bind(window.document);
+    ok(!$(this.BACK).disabled, "back button is enabled");
+    ok(!$(this.FORWARD).disabled, "forward button is enabled");
+    ok(!$(this.START), "start button is hidden");
+    ok(!!$(this.STOP), "stop button is showing");
+  },
+
+  selectVoice: function(window, voiceUri) {
+    if (!this.isVisible(window.document.querySelector(this.VOICE_OPTIONS))) {
+      window.document.querySelector(this.VOICE_SELECT).click();
+    }
+
+    let voiceOption = window.document.querySelector(
+      `#narrate-voices .option[data-value="${voiceUri}"]`);
+
+    voiceOption.focus();
+    voiceOption.click();
+
+    return voiceOption.classList.contains("selected");
+  },
+
+  getEventUtils: function(window) {
+    let eventUtils = {
+      "_EU_Ci": Components.interfaces,
+      "_EU_Cc": Components.classes,
+      window: window,
+      parent: window,
+      navigator: window.navigator,
+      KeyboardEvent: window.KeyboardEvent,
+      KeyEvent: window.KeyEvent
+    };
+    Services.scriptloader.loadSubScript(
+      "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", eventUtils);
+    return eventUtils;
+  },
+
+  getReaderReadyPromise: function(window) {
+    return new Promise(resolve => {
+      function observeReady(subject, topic) {
+        if (subject == window) {
+          Services.obs.removeObserver(observeReady, topic);
+          resolve();
+        }
+      }
+
+      if (window.document.body.classList.contains("loaded")) {
+        resolve();
+      } else {
+        Services.obs.addObserver(observeReady, "AboutReader:Ready", false);
+      }
+    });
+  },
+
+  waitForPrefChange: function(pref) {
+    return new Promise(resolve => {
+      function observeChange() {
+        Services.prefs.removeObserver(pref, observeChange);
+        resolve();
+      }
+
+      Services.prefs.addObserver(pref, observeChange, false);
+    });
+  }
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/test/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+  head.js
+  NarrateTestUtils.jsm
+
+[browser_narrate.js]
+[browser_narrate_disable.js]
+[browser_voiceselect.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/test/browser_narrate.js
@@ -0,0 +1,101 @@
+/* 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/. */
+
+/* globals is, isnot, registerCleanupFunction, add_task */
+
+"use strict";
+
+registerCleanupFunction(teardown);
+
+add_task(function* testNarrate() {
+  setup();
+
+  yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
+    let TEST_VOICE = "urn:moz-tts:fake-indirect:teresa";
+    let $ = content.document.querySelector.bind(content.document);
+
+    let popup = $(NarrateTestUtils.POPUP);
+    ok(!NarrateTestUtils.isVisible(popup), "popup is initially hidden");
+
+    let toggle = $(NarrateTestUtils.TOGGLE);
+    toggle.click();
+
+    ok(NarrateTestUtils.isVisible(popup), "popup toggled");
+
+    let voiceOptions = $(NarrateTestUtils.VOICE_OPTIONS);
+    ok(!NarrateTestUtils.isVisible(voiceOptions),
+      "voice options are initially hidden");
+
+    $(NarrateTestUtils.VOICE_SELECT).click();
+    ok(NarrateTestUtils.isVisible(voiceOptions), "voice options pop up");
+
+    let prefChanged = NarrateTestUtils.waitForPrefChange("narrate.voice");
+    ok(NarrateTestUtils.selectVoice(content, TEST_VOICE),
+      "test voice selected");
+    yield prefChanged;
+
+    ok(!NarrateTestUtils.isVisible(voiceOptions), "voice options hidden again");
+
+    NarrateTestUtils.isStoppedState(content, ok);
+
+    let promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
+    $(NarrateTestUtils.START).click();
+    let speechinfo = (yield promiseEvent).detail;
+    is(speechinfo.voice, TEST_VOICE, "correct voice is being used");
+    is(speechinfo.paragraph, 0, "first paragraph is being spoken");
+
+    NarrateTestUtils.isStartedState(content, ok);
+
+    promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
+    $(NarrateTestUtils.FORWARD).click();
+    speechinfo = (yield promiseEvent).detail;
+    is(speechinfo.voice, TEST_VOICE, "same voice is used");
+    is(speechinfo.paragraph, 1, "second paragraph is being spoken");
+
+    NarrateTestUtils.isStartedState(content, ok);
+
+    let eventUtils = NarrateTestUtils.getEventUtils(content);
+
+    promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
+    prefChanged = NarrateTestUtils.waitForPrefChange("narrate.rate");
+    $(NarrateTestUtils.RATE).focus();
+    eventUtils.sendKey("PAGE_UP", content);
+    let newspeechinfo = (yield promiseEvent).detail;
+    is(newspeechinfo.paragraph, speechinfo.paragraph, "same paragraph");
+    isnot(newspeechinfo.rate, speechinfo.rate, "rate changed");
+    yield prefChanged;
+
+    promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphend");
+    $(NarrateTestUtils.STOP).click();
+    yield promiseEvent;
+
+    yield ContentTaskUtils.waitForCondition(
+      () => !$(NarrateTestUtils.STOP), "transitioned to stopped state");
+    NarrateTestUtils.isStoppedState(content, ok);
+
+    promiseEvent = ContentTaskUtils.waitForEvent(content, "scroll");
+    content.scrollBy(0, 10);
+    yield promiseEvent;
+    ok(!NarrateTestUtils.isVisible(popup), "popup is hidden after scroll");
+
+    toggle.click();
+    ok(NarrateTestUtils.isVisible(popup), "popup is toggled again");
+
+    promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
+    $(NarrateTestUtils.START).click();
+    yield promiseEvent;
+    NarrateTestUtils.isStartedState(content, ok);
+
+    promiseEvent = ContentTaskUtils.waitForEvent(content, "scroll");
+    content.scrollBy(0, -10);
+    yield promiseEvent;
+    ok(NarrateTestUtils.isVisible(popup), "popup stays visible after scroll");
+
+    promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphend");
+    toggle.click();
+    yield promiseEvent;
+    ok(!NarrateTestUtils.isVisible(popup), "popup is dismissed while speaking");
+    ok(true, "speech stopped when popup is dismissed");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/test/browser_narrate_disable.js
@@ -0,0 +1,37 @@
+/* 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/. */
+
+/* globals registerCleanupFunction, add_task */
+
+"use strict";
+
+const ENABLE_PREF = "narrate.enabled";
+
+registerCleanupFunction(() => {
+  clearUserPref(ENABLE_PREF);
+  teardown();
+});
+
+add_task(function* testNarratePref() {
+  setup();
+
+  yield spawnInNewReaderTab(TEST_ARTICLE, function() {
+    is(content.document.querySelectorAll(NarrateTestUtils.TOGGLE).length, 1,
+      "narrate is inserted by default");
+  });
+
+  setBoolPref(ENABLE_PREF, false);
+
+  yield spawnInNewReaderTab(TEST_ARTICLE, function() {
+    ok(!content.document.querySelector(NarrateTestUtils.TOGGLE),
+      "narrate is disabled and is not in reader mode");
+  });
+
+  setBoolPref(ENABLE_PREF, true);
+
+  yield spawnInNewReaderTab(TEST_ARTICLE, function() {
+    is(content.document.querySelectorAll(NarrateTestUtils.TOGGLE).length, 1,
+      "narrate is re-enabled and appears only once");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/test/browser_voiceselect.js
@@ -0,0 +1,106 @@
+/* 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/. */
+
+/* globals registerCleanupFunction, add_task, is, isnot */
+
+"use strict";
+
+registerCleanupFunction(teardown);
+
+add_task(function* testVoiceselectDropdownAutoclose() {
+  setup();
+
+  yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
+    let $ = content.document.querySelector.bind(content.document);
+
+    $(NarrateTestUtils.TOGGLE).click();
+    ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
+      "popup is toggled");
+
+    ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
+      "voice options are initially hidden");
+
+    $(NarrateTestUtils.VOICE_SELECT).click();
+    ok(NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
+      "voice options are toggled");
+
+    $(NarrateTestUtils.TOGGLE).click();
+    // A focus will follow a real click.
+    $(NarrateTestUtils.TOGGLE).focus();
+    ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
+      "narrate popup is dismissed");
+
+    $(NarrateTestUtils.TOGGLE).click();
+    // A focus will follow a real click.
+    $(NarrateTestUtils.TOGGLE).focus();
+    ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
+      "narrate popup is showing again");
+    ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
+      "voice options are hidden after popup comes back");
+  });
+});
+
+add_task(function* testVoiceselectLabelChange() {
+  setup();
+
+  yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
+    let $ = content.document.querySelector.bind(content.document);
+
+    $(NarrateTestUtils.TOGGLE).click();
+    ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
+      "popup is toggled");
+
+    ok(NarrateTestUtils.selectVoice(content, "urn:moz-tts:fake-direct:lenny"),
+      "voice selected");
+
+    let selectedOption = $(NarrateTestUtils.VOICE_SELECTED);
+    let selectLabel = $(NarrateTestUtils.VOICE_SELECT_LABEL);
+
+    is(selectedOption.textContent, selectLabel.textContent,
+      "new label matches selected voice");
+  });
+});
+
+add_task(function* testVoiceselectKeyboard() {
+  setup();
+
+  yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
+    let $ = content.document.querySelector.bind(content.document);
+
+    $(NarrateTestUtils.TOGGLE).click();
+    ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
+      "popup is toggled");
+
+    let eventUtils = NarrateTestUtils.getEventUtils(content);
+
+    let firstValue = $(NarrateTestUtils.VOICE_SELECTED).dataset.value;
+
+    ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
+      "voice options initially are hidden");
+
+    $(NarrateTestUtils.VOICE_SELECT).focus();
+
+    eventUtils.sendKey("DOWN", content);
+
+    yield ContentTaskUtils.waitForCondition(
+      () => $(NarrateTestUtils.VOICE_SELECTED).dataset.value != firstValue,
+      "value changed after pressing DOWN key");
+
+    eventUtils.sendKey("RETURN", content);
+
+    ok(NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
+      "voice options showing after pressing RETURN");
+
+    eventUtils.sendKey("UP", content);
+
+    eventUtils.sendKey("RETURN", content);
+
+    ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
+      "voice options hidden after pressing RETURN");
+
+    yield ContentTaskUtils.waitForCondition(
+      () => $(NarrateTestUtils.VOICE_SELECTED).dataset.value == firstValue,
+      "value changed back to original after pressing RETURN");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/narrate/test/head.js
@@ -0,0 +1,67 @@
+/* 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/. */
+
+/* exported teardown, setup, toggleExtension,
+   spawnInNewReaderTab, TEST_ARTICLE  */
+
+"use strict";
+
+const TEST_ARTICLE = "http://example.com/browser/browser/base/content/test/" +
+  "general/readerModeArticle.html";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+  "resource://gre/modules/AddonManager.jsm");
+
+const TEST_PREFS = [
+  ["reader.parse-on-load.enabled", true],
+  ["media.webspeech.synth.enabled", true],
+  ["media.webspeech.synth.test", true],
+  ["narrate.enabled", true],
+  ["narrate.test", true]
+];
+
+function setup() {
+  // Set required test prefs.
+  TEST_PREFS.forEach(([name, value]) => {
+    setBoolPref(name, value);
+  });
+}
+
+function teardown() {
+  // Reset test prefs.
+  TEST_PREFS.forEach(pref => {
+    clearUserPref(pref[0]);
+  });
+}
+
+function spawnInNewReaderTab(url, func) {
+  return BrowserTestUtils.withNewTab(
+    { gBrowser,
+      url: `about:reader?url=${encodeURIComponent(url)}` },
+      function* (browser) {
+        yield ContentTask.spawn(browser, null, function* () {
+          Components.utils.import("chrome://mochitests/content/browser/" +
+            "toolkit/components/narrate/test/NarrateTestUtils.jsm");
+
+          yield NarrateTestUtils.getReaderReadyPromise(content);
+        });
+
+        yield ContentTask.spawn(browser, null, func);
+      });
+}
+
+function setBoolPref(name, value) {
+  Services.prefs.setBoolPref(name, value);
+}
+
+function clearUserPref(name) {
+  Services.prefs.clearUserPref(name);
+}
+
--- a/toolkit/components/reader/AboutReader.jsm
+++ b/toolkit/components/reader/AboutReader.jsm
@@ -10,16 +10,17 @@ this.EXPORTED_SYMBOLS = [ "AboutReader" 
 
 Cu.import("resource://gre/modules/ReaderMode.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NarrateControls", "resource://gre/modules/narrate/NarrateControls.jsm");
 
 var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
 
 var AboutReader = function(mm, win, articlePromise) {
   let url = this._getOriginalUrl(win);
   if (!(url.startsWith("http://") || url.startsWith("https://"))) {
     let errorMsg = "Only http:// and https:// URLs can be loaded in about:reader.";
     if (Services.prefs.getBoolPref("reader.errors.includeURLs"))
@@ -97,16 +98,20 @@ var AboutReader = function(mm, win, arti
   ];
 
   let fontType = Services.prefs.getCharPref("reader.font_type");
   this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
   this._setFontType(fontType);
 
   this._setupFontSizeButtons();
 
+  if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
+    new NarrateControls(mm, win);
+  }
+
   this._loadArticle();
 }
 
 AboutReader.prototype = {
   _BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
                           ".content p > a:only-child > img:only-child, " +
                           ".content .wp-caption img, " +
                           ".content figure img",
@@ -163,17 +168,17 @@ AboutReader.prototype = {
     return _viewId;
   },
 
   receiveMessage: function (message) {
     switch (message.name) {
       // Triggered by Android user pressing BACK while the banner font-dropdown is open.
       case "Reader:CloseDropdown": {
         // Just close it.
-        this._closeDropdown();
+        this._closeDropdowns();
         break;
       }
 
       case "Reader:AddButton": {
         if (message.data.id && message.data.image &&
             !this._doc.getElementById(message.data.id)) {
           let btn = this._doc.createElement("button");
           btn.setAttribute("class", "button");
@@ -204,42 +209,50 @@ AboutReader.prototype = {
 
   handleEvent: function(aEvent) {
     if (!aEvent.isTrusted)
       return;
 
     switch (aEvent.type) {
       case "click":
         let target = aEvent.target;
-        while (target && target.id != "reader-popup")
-          target = target.parentNode;
-        if (!target)
-          this._closeDropdown();
+        if (target.classList.contains('dropdown-toggle')) {
+          this._toggleDropdownClicked(aEvent);
+        } else if (!target.closest('.dropdown-popup')) {
+          this._closeDropdowns();
+        }
         break;
       case "scroll":
-        this._closeDropdown();
+        this._closeDropdowns();
         let isScrollingUp = this._scrollOffset > aEvent.pageY;
         this._setSystemUIVisibility(isScrollingUp);
         this._scrollOffset = aEvent.pageY;
         break;
       case "resize":
         this._updateImageMargins();
+        if (this._isToolbarVertical) {
+          this._win.setTimeout(() => {
+            for (let dropdown of this._doc.querySelectorAll('.dropdown.open')) {
+              this._updatePopupPosition(dropdown);
+            }
+          }, 0);
+        }
         break;
 
       case "devicelight":
         this._handleDeviceLight(aEvent.value);
         break;
 
       case "visibilitychange":
         this._handleVisibilityChange();
         break;
 
       case "unload":
         // Close the Banners Font-dropdown, cleanup Android BackPressListener.
-        this._closeDropdown();
+        this._closeDropdowns();
 
         this._mm.removeMessageListener("Reader:CloseDropdown", this);
         this._mm.removeMessageListener("Reader:AddButton", this);
         this._mm.removeMessageListener("Reader:RemoveButton", this);
         this._windowUnloaded = true;
         break;
     }
   },
@@ -598,17 +611,17 @@ AboutReader.prototype = {
     this._maybeSetTextDirection(article);
 
     this._contentElement.style.display = "block";
     this._updateImageMargins();
 
     this._requestFavicon();
     this._doc.body.classList.add("loaded");
 
-    Services.obs.notifyObservers(null, "AboutReader:Ready", "");
+    Services.obs.notifyObservers(this._win, "AboutReader:Ready", "");
   },
 
   _hideContent: function() {
     this._headerElement.style.display = "none";
     this._contentElement.style.display = "none";
   },
 
   _showProgressDelayed: function() {
@@ -713,80 +726,71 @@ AboutReader.prototype = {
    * @param   Localizable string providing UI element usage tip.
    */
   _setButtonTip: function(id, titleEntity) {
     let button = this._doc.getElementById(id);
     button.setAttribute("title", gStrings.GetStringFromName(titleEntity));
   },
 
   _setupStyleDropdown: function() {
-    let doc = this._doc;
-    let win = this._win;
+    let dropdownToggle = this._doc.querySelector("#style-dropdown .dropdown-toggle");
+    dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
+  },
 
-    let dropdown = doc.getElementById("style-dropdown");
+  _updatePopupPosition: function(dropdown) {
     let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
     let dropdownPopup = dropdown.querySelector(".dropdown-popup");
 
-    // Helper function used to position the popup on desktop,
-    // where there is a vertical toolbar.
-    function updatePopupPosition() {
-      let toggleHeight = dropdownToggle.offsetHeight;
-      let toggleTop = dropdownToggle.offsetTop;
-      let popupTop = toggleTop - toggleHeight / 2;
-      dropdownPopup.style.top = popupTop + "px";
-    }
+    let toggleHeight = dropdownToggle.offsetHeight;
+    let toggleTop = dropdownToggle.offsetTop;
+    let popupTop = toggleTop - toggleHeight / 2;
+
+    dropdownPopup.style.top = popupTop + "px";
+  },
+
+  _toggleDropdownClicked: function(event) {
+    let dropdown = event.target.closest('.dropdown');
 
-    if (this._isToolbarVertical) {
-      win.addEventListener("resize", event => {
-        if (!event.isTrusted)
-          return;
+    if (!dropdown)
+      return;
+
+    event.stopPropagation();
 
-        // Wait for reflow before calculating the new position of the popup.
-        win.setTimeout(updatePopupPosition, 0);
-      }, true);
+    if (dropdown.classList.contains("open")) {
+      this._closeDropdowns();
+    } else {
+      this._openDropdown(dropdown);
+      if (this._isToolbarVertical) {
+        this._updatePopupPosition(dropdown);
+      }
     }
-
-    dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
-    dropdownToggle.addEventListener("click", event => {
-      if (!event.isTrusted)
-        return;
-
-      event.stopPropagation();
-
-      if (dropdown.classList.contains("open")) {
-        this._closeDropdown();
-      } else {
-        this._openDropdown();
-        if (this._isToolbarVertical) {
-          updatePopupPosition();
-        }
-      }
-    }, true);
   },
 
   /*
    * If the ReaderView banner font-dropdown is closed, open it.
    */
-  _openDropdown: function() {
-    let dropdown = this._doc.getElementById("style-dropdown");
+  _openDropdown: function(dropdown) {
     if (dropdown.classList.contains("open")) {
       return;
     }
 
+    this._closeDropdowns();
+
     // Trigger BackPressListener initialization in Android.
     dropdown.classList.add("open");
     this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
   },
 
   /*
-   * If the ReaderView banner font-dropdown is opened, close it.
+   * If the ReaderView has open dropdowns, close them.
    */
-  _closeDropdown: function() {
-    let dropdown = this._doc.getElementById("style-dropdown");
-    if (!dropdown.classList.contains("open")) {
-      return;
+  _closeDropdowns: function() {
+    let openDropdowns = this._doc.querySelectorAll(".dropdown.open:not(.keep-open)");
+    for (let dropdown of openDropdowns) {
+      dropdown.classList.remove("open");
     }
 
     // Trigger BackPressListener cleanup in Android.
-    dropdown.classList.remove("open");
-    this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
-  },
+    if (openDropdowns.length) {
+      this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
+    }
+  }
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/global/narrate.properties
@@ -0,0 +1,19 @@
+# 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/.
+
+# Narrate, meaning "read the page out loud". This is the name of the feature
+# and it is the label for the popup button.
+narrate = Narrate
+back = Back
+start = Start
+stop = Stop
+forward = Forward
+speed = Speed
+selectvoicelabel = Voice:
+# Default voice is determined by the language of the document.
+defaultvoice = Default
+
+# Voice name and language.
+# eg. David (English)
+voiceLabel = %S (%S)
\ No newline at end of file
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -57,16 +57,17 @@
 #endif
   locale/@AB_CD@/global/globalKeys.dtd                  (%chrome/global/globalKeys.dtd)
   locale/@AB_CD@/global/headsUpDisplay.properties       (%chrome/global/headsUpDisplay.properties)
   locale/@AB_CD@/global/intl.css                        (%chrome/global/intl.css)
   locale/@AB_CD@/global/intl.properties                 (%chrome/global/intl.properties)
   locale/@AB_CD@/global/keys.properties                 (%chrome/global/keys.properties)
   locale/@AB_CD@/global/languageNames.properties        (%chrome/global/languageNames.properties)
   locale/@AB_CD@/global/mozilla.dtd                     (%chrome/global/mozilla.dtd)
+  locale/@AB_CD@/global/narrate.properties              (%chrome/global/narrate.properties)
   locale/@AB_CD@/global/notification.dtd                (%chrome/global/notification.dtd)
   locale/@AB_CD@/global/preferences.dtd                 (%chrome/global/preferences.dtd)
   locale/@AB_CD@/global/printdialog.dtd                 (%chrome/global/printdialog.dtd)
   locale/@AB_CD@/global/printjoboptions.dtd             (%chrome/global/printjoboptions.dtd)
   locale/@AB_CD@/global/printPageSetup.dtd              (%chrome/global/printPageSetup.dtd)
   locale/@AB_CD@/global/printPreview.dtd                (%chrome/global/printPreview.dtd)
   locale/@AB_CD@/global/printPreviewProgress.dtd        (%chrome/global/printPreviewProgress.dtd)
   locale/@AB_CD@/global/printdialog.properties          (%chrome/global/printdialog.properties)
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -82,16 +82,30 @@ function parseExtra(extra, allowed) {
   for (let al of allowed) {
     if (extra && extra.indexOf(al) != -1) {
       result[al] = true;
     }
   }
   return result;
 }
 
+function mergeStatus(data, channel) {
+  try {
+    data.statusCode = channel.responseStatus;
+    let statusText = channel.responseStatusText;
+    let maj = {};
+    let min = {};
+    channel.QueryInterface(Ci.nsIHttpChannelInternal).getResponseVersion(maj, min);
+    data.statusLine = `HTTP/${maj.value}.${min.value} ${data.statusCode} ${statusText}`;
+  } catch (e) {
+    // NS_ERROR_NOT_AVAILABLE might be thrown.
+    Cu.reportError(e);
+  }
+}
+
 var HttpObserverManager;
 
 var ContentPolicyManager = {
   policyData: new Map(),
   policies: new Map(),
   idMap: new Map(),
   nextId: 0,
 
@@ -399,17 +413,17 @@ HttpObserverManager = {
     let policyType = loadInfo ?
                      loadInfo.externalContentPolicyType :
                      Ci.nsIContentPolicy.TYPE_OTHER;
 
     let requestHeaderNames;
     let responseHeaderNames;
 
     let includeStatus = kind === "headersReceived" ||
-                        kind === "onBeforeRedirect" ||
+                        kind === "onRedirect" ||
                         kind === "onStart" ||
                         kind === "onStop";
 
     for (let [callback, opts] of listeners.entries()) {
       if (!this.shouldRunListener(policyType, channel.URI, opts.filter)) {
         continue;
       }
 
@@ -438,17 +452,17 @@ HttpObserverManager = {
         data.requestHeaders = this.getHeaders(channel, "visitRequestHeaders");
         requestHeaderNames = data.requestHeaders.map(h => h.name);
       }
       if (opts.responseHeaders) {
         data.responseHeaders = this.getHeaders(channel, "visitResponseHeaders");
         responseHeaderNames = data.responseHeaders.map(h => h.name);
       }
       if (includeStatus) {
-        data.statusCode = channel.responseStatus;
+        mergeStatus(data, channel);
       }
 
       let result = null;
       try {
         result = callback(data);
       } catch (e) {
         Cu.reportError(e);
       }
--- a/toolkit/themes/shared/aboutReaderContent.css
+++ b/toolkit/themes/shared/aboutReaderContent.css
@@ -50,17 +50,19 @@ p,
 code,
 pre,
 blockquote,
 ul,
 ol,
 li,
 figure,
 .wp-caption {
-  margin: 0 0 30px 0;
+  margin: -10px -10px 20px -10px;
+  padding: 10px;
+  border-radius: 5px;
 }
 
 p > img:only-child,
 p > a:only-child > img:only-child,
 .wp-caption img,
 figure img {
   display: block;
 }
--- a/toolkit/themes/shared/aboutReaderControls.css
+++ b/toolkit/themes/shared/aboutReaderControls.css
@@ -115,32 +115,36 @@
   padding: 0;
 }
 
 .dropdown li {
   margin: 0;
   padding: 0;
 }
 
-/*======= Font style popup =======*/
+/*======= Popup =======*/
 
 .dropdown-popup {
   min-width: 300px;
   text-align: start;
   position: absolute;
   left: 48px; /* offset to account for toolbar width */
   z-index: 1000;
   background-color: #fbfbfb;
   visibility: hidden;
   border-radius: 4px;
   border: 1px solid #b5b5b5;
   border-bottom-width: 0;
   box-shadow: 0 1px 12px #666;
 }
 
+.keep-open .dropdown-popup {
+  z-index: initial;
+}
+
 .dropdown-popup > hr {
   display: none;
 }
 
 .open > .dropdown-popup {
   visibility: visible;
 }
 
@@ -149,16 +153,18 @@
   top: 30px; /* offset arrow from top of popup */
   left: -16px;
   width: 24px;
   height: 24px;
   background-image: url("chrome://global/skin/reader/RM-Type-Controls-Arrow.svg");
   display: block;
 }
 
+/*======= Font style popup =======*/
+
 #font-type-buttons,
 #font-size-buttons,
 #color-scheme-buttons {
   display: flex;
   flex-direction: row;
 }
 
 #font-type-buttons > button:first-child {
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -21,16 +21,26 @@ toolkit.jar:
   skin/classic/global/aboutSupport.css                     (../../shared/aboutSupport.css)
   skin/classic/global/appPicker.css                        (../../shared/appPicker.css)
   skin/classic/global/config.css                           (../../shared/config.css)
   skin/classic/global/icons/info.svg                       (../../shared/incontent-icons/info.svg)
   skin/classic/global/icons/loading-inverted.png           (../../shared/icons/loading-inverted.png)
   skin/classic/global/icons/loading-inverted@2x.png        (../../shared/icons/loading-inverted@2x.png)
   skin/classic/global/icons/warning.svg                    (../../shared/incontent-icons/warning.svg)
   skin/classic/global/alerts/alert-common.css              (../../shared/alert-common.css)
+  skin/classic/global/narrate.css                          (../../shared/narrate.css)
+  skin/classic/global/narrateControls.css                  (../../shared/narrateControls.css)
+  skin/classic/global/narrate/arrow.svg                     (../../shared/narrate/arrow.svg)
+  skin/classic/global/narrate/back.svg                     (../../shared/narrate/back.svg)
+  skin/classic/global/narrate/fast.svg                     (../../shared/narrate/fast.svg)
+  skin/classic/global/narrate/forward.svg                  (../../shared/narrate/forward.svg)
+  skin/classic/global/narrate/narrate.svg                  (../../shared/narrate/narrate.svg)
+  skin/classic/global/narrate/slow.svg                     (../../shared/narrate/slow.svg)
+  skin/classic/global/narrate/start.svg                    (../../shared/narrate/start.svg)
+  skin/classic/global/narrate/stop.svg                     (../../shared/narrate/stop.svg)
   skin/classic/global/menu/shared-menu-check@2x.png        (../../shared/menu-check@2x.png)
   skin/classic/global/menu/shared-menu-check.png           (../../shared/menu-check.png)
   skin/classic/global/menu/shared-menu-check-active.svg    (../../shared/menu-check-active.svg)
   skin/classic/global/menu/shared-menu-check-black.svg     (../../shared/menu-check-black.svg)
   skin/classic/global/menu/shared-menu-check-hover.svg     (../../shared/menu-check-hover.svg)
   skin/classic/global/in-content/check.svg                 (../../shared/in-content/check.svg)
   skin/classic/global/in-content/check-partial.svg         (../../shared/in-content/check-partial.svg)
   skin/classic/global/in-content/dropdown.svg              (../../shared/in-content/dropdown.svg)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate.css
@@ -0,0 +1,11 @@
+body.light .narrating {
+  background-color: #ffc;
+}
+
+body.sepia .narrating {
+  background-color: #e0d7c5;
+}
+
+body.dark .narrating {
+  background-color: #242424;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/arrow.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12">
+  <path d="M6 9L1 4l1-1 4 4 4-4 1 1z" fill="#4C4C4C"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/back.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
+  <defs>
+    <style>
+      use:not(:target) {
+        display: none;
+      }
+      #disabled {
+        opacity: 0.5;
+      }
+    </style>
+    <path id="shape" d="M 5 0 C 4.446 0 4 0.446 4 1 L 4 23 C 4 23.554 4.446 24 5 24 L 7 24 C 7.554 24 8 23.554 8 23 L 8 12.404297 C 8.04108 12.509297 8.109944 12.610125 8.203125 12.703125 L 19.296875 23.775391 C 19.495259 23.972391 19.661613 24.039562 19.796875 23.976562 C 19.932137 23.915564 20 23.748516 20 23.478516 L 20 0.52148438 C 20 0.25248437 19.93214 0.084484365 19.796875 0.021484375 C 19.661613 -0.040515625 19.495259 0.02856248 19.296875 0.2265625 L 8.203125 11.298828 C 8.1099445 11.381828 8.04108 11.481703 8 11.595703 L 8 1 C 8 0.446 7.554 0 7 0 L 5 0 z " fill="gray"/>
+  </defs>
+  <use id="enabled" xlink:href="#shape"/>
+  <use id="disabled" xlink:href="#shape"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/fast.svg
@@ -0,0 +1,3 @@
+<svg id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20.4">
+    <path fill="gray" d="M14.42 16.68a.77.77 0 0 0 .54.7l2.51.68a1.58 1.58 0 0 1 1.06 1.22l.05.39-3.89-.53a4.34 4.34 0 0 1-1.74-.72L7.2 14.03a5.79 5.79 0 0 1-5.34-4.88h-.82a1 1 0 0 1-1-1l2.9-3.24a6.16 6.16 0 0 1 4.7-2.39 5.88 5.88 0 0 1 .77.05 5 5 0 0 1 .87.15c3.75 1 6.5 5.84 6.5 5.84a2.27 2.27 0 0 0 1.14.85h.17a1.27 1.27 0 0 0 1.22-.4l.78-1-2.47-1.2c-3.38-1.46-2.46-5.71-2.46-5.71 0-.26.23-.32.42-.14l5.32 5-4.31-4.81a1.39 1.39 0 0 1 .81-1.22l4.17 6.65.33.31 2.19 1.54a2.44 2.44 0 0 1 .92 1.75v2.77l-.16.13a1.66 1.66 0 0 1-1.63.19l-.75-.36a2.57 2.57 0 0 0-2.55.32l-2.18 1.82a4.28 4.28 0 0 1-.89.55 10.18 10.18 0 0 0-4.62-8.46c-.27-.16-.66.31-.47.48a10.52 10.52 0 0 1 3.68 8.5v.48zm8.38-5.42a.49.49 0 1 0-.49-.49.49.49 0 0 0 .49.49zm-18 9.14v-.52a1.39 1.39 0 0 1 .93-1.25s2.7-.66 3.43-1.84l2.06 1.63a25.62 25.62 0 0 1-6.43 2z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/forward.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
+  <defs>
+    <style>
+      use:not(:target) {
+        display: none;
+      }
+      #disabled {
+        opacity: 0.5;
+      }
+    </style>
+    <path id="shape" d="m 19,0 c 0.554,0 1,0.446 1,1 l 0,22 c 0,0.554 -0.446,1 -1,1 l -2,0 c -0.554,0 -1,-0.446 -1,-1 l 0,-10.595703 c -0.04108,0.105 -0.109944,0.205828 -0.203125,0.298828 L 4.703125,23.775391 c -0.198384,0.197 -0.364738,0.264171 -0.5,0.201171 C 4.067863,23.915564 4,23.748516 4,23.478516 L 4,0.52148438 c 0,-0.26900001 0.06786,-0.43700001 0.203125,-0.5 0.135262,-0.062 0.301616,0.0070781 0.5,0.20507812 l 11.09375,11.0722655 c 0.09318,0.083 0.162045,0.182875 0.203125,0.296875 L 16,1 c 0,-0.554 0.446,-1 1,-1 l 2,0 z" fill="gray"/>
+  </defs>
+  <use id="enabled" xlink:href="#shape"/>
+  <use id="disabled" xlink:href="#shape"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/narrate.svg
@@ -0,0 +1,3 @@
+<svg id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 18.77">
+    <path fill="gray" d="M3.13 13.72a1.57 1.57 0 0 1-3.13 0V5.41a1.57 1.57 0 0 1 3.13 0v8.31zm6.29 3.62a1.57 1.57 0 0 1-3.13 0V1.44a1.57 1.57 0 0 1 3.13 0v15.9zm6.29-2.9a1.57 1.57 0 0 1-3.13 0V4.83a1.57 1.57 0 0 1 3.13 0v9.61zM22 12.62a1.57 1.57 0 0 1-3.13 0V6.15a1.57 1.57 0 0 1 3.13 0v6.47z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/slow.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <g fill="gray">
+        <path d="M1.684,13.486c-0.209,0-0.404-0.132-0.474-0.341c-0.528-1.58-0.23-5.767,4.097-7.921 c1.315-0.656,2.589-0.988,3.787-0.988c3.237,0,5.096,2.341,5.99,3.465c0.158,0.199,0.181,0.533,0,0.713 c-0.793,0.794-1.852,1.542-3.231,2.286c-2.46,1.327-5.045,1.775-7.121,2.134c-1.123,0.194-2.093,0.361-2.89,0.627 C1.789,13.479,1.735,13.486,1.684,13.486L1.684,13.486z"/>
+        <path d="M23.185,5.465c-0.86-1.121-2.074-1.819-3.168-1.819c-0.641,0-1.556,0.23-2.273,1.328 c-0.374,0.571-0.577,1.161-0.773,1.73c-0.512,1.482-1.041,3.016-4.662,4.969c-2.316,1.249-4.707,1.664-6.815,2.03 c-2.524,0.438-4.704,0.814-5.455,2.622c-0.069,0.165-0.045,0.354,0.062,0.495c0.107,0.143,0.281,0.217,0.46,0.193 c0.667-0.081,1.533,0.041,2.434,0.217c-0.122,0.146-0.261,0.286-0.391,0.418c-0.38,0.385-0.774,0.783-0.657,1.292 c0.108,0.474,0.604,0.699,0.966,0.828c0.399,0.142,0.843,0.217,1.283,0.217c1.241,0,2.216-0.579,2.649-1.539 c1.704,0.287,3.487,0.313,5.043,0.313l1.639-0.006c0.066,0.056,0.178,0.166,0.264,0.25c0.504,0.506,1.348,1.351,2.721,1.351 c0.129,0,0.264-0.008,0.416-0.026c0.687-0.102,1.351-0.267,1.574-0.787c0.227-0.528-0.123-1.023-0.526-1.597 c-0.481-0.685-1.08-1.532-0.998-2.652c0.196-0.397,0.368-0.824,0.546-1.267c0.479-1.19,0.975-2.421,2.12-3.513 c0.431,0.343,1.022,0.549,1.63,0.549l0,0c0.439,0,0.876-0.102,1.295-0.3c0.624-0.293,1.104-0.967,1.316-1.847 C24.175,7.707,23.914,6.418,23.185,5.465L23.185,5.465z M20.397,7.757c-0.276,0-0.5-0.224-0.5-0.5s0.224-0.5,0.5-0.5 c0.275,0,0.5,0.224,0.5,0.5S20.674,7.757,20.397,7.757z"/>
+    </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/start.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <path d="M21.64 12.44L2.827 22.895c-.217.123-.403.137-.56.042-.155-.094-.233-.264-.233-.51V1.572c0-.244.08-.414.233-.51.157-.093.343-.08.56.044L21.642 11.56c.217.124.326.27.326.44 0 .17-.11.316-.327.44z" fill="gray"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrate/stop.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <rect ry="1" rx="1" y="2" x="2" height="20" width="20" fill="gray"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/narrateControls.css
@@ -0,0 +1,184 @@
+:scope {
+  --border-color: #e5e5e5;
+}
+
+#narrate-toggle {
+  background-image: url("chrome://global/skin/narrate/narrate.svg");
+}
+
+.dropdown-popup button {
+  background-color: transparent;
+}
+
+.dropdown-popup button:hover:not(:disabled) {
+  background-color: #eaeaea;
+}
+
+.narrate-row {
+  display: flex;
+  align-items: center;
+  min-height: 40px;
+  box-sizing: border-box;
+}
+
+.narrate-row:not(:first-child) {
+  border-top: 1px solid var(--border-color);
+}
+
+/* Control buttons */
+
+#narrate-control > button {
+  background-size: 24px 24px;
+  background-repeat: no-repeat;
+  background-position: center center;
+  height: 64px;
+  width: 100px;
+  border: none;
+  color: #666;
+  box-sizing: border-box;
+}
+
+#narrate-control > button:not(:first-child) {
+  border-left: 1px solid var(--border-color);
+}
+
+#narrate-skip-previous {
+  border-top-left-radius: 3px;
+  background-image: url("chrome://global/skin/narrate/back.svg#enabled");
+}
+
+#narrate-skip-next {
+  border-top-right-radius: 3px;
+  background-image: url("chrome://global/skin/narrate/forward.svg#enabled");
+}
+
+#narrate-skip-previous:disabled {
+  background-image: url("chrome://global/skin/narrate/back.svg#disabled");
+}
+
+#narrate-skip-next:disabled {
+  background-image: url("chrome://global/skin/narrate/forward.svg#disabled");
+}
+
+#narrate-start-stop {
+  background-image: url("chrome://global/skin/narrate/start.svg");
+}
+
+#narrate-start-stop.speaking {
+  background-image: url("chrome://global/skin/narrate/stop.svg");
+}
+
+/* Rate control */
+
+#narrate-rate::before, #narrate-rate::after {
+  content: '';
+  width: 48px;
+  height: 40px;
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 24px auto;
+}
+
+#narrate-rate::before {
+  background-image: url("chrome://global/skin/narrate/slow.svg");
+}
+
+#narrate-rate::after {
+  background-image: url("chrome://global/skin/narrate/fast.svg");
+}
+
+#narrate-rate-input {
+  margin: 0 1px;
+  flex-grow: 1;
+}
+
+#narrate-rate-input::-moz-range-track {
+  background-color: #979797;
+  height: 2px;
+}
+
+#narrate-rate-input::-moz-range-progress {
+  background-color: #2EA3FF;
+  height: 2px;
+}
+
+#narrate-rate-input::-moz-range-thumb {
+  background-color: #808080;
+  height: 16px;
+  width: 16px;
+  border-radius: 8px;
+  border-width: 0;
+}
+
+#narrate-rate-input:active::-moz-range-thumb {
+  background-color: #2EA3FF;
+}
+
+/* Voice selection */
+
+.voiceselect {
+  width: 100%;
+}
+
+.voiceselect > button.select-toggle,
+.voiceselect > .options > button.option {
+  -moz-appearance: none;
+  border: none;
+  width: 100%;
+  min-height: 40px;
+}
+
+.voiceselect.open > button.select-toggle {
+  border-bottom: 1px solid var(--border-color);
+}
+
+.voiceselect > button.select-toggle::after {
+  content: '';
+  background-image: url("chrome://global/skin/narrate/arrow.svg");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 12px 12px;
+  display: inline-block;
+  width: 1.5em;
+  height: 1em;
+  vertical-align: middle;
+}
+
+.voiceselect > .options > button.option:not(:first-child) {
+  border-top: 1px solid var(--border-color);
+}
+
+.voiceselect > .options > button.option  {
+  box-sizing: border-box;
+}
+
+.voiceselect > .options:not(.hovering) > button.option:focus {
+  background-color: #eaeaea;
+}
+
+.voiceselect > .options:not(.hovering) > button.option:hover:not(:focus) {
+  background-color: transparent;
+}
+
+.voiceselect > .options > button.option::-moz-focus-inner {
+  outline: none;
+  border: 0;
+}
+
+.voiceselect > .options {
+  display: none;
+  overflow-y: auto;
+}
+
+.voiceselect.open > .options {
+  display: block;
+}
+
+.current-voice {
+  color: #7f7f7f;
+}
+
+.voiceselect:not(.open) > button,
+.option:last-child {
+  border-radius: 0 0 3px 3px;
+}
--- a/xpcom/base/nsIMemory.idl
+++ b/xpcom/base/nsIMemory.idl
@@ -63,25 +63,16 @@ interface nsIMemory : nsISupports
      *   false, the flush will be scheduled to happen when the app is
      *   idle.
      * @throws NS_ERROR_FAILURE if 'immediate' is set an the call
      *   was not on the application's main thread.
      */
     void heapMinimize(in boolean immediate);
 
     /**
-     * This predicate can be used to determine if we're in a low-memory
-     * situation (what constitutes low-memory is platform dependent). This
-     * can be used to trigger the memory pressure observers.
-     *
-     * DEPRECATED - Always returns false.  See bug 592308.
-     */
-    boolean isLowMemory();
-
-    /**
      * This predicate can be used to determine if the platform is a "low-memory"
      * platform. Callers may use this to dynamically tune their behaviour
      * to favour reduced memory usage at the expense of performance. The value
      * returned by this function will not change over the lifetime of the process.
      */
     boolean isLowMemoryPlatform();
 };
 
--- a/xpcom/base/nsMemoryImpl.cpp
+++ b/xpcom/base/nsMemoryImpl.cpp
@@ -30,24 +30,16 @@ NS_IMPL_QUERY_INTERFACE(nsMemoryImpl, ns
 
 NS_IMETHODIMP
 nsMemoryImpl::HeapMinimize(bool aImmediate)
 {
   return FlushMemory(MOZ_UTF16("heap-minimize"), aImmediate);
 }
 
 NS_IMETHODIMP
-nsMemoryImpl::IsLowMemory(bool* aResult)
-{
-  NS_ERROR("IsLowMemory is deprecated.  See bug 592308.");
-  *aResult = false;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsMemoryImpl::IsLowMemoryPlatform(bool* aResult)
 {
 #ifdef ANDROID
   static int sLowMemory = -1; // initialize to unknown, lazily evaluate to 0 or 1
   if (sLowMemory == -1) {
     sLowMemory = 0; // assume "not low memory" in case file operations fail
     *aResult = false;