Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 09 Jan 2017 16:34:44 -0800
changeset 328613 845cc4dea57f6cc93f46810d24b1058b640c3b74
parent 328612 6bcc24319ba51ce95b411a54c829294d0c1d1cef (current diff)
parent 328535 dd6d4ca2066803fae7dc99fb872d7191a6421478 (diff)
child 328614 7822749b1b1427e3d357608653a9121b9a08b8b4
child 328619 8f3b24109e3412b36f97277e31ad66856dc609d6
child 328703 2281a0ea01449ff5d7bc9c73daf80aa27ba1c349
push id85493
push userkwierso@gmail.com
push dateTue, 10 Jan 2017 00:45:12 +0000
treeherdermozilla-inbound@7822749b1b14 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: 6jzg7wfFO2l
image/test/reftest/gif/webcam-simulacrum.mgif
image/test/reftest/gif/webcam-simulacrum.mgif^headers^
image/test/reftest/gif/webcam.html
--- a/accessible/html/HTMLListAccessible.h
+++ b/accessible/html/HTMLListAccessible.h
@@ -58,17 +58,17 @@ public:
   // HTMLLIAccessible
   HTMLListBulletAccessible* Bullet() const { return mBullet; }
   void UpdateBullet(bool aHasBullet);
 
 protected:
   virtual ~HTMLLIAccessible() { }
 
 private:
-  RefPtr<HTMLListBulletAccessible> mBullet;
+  HTMLListBulletAccessible* mBullet;
 };
 
 
 /**
  * Used for bullet of HTML list item element (for example, HTML li).
  */
 class HTMLListBulletAccessible : public LeafAccessible
 {
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -234,17 +234,17 @@ early_options = early_options()
 @imports('os')
 def mozconfig_options(mozconfig, automation, help):
     if mozconfig['path']:
         if 'MOZ_AUTOMATION_MOZCONFIG' in mozconfig['env']['added']:
             if not automation:
                 log.error('%s directly or indirectly includes an in-tree '
                           'mozconfig.', mozconfig['path'])
                 log.error('In-tree mozconfigs make strong assumptions about '
-                          'and are only meant to be used by Mozilla'
+                          'and are only meant to be used by Mozilla '
                           'automation.')
                 die("Please don't use them.")
         helper = __sandbox__._helper
         log.info('Adding configure options from %s' % mozconfig['path'])
         for arg in mozconfig['configure_args']:
             log.info('  %s' % arg)
             # We could be using imply_option() here, but it has other
             # contraints that don't really apply to the command-line
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1895,24 +1895,25 @@ nsFocusManager::Focus(nsPIDOMWindowOuter
       mFocusedWindow == aWindow && mFocusedContent == nullptr) {
     mFocusedContent = aContent;
 
     nsIContent* focusedNode = aWindow->GetFocusedNode();
     bool isRefocus = focusedNode && focusedNode->IsEqualNode(aContent);
 
     aWindow->SetFocusedNode(aContent, focusMethod);
 
+    // if the focused element changed, scroll it into view
+    if (aContent && aFocusChanged) {
+      ScrollIntoView(presShell, aContent, aFlags);
+    }
+
     bool sendFocusEvent =
       aContent && aContent->IsInComposedDoc() && !IsNonFocusableRoot(aContent);
     nsPresContext* presContext = presShell->GetPresContext();
     if (sendFocusEvent) {
-      // if the focused element changed, scroll it into view
-      if (aFocusChanged)
-        ScrollIntoView(presShell, aContent, aFlags);
-
       NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
 
       // if this is an object/plug-in/remote browser, focus its widget.  Note that we might
       // no longer be in the same document, due to the events we fired above when
       // aIsNewDocument.
       if (presShell->GetDocument() == aContent->GetComposedDoc()) {
         if (aAdjustWidgets && objectFrameWidget && !sTestMode)
           objectFrameWidget->SetFocus(false);
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -79,16 +79,17 @@ subsuite = clipboard
 subsuite = clipboard
 [test_consoleAPI.html]
 [test_contentViewer_overrideDPPX.html]
 [test_DOMMatrix.html]
 [test_domWindowUtils.html]
 [test_domWindowUtils_scrollbarSize.html]
 [test_domWindowUtils_scrollXY.html]
 [test_donottrack.html]
+[test_focus_scrollchildframe.html]
 [test_focus_legend_noparent.html]
 [test_focusrings.xul]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_for_of.html]
 [test_framedhistoryframes.html]
 [test_frameElementWrapping.html]
 [test_img_mutations.html]
 [test_interfaces.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_focus_scrollchildframe.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Tests for for-of loops</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body onload="doTest()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<script>
+function doTest() {
+  document.getElementById("button").focus();
+  is(window.scrollY, 0, "Scrolled position initially 0");
+  synthesizeKey("VK_TAB", { }),
+  ok(window.scrollY > 200, "Scrolled child frame into view");
+  SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+</script>
+<button id="button">B</button><br><iframe style="margin-top:10000px;height:400px"></iframe>
+</body>
+</html>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..339f3702fb6deada1306e6cee76e0f74c0eb0947
GIT binary patch
literal 45
sc${<hbhEHbWMp7u_`txxAn_kaDE?$&00A8k0g_>0Vsc?*u;%3g0IDDbnE(I)
--- a/image/test/mochitest/mochitest.ini
+++ b/image/test/mochitest/mochitest.ini
@@ -6,16 +6,17 @@ support-files =
   animated-gif.gif
   animated-gif2.gif
   animated-gif_trailing-garbage.gif
   animated-gif-finalframe.gif
   animation.svg
   animationPolling.js
   bad.jpg
   big.png
+  blue.gif
   blue.png
   bug399925.gif
   bug468160.sjs
   bug478398_ONLY.png
   bug490949-iframe.html
   bug490949.sjs
   bug496292-1.sjs
   bug496292-2.sjs
@@ -75,16 +76,17 @@ support-files =
   rillybad.jpg
   schrep.png
   shaver.png
   short_header.gif
   source.png
   transparent.gif
   transparent.png
   over.png
+  webcam-simulacrum.sjs
   6M-pixels.png
   12M-pixels-1.png
   12M-pixels-2.png
 
 [test_animation.html]
 skip-if = os == 'android'
 [test_animation_operators.html]
 [test_animation2.html]
@@ -146,10 +148,11 @@ skip-if = os == 'android'
 skip-if = os == 'android'
 [test_svg_filter_animation.html]
 skip-if = os == 'android'
 [test_synchronized_animation.html]
 #skip-if = os == 'android'
 disabled = bug 1295501
 [test_undisplayed_iframe.html]
 skip-if = os == 'android'
+[test_webcam.html]
 [test_xultree_animation.xhtml]
 skip-if = os == 'android'
new file mode 100644
--- /dev/null
+++ b/image/test/mochitest/test_webcam.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=641748
+
+webcam-simulacrum.mgif is a hand-edited file containing red.gif and blue.gif,
+concatenated together with the relevant headers for
+multipart/x-mixed-replace. Specifically, with the headers in
+webcam-simulacrum.mjpg^headers^, the web browser will get the following:
+
+HTTP 200 OK
+Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG
+
+\-\-BOUNDARYOMG\r\n
+Content-Type: image/gif\r\n
+\r\n
+<contents of red.gif> (no newline)
+\-\-BOUNDARYOMG\r\n
+Content-Type: image/gif\r\n
+\r\n
+<contents of blue.gif> (no newline)
+\-\-BOUNDARYOMG\-\-\r\n
+
+(The boundary is arbitrary, and just has to be defined as something that
+won't be in the text of the contents themselves. \-\-$(boundary)\r\n means
+"Here is the beginning of a boundary," and \-\-$(boundary)\-\- means "All done
+sending you parts.")
+-->
+<head>
+  <title>Test for Bug 641748 - WebCam Simulacrum</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641748">
+Mozilla Bug 641748: GIF decoder doesn't support multipart/x-mixed-replace
+</a>
+<p id="display"></p>
+
+<div id="content">
+  <div id="referenceDiv" style="height: 100px; width: 100px;
+                                display: none; background: #0018ff;"></div>
+  <div id="animatedImage">
+    <img id="animatedGif" src="webcam-simulacrum.sjs" style="display: none; height: 100px; width: 100px;">
+      <div id="text-descr"></div>
+  </div>
+  <div id="debug" style="display:none">
+  </div>
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+const FAILURE_TIMEOUT = 60000; // Fail early after 60 seconds
+
+function main()
+{
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+                                   'animatedGif', 'debug');
+  animTest.beginTest();
+}
+
+window.onload = main;
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/image/test/mochitest/webcam-simulacrum.sjs
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var counter = 2;
+var frames = ['red.gif', 'blue.gif'];
+var timer = Components.classes["@mozilla.org/timer;1"];
+var partTimer = timer.createInstance(Components.interfaces.nsITimer);
+
+function getFileAsInputStream(aFilename) {
+  var file = Components.classes["@mozilla.org/file/directory_service;1"]
+             .getService(Components.interfaces.nsIProperties)
+             .get("CurWorkD", Components.interfaces.nsIFile);
+
+  file.append("tests");
+  file.append("image");
+  file.append("test");
+  file.append("mochitest");
+  file.append(aFilename);
+
+  var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1']
+                   .createInstance(Components.interfaces.nsIFileInputStream);
+  fileStream.init(file, 1, 0, false);
+  return fileStream;
+}
+
+function handleRequest(request, response)
+{
+  response.setHeader("Content-Type",
+                     "multipart/x-mixed-replace;boundary=BOUNDARYOMG", false);
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.processAsync();
+  response.write("--BOUNDARYOMG\r\n");
+  while (frames.length > 0) {
+    sendNextPart(response);
+  }
+  response.write("--BOUNDARYOMG--\r\n");
+  response.finish();
+}
+
+function sendNextPart(response) {
+  var nextPartHead = "Content-Type: image/gif\r\n\r\n";
+  var inputStream = getFileAsInputStream(frames.shift());
+  response.bodyOutputStream.write(nextPartHead, nextPartHead.length);
+  response.bodyOutputStream.writeFrom(inputStream, inputStream.available());
+  inputStream.close();
+  // Toss in the boundary, so the browser can know this part is complete
+  response.write("--BOUNDARYOMG\r\n");
+}
+
--- a/image/test/reftest/gif/reftest.list
+++ b/image/test/reftest/gif/reftest.list
@@ -22,32 +22,8 @@ skip == test_bug641198.html animation2a-
 # Bug 1062886: a gif with a single color and an offset
 == one-color-offset.gif one-color-offset-ref.gif
 
 # Bug 1068230
 == tile-transform.html tile-transform-ref.html
 
 # Bug 1234077
 == truncated-framerect.html truncated-framerect-ref.html
-
-# webcam-simulacrum.mgif is a hand-edited file containing red.gif and blue.gif,
-# concatenated together with the relevant headers for
-# multipart/x-mixed-replace. Specifically, with the headers in
-# webcam-simulacrum.mjpg^headers^, the web browser will get the following:
-#
-# HTTP 200 OK
-# Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG
-#
-# --BOUNDARYOMG\r\n
-# Content-Type: image/gif\r\n
-# \r\n
-# <contents of red.gif> (no newline)
-# --BOUNDARYOMG\r\n
-# Content-Type: image/gif\r\n
-# \r\n
-# <contents of blue.gif> (no newline)
-# --BOUNDARYOMG--\r\n
-#
-# (The boundary is arbitrary, and just has to be defined as something that
-# won't be in the text of the contents themselves. --$(boundary)\r\n means
-# "Here is the beginning of a boundary," and --$(boundary)-- means "All done
-# sending you parts.")
-HTTP == webcam.html blue.gif
deleted file mode 100644
index 8302c3955cf66769a988a6b239e76e25cc515122..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/image/test/reftest/gif/webcam-simulacrum.mgif^headers^
+++ /dev/null
@@ -1,3 +0,0 @@
-HTTP 200 OK
-Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG
-Cache-Control: no-cache
deleted file mode 100644
--- a/image/test/reftest/gif/webcam.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<head><link rel="stylesheet" href="ImageDocument.css"></head>
-<body>
-<img src="webcam-simulacrum.mgif">
-</body></html>
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -639,22 +639,22 @@ ExposeGCThingToActiveJS(JS::GCCellPtr th
     if (IsInsideNursery(thing.asCell()))
         return;
 
     // There's nothing to do for permanent GC things that might be owned by
     // another runtime.
     if (thing.mayBeOwnedByOtherRuntime())
         return;
 
-    JS::shadow::Runtime* rt = detail::GetGCThingRuntime(thing.unsafeAsUIntPtr());
+    JS::shadow::Runtime* rt = detail::GetCellRuntime(thing.asCell());
     MOZ_DIAGNOSTIC_ASSERT(rt->allowGCBarriers());
 
     if (IsIncrementalBarrierNeededOnTenuredGCThing(rt, thing))
         JS::IncrementalReferenceBarrier(thing);
-    else if (JS::GCThingIsMarkedGray(thing))
+    else if (!thing.mayBeOwnedByOtherRuntime() && js::gc::detail::CellIsMarkedGray(thing.asCell()))
         JS::UnmarkGrayGCThingRecursively(thing);
 }
 
 static MOZ_ALWAYS_INLINE void
 MarkGCThingAsLive(JSRuntime* aRt, JS::GCCellPtr thing)
 {
     // Any object in the nursery will not be freed during any GC running at that
     // time.
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -275,24 +275,16 @@ namespace detail {
 static MOZ_ALWAYS_INLINE uintptr_t*
 GetGCThingMarkBitmap(const uintptr_t addr)
 {
     MOZ_ASSERT(addr);
     const uintptr_t bmap_addr = (addr & ~ChunkMask) | ChunkMarkBitmapOffset;
     return reinterpret_cast<uintptr_t*>(bmap_addr);
 }
 
-static MOZ_ALWAYS_INLINE JS::shadow::Runtime*
-GetGCThingRuntime(const uintptr_t addr)
-{
-    MOZ_ASSERT(addr);
-    const uintptr_t rt_addr = (addr & ~ChunkMask) | ChunkRuntimeOffset;
-    return *reinterpret_cast<JS::shadow::Runtime**>(rt_addr);
-}
-
 static MOZ_ALWAYS_INLINE void
 GetGCThingMarkWordAndMask(const uintptr_t addr, uint32_t color,
                           uintptr_t** wordp, uintptr_t* maskp)
 {
     MOZ_ASSERT(addr);
     const size_t bit = (addr & js::gc::ChunkMask) / js::gc::CellSize + color;
     MOZ_ASSERT(bit < js::gc::ChunkMarkBitmapBits);
     uintptr_t* bitmap = GetGCThingMarkBitmap(addr);
@@ -305,26 +297,45 @@ static MOZ_ALWAYS_INLINE JS::Zone*
 GetGCThingZone(const uintptr_t addr)
 {
     MOZ_ASSERT(addr);
     const uintptr_t zone_addr = (addr & ~ArenaMask) | ArenaZoneOffset;
     return *reinterpret_cast<JS::Zone**>(zone_addr);
 
 }
 
+static MOZ_ALWAYS_INLINE JS::shadow::Runtime*
+GetCellRuntime(const Cell* cell)
+{
+    MOZ_ASSERT(cell);
+    const uintptr_t addr = uintptr_t(cell);
+    const uintptr_t rt_addr = (addr & ~ChunkMask) | ChunkRuntimeOffset;
+    return *reinterpret_cast<JS::shadow::Runtime**>(rt_addr);
+}
+
 static MOZ_ALWAYS_INLINE bool
 CellIsMarkedGray(const Cell* cell)
 {
     MOZ_ASSERT(cell);
-    MOZ_ASSERT(!js::gc::IsInsideNursery(cell));
+    if (js::gc::IsInsideNursery(cell))
+        return false;
+
     uintptr_t* word, mask;
     js::gc::detail::GetGCThingMarkWordAndMask(uintptr_t(cell), js::gc::GRAY, &word, &mask);
     return *word & mask;
 }
 
+static MOZ_ALWAYS_INLINE bool
+CellIsMarkedGrayIfKnown(const Cell* cell)
+{
+    MOZ_ASSERT(cell);
+    auto rt = js::gc::detail::GetCellRuntime(cell);
+    return rt->areGCGrayBitsValid() && CellIsMarkedGray(cell);
+}
+
 } /* namespace detail */
 
 MOZ_ALWAYS_INLINE bool
 IsInsideNursery(const js::gc::Cell* cell)
 {
     if (!cell)
         return false;
     uintptr_t addr = uintptr_t(cell);
@@ -354,21 +365,19 @@ GetStringZone(JSString* str)
 }
 
 extern JS_PUBLIC_API(Zone*)
 GetObjectZone(JSObject* obj);
 
 static MOZ_ALWAYS_INLINE bool
 GCThingIsMarkedGray(GCCellPtr thing)
 {
-    if (js::gc::IsInsideNursery(thing.asCell()))
-        return false;
     if (thing.mayBeOwnedByOtherRuntime())
         return false;
-    return js::gc::detail::CellIsMarkedGray(thing.asCell());
+    return js::gc::detail::CellIsMarkedGrayIfKnown(thing.asCell());
 }
 
 extern JS_PUBLIC_API(JS::TraceKind)
 GCThingTraceKind(void* thing);
 
 } /* namespace JS */
 
 namespace js {
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -304,36 +304,31 @@ static MOZ_ALWAYS_INLINE bool
 ObjectIsTenured(const Heap<JSObject*>& obj)
 {
     return ObjectIsTenured(obj.unbarrieredGet());
 }
 
 static MOZ_ALWAYS_INLINE bool
 ObjectIsMarkedGray(JSObject* obj)
 {
-    /*
-     * GC things residing in the nursery cannot be gray: they have no mark bits.
-     * All live objects in the nursery are moved to tenured at the beginning of
-     * each GC slice, so the gray marker never sees nursery things.
-     */
-    if (js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(obj)))
-        return false;
-    return js::gc::detail::CellIsMarkedGray(reinterpret_cast<js::gc::Cell*>(obj));
+    auto cell = reinterpret_cast<js::gc::Cell*>(obj);
+    return js::gc::detail::CellIsMarkedGrayIfKnown(cell);
 }
 
 static MOZ_ALWAYS_INLINE bool
 ObjectIsMarkedGray(const JS::Heap<JSObject*>& obj)
 {
     return ObjectIsMarkedGray(obj.unbarrieredGet());
 }
 
 static MOZ_ALWAYS_INLINE bool
 ScriptIsMarkedGray(JSScript* script)
 {
-    return js::gc::detail::CellIsMarkedGray(reinterpret_cast<js::gc::Cell*>(script));
+    auto cell = reinterpret_cast<js::gc::Cell*>(script);
+    return js::gc::detail::CellIsMarkedGrayIfKnown(cell);
 }
 
 static MOZ_ALWAYS_INLINE bool
 ScriptIsMarkedGray(const Heap<JSScript*>& script)
 {
     return ScriptIsMarkedGray(script.unbarrieredGet());
 }
 
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -55,16 +55,30 @@ namespace JS {
     _(BinaryArith_Concat)                               \
     _(BinaryArith_SpecializedTypes)                     \
     _(BinaryArith_SpecializedOnBaselineTypes)           \
     _(BinaryArith_SharedCache)                          \
     _(BinaryArith_Call)                                 \
                                                         \
     _(InlineCache_OptimizedStub)                        \
                                                         \
+    _(NewArray_TemplateObject)                          \
+    _(NewArray_SharedCache)                             \
+    _(NewArray_Call)                                    \
+                                                        \
+    _(NewObject_TemplateObject)                         \
+    _(NewObject_SharedCache)                            \
+    _(NewObject_Call)                                   \
+                                                        \
+    _(Compare_SpecializedTypes)                         \
+    _(Compare_Bitwise)                                  \
+    _(Compare_SpecializedOnBaselineTypes)               \
+    _(Compare_SharedCache)                              \
+    _(Compare_Call)                                     \
+                                                        \
     _(Call_Inline)
 
 
 // Ordering is important below. All outcomes before GenericSuccess will be
 // considered failures, and all outcomes after GenericSuccess will be
 // considered successes.
 #define TRACKED_OUTCOME_LIST(_)                                         \
     _(GenericFailure)                                                   \
@@ -115,16 +129,28 @@ namespace JS {
     _(NonNativeReceiver)                                                \
     _(IndexType)                                                        \
     _(SetElemNonDenseNonTANotCached)                                    \
     _(NoSimdJitSupport)                                                 \
     _(SimdTypeNotOptimized)                                             \
     _(UnknownSimdProperty)                                              \
     _(NotModuleNamespace)                                               \
     _(UnknownProperty)                                                  \
+    _(NoTemplateObject)                                                 \
+    _(TemplateObjectIsUnboxedWithoutInlineElements)                     \
+    _(TemplateObjectIsPlainObjectWithDynamicSlots)                      \
+    _(LengthTooBig)                                                     \
+    _(SpeculationOnInputTypesFailed)                                    \
+    _(RelationalCompare)                                                \
+    _(OperandTypeNotBitwiseComparable)                                  \
+    _(OperandMaybeEmulatesUndefined)                                    \
+    _(LoosyUndefinedNullCompare)                                        \
+    _(LoosyInt32BooleanCompare)                                         \
+    _(CallsValueOf)                                                     \
+    _(StrictCompare)                                                    \
                                                                         \
     _(ICOptStub_GenericSuccess)                                         \
                                                                         \
     _(ICGetPropStub_ReadSlot)                                           \
     _(ICGetPropStub_CallGetter)                                         \
     _(ICGetPropStub_ArrayLength)                                        \
     _(ICGetPropStub_UnboxedRead)                                        \
     _(ICGetPropStub_UnboxedReadExpando)                                 \
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -811,19 +811,16 @@ class GCRuntime
     void incMajorGcNumber() { ++majorGCNumber; ++number; }
 
     int64_t defaultSliceBudget() const { return defaultTimeBudget_; }
 
     bool isIncrementalGc() const { return isIncremental; }
     bool isFullGc() const { return isFull; }
     bool isCompactingGc() const { return isCompacting; }
 
-    bool areGrayBitsValid() const { return grayBitsValid; }
-    void setGrayBitsInvalid() { grayBitsValid = false; }
-
     bool minorGCRequested() const { return minorGCTriggerReason != JS::gcreason::NO_REASON; }
     bool majorGCRequested() const { return majorGCTriggerReason != JS::gcreason::NO_REASON; }
     bool isGcNeeded() { return minorGCRequested() || majorGCRequested(); }
 
     bool fullGCForAtomsRequested() const { return fullGCForAtomsRequested_; }
 
     double computeHeapGrowthFactor(size_t lastBytes);
     size_t computeTriggerBytes(double growthFactor, size_t lastBytes);
@@ -1125,22 +1122,16 @@ class GCRuntime
     void resetBufferedGrayRoots() const;
 
     // Reset the gray buffering state to Unused.
     void clearBufferedGrayRoots() {
         grayBufferState = GrayBufferState::Unused;
         resetBufferedGrayRoots();
     }
 
-    /*
-     * The gray bits can become invalid if UnmarkGray overflows the stack. A
-     * full GC will reset this bit, since it fills in all the gray bits.
-     */
-    bool grayBitsValid;
-
     mozilla::Atomic<JS::gcreason::Reason, mozilla::Relaxed> majorGCTriggerReason;
 
     JS::gcreason::Reason minorGCTriggerReason;
 
     /* Perform full GC if rt->keepAtoms() becomes false. */
     bool fullGCForAtomsRequested_;
 
     /* Incremented at the start of every minor GC. */
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2912,17 +2912,17 @@ UnmarkGrayTracer::onChild(const JS::GCCe
 {
     int stackDummy;
     JSContext* cx = runtime()->contextFromMainThread();
     if (!JS_CHECK_STACK_SIZE(cx->nativeStackLimit[StackForSystemCode], &stackDummy)) {
         /*
          * If we run out of stack, we take a more drastic measure: require that
          * we GC again before the next CC.
          */
-        runtime()->gc.setGrayBitsInvalid();
+        runtime()->setGCGrayBitsValid(false);
         return;
     }
 
     Cell* cell = thing.asCell();
 
     // Cells in the nursery cannot be gray, and therefore must necessarily point
     // to only black edges.
     if (!cell->isTenured()) {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/baseline-abs-addr-opt.js
@@ -0,0 +1,96 @@
+// Test bounds checking at the end of memory with a constant base pointer.
+// This is intended to verify a bounds check elimination optimization in
+// the baseline compiler.
+
+// This can be only a functional test, we can't really see whether
+// the optimization is being applied.  However, manual inspection
+// of the generated code has verified that the optimization is
+// being applied.
+
+load(libdir + "wasm.js");
+
+// Memory is one page and minimum memory is one page, so accesses across
+// the end of the first page should fail.
+
+function gen(base, offset) {
+  return wasmEvalText(`(module
+			(memory 1)
+			(data (i32.const 65528) "aaaaaaaa")
+			(func (result i32)
+			 (i32.load offset=${offset} (i32.const ${base})))
+			(export "f" 0))`).exports["f"];
+}
+
+// Memory is two pages but minimum memory is one page, so accesses across
+// the end of the first page should succeed.
+
+function gen2(base, offset) {
+  return wasmEvalText(`(module
+			(memory 1)
+			(data (i32.const 65528) "aaaaaaaa")
+			(func (result i32)
+			 (drop (grow_memory (i32.const 1)))
+			 (i32.store (i32.const 65536) (i32.const 0x61616161))
+			 (i32.store (i32.const 65540) (i32.const 0x61616161))
+			 (i32.store (i32.const 80000) (i32.const 0x61616161))
+			 (i32.store (i32.const 120000) (i32.const 0x61616161))
+			 (i32.load offset=${offset} (i32.const ${base})))
+			(export "f" 0))`).exports["f"];
+}
+
+// Access the first page.
+// None of these should have an explicit bounds check generated for them as the
+// effective address is less than the sum of the minimum heap length and the
+// offset guard length.
+
+let onfirst = [[4,65528], [3,65528], [2,65528], [1,65528], [0,65528],
+	       [3,65529], [2,65529], [1,65529], [0,65529],
+	       [2,65530], [1,65530], [0,65530],
+	       [1,65531], [0,65531],
+	       [0,65532],
+	       [256, 65536-256-4],
+	       [2048, 65536-2048-4],
+	       [30000, 65536-30000-4]];
+
+for ( let [x,y] of onfirst ) {
+  assertEq(gen(x,y)(), 0x61616161|0);
+  assertEq(gen(y,x)(), 0x61616161|0);
+}
+
+// Access the second page.
+// Again, no explicit bounds checks should be generated here.
+
+let onsecond = [[65532,1], [65532,2], [65532,3], [65532,4], [65532,5], [65532,6], [65532,7], [65532,8],
+		[65531,2], [65531,3], [65531,4], [65531,5], [65531,6], [65531,7], [65531,8], [65531,9],
+		[65530,3], [65530,4], [65530,5], [65530,6], [65530,7], [65530,8], [65530,9], [65530,10],
+		[65529,4], [65529,5], [65529,6], [65529,7], [65529,8], [65529,9], [65529,10], [65529,11],
+		[65528,5], [65528,6], [65528,7], [65528,8], [65528,9], [65528,10], [65528,11], [65528,12],
+		[65527,6], [65527,7], [65527,8], [65527,9], [65527,10], [65527,11], [65527,12], [65527,13],
+		[65526,7], [65526,8], [65526,9], [65526,10], [65526,11], [65526,12], [65526,13], [65526,14],
+		[65525,8], [65525,9], [65525,10], [65525,11], [65525,12], [65525,13], [65525,14], [65525,15],
+		[256, 65536-256],
+		[2048, 65536-2048],
+		[30000, 50000],
+		[30000, 90000]];
+
+for ( let [x,y] of onsecond ) {
+  assertErrorMessage(() => { gen(x,y)() }, WebAssembly.RuntimeError, /index out of bounds/);
+  assertErrorMessage(() => { gen(y,x)() }, WebAssembly.RuntimeError, /index out of bounds/);
+
+  assertEq(gen2(x,y)(), 0x61616161|0);
+  assertEq(gen2(y,x)(), 0x61616161|0);
+}
+
+// Access the third page.
+// Here the explicit bounds check cannot be omitted, as the access is
+// beyond min + offset guard.
+
+let onthird = [[60000, 90000]];
+
+for ( let [x,y] of onthird ) {
+  assertErrorMessage(() => { gen(x,y)() }, WebAssembly.RuntimeError, /index out of bounds/);
+  assertErrorMessage(() => { gen(y,x)() }, WebAssembly.RuntimeError, /index out of bounds/);
+
+  assertErrorMessage(() => { gen2(x,y)() }, WebAssembly.RuntimeError, /index out of bounds/);
+  assertErrorMessage(() => { gen2(y,x)() }, WebAssembly.RuntimeError, /index out of bounds/);
+}
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -360,43 +360,56 @@ BaselineInspector::expectedCompareType(j
     return MCompare::Compare_Unknown;
 }
 
 static bool
 TryToSpecializeBinaryArithOp(ICStub** stubs,
                              uint32_t nstubs,
                              MIRType* result)
 {
-    DebugOnly<bool> sawInt32 = false;
+    bool sawInt32 = false;
     bool sawDouble = false;
     bool sawOther = false;
+    bool sawString = false;
 
     for (uint32_t i = 0; i < nstubs; i++) {
         switch (stubs[i]->kind()) {
           case ICStub::BinaryArith_Int32:
             sawInt32 = true;
             break;
           case ICStub::BinaryArith_BooleanWithInt32:
             sawInt32 = true;
             break;
           case ICStub::BinaryArith_Double:
             sawDouble = true;
             break;
           case ICStub::BinaryArith_DoubleWithInt32:
             sawDouble = true;
             break;
+          case ICStub::BinaryArith_StringConcat:
+            // Don't report true for BinaryArith_StringObjectConcat, since
+            // IonMonkey doesn't support MConcat with objects yet.
+            sawString = true;
+            break;
           default:
             sawOther = true;
             break;
         }
     }
 
     if (sawOther)
         return false;
 
+    if (sawString) {
+        if (sawDouble || sawInt32)
+            return false;
+        *result = MIRType::String;
+        return true;
+    }
+
     if (sawDouble) {
         *result = MIRType::Double;
         return true;
     }
 
     MOZ_ASSERT(sawInt32);
     *result = MIRType::Int32;
     return true;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -5180,16 +5180,42 @@ CodeGenerator::emitDebugForceBailing(LIn
         }
         masm.bind(&notBail);
         masm.pop(temp);
     }
     masm.bind(&done);
 }
 #endif
 
+static void
+DumpTrackedSite(const BytecodeSite* site)
+{
+    if (!JitSpewEnabled(JitSpew_OptimizationTracking))
+        return;
+
+#ifdef JS_JITSPEW
+    unsigned column = 0;
+    unsigned lineNumber = PCToLineNumber(site->script(), site->pc(), &column);
+    JitSpew(JitSpew_OptimizationTracking, "Types for %s at %s:%u:%u",
+            CodeName[JSOp(*site->pc())],
+            site->script()->filename(),
+            lineNumber,
+            column);
+#endif
+}
+
+static void
+DumpTrackedOptimizations(TrackedOptimizations* optimizations)
+{
+    if (!JitSpewEnabled(JitSpew_OptimizationTracking))
+        return;
+
+    optimizations->spew(JitSpew_OptimizationTracking);
+}
+
 bool
 CodeGenerator::generateBody()
 {
     IonScriptCounts* counts = maybeCreateScriptCounts();
 
 #if defined(JS_ION_PERF)
     PerfSpewer* perfSpewer = &perfSpewer_;
     if (gen->compilingWasm())
@@ -5228,16 +5254,17 @@ CodeGenerator::generateBody()
         masm.bind(current->label());
 
         mozilla::Maybe<ScriptCountBlockState> blockCounts;
         if (counts) {
             blockCounts.emplace(&counts->block(i), &masm);
             if (!blockCounts->init())
                 return false;
         }
+        TrackedOptimizations* last = nullptr;
 
 #if defined(JS_ION_PERF)
         perfSpewer->startBasicBlock(current->mir(), masm);
 #endif
 
         for (LInstructionIterator iter = current->begin(); iter != current->end(); iter++) {
             if (!alloc().ensureBallast())
                 return false;
@@ -5261,16 +5288,21 @@ CodeGenerator::generateBody()
                 // Only add instructions that have a tracked inline script tree.
                 if (iter->mirRaw()->trackedTree()) {
                     if (!addNativeToBytecodeEntry(iter->mirRaw()->trackedSite()))
                         return false;
                 }
 
                 // Track the start native offset of optimizations.
                 if (iter->mirRaw()->trackedOptimizations()) {
+                    if (last != iter->mirRaw()->trackedOptimizations()) {
+                        DumpTrackedSite(iter->mirRaw()->trackedSite());
+                        DumpTrackedOptimizations(iter->mirRaw()->trackedOptimizations());
+                        last = iter->mirRaw()->trackedOptimizations();
+                    }
                     if (!addTrackedOptimizationsEntry(iter->mirRaw()->trackedOptimizations()))
                         return false;
                 }
             }
 
 #ifdef DEBUG
             setElement(*iter); // needed to encode correct snapshot location.
             emitDebugForceBailing(*iter);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -3304,23 +3304,30 @@ IonBuilder::binaryArithTrySpecializedOnB
 
     // Try to emit a specialized binary instruction speculating the
     // type using the baseline caches.
 
     trackOptimizationAttempt(TrackedStrategy::BinaryArith_SpecializedOnBaselineTypes);
 
     MIRType specialization = inspector->expectedBinaryArithSpecialization(pc);
     if (specialization == MIRType::None) {
-        trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
-        return Ok();
-    }
-
-    MDefinition::Opcode def_op = JSOpToMDefinition(op);
-    MBinaryArithInstruction* ins = MBinaryArithInstruction::New(alloc(), def_op, left, right);
-    ins->setSpecialization(specialization);
+        trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
+        return Ok();
+    }
+
+    MInstruction* ins;
+    if (specialization == MIRType::String) {
+        MOZ_ASSERT(op == JSOP_ADD);
+        ins = MConcat::New(alloc(), left, right);
+    } else {
+        MDefinition::Opcode def_op = JSOpToMDefinition(op);
+        MBinaryArithInstruction* arith = MBinaryArithInstruction::New(alloc(), def_op, left, right);
+        arith->setSpecialization(specialization);
+        ins = arith;
+    }
 
     current->add(ins);
     current->push(ins);
 
     MOZ_ASSERT(!ins->isEffectful());
     MOZ_TRY(maybeInsertResume());
 
     trackOptimizationSuccess();
@@ -5460,16 +5467,17 @@ IonBuilder::jsop_compare(JSOp op)
 
     return jsop_compare(op, left, right);
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_compare(JSOp op, MDefinition* left, MDefinition* right)
 {
     bool emitted = false;
+    startTrackingOptimizations();
 
     if (!forceInlineCaches()) {
         MOZ_TRY(compareTrySpecialized(&emitted, op, left, right));
         if (emitted)
             return Ok();
         MOZ_TRY(compareTryBitwise(&emitted, op, left, right));
         if (emitted)
             return Ok();
@@ -5477,24 +5485,28 @@ IonBuilder::jsop_compare(JSOp op, MDefin
         if (emitted)
             return Ok();
     }
 
     MOZ_TRY(compareTrySharedStub(&emitted, op, left, right));
     if (emitted)
         return Ok();
 
+    trackOptimizationAttempt(TrackedStrategy::Compare_Call);
+
     // Not possible to optimize. Do a slow vm call.
     MCompare* ins = MCompare::New(alloc(), left, right, op);
     ins->cacheOperandMightEmulateUndefined(constraints());
 
     current->add(ins);
     current->push(ins);
     if (ins->isEffectful())
         MOZ_TRY(resumeAfter(ins));
+
+    trackOptimizationSuccess();
     return Ok();
 }
 
 static bool
 ObjectOrSimplePrimitive(MDefinition* op)
 {
     // Return true if op is either undefined/null/boolean/int32 or an object.
     return !op->mightBeType(MIRType::String)
@@ -5505,22 +5517,25 @@ ObjectOrSimplePrimitive(MDefinition* op)
         && !op->mightBeType(MIRType::MagicHole)
         && !op->mightBeType(MIRType::MagicIsConstructing);
 }
 
 AbortReasonOr<Ok>
 IonBuilder::compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
+    trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedTypes);
 
     // Try to emit an compare based on the input types.
 
     MCompare::CompareType type = MCompare::determineCompareType(op, left, right);
-    if (type == MCompare::Compare_Unknown)
-        return Ok();
+    if (type == MCompare::Compare_Unknown) {
+        trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
+        return Ok();
+    }
 
     MCompare* ins = MCompare::New(alloc(), left, right, op);
     ins->setCompareType(type);
     ins->cacheOperandMightEmulateUndefined(constraints());
 
     // Some compare types need to have the specific type in the rhs.
     // Swap operands if that is not the case.
     if (type == MCompare::Compare_StrictString && right->type() != MIRType::String)
@@ -5535,110 +5550,130 @@ IonBuilder::compareTrySpecialized(bool* 
     // Replace inputs with unsigned variants if needed.
     if (type == MCompare::Compare_UInt32)
         ins->replaceWithUnsignedOperands();
 
     current->add(ins);
     current->push(ins);
 
     MOZ_ASSERT(!ins->isEffectful());
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::compareTryBitwise(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
+    trackOptimizationAttempt(TrackedStrategy::Compare_Bitwise);
 
     // Try to emit a bitwise compare. Check if a bitwise compare equals the wanted
     // result for all observed operand types.
 
     // Onlye allow loose and strict equality.
-    if (op != JSOP_EQ && op != JSOP_NE && op != JSOP_STRICTEQ && op != JSOP_STRICTNE)
-        return Ok();
+    if (op != JSOP_EQ && op != JSOP_NE && op != JSOP_STRICTEQ && op != JSOP_STRICTNE) {
+        trackOptimizationOutcome(TrackedOutcome::RelationalCompare);
+        return Ok();
+    }
 
     // Only primitive (not double/string) or objects are supported.
     // I.e. Undefined/Null/Boolean/Int32 and Object
-    if (!ObjectOrSimplePrimitive(left) || !ObjectOrSimplePrimitive(right))
-        return Ok();
+    if (!ObjectOrSimplePrimitive(left) || !ObjectOrSimplePrimitive(right)) {
+        trackOptimizationOutcome(TrackedOutcome::OperandTypeNotBitwiseComparable);
+        return Ok();
+    }
 
     // Objects that emulate undefined are not supported.
-    if (left->maybeEmulatesUndefined(constraints()) || right->maybeEmulatesUndefined(constraints()))
-        return Ok();
+    if (left->maybeEmulatesUndefined(constraints()) ||
+        right->maybeEmulatesUndefined(constraints()))
+    {
+        trackOptimizationOutcome(TrackedOutcome::OperandMaybeEmulatesUndefined);
+        return Ok();
+    }
 
     // In the loose comparison more values could be the same,
     // but value comparison reporting otherwise.
     if (op == JSOP_EQ || op == JSOP_NE) {
 
         // Undefined compared loosy to Null is not supported,
         // because tag is different, but value can be the same (undefined == null).
         if ((left->mightBeType(MIRType::Undefined) && right->mightBeType(MIRType::Null)) ||
             (left->mightBeType(MIRType::Null) && right->mightBeType(MIRType::Undefined)))
         {
+            trackOptimizationOutcome(TrackedOutcome::LoosyUndefinedNullCompare);
             return Ok();
         }
 
         // Int32 compared loosy to Boolean is not supported,
         // because tag is different, but value can be the same (1 == true).
         if ((left->mightBeType(MIRType::Int32) && right->mightBeType(MIRType::Boolean)) ||
             (left->mightBeType(MIRType::Boolean) && right->mightBeType(MIRType::Int32)))
         {
+            trackOptimizationOutcome(TrackedOutcome::LoosyInt32BooleanCompare);
             return Ok();
         }
 
         // For loosy comparison of an object with a Boolean/Number/String
         // the valueOf the object is taken. Therefore not supported.
         bool simpleLHS = left->mightBeType(MIRType::Boolean) || left->mightBeType(MIRType::Int32);
         bool simpleRHS = right->mightBeType(MIRType::Boolean) || right->mightBeType(MIRType::Int32);
         if ((left->mightBeType(MIRType::Object) && simpleRHS) ||
             (right->mightBeType(MIRType::Object) && simpleLHS))
         {
+            trackOptimizationOutcome(TrackedOutcome::CallsValueOf);
             return Ok();
         }
     }
 
     MCompare* ins = MCompare::New(alloc(), left, right, op);
     ins->setCompareType(MCompare::Compare_Bitwise);
     ins->cacheOperandMightEmulateUndefined(constraints());
 
     current->add(ins);
     current->push(ins);
 
     MOZ_ASSERT(!ins->isEffectful());
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::compareTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, MDefinition* left,
                                                      MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
+    trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedOnBaselineTypes);
 
     // Try to specialize based on any baseline caches that have been generated
     // for the opcode. These will cause the instruction's type policy to insert
     // fallible unboxes to the appropriate input types.
 
     // Strict equality isn't supported.
-    if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE)
-        return Ok();
+    if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) {
+        trackOptimizationOutcome(TrackedOutcome::StrictCompare);
+        return Ok();
+    }
 
     MCompare::CompareType type = inspector->expectedCompareType(pc);
-    if (type == MCompare::Compare_Unknown)
-        return Ok();
+    if (type == MCompare::Compare_Unknown) {
+        trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
+        return Ok();
+    }
 
     MCompare* ins = MCompare::New(alloc(), left, right, op);
     ins->setCompareType(type);
     ins->cacheOperandMightEmulateUndefined(constraints());
 
     current->add(ins);
     current->push(ins);
 
     MOZ_ASSERT(!ins->isEffectful());
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::compareTrySharedStub(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
 {
     MOZ_ASSERT(*emitted == false);
@@ -5646,61 +5681,73 @@ IonBuilder::compareTrySharedStub(bool* e
     // Try to emit a shared stub cache.
 
     if (JitOptions.disableSharedStubs)
         return Ok();
 
     if (JSOp(*pc) == JSOP_CASE)
         return Ok();
 
+    trackOptimizationAttempt(TrackedStrategy::Compare_SharedCache);
+
     MBinarySharedStub* stub = MBinarySharedStub::New(alloc(), left, right);
     current->add(stub);
     current->push(stub);
     MOZ_TRY(resumeAfter(stub));
 
     MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Boolean, MUnbox::Infallible);
     current->add(unbox);
     current->push(unbox);
 
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, uint32_t length)
 {
     MOZ_ASSERT(*emitted == false);
 
-    if (!templateObject)
-        return Ok();
+    trackOptimizationAttempt(TrackedStrategy::NewArray_TemplateObject);
+
+    if (!templateObject) {
+        trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
+        return Ok();
+    }
 
     if (templateObject->is<UnboxedArrayObject>()) {
         MOZ_ASSERT(templateObject->as<UnboxedArrayObject>().capacity() >= length);
-        if (!templateObject->as<UnboxedArrayObject>().hasInlineElements())
-            return Ok();
+        if (!templateObject->as<UnboxedArrayObject>().hasInlineElements()) {
+            trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsUnboxedWithoutInlineElements);
+            return Ok();
+        }
     }
 
     MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
 
     size_t arraySlots =
         gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER;
 
-    if (length > arraySlots)
-        return Ok();
+    if (length > arraySlots) {
+        trackOptimizationOutcome(TrackedOutcome::LengthTooBig);
+        return Ok();
+    }
 
     // Emit fastpath.
 
     gc::InitialHeap heap = templateObject->group()->initialHeap(constraints());
     MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::New(alloc(), constraints(), length, templateConst, heap, pc);
     current->add(ins);
     current->push(ins);
 
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::newArrayTrySharedStub(bool* emitted)
 {
     MOZ_ASSERT(*emitted == false);
@@ -5708,51 +5755,57 @@ IonBuilder::newArrayTrySharedStub(bool* 
     // Try to emit a shared stub cache.
 
     if (JitOptions.disableSharedStubs)
         return Ok();
 
     if (*pc != JSOP_NEWINIT && *pc != JSOP_NEWARRAY)
         return Ok();
 
+    trackOptimizationAttempt(TrackedStrategy::NewArray_SharedCache);
+
     MInstruction* stub = MNullarySharedStub::New(alloc());
     current->add(stub);
     current->push(stub);
 
     MOZ_TRY(resumeAfter(stub));
 
     MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Object, MUnbox::Infallible);
     current->add(unbox);
     current->push(unbox);
 
+    trackOptimizationSuccess();
+
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t length)
 {
     MOZ_ASSERT(*emitted == false);
 
     // Emit a VM call.
+    trackOptimizationAttempt(TrackedStrategy::NewArray_Call);
 
     gc::InitialHeap heap = gc::DefaultHeap;
     MConstant* templateConst = MConstant::New(alloc(), NullValue());
 
     if (templateObject) {
         heap = templateObject->group()->initialHeap(constraints());
         templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     }
 
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::NewVM(alloc(), constraints(), length, templateConst, heap, pc);
     current->add(ins);
     current->push(ins);
 
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_newarray(uint32_t length)
 {
     JSObject* templateObject = inspector->getTemplateObject(pc);
@@ -5767,16 +5820,17 @@ IonBuilder::jsop_newarray(uint32_t lengt
 
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_newarray(JSObject* templateObject, uint32_t length)
 {
     bool emitted = false;
+    startTrackingOptimizations();
 
     if (!forceInlineCaches()) {
         MOZ_TRY(newArrayTryTemplateObject(&emitted, templateObject, length));
         if (emitted)
             return Ok();
     }
 
     MOZ_TRY(newArrayTrySharedStub(&emitted));
@@ -5812,21 +5866,26 @@ IonBuilder::jsop_newarray_copyonwrite()
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::newObjectTryTemplateObject(bool* emitted, JSObject* templateObject)
 {
     MOZ_ASSERT(*emitted == false);
 
-    if (!templateObject)
-        return Ok();
-
-    if (templateObject->is<PlainObject>() && templateObject->as<PlainObject>().hasDynamicSlots())
-        return Ok();
+    trackOptimizationAttempt(TrackedStrategy::NewObject_TemplateObject);
+    if (!templateObject) {
+        trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
+        return Ok();
+    }
+
+    if (templateObject->is<PlainObject>() && templateObject->as<PlainObject>().hasDynamicSlots()) {
+        trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsPlainObjectWithDynamicSlots);
+        return Ok();
+    }
 
     // Emit fastpath.
 
     MNewObject::Mode mode;
     if (JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT)
         mode = MNewObject::ObjectLiteral;
     else
         mode = MNewObject::ObjectCreate;
@@ -5836,50 +5895,56 @@ IonBuilder::newObjectTryTemplateObject(b
     current->add(templateConst);
 
     MNewObject* ins = MNewObject::New(alloc(), constraints(), templateConst, heap, mode);
     current->add(ins);
     current->push(ins);
 
     MOZ_TRY(resumeAfter(ins));
 
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::newObjectTrySharedStub(bool* emitted)
 {
     MOZ_ASSERT(*emitted == false);
 
     // Try to emit a shared stub cache.
 
     if (JitOptions.disableSharedStubs)
         return Ok();
 
+    trackOptimizationAttempt(TrackedStrategy::NewObject_SharedCache);
+
     MInstruction* stub = MNullarySharedStub::New(alloc());
     current->add(stub);
     current->push(stub);
 
     MOZ_TRY(resumeAfter(stub));
 
     MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Object, MUnbox::Infallible);
     current->add(unbox);
     current->push(unbox);
 
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::newObjectTryVM(bool* emitted, JSObject* templateObject)
 {
     // Emit a VM call.
     MOZ_ASSERT(JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT);
 
+    trackOptimizationAttempt(TrackedStrategy::NewObject_Call);
+
     gc::InitialHeap heap = gc::DefaultHeap;
     MConstant* templateConst = MConstant::New(alloc(), NullValue());
 
     if (templateObject) {
         heap = templateObject->group()->initialHeap(constraints());
         templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     }
 
@@ -5887,24 +5952,26 @@ IonBuilder::newObjectTryVM(bool* emitted
 
     MNewObject* ins = MNewObject::NewVM(alloc(), constraints(), templateConst, heap,
                                         MNewObject::ObjectLiteral);
     current->add(ins);
     current->push(ins);
 
     MOZ_TRY(resumeAfter(ins));
 
+    trackOptimizationSuccess();
     *emitted = true;
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_newobject()
 {
     bool emitted = false;
+    startTrackingOptimizations();
 
     JSObject* templateObject = inspector->getTemplateObject(pc);
 
     if (!forceInlineCaches()) {
         MOZ_TRY(newObjectTryTemplateObject(&emitted, templateObject));
         if (emitted)
             return Ok();
     }
--- a/js/src/jit/JitSpewer.cpp
+++ b/js/src/jit/JitSpewer.cpp
@@ -436,16 +436,17 @@ jit::CheckLogging()
             "  cacheflush    Instruction Cache flushes (ARM only for now)\n"
             "  range         Range Analysis\n"
             "  unroll        Loop unrolling\n"
             "  logs          C1 and JSON visualization logging\n"
             "  logs-sync     Same as logs, but flushes between each pass (sync. compiled functions only).\n"
             "  profiling     Profiling-related information\n"
             "  trackopts     Optimization tracking information gathered by SPS. "
                             "(Note: call enableSPSProfiling() in your script to enable it).\n"
+            "  trackopts-ext Encoding information about optimization tracking"
             "  dump-mir-expr Dump the MIR expressions\n"
             "  cfg           Control flow graph generation\n"
             "  all           Everything\n"
             "\n"
             "  bl-aborts     Baseline compiler abort messages\n"
             "  bl-scripts    Baseline script-compilation\n"
             "  bl-op         Baseline compiler detailed op-specific messages\n"
             "  bl-ic         Baseline inline-cache messages\n"
@@ -512,16 +513,18 @@ jit::CheckLogging()
     if (ContainsFlag(env, "logs"))
         EnableIonDebugAsyncLogging();
     if (ContainsFlag(env, "logs-sync"))
         EnableIonDebugSyncLogging();
     if (ContainsFlag(env, "profiling"))
         EnableChannel(JitSpew_Profiling);
     if (ContainsFlag(env, "trackopts"))
         EnableChannel(JitSpew_OptimizationTracking);
+    if (ContainsFlag(env, "trackopts-ext"))
+        EnableChannel(JitSpew_OptimizationTrackingExtended);
     if (ContainsFlag(env, "dump-mir-expr"))
         EnableChannel(JitSpew_MIRExpressions);
     if (ContainsFlag(env, "cfg"))
         EnableChannel(JitSpew_CFG);
     if (ContainsFlag(env, "all"))
         LoggingBits = uint64_t(-1);
 
     if (ContainsFlag(env, "bl-aborts"))
--- a/js/src/jit/JitSpewer.h
+++ b/js/src/jit/JitSpewer.h
@@ -57,16 +57,17 @@ namespace jit {
     /* Debug info about safepoints */       \
     _(Safepoints)                           \
     /* Debug info about Pools*/             \
     _(Pools)                                \
     /* Profiling-related information */     \
     _(Profiling)                            \
     /* Information of tracked opt strats */ \
     _(OptimizationTracking)                 \
+    _(OptimizationTrackingExtended)         \
     /* Debug info about the I$ */           \
     _(CacheFlush)                           \
     /* Output a list of MIR expressions */  \
     _(MIRExpressions)                       \
     /* Print control flow graph */          \
     _(CFG)                                  \
                                             \
     /* BASELINE COMPILER SPEW */            \
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -7207,19 +7207,16 @@ class MMod : public MBinaryArithInstruct
 
 class MConcat
   : public MBinaryInstruction,
     public MixPolicy<ConvertToStringPolicy<0>, ConvertToStringPolicy<1> >::Data
 {
     MConcat(MDefinition* left, MDefinition* right)
       : MBinaryInstruction(left, right)
     {
-        // At least one input should be definitely string
-        MOZ_ASSERT(left->type() == MIRType::String || right->type() == MIRType::String);
-
         setMovable();
         setResultType(MIRType::String);
     }
 
   public:
     INSTRUCTION_HEADER(Concat)
     TRIVIAL_NEW_WRAPPERS
 
--- a/js/src/jit/OptimizationTracking.cpp
+++ b/js/src/jit/OptimizationTracking.cpp
@@ -131,49 +131,51 @@ JS::TrackedTypeSiteString(TrackedTypeSit
 #undef TYPESITE_CASE
 
       default:
         MOZ_CRASH("bad type site");
     }
 }
 
 void
-SpewTempOptimizationTypeInfoVector(const TempOptimizationTypeInfoVector* types,
+SpewTempOptimizationTypeInfoVector(JitSpewChannel channel,
+                                   const TempOptimizationTypeInfoVector* types,
                                    const char* indent = nullptr)
 {
 #ifdef JS_JITSPEW
     for (const OptimizationTypeInfo* t = types->begin(); t != types->end(); t++) {
-        JitSpewStart(JitSpew_OptimizationTracking, "   %s%s of type %s, type set",
+        JitSpewStart(channel, "   %s%s of type %s, type set",
                      indent ? indent : "",
                      TrackedTypeSiteString(t->site()), StringFromMIRType(t->mirType()));
         for (uint32_t i = 0; i < t->types().length(); i++)
-            JitSpewCont(JitSpew_OptimizationTracking, " %s", TypeSet::TypeString(t->types()[i]));
-        JitSpewFin(JitSpew_OptimizationTracking);
+            JitSpewCont(channel, " %s", TypeSet::TypeString(t->types()[i]));
+        JitSpewFin(channel);
     }
 #endif
 }
 
 void
-SpewTempOptimizationAttemptsVector(const TempOptimizationAttemptsVector* attempts,
+SpewTempOptimizationAttemptsVector(JitSpewChannel channel,
+                                   const TempOptimizationAttemptsVector* attempts,
                                    const char* indent = nullptr)
 {
 #ifdef JS_JITSPEW
     for (const OptimizationAttempt* a = attempts->begin(); a != attempts->end(); a++) {
-        JitSpew(JitSpew_OptimizationTracking, "   %s%s: %s", indent ? indent : "",
+        JitSpew(channel, "   %s%s: %s", indent ? indent : "",
                 TrackedStrategyString(a->strategy()), TrackedOutcomeString(a->outcome()));
     }
 #endif
 }
 
 void
-TrackedOptimizations::spew() const
+TrackedOptimizations::spew(JitSpewChannel channel) const
 {
 #ifdef JS_JITSPEW
-    SpewTempOptimizationTypeInfoVector(&types_);
-    SpewTempOptimizationAttemptsVector(&attempts_);
+    SpewTempOptimizationTypeInfoVector(channel, &types_);
+    SpewTempOptimizationAttemptsVector(channel, &attempts_);
 #endif
 }
 
 bool
 OptimizationTypeInfo::trackTypeSet(TemporaryTypeSet* typeSet)
 {
     if (!typeSet)
         return true;
@@ -287,17 +289,17 @@ struct FrequencyComparator
     }
 };
 
 bool
 UniqueTrackedOptimizations::sortByFrequency(JSContext* cx)
 {
     MOZ_ASSERT(!sorted());
 
-    JitSpew(JitSpew_OptimizationTracking, "=> Sorting unique optimizations by frequency");
+    JitSpew(JitSpew_OptimizationTrackingExtended, "=> Sorting unique optimizations by frequency");
 
     // Sort by frequency.
     Vector<SortEntry> entries(cx);
     for (AttemptsMap::Range r = map_.all(); !r.empty(); r.popFront()) {
         SortEntry entry;
         entry.types = r.front().key().types;
         entry.attempts = r.front().key().attempts;
         entry.frequency = r.front().value().frequency;
@@ -321,17 +323,17 @@ UniqueTrackedOptimizations::sortByFreque
     for (size_t i = 0; i < entries.length(); i++) {
         Key key;
         key.types = entries[i].types;
         key.attempts = entries[i].attempts;
         AttemptsMap::Ptr p = map_.lookup(key);
         MOZ_ASSERT(p);
         p->value().index = sorted_.length();
 
-        JitSpew(JitSpew_OptimizationTracking, "   Entry %" PRIuSIZE " has frequency %" PRIu32,
+        JitSpew(JitSpew_OptimizationTrackingExtended, "   Entry %" PRIuSIZE " has frequency %" PRIu32,
                 sorted_.length(), p->value().frequency);
 
         if (!sorted_.append(entries[i]))
             return false;
     }
 
     return true;
 }
@@ -760,40 +762,40 @@ IonTrackedOptimizationsRegion::WriteDelt
 
 /* static */ bool
 IonTrackedOptimizationsRegion::WriteRun(CompactBufferWriter& writer,
                                         const NativeToTrackedOptimizations* start,
                                         const NativeToTrackedOptimizations* end,
                                         const UniqueTrackedOptimizations& unique)
 {
     // Write the header, which is the range that this whole run encompasses.
-    JitSpew(JitSpew_OptimizationTracking, "     Header: [%" PRIuSIZE ", %" PRIuSIZE "]",
+    JitSpew(JitSpew_OptimizationTrackingExtended, "     Header: [%" PRIuSIZE ", %" PRIuSIZE "]",
             start->startOffset.offset(), (end - 1)->endOffset.offset());
     writer.writeUnsigned(start->startOffset.offset());
     writer.writeUnsigned((end - 1)->endOffset.offset());
 
     // Write the first entry of the run, which is not delta-encoded.
-    JitSpew(JitSpew_OptimizationTracking,
+    JitSpew(JitSpew_OptimizationTrackingExtended,
             "     [%6" PRIuSIZE ", %6" PRIuSIZE "]                        vector %3u, offset %4" PRIuSIZE,
             start->startOffset.offset(), start->endOffset.offset(),
             unique.indexOf(start->optimizations), writer.length());
     uint32_t prevEndOffset = start->endOffset.offset();
     writer.writeUnsigned(prevEndOffset);
     writer.writeByte(unique.indexOf(start->optimizations));
 
     // Delta encode the run.
     for (const NativeToTrackedOptimizations* entry = start + 1; entry != end; entry++) {
         uint32_t startOffset = entry->startOffset.offset();
         uint32_t endOffset = entry->endOffset.offset();
 
         uint32_t startDelta = startOffset - prevEndOffset;
         uint32_t length = endOffset - startOffset;
         uint8_t index = unique.indexOf(entry->optimizations);
 
-        JitSpew(JitSpew_OptimizationTracking,
+        JitSpew(JitSpew_OptimizationTrackingExtended,
                 "     [%6u, %6u] delta [+%5u, +%5u] vector %3u, offset %4" PRIuSIZE,
                 startOffset, endOffset, startDelta, length, index, writer.length());
 
         WriteDelta(writer, startDelta, length, index);
 
         prevEndOffset = endOffset;
     }
 
@@ -806,31 +808,31 @@ IonTrackedOptimizationsRegion::WriteRun(
 static bool
 WriteOffsetsTable(CompactBufferWriter& writer, const Vector<uint32_t, 16>& offsets,
                   uint32_t* tableOffsetp)
 {
     // 4-byte align for the uint32s.
     uint32_t padding = sizeof(uint32_t) - (writer.length() % sizeof(uint32_t));
     if (padding == sizeof(uint32_t))
         padding = 0;
-    JitSpew(JitSpew_OptimizationTracking, "   Padding %u byte%s",
+    JitSpew(JitSpew_OptimizationTrackingExtended, "   Padding %u byte%s",
             padding, padding == 1 ? "" : "s");
     for (uint32_t i = 0; i < padding; i++)
         writer.writeByte(0);
 
     // Record the start of the table to compute reverse offsets for entries.
     uint32_t tableOffset = writer.length();
 
     // Write how many bytes were padded and numEntries.
     writer.writeNativeEndianUint32_t(padding);
     writer.writeNativeEndianUint32_t(offsets.length());
 
     // Write entry offset table.
     for (size_t i = 0; i < offsets.length(); i++) {
-        JitSpew(JitSpew_OptimizationTracking, "   Entry %" PRIuSIZE " reverse offset %u",
+        JitSpew(JitSpew_OptimizationTrackingExtended, "   Entry %" PRIuSIZE " reverse offset %u",
                 i, tableOffset - padding - offsets[i]);
         writer.writeNativeEndianUint32_t(tableOffset - padding - offsets[i]);
     }
 
     if (writer.oom())
         return false;
 
     *tableOffsetp = tableOffset;
@@ -865,41 +867,44 @@ InterpretedFunctionFilenameAndLineNumber
     }
 }
 
 static void
 SpewConstructor(TypeSet::Type ty, JSFunction* constructor)
 {
 #ifdef JS_JITSPEW
     if (!constructor->isInterpreted()) {
-        JitSpew(JitSpew_OptimizationTracking, "   Unique type %s has native constructor",
+        JitSpew(JitSpew_OptimizationTrackingExtended, "   Unique type %s has native constructor",
                 TypeSet::TypeString(ty));
         return;
     }
 
     char buf[512];
     if (constructor->displayAtom())
         PutEscapedString(buf, 512, constructor->displayAtom(), 0);
     else
         snprintf(buf, mozilla::ArrayLength(buf), "??");
 
     const char* filename;
     Maybe<unsigned> lineno;
     InterpretedFunctionFilenameAndLineNumber(constructor, &filename, &lineno);
 
-    JitSpew(JitSpew_OptimizationTracking, "   Unique type %s has constructor %s (%s:%u)",
+    JitSpew(JitSpew_OptimizationTrackingExtended, "   Unique type %s has constructor %s (%s:%u)",
             TypeSet::TypeString(ty), buf, filename, lineno.isSome() ? *lineno : 0);
 #endif
 }
 
 static void
 SpewAllocationSite(TypeSet::Type ty, JSScript* script, uint32_t offset)
 {
 #ifdef JS_JITSPEW
-    JitSpew(JitSpew_OptimizationTracking, "   Unique type %s has alloc site %s:%u",
+    if (!JitSpewEnabled(JitSpew_OptimizationTrackingExtended))
+        return;
+
+    JitSpew(JitSpew_OptimizationTrackingExtended, "   Unique type %s has alloc site %s:%u",
             TypeSet::TypeString(ty), script->filename(),
             PCToLineNumber(script, script->offsetToPC(offset)));
 #endif
 }
 
 bool
 jit::WriteIonTrackedOptimizationsTable(JSContext* cx, CompactBufferWriter& writer,
                                        const NativeToTrackedOptimizations* start,
@@ -911,35 +916,35 @@ jit::WriteIonTrackedOptimizationsTable(J
                                        uint32_t* optimizationTableOffsetp,
                                        IonTrackedTypeVector* allTypes)
 {
     MOZ_ASSERT(unique.sorted());
 
 #ifdef JS_JITSPEW
     // Spew training data, which may be fed into a script to determine a good
     // encoding strategy.
-    if (JitSpewEnabled(JitSpew_OptimizationTracking)) {
-        JitSpewStart(JitSpew_OptimizationTracking, "=> Training data: ");
+    if (JitSpewEnabled(JitSpew_OptimizationTrackingExtended)) {
+        JitSpewStart(JitSpew_OptimizationTrackingExtended, "=> Training data: ");
         for (const NativeToTrackedOptimizations* entry = start; entry != end; entry++) {
-            JitSpewCont(JitSpew_OptimizationTracking, "%" PRIuSIZE ",%" PRIuSIZE ",%u ",
+            JitSpewCont(JitSpew_OptimizationTrackingExtended, "%" PRIuSIZE ",%" PRIuSIZE ",%u ",
                         entry->startOffset.offset(), entry->endOffset.offset(),
                         unique.indexOf(entry->optimizations));
         }
-        JitSpewFin(JitSpew_OptimizationTracking);
+        JitSpewFin(JitSpew_OptimizationTrackingExtended);
     }
 #endif
 
     Vector<uint32_t, 16> offsets(cx);
     const NativeToTrackedOptimizations* entry = start;
 
     // Write out region offloads, partitioned into runs.
     JitSpew(JitSpew_Profiling, "=> Writing regions");
     while (entry != end) {
         uint32_t runLength = IonTrackedOptimizationsRegion::ExpectedRunLength(entry, end);
-        JitSpew(JitSpew_OptimizationTracking,
+        JitSpew(JitSpew_OptimizationTrackingExtended,
                 "   Run at entry %" PRIuSIZE ", length %" PRIu32 ", offset %" PRIuSIZE,
                 size_t(entry - start), runLength, writer.length());
 
         if (!offsets.append(writer.length()))
             return false;
 
         if (!IonTrackedOptimizationsRegion::WriteRun(writer, entry, entry + runLength, unique))
             return false;
@@ -953,30 +958,30 @@ jit::WriteIonTrackedOptimizationsTable(J
 
     *numRegions = offsets.length();
 
     // Clear offsets so that it may be reused below for the unique
     // optimizations table.
     offsets.clear();
 
     const UniqueTrackedOptimizations::SortedVector& vec = unique.sortedVector();
-    JitSpew(JitSpew_OptimizationTracking, "=> Writing unique optimizations table with %" PRIuSIZE " entr%s",
+    JitSpew(JitSpew_OptimizationTrackingExtended, "=> Writing unique optimizations table with %" PRIuSIZE " entr%s",
             vec.length(), vec.length() == 1 ? "y" : "ies");
 
     // Write out type info payloads.
     UniqueTrackedTypes uniqueTypes(cx);
     if (!uniqueTypes.init())
         return false;
 
     for (const UniqueTrackedOptimizations::SortEntry* p = vec.begin(); p != vec.end(); p++) {
         const TempOptimizationTypeInfoVector* v = p->types;
-        JitSpew(JitSpew_OptimizationTracking,
+        JitSpew(JitSpew_OptimizationTrackingExtended,
                 "   Type info entry %" PRIuSIZE " of length %" PRIuSIZE ", offset %" PRIuSIZE,
                 size_t(p - vec.begin()), v->length(), writer.length());
-        SpewTempOptimizationTypeInfoVector(v, "  ");
+        SpewTempOptimizationTypeInfoVector(JitSpew_OptimizationTrackingExtended, v, "  ");
 
         if (!offsets.append(writer.length()))
             return false;
 
         for (const OptimizationTypeInfo* t = v->begin(); t != v->end(); t++) {
             if (!t->writeCompact(cx, writer, uniqueTypes))
                 return false;
         }
@@ -1014,20 +1019,22 @@ jit::WriteIonTrackedOptimizationsTable(J
 
     if (!WriteOffsetsTable(writer, offsets, typesTableOffsetp))
         return false;
     offsets.clear();
 
     // Write out attempts payloads.
     for (const UniqueTrackedOptimizations::SortEntry* p = vec.begin(); p != vec.end(); p++) {
         const TempOptimizationAttemptsVector* v = p->attempts;
-        JitSpew(JitSpew_OptimizationTracking,
-                "   Attempts entry %" PRIuSIZE " of length %" PRIuSIZE ", offset %" PRIuSIZE,
-                size_t(p - vec.begin()), v->length(), writer.length());
-        SpewTempOptimizationAttemptsVector(v, "  ");
+        if (JitSpewEnabled(JitSpew_OptimizationTrackingExtended)) {
+            JitSpew(JitSpew_OptimizationTrackingExtended,
+                    "   Attempts entry %" PRIuSIZE " of length %" PRIuSIZE ", offset %" PRIuSIZE,
+                    size_t(p - vec.begin()), v->length(), writer.length());
+            SpewTempOptimizationAttemptsVector(JitSpew_OptimizationTrackingExtended, v, "  ");
+        }
 
         if (!offsets.append(writer.length()))
             return false;
 
         for (const OptimizationAttempt* a = v->begin(); a != v->end(); a++)
             a->writeCompact(writer);
     }
 
--- a/js/src/jit/OptimizationTracking.h
+++ b/js/src/jit/OptimizationTracking.h
@@ -7,16 +7,17 @@
 #ifndef jit_OptimizationTracking_h
 #define jit_OptimizationTracking_h
 
 #include "mozilla/Maybe.h"
 
 #include "jit/CompactBuffer.h"
 #include "jit/CompileInfo.h"
 #include "jit/JitAllocPolicy.h"
+#include "jit/JitSpewer.h"
 #include "js/TrackedOptimizationInfo.h"
 #include "vm/TypeInference.h"
 
 namespace js {
 
 namespace jit {
 
 struct NativeToTrackedOptimizations;
@@ -119,17 +120,17 @@ class TrackedOptimizations : public Temp
     MOZ_MUST_USE bool trackAttempt(JS::TrackedStrategy strategy);
     void amendAttempt(uint32_t index);
     void trackOutcome(JS::TrackedOutcome outcome);
     void trackSuccess();
 
     bool matchTypes(const TempOptimizationTypeInfoVector& other) const;
     bool matchAttempts(const TempOptimizationAttemptsVector& other) const;
 
-    void spew() const;
+    void spew(JitSpewChannel channel) const;
 };
 
 // Assigns each unique sequence of optimization attempts an index; outputs a
 // compact table.
 class UniqueTrackedOptimizations
 {
   public:
     struct SortEntry
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -919,20 +919,20 @@ CodeGeneratorShared::generateCompactTrac
     trackedOptimizationsMap_ = data;
     trackedOptimizationsMapSize_ = writer.length();
     trackedOptimizationsRegionTableOffset_ = regionTableOffset;
     trackedOptimizationsTypesTableOffset_ = typesTableOffset;
     trackedOptimizationsAttemptsTableOffset_ = attemptsTableOffset;
 
     verifyCompactTrackedOptimizationsMap(code, numRegions, unique, allTypes);
 
-    JitSpew(JitSpew_OptimizationTracking,
+    JitSpew(JitSpew_OptimizationTrackingExtended,
             "== Compact Native To Optimizations Map [%p-%p] size %u",
             data, data + trackedOptimizationsMapSize_, trackedOptimizationsMapSize_);
-    JitSpew(JitSpew_OptimizationTracking,
+    JitSpew(JitSpew_OptimizationTrackingExtended,
             "     with type list of length %" PRIuSIZE ", size %" PRIuSIZE,
             allTypes->length(), allTypes->length() * sizeof(IonTrackedTypeWithAddendum));
 
     return true;
 }
 
 #ifdef DEBUG
 class ReadTempAttemptsVectorOp : public JS::ForEachTrackedOptimizationAttemptOp
--- a/js/src/jsapi-tests/testGCCellPtr.cpp
+++ b/js/src/jsapi-tests/testGCCellPtr.cpp
@@ -49,13 +49,13 @@ BEGIN_TEST(testGCCellPtr)
     JS::GCCellPtr objcell(obj.get());
     JS::GCCellPtr scriptcell = JS::GCCellPtr(script.get());
     CHECK(GivesAndTakesCells(objcell));
     CHECK(GivesAndTakesCells(scriptcell));
 
     JS::GCCellPtr copy = objcell;
     CHECK(copy == objcell);
 
-    CHECK(js::gc::detail::GetGCThingRuntime(scriptcell.unsafeAsUIntPtr()) == cx->runtime());
+    CHECK(js::gc::detail::GetCellRuntime(scriptcell.asCell()) == cx->runtime());
 
     return true;
 }
 END_TEST(testGCCellPtr)
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -592,17 +592,17 @@ js::TraceWeakMaps(WeakMapTracer* trc)
 {
     WeakMapBase::traceAllMappings(trc);
     WatchpointMap::traceAll(trc);
 }
 
 extern JS_FRIEND_API(bool)
 js::AreGCGrayBitsValid(JSContext* cx)
 {
-    return cx->gc.areGrayBitsValid();
+    return cx->areGCGrayBitsValid();
 }
 
 JS_FRIEND_API(bool)
 js::ZoneGlobalsAreAllGray(JS::Zone* zone)
 {
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
         JSObject* obj = comp->unsafeUnbarrieredMaybeGlobal();
         if (!obj || !JS::ObjectIsMarkedGray(obj))
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -818,17 +818,16 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
     interruptCallbackRequested(false),
     currentBudget(nullptr),
     chunkAllocationSinceLastGC(false),
     lastGCTime(PRMJ_Now()),
     mode(JSGC_MODE_INCREMENTAL),
     numActiveZoneIters(0),
     cleanUpEverything(false),
     grayBufferState(GCRuntime::GrayBufferState::Unused),
-    grayBitsValid(false),
     majorGCTriggerReason(JS::gcreason::NO_REASON),
     minorGCTriggerReason(JS::gcreason::NO_REASON),
     fullGCForAtomsRequested_(false),
     minorGCNumber(0),
     majorGCNumber(0),
     jitReleaseNumber(0),
     number(0),
     startNumber(0),
@@ -5443,17 +5442,17 @@ GCRuntime::endSweepPhase(bool destroying
     }
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
         callFinalizeCallbacks(&fop, JSFINALIZE_COLLECTION_END);
 
         /* If we finished a full GC, then the gray bits are correct. */
         if (isFull)
-            grayBitsValid = true;
+            rt->setGCGrayBitsValid(true);
     }
 
     finishMarkingValidation();
 
 #ifdef DEBUG
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         for (auto i : AllAllocKinds()) {
             MOZ_ASSERT_IF(!IsBackgroundFinalized(i) ||
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -182,36 +182,44 @@ struct Runtime
 
     // In some cases, invoking GC barriers (incremental or otherwise) will break
     // things. These barriers assert if this flag is set.
     bool allowGCBarriers_;
     friend class JS::AutoAssertOnBarrier;
 
     js::gc::StoreBuffer* gcStoreBufferPtr_;
 
+    // The gray bits can become invalid if UnmarkGray overflows the stack. A
+    // full GC will reset this bit, since it fills in all the gray bits.
+    bool gcGrayBitsValid_;
+
   public:
     Runtime()
       : heapState_(JS::HeapState::Idle)
       , allowGCBarriers_(true)
       , gcStoreBufferPtr_(nullptr)
+      , gcGrayBitsValid_(false)
     {}
 
     bool isHeapBusy() const { return heapState() != JS::HeapState::Idle; }
     bool isHeapTracing() const { return heapState() == JS::HeapState::Tracing; }
     bool isHeapMajorCollecting() const { return heapState() == JS::HeapState::MajorCollecting; }
     bool isHeapMinorCollecting() const { return heapState() == JS::HeapState::MinorCollecting; }
     bool isHeapCollecting() const { return isHeapMinorCollecting() || isHeapMajorCollecting(); }
     bool isCycleCollecting() const {
         return heapState() == JS::HeapState::CycleCollecting;
     }
 
     bool allowGCBarriers() const { return allowGCBarriers_; }
 
     js::gc::StoreBuffer* gcStoreBufferPtr() { return gcStoreBufferPtr_; }
 
+    bool areGCGrayBitsValid() const { return gcGrayBitsValid_; }
+    void setGCGrayBitsValid(bool valid) { gcGrayBitsValid_ = valid; }
+
     const JSRuntime* asRuntime() const {
         return reinterpret_cast<const JSRuntime*>(this);
     }
 
     static JS::shadow::Runtime* asShadowRuntime(JSRuntime* rt) {
         return reinterpret_cast<JS::shadow::Runtime*>(rt);
     }
 
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -1085,16 +1085,20 @@ class BaseCompiler
     void setI64(int64_t v, RegI64 r) {
         masm.move64(Imm64(v), r);
     }
 
     void loadConstI32(Register r, Stk& src) {
         masm.mov(ImmWord((uint32_t)src.i32val() & 0xFFFFFFFFU), r);
     }
 
+    void loadConstI32(Register r, int32_t v) {
+        masm.mov(ImmWord(v), r);
+    }
+
     void loadMemI32(Register r, Stk& src) {
         loadFromFrameI32(r, src.offs());
     }
 
     void loadLocalI32(Register r, Stk& src) {
         loadFromFrameI32(r, frameOffsetFromSlot(src.slot(), MIRType::Int32));
     }
 
@@ -3353,31 +3357,33 @@ class BaseCompiler
         return 0;
 #else
         return 0;
 #endif
     }
 
     // ptr and dest may be the same iff dest is I32.
     // This may destroy ptr even if ptr and dest are not the same.
-    MOZ_MUST_USE bool load(MemoryAccessDesc& access, RegI32 ptr, AnyReg dest, RegI32 tmp1,
-                           RegI32 tmp2)
+    MOZ_MUST_USE bool load(MemoryAccessDesc& access, RegI32 ptr, bool omitBoundsCheck,
+                           AnyReg dest, RegI32 tmp1, RegI32 tmp2)
     {
         checkOffset(&access, ptr);
 
         OutOfLineCode* ool = nullptr;
 #ifndef WASM_HUGE_MEMORY
-        if (access.isPlainAsmJS()) {
-            ool = new (alloc_) AsmJSLoadOOB(access.type(), dest.any());
-            if (!addOutOfLineCode(ool))
-                return false;
-
-            masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, ool->entry());
-        } else {
-            masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, trap(Trap::OutOfBounds));
+        if (!omitBoundsCheck) {
+            if (access.isPlainAsmJS()) {
+                ool = new (alloc_) AsmJSLoadOOB(access.type(), dest.any());
+                if (!addOutOfLineCode(ool))
+                    return false;
+
+                masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, ool->entry());
+            } else {
+                masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, trap(Trap::OutOfBounds));
+            }
         }
 #endif
 
 #if defined(JS_CODEGEN_X64)
         Operand srcAddr(HeapReg, ptr, TimesOne, access.offset());
 
         if (dest.tag == AnyReg::I64)
             masm.wasmLoadI64(access, srcAddr, dest.i64());
@@ -3440,27 +3446,29 @@ class BaseCompiler
 
         if (ool)
             masm.bind(ool->rejoin());
         return true;
     }
 
     // ptr and src must not be the same register.
     // This may destroy ptr.
-    MOZ_MUST_USE bool store(MemoryAccessDesc access, RegI32 ptr, AnyReg src, RegI32 tmp1,
-                            RegI32 tmp2)
+    MOZ_MUST_USE bool store(MemoryAccessDesc access, RegI32 ptr, bool omitBoundsCheck,
+                            AnyReg src, RegI32 tmp1, RegI32 tmp2)
     {
         checkOffset(&access, ptr);
 
         Label rejoin;
 #ifndef WASM_HUGE_MEMORY
-        if (access.isPlainAsmJS())
-            masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, &rejoin);
-        else
-            masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, trap(Trap::OutOfBounds));
+        if (!omitBoundsCheck) {
+            if (access.isPlainAsmJS())
+                masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, &rejoin);
+            else
+                masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, trap(Trap::OutOfBounds));
+        }
 #endif
 
         // Emit the store
 #if defined(JS_CODEGEN_X64)
         Operand dstAddr(HeapReg, ptr, TimesOne, access.offset());
 
         masm.wasmStore(access, src.any(), dstAddr);
 #elif defined(JS_CODEGEN_X86)
@@ -3691,16 +3699,18 @@ class BaseCompiler
         *r0 = popF32();
     }
 
     void pop2xF64(RegF64* r0, RegF64* r1) {
         *r1 = popF64();
         *r0 = popF64();
     }
 
+    RegI32 popMemoryAccess(MemoryAccessDesc* access, bool* omitBoundsCheck);
+
     template<bool freeIt> void freeOrPushI32(RegI32 r) {
         if (freeIt)
             freeI32(r);
         else
             pushI32(r);
     }
 
     template<bool freeIt> void freeOrPushI64(RegI64 r) {
@@ -6521,80 +6531,121 @@ BaseCompiler::emitTeeGlobal()
     uint32_t id;
     Nothing unused_value;
     if (!iter_.readTeeGlobal(env_.globals, &id, &unused_value))
         return false;
 
     return emitSetOrTeeGlobal<false>(id);
 }
 
+// See EffectiveAddressAnalysis::analyzeAsmJSHeapAccess() for comparable Ion code.
+//
+// TODO / OPTIMIZE (bug 1329576): There are opportunities to generate better
+// code by not moving a constant address with a zero offset into a register.
+
+BaseCompiler::RegI32
+BaseCompiler::popMemoryAccess(MemoryAccessDesc* access, bool* omitBoundsCheck)
+{
+    // Caller must initialize.
+    MOZ_ASSERT(!*omitBoundsCheck);
+
+    if (isCompilingAsmJS())
+        return popI32();
+
+    int32_t addrTmp;
+    if (popConstI32(addrTmp)) {
+        uint32_t addr = addrTmp;
+
+        // We can eliminate the bounds check if the sum of the constant address
+        // and the known offset are below the sum of the minimum memory length
+        // and the offset guard length.
+
+        uint64_t ea = uint64_t(addr) + uint64_t(access->offset());
+        uint64_t limit = uint64_t(env_.minMemoryLength) + uint64_t(wasm::OffsetGuardLimit);
+
+        *omitBoundsCheck = ea < limit;
+
+        // Fold the offset into the pointer if we can, as this is always
+        // beneficial.
+
+        if (ea <= UINT32_MAX) {
+            addr = uint32_t(ea);
+            access->clearOffset();
+        }
+
+        RegI32 r = needI32();
+        loadConstI32(r, int32_t(addr));
+        return r;
+    }
+
+    return popI32();
+}
+
 bool
 BaseCompiler::emitLoad(ValType type, Scalar::Type viewType)
 {
     LinearMemoryAddress<Nothing> addr;
     if (!iter_.readLoad(type, Scalar::byteSize(viewType), &addr))
         return false;
 
     if (deadCode_)
         return true;
 
-    // TODO / OPTIMIZE (bug 1316831): Disable bounds checking on constant
-    // accesses below the minimum heap length.
-
+    bool omitBoundsCheck = false;
     MemoryAccessDesc access(viewType, addr.align, addr.offset, trapIfNotAsmJS());
 
     size_t temps = loadStoreTemps(access);
     RegI32 tmp1 = temps >= 1 ? needI32() : invalidI32();
     RegI32 tmp2 = temps >= 2 ? needI32() : invalidI32();
 
     switch (type) {
       case ValType::I32: {
-        RegI32 rp = popI32();
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
 #ifdef JS_CODEGEN_ARM
         RegI32 rv = access.isUnaligned() ? needI32() : rp;
 #else
         RegI32 rv = rp;
 #endif
-        if (!load(access, rp, AnyReg(rv), tmp1, tmp2))
+        if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         pushI32(rv);
         if (rp != rv)
             freeI32(rp);
         break;
       }
       case ValType::I64: {
         RegI64 rv;
         RegI32 rp;
 #ifdef JS_CODEGEN_X86
         rv = abiReturnRegI64;
         needI64(rv);
-        rp = popI32();
+        rp = popMemoryAccess(&access, &omitBoundsCheck);
 #else
-        rp = popI32();
+        rp = popMemoryAccess(&access, &omitBoundsCheck);
         rv = needI64();
 #endif
-        if (!load(access, rp, AnyReg(rv), tmp1, tmp2))
+        if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         pushI64(rv);
         freeI32(rp);
         break;
       }
       case ValType::F32: {
-        RegI32 rp = popI32();
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
         RegF32 rv = needF32();
-        if (!load(access, rp, AnyReg(rv), tmp1, tmp2))
+        if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         pushF32(rv);
         freeI32(rp);
         break;
       }
       case ValType::F64: {
-        RegI32 rp = popI32();
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
         RegF64 rv = needF64();
-        if (!load(access, rp, AnyReg(rv), tmp1, tmp2))
+        if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         pushF64(rv);
         freeI32(rp);
         break;
       }
       default:
         MOZ_CRASH("load type");
         break;
@@ -6611,57 +6662,55 @@ BaseCompiler::emitLoad(ValType type, Sca
 template<bool isStore>
 bool
 BaseCompiler::emitStoreOrTeeStore(ValType resultType, Scalar::Type viewType,
                                   LinearMemoryAddress<Nothing> addr)
 {
     if (deadCode_)
         return true;
 
-    // TODO / OPTIMIZE (bug 1316831): Disable bounds checking on constant
-    // accesses below the minimum heap length.
-
+    bool omitBoundsCheck = false;
     MemoryAccessDesc access(viewType, addr.align, addr.offset, trapIfNotAsmJS());
 
     size_t temps = loadStoreTemps(access);
     RegI32 tmp1 = temps >= 1 ? needI32() : invalidI32();
     RegI32 tmp2 = temps >= 2 ? needI32() : invalidI32();
 
     switch (resultType) {
       case ValType::I32: {
-        RegI32 rp, rv;
-        pop2xI32(&rp, &rv);
-        if (!store(access, rp, AnyReg(rv), tmp1, tmp2))
+        RegI32 rv = popI32();
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
+        if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         freeI32(rp);
         freeOrPushI32<isStore>(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = popI64();
-        RegI32 rp = popI32();
-        if (!store(access, rp, AnyReg(rv), tmp1, tmp2))
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
+        if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         freeI32(rp);
         freeOrPushI64<isStore>(rv);
         break;
       }
       case ValType::F32: {
         RegF32 rv = popF32();
-        RegI32 rp = popI32();
-        if (!store(access, rp, AnyReg(rv), tmp1, tmp2))
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
+        if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         freeI32(rp);
         freeOrPushF32<isStore>(rv);
         break;
       }
       case ValType::F64: {
         RegF64 rv = popF64();
-        RegI32 rp = popI32();
-        if (!store(access, rp, AnyReg(rv), tmp1, tmp2))
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
+        if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2))
             return false;
         freeI32(rp);
         freeOrPushF64<isStore>(rv);
         break;
       }
       default:
         MOZ_CRASH("store type");
         break;
@@ -6857,39 +6906,40 @@ BaseCompiler::emitTeeStoreWithCoercion(V
         return false;
 
     if (deadCode_)
         return true;
 
     // TODO / OPTIMIZE (bug 1316831): Disable bounds checking on constant
     // accesses below the minimum heap length.
 
+    bool omitBoundsCheck = false;
     MemoryAccessDesc access(viewType, addr.align, addr.offset, trapIfNotAsmJS());
 
     size_t temps = loadStoreTemps(access);
     RegI32 tmp1 = temps >= 1 ? needI32() : invalidI32();
     RegI32 tmp2 = temps >= 2 ? needI32() : invalidI32();
 
     if (resultType == ValType::F32 && viewType == Scalar::Float64) {
         RegF32 rv = popF32();
         RegF64 rw = needF64();
         masm.convertFloat32ToDouble(rv, rw);
-        RegI32 rp = popI32();
-        if (!store(access, rp, AnyReg(rw), tmp1, tmp2))
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
+        if (!store(access, rp, omitBoundsCheck, AnyReg(rw), tmp1, tmp2))
             return false;
         pushF32(rv);
         freeI32(rp);
         freeF64(rw);
     }
     else if (resultType == ValType::F64 && viewType == Scalar::Float32) {
         RegF64 rv = popF64();
         RegF32 rw = needF32();
         masm.convertDoubleToFloat32(rv, rw);
-        RegI32 rp = popI32();
-        if (!store(access, rp, AnyReg(rw), tmp1, tmp2))
+        RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck);
+        if (!store(access, rp, omitBoundsCheck, AnyReg(rw), tmp1, tmp2))
             return false;
         pushF64(rv);
         freeI32(rp);
         freeF32(rw);
     }
     else
         MOZ_CRASH("unexpected coerced store");
 
--- a/layout/printing/nsPrintEngine.cpp
+++ b/layout/printing/nsPrintEngine.cpp
@@ -10,17 +10,16 @@
 #include "nsCRT.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/CustomEvent.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
-#include "nsIFrame.h"
 #include "nsIURI.h"
 #include "nsITextToSubURI.h"
 #include "nsError.h"
 
 #include "nsView.h"
 #include <algorithm>
 
 // Print Options
@@ -213,17 +212,16 @@ NS_IMPL_ISUPPORTS(nsPrintEngine, nsIWebP
 //---------------------------------------------------
 nsPrintEngine::nsPrintEngine() :
   mIsCreatingPrintPreview(false),
   mIsDoingPrinting(false),
   mIsDoingPrintPreview(false),
   mProgressDialogIsShown(false),
   mScreenDPI(115.0f),
   mPagePrintTimer(nullptr),
-  mPageSeqFrame(nullptr),
   mDebugFile(nullptr),
   mLoadCounter(0),
   mDidLoadDataForPrinting(false),
   mIsDestroying(false),
   mDisallowSelectionPrint(false)
 {
 }
 
@@ -2507,18 +2505,18 @@ nsPrintEngine::DoPrint(nsPrintObject * a
       }
 
       nsIFrame * seqFrame = do_QueryFrame(pageSequence);
       if (!seqFrame) {
         SetIsPrinting(false);
         return NS_ERROR_FAILURE;
       }
 
-      mPageSeqFrame = pageSequence;
-      mPageSeqFrame->StartPrint(poPresContext, mPrt->mPrintSettings, docTitleStr, docURLStr);
+      mPageSeqFrame = seqFrame;
+      pageSequence->StartPrint(poPresContext, mPrt->mPrintSettings, docTitleStr, docURLStr);
 
       // Schedule Page to Print
       PR_PL(("Scheduling Print of PO: %p (%s) \n", aPO, gFrameTypesStr[aPO->mFrameType]));
       StartPagePrintTimer(aPO);
     }
   }
 
   return NS_OK;
@@ -2618,35 +2616,36 @@ nsPrintEngine::HasPrintCallbackCanvas()
   // Also check the sub documents.
   return result || DocHasPrintCallbackCanvas(mDocument);
 }
 
 //-------------------------------------------------------
 bool
 nsPrintEngine::PrePrintPage()
 {
-  NS_ASSERTION(mPageSeqFrame,  "mPageSeqFrame is null!");
+  NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
   NS_ASSERTION(mPrt,           "mPrt is null!");
 
   // Although these should NEVER be nullptr
   // This is added insurance, to make sure we don't crash in optimized builds
-  if (!mPrt || !mPageSeqFrame) {
+  if (!mPrt || !mPageSeqFrame.IsAlive()) {
     return true; // means we are done preparing the page.
   }
 
   // Check setting to see if someone request it be cancelled
   bool isCancelled = false;
   mPrt->mPrintSettings->GetIsCancelled(&isCancelled);
   if (isCancelled)
     return true;
 
   // Ask mPageSeqFrame if the page is ready to be printed.
   // If the page doesn't get printed at all, the |done| will be |true|.
   bool done = false;
-  nsresult rv = mPageSeqFrame->PrePrintNextPage(mPagePrintTimer, &done);
+  nsIPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+  nsresult rv = pageSeqFrame->PrePrintNextPage(mPagePrintTimer, &done);
   if (NS_FAILED(rv)) {
     // ??? ::PrintPage doesn't set |mPrt->mIsAborted = true| if rv != NS_ERROR_ABORT,
     // but I don't really understand why this should be the right thing to do?
     // Shouldn't |mPrt->mIsAborted| set to true all the time if something
     // wents wrong?
     if (rv != NS_ERROR_ABORT) {
       FirePrintingErrorEvent(rv);
       mPrt->mIsAborted = true;
@@ -2656,46 +2655,47 @@ nsPrintEngine::PrePrintPage()
   return done;
 }
 
 bool
 nsPrintEngine::PrintPage(nsPrintObject*    aPO,
                          bool&           aInRange)
 {
   NS_ASSERTION(aPO,            "aPO is null!");
-  NS_ASSERTION(mPageSeqFrame,  "mPageSeqFrame is null!");
+  NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
   NS_ASSERTION(mPrt,           "mPrt is null!");
 
   // Although these should NEVER be nullptr
   // This is added insurance, to make sure we don't crash in optimized builds
-  if (!mPrt || !aPO || !mPageSeqFrame) {
+  if (!mPrt || !aPO || !mPageSeqFrame.IsAlive()) {
     FirePrintingErrorEvent(NS_ERROR_FAILURE);
     return true; // means we are done printing
   }
 
   PR_PL(("-----------------------------------\n"));
   PR_PL(("------ In DV::PrintPage PO: %p (%s)\n", aPO, gFrameTypesStr[aPO->mFrameType]));
 
   // Check setting to see if someone request it be cancelled
   bool isCancelled = false;
   mPrt->mPrintSettings->GetIsCancelled(&isCancelled);
   if (isCancelled || mPrt->mIsAborted)
     return true;
 
   int32_t pageNum, numPages, endPage;
-  mPageSeqFrame->GetCurrentPageNum(&pageNum);
-  mPageSeqFrame->GetNumPages(&numPages);
+  nsIPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+  pageSeqFrame->GetCurrentPageNum(&pageNum);
+  pageSeqFrame->GetNumPages(&numPages);
 
   bool donePrinting;
   bool isDoingPrintRange;
-  mPageSeqFrame->IsDoingPrintRange(&isDoingPrintRange);
+  pageSeqFrame->IsDoingPrintRange(&isDoingPrintRange);
   if (isDoingPrintRange) {
     int32_t fromPage;
     int32_t toPage;
-    mPageSeqFrame->GetPrintRange(&fromPage, &toPage);
+    pageSeqFrame->GetPrintRange(&fromPage, &toPage);
 
     if (fromPage > numPages) {
       return true;
     }
     if (toPage > numPages) {
       toPage = numPages;
     }
 
@@ -2721,26 +2721,26 @@ nsPrintEngine::PrintPage(nsPrintObject* 
 
   // Print the Page
   // if a print job was cancelled externally, an EndPage or BeginPage may
   // fail and the failure is passed back here.
   // Returning true means we are done printing.
   //
   // When rv == NS_ERROR_ABORT, it means we want out of the
   // print job without displaying any error messages
-  nsresult rv = mPageSeqFrame->PrintNextPage();
+  nsresult rv = pageSeqFrame->PrintNextPage();
   if (NS_FAILED(rv)) {
     if (rv != NS_ERROR_ABORT) {
       FirePrintingErrorEvent(rv);
       mPrt->mIsAborted = true;
     }
     return true;
   }
 
-  mPageSeqFrame->DoPageEnd();
+  pageSeqFrame->DoPageEnd();
 
   return donePrinting;
 }
 
 /** ---------------------------------------------------
  *  Find by checking frames type
  */
 nsresult
@@ -3037,18 +3037,19 @@ bool
 nsPrintEngine::DonePrintingPages(nsPrintObject* aPO, nsresult aResult)
 {
   //NS_ASSERTION(aPO, "Pointer is null!");
   PR_PL(("****** In DV::DonePrintingPages PO: %p (%s)\n", aPO, aPO?gFrameTypesStr[aPO->mFrameType]:""));
 
   // If there is a pageSeqFrame, make sure there are no more printCanvas active
   // that might call |Notify| on the pagePrintTimer after things are cleaned up
   // and printing was marked as being done.
-  if (mPageSeqFrame) {
-    mPageSeqFrame->ResetPrintCanvasList();
+  if (mPageSeqFrame.IsAlive()) {
+    nsIPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+    pageSeqFrame->ResetPrintCanvasList();
   }
 
   if (aPO && !mPrt->mIsAborted) {
     aPO->mHasBeenPrinted = true;
     nsresult rv;
     bool didPrint = PrintDocContent(mPrt->mPrintObject, rv);
     if (NS_SUCCEEDED(rv) && didPrint) {
       PR_PL(("****** In DV::DonePrintingPages PO: %p (%s) didPrint:%s (Not Done Printing)\n", aPO, gFrameTypesStr[aPO->mFrameType], PRT_YESNO(didPrint)));
--- a/layout/printing/nsPrintEngine.h
+++ b/layout/printing/nsPrintEngine.h
@@ -8,16 +8,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsCOMPtr.h"
 
 #include "nsPrintObject.h"
 #include "nsPrintData.h"
 #include "nsFrameList.h"
+#include "nsIFrame.h"
 #include "nsIWebProgress.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsIWebProgressListener.h"
 #include "nsWeakReference.h"
 
 // Interfaces
 #include "nsIObserver.h"
 
@@ -257,17 +258,17 @@ protected:
   bool mProgressDialogIsShown;
 
   nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
   nsWeakPtr               mContainer;
   float                   mScreenDPI;
   
   mozilla::UniquePtr<nsPrintData> mPrt;
   nsPagePrintTimer*       mPagePrintTimer;
-  nsIPageSequenceFrame*   mPageSeqFrame;
+  nsWeakFrame             mPageSeqFrame;
 
   // Print Preview
   mozilla::UniquePtr<nsPrintData> mPrtPreview;
   mozilla::UniquePtr<nsPrintData> mOldPrtPreview;
 
   nsCOMPtr<nsIDocument>   mDocument;
 
   FILE* mDebugFile;
--- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -819,36 +819,31 @@ protected:
         }
       }
 
       void CheckDefaultRtpCandidate(bool expectDefault,
                                     const SdpMediaSection& msection,
                                     size_t transportLevel,
                                     const std::string& context) const
       {
-        Address expectedAddress = "0.0.0.0";
-        Port expectedPort = 9U;
-
         if (expectDefault) {
           // Copy so we can be terse and use []
           auto defaultCandidates = mDefaultCandidates;
-          expectedAddress = defaultCandidates[transportLevel][RTP].first;
-          expectedPort = defaultCandidates[transportLevel][RTP].second;
+          ASSERT_EQ(defaultCandidates[transportLevel][RTP].first,
+                    msection.GetConnection().GetAddress())
+            << context << " (level " << msection.GetLevel() << ")";
+          ASSERT_EQ(defaultCandidates[transportLevel][RTP].second,
+                    msection.GetPort())
+            << context << " (level " << msection.GetLevel() << ")";
+        } else {
+          ASSERT_EQ("0.0.0.0", msection.GetConnection().GetAddress())
+            << context << " (level " << msection.GetLevel() << ")";
+          ASSERT_EQ(9U, msection.GetPort())
+            << context << " (level " << msection.GetLevel() << ")";
         }
-
-        // if bundle-only attribute is present, expect port 0
-        const SdpAttributeList& attrs = msection.GetAttributeList();
-        if (attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute)) {
-          expectedPort = 0U;
-        }
-
-        ASSERT_EQ(expectedAddress, msection.GetConnection().GetAddress())
-          << context << " (level " << msection.GetLevel() << ")";
-        ASSERT_EQ(expectedPort, msection.GetPort())
-          << context << " (level " << msection.GetLevel() << ")";
       }
 
       void CheckDefaultRtcpCandidate(bool expectDefault,
                                      const SdpMediaSection& msection,
                                      size_t transportLevel,
                                      const std::string& context) const
       {
         if (expectDefault) {
@@ -1074,25 +1069,21 @@ private:
       auto& msection = sdp->GetMediaSection(i);
 
       if (msection.GetMediaType() == SdpMediaSection::kApplication) {
         ASSERT_EQ(SdpMediaSection::kDtlsSctp, msection.GetProtocol());
       } else {
         ASSERT_EQ(SdpMediaSection::kUdpTlsRtpSavpf, msection.GetProtocol());
       }
 
-      const SdpAttributeList& attrs = msection.GetAttributeList();
-      bool bundle_only = attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute);
-
-      // port 0 only means disabled when the bundle-only attribute is missing
-      if (!bundle_only && msection.GetPort() == 0) {
+      if (msection.GetPort() == 0) {
         ValidateDisabledMSection(&msection);
         continue;
       }
-
+      const SdpAttributeList& attrs = msection.GetAttributeList();
       ASSERT_EQ(source.mIceUfrag, attrs.GetIceUfrag());
       ASSERT_EQ(source.mIcePwd, attrs.GetIcePwd());
       const SdpFingerprintAttributeList& fps = attrs.GetFingerprint();
       for (auto fp = fps.mFingerprints.begin(); fp != fps.mFingerprints.end();
            ++fp) {
         std::string alg_str = "None";
 
         if (fp->hashFunc == SdpFingerprintAttributeList::kSha1) {
@@ -4257,22 +4248,20 @@ TEST_P(JsepSessionTest, TestMaxBundle)
   std::string offer = mSessionOff.GetLocalDescription();
   SipccSdpParser parser;
   UniquePtr<Sdp> parsedOffer = parser.Parse(offer);
   ASSERT_TRUE(parsedOffer.get());
 
   ASSERT_FALSE(
       parsedOffer->GetMediaSection(0).GetAttributeList().HasAttribute(
         SdpAttribute::kBundleOnlyAttribute));
-  ASSERT_NE(0U, parsedOffer->GetMediaSection(0).GetPort());
   for (size_t i = 1; i < parsedOffer->GetMediaSectionCount(); ++i) {
     ASSERT_TRUE(
         parsedOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
           SdpAttribute::kBundleOnlyAttribute));
-    ASSERT_EQ(0U, parsedOffer->GetMediaSection(i).GetPort());
   }
 
 
   CheckPairs(mSessionOff, "Offerer pairs");
   CheckPairs(mSessionAns, "Answerer pairs");
   EXPECT_EQ(1U, GetActiveTransportCount(mSessionOff));
   EXPECT_EQ(1U, GetActiveTransportCount(mSessionAns));
 }
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -661,18 +661,16 @@ JsepSessionImpl::SetupBundle(Sdp* sdp) c
           // m-section
           useBundleOnly = !mids.empty();
           break;
       }
 
       if (useBundleOnly) {
         attrs.SetAttribute(
             new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
-        // Set port to 0 for sections with bundle-only attribute. (mjf)
-        sdp->GetMediaSection(i).SetPort(0);
       }
 
       mids.push_back(attrs.GetMid());
     }
   }
 
   if (mids.size() > 1) {
     UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
--- a/media/webrtc/signaling/src/sdp/SdpHelper.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.cpp
@@ -419,29 +419,24 @@ SdpHelper::SetDefaultAddresses(const std
 void
 SdpHelper::SetDefaultAddresses(const std::string& defaultCandidateAddr,
                                uint16_t defaultCandidatePort,
                                const std::string& defaultRtcpCandidateAddr,
                                uint16_t defaultRtcpCandidatePort,
                                SdpMediaSection* msection)
 {
   msection->GetConnection().SetAddress(defaultCandidateAddr);
-  SdpAttributeList& attrList = msection->GetAttributeList();
-
-  // only set the port if there is no bundle-only attribute
-  if (!attrList.HasAttribute(SdpAttribute::kBundleOnlyAttribute)) {
-    msection->SetPort(defaultCandidatePort);
-  }
+  msection->SetPort(defaultCandidatePort);
 
   if (!defaultRtcpCandidateAddr.empty()) {
     sdp::AddrType ipVersion = sdp::kIPv4;
     if (defaultRtcpCandidateAddr.find(':') != std::string::npos) {
       ipVersion = sdp::kIPv6;
     }
-    attrList.SetAttribute(new SdpRtcpAttribute(
+    msection->GetAttributeList().SetAttribute(new SdpRtcpAttribute(
           defaultRtcpCandidatePort,
           sdp::kInternet,
           ipVersion,
           defaultRtcpCandidateAddr));
   }
 }
 
 nsresult
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4675,20 +4675,25 @@ pref("gfx.direct2d.force-enabled", false
 
 pref("layers.prefer-opengl", false);
 pref("layers.prefer-d3d9", false);
 // Disable for now due to bug 1304360
 pref("layers.allow-d3d9-fallback", false);
 #endif
 
 // Copy-on-write canvas
+pref("layers.shared-buffer-provider.enabled", true);
+
 #ifdef XP_WIN
 pref("layers.shared-buffer-provider.enabled", false);
-#else
-pref("layers.shared-buffer-provider.enabled", true);
+#endif
+
+#ifdef XP_MACOSX
+// cf. Bug 1324908
+pref("layers.shared-buffer-provider.enabled", false);
 #endif
 
 // Force all possible layers to be always active layers
 pref("layers.force-active", false);
 
 // Never use gralloc surfaces, even when they're available on this
 // platform and are the optimal surface type.
 pref("layers.gralloc.disable", false);
--- a/netwerk/base/CaptivePortalService.cpp
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -131,16 +131,17 @@ CaptivePortalService::Start()
     // Doesn't do anything if called in the content process.
     return NS_OK;
   }
 
   if (mStarted) {
     return NS_OK;
   }
 
+  MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
   mStarted = true;
   mEverBeenCaptive = false;
 
   // Get the delay prefs
   Preferences::GetUint("network.captive-portal-service.minInterval", &mMinInterval);
   Preferences::GetUint("network.captive-portal-service.maxInterval", &mMaxInterval);
   Preferences::GetFloat("network.captive-portal-service.backoffFactor", &mBackoffFactor);
 
@@ -175,16 +176,19 @@ CaptivePortalService::Stop()
   }
   mTimer = nullptr;
   mRequestInProgress = false;
   mStarted = false;
   if (mCaptivePortalDetector) {
     mCaptivePortalDetector->Abort(kInterfaceName);
   }
   mCaptivePortalDetector = nullptr;
+
+  // Clear the state in case anyone queries the state while detection is off.
+  mState = UNKNOWN;
   return NS_OK;
 }
 
 void
 CaptivePortalService::SetStateInChild(int32_t aState)
 {
   // This should only be called in the content process, from ContentChild.cpp
   // in order to mirror the captive portal state set in the chrome process.
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -1145,16 +1145,25 @@ nsIOService::SetConnectivityInternal(boo
         return NS_OK;
     }
     mConnectivity = aConnectivity;
 
     // This is used for PR_Connect PR_Close telemetry so it is important that
     // we have statistic about network change event even if we are offline.
     mLastConnectivityChange = PR_IntervalNow();
 
+    if (mCaptivePortalService) {
+        if (aConnectivity && !xpc::AreNonLocalConnectionsDisabled()) {
+            // This will also trigger a captive portal check for the new network
+            static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
+        } else {
+            static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+        }
+    }
+
     nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
     if (!observerService) {
         return NS_OK;
     }
     // This notification sends the connectivity to the child processes
     if (XRE_IsParentProcess()) {
         observerService->NotifyObservers(nullptr,
             NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC, aConnectivity ?
@@ -1612,18 +1621,16 @@ nsIOService::OnNetworkLinkEvent(const ch
     bool isUp = true;
     if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) {
         mLastNetworkLinkChange = PR_IntervalNow();
         // CHANGED means UP/DOWN didn't change
         return NS_OK;
     } else if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) {
         isUp = false;
     } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) {
-        // Interface is up. Triggering a captive portal recheck.
-        RecheckCaptivePortal();
         isUp = true;
     } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) {
         nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp);
         NS_ENSURE_SUCCESS(rv, rv);
     } else {
         NS_WARNING("Unhandled network event!");
         return NS_OK;
     }
--- a/taskcluster/docker/recipes/ubuntu1204-test-system-setup.sh
+++ b/taskcluster/docker/recipes/ubuntu1204-test-system-setup.sh
@@ -252,16 +252,24 @@ apt-get -q -y --force-yes install \
     libgl1-mesa-glx-lts-saucy:i386 \
     libglapi-mesa-lts-saucy \
     libglapi-mesa-lts-saucy:i386 \
     libxatracker1-lts-saucy \
     mesa-common-dev-lts-saucy:i386
 mesa_version=$(dpkg-query -s libgl1-mesa-dri-lts-saucy | grep ^Version | awk '{ print $2 }')
 [ "$mesa_version" = "9.2.1-1ubuntu3~precise1mozilla2" ] || exit 1
 
+# additional packages for linux32 tests
+apt-get -q -y --force-yes install \
+    libcanberra-gtk3-module:i386 \
+    libcanberra-gtk-module:i386 \
+    libdbus-glib-1-2:i386 \
+    libgtk-3-0:i386 \
+    openjdk-7-jdk:i386
+
 # revert the list of repos
 cp sources.list.orig /etc/apt/sources.list
 apt-get update
 
 # node 5 requires a C++11 compiler.
 add-apt-repository ppa:ubuntu-toolchain-r/test
 apt-get update
 apt-get -y install gcc-4.8 g++-4.8