Merge mozilla-inbound to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Sat, 23 Feb 2019 06:15:14 +0200
changeset 518606 6924dd16f7b1
parent 518584 fdd04819e350 (current diff)
parent 518605 1c74156717f1 (diff)
child 518607 9f20371d98ec
child 518610 28b53210d522
child 518620 0840454c1459
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
6924dd16f7b1 / 67.0a1 / 20190223041557 / files
nightly linux64
6924dd16f7b1 / 67.0a1 / 20190223041557 / files
nightly mac
6924dd16f7b1 / 67.0a1 / 20190223041557 / files
nightly win32
6924dd16f7b1 / 67.0a1 / 20190223041557 / files
nightly win64
6924dd16f7b1 / 67.0a1 / 20190223041557 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/picture.rs
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
layout/svg/SVGTextFrame.cpp
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -514,17 +514,17 @@ uint32_t HyperTextAccessible::FindOffset
 
   const bool kIsJumpLinesOk = true;       // okay to jump lines
   const bool kIsScrollViewAStop = false;  // do not stop at scroll views
   const bool kIsKeyboardSelect = true;    // is keyboard selection
   const bool kIsVisualBidi = false;       // use visual order for bidi text
   nsPeekOffsetStruct pos(
       aAmount, aDirection, innerContentOffset, nsPoint(0, 0), kIsJumpLinesOk,
       kIsScrollViewAStop, kIsKeyboardSelect, kIsVisualBidi, false,
-      nsPeekOffsetStruct::ForceEditableRegion::No, aWordMovementType);
+      nsPeekOffsetStruct::ForceEditableRegion::No, aWordMovementType, false);
   nsresult rv = frameAtOffset->PeekOffset(&pos);
 
   // PeekOffset fails on last/first lines of the text in certain cases.
   if (NS_FAILED(rv) && aAmount == eSelectLine) {
     pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
     frameAtOffset->PeekOffset(&pos);
   }
   if (!pos.mResultContent) {
--- a/accessible/tests/mochitest/text/test_wordboundary.html
+++ b/accessible/tests/mochitest/text/test_wordboundary.html
@@ -31,17 +31,17 @@
       testTextAfterOffset(ids, BOUNDARY_WORD_START,
                           [ [ 0, 5, "", 5, 5 ] ]);
       testTextAfterOffset(ids, BOUNDARY_WORD_END,
                           [ [ 0, 5, "", 5, 5 ] ]);
 
       // "hello "
       // __h__e__l__l__o__ __
       //  0  1  2  3  4  5  6
-      ids = [ "i2", "d2", "e2", "t2" ];
+      ids = [ "i2", "d2", "p2", "e2", "t2" ];
       testTextBeforeOffset(ids, BOUNDARY_WORD_START,
                            [ [ 0, 6, "", 0, 0 ] ]);
       testTextBeforeOffset(ids, BOUNDARY_WORD_END,
                            [ [ 0, 5, "", 0, 0 ],
                              [ 6, 6, "hello", 0, 5 ],
                            ]);
 
       testTextAtOffset(ids, BOUNDARY_WORD_START,
@@ -78,20 +78,48 @@
 
       testTextAfterOffset(ids, BOUNDARY_WORD_START,
                           [ [ 0, 5, "all", 6, 9 ],
                             [ 6, 9, "", 9, 9 ] ]);
       testTextAfterOffset(ids, BOUNDARY_WORD_END,
                           [ [ 0, 5, " all", 5, 9 ],
                             [ 6, 9, "", 9, 9 ] ]);
 
+      // "  hello  all  " (with whitespace collapsing)
+      // __h__e__l__l__o__ __a__l__l__ __
+      //  0  1  2  3  4  5  6  7  8  9 10
+      ids = [ "d6a", "e6a" ];
+      testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+                           [ [ 0, 5, "", 0, 0 ],
+                             [ 6, 10, "hello ", 0, 6 ] ]);
+      testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+                           [ [ 0, 5, "", 0, 0 ],
+                             [ 6, 9, "hello", 0, 5 ],
+                             [ 10, 10, " all", 5, 9 ] ]);
+
+      testTextAtOffset(ids, BOUNDARY_WORD_START,
+                       [ [ 0, 5, "hello ", 0, 6 ],
+                         [ 6, 10, "all ", 6, 10 ] ]);
+      testTextAtOffset(ids, BOUNDARY_WORD_END,
+                       [ [ 0, 4, "hello", 0, 5 ],
+                         [ 5, 8, " all", 5, 9 ],
+                         [ 9, 10, " ", 9, 10 ] ]);
+
+      testTextAfterOffset(ids, BOUNDARY_WORD_START,
+                          [ [ 0, 5, "all ", 6, 10 ],
+                            [ 6, 10, "", 10, 10 ] ]);
+      testTextAfterOffset(ids, BOUNDARY_WORD_END,
+                          [ [ 0, 5, " all", 5, 9 ],
+                            [ 6, 9, " ", 9, 10 ],
+                            [ 10, 10, "", 10, 10 ] ]);
+
       // "hello my friend"
       // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
       //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
-      ids = [ "i7", "d7", "e7", "t7" ];
+      ids = [ "i7", "d7", "e7", "t7", "w7" ];
       testTextBeforeOffset(ids, BOUNDARY_WORD_START,
                            [ [ 0, 5, "", 0, 0 ],
                              [ 6, 8, "hello ", 0, 6 ],
                              [ 9, 15, "my ", 6, 9 ] ]);
       testTextBeforeOffset(ids, BOUNDARY_WORD_END,
                            [ [ 0, 5, "", 0, 0 ],
                              [ 6, 8, "hello", 0, 5 ],
                              [ 9, 15, " my", 5, 8 ] ]);
@@ -242,29 +270,34 @@
   </pre>
 
   <input id="i1" value="hello"/>
   <div id="d1">hello</div>
   <div id="e1" contenteditable="true">hello</div>
   <textarea id="t1">hello</textarea>
 
   <input id="i2" value="hello "/>
-  <pre><div id="d2">hello </div></pre>
+  <div id="d2"> hello </div>
+  <pre><div id="p2">hello </div></pre>
   <div id="e2" contenteditable="true" style='white-space:pre'>hello </div>
   <textarea id="t2">hello </textarea>
 
   <input id="i6" value="hello all"/>
-  <div id="d6">hello all</div>
+  <div id="d6"> hello  all</div>
   <div id="e6" contenteditable="true">hello all</div>
   <textarea id="t6">hello all</textarea>
 
+  <div id="d6a">  hello  all  </div>
+  <div id="e6a" contenteditable="true">  hello  all  </div>
+
   <input id="i7" value="hello my friend"/>
-  <div id="d7">hello my friend</div>
+  <div id="d7"> hello  my   friend</div>
   <div id="e7" contenteditable="true">hello my friend</div>
   <textarea id="t7">hello my friend</textarea>
+  <div id="w7" style="width:1em"> hello my friend</div>
 
   <input id="i8" value="Brave Sir  Robin   ran"/>
   <pre>
     <div id="d8">Brave Sir  Robin   ran</div>
     <div id="e8" contenteditable="true">Brave Sir  Robin   ran</div>
   </pre>
   <textarea id="t8" cols="300">Brave Sir  Robin   ran</textarea>
 
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -147,16 +147,17 @@ Inspector.prototype = {
     // Localize all the nodes containing a data-localization attribute.
     localizeMarkup(this.panelDoc);
 
     await Promise.all([
       this._getCssProperties(),
       this._getPageStyle(),
       this._getDefaultSelection(),
       this._getAccessibilityFront(),
+      this._getChangesFront(),
     ]);
 
     return this._deferredOpen();
   },
 
   get toolbox() {
     return this._toolbox;
   },
@@ -233,26 +234,16 @@ Inspector.prototype = {
     return this._search;
   },
 
   get cssProperties() {
     return this._cssProperties.cssProperties;
   },
 
   /**
-   * Check if the changes panel is enabled and supported by the server.
-   */
-  _supportsChangesPanel() {
-    // The changes actor was introduced in Fx65, we are checking this for backward
-    // compatibility when connecting to an older server. Can be removed once Fx65 hit the
-    // release channel.
-    return this._target.hasActor("changes");
-  },
-
-  /**
    * Handle promise rejections for various asynchronous actions, and only log errors if
    * the inspector panel still exists.
    * This is useful to silence useless errors that happen when the inspector is closed
    * while still initializing (and making protocol requests).
    */
   _handleRejectionIfNotDestroyed: function(e) {
     if (!this._panelDestroyer) {
       console.error(e);
@@ -264,24 +255,16 @@ Inspector.prototype = {
     this.isReady = false;
 
     // Set the node front so that the markup and sidebar panels will have the selected
     // nodeFront ready when they're initialized.
     if (this._defaultNode) {
       this.selection.setNodeFront(this._defaultNode, { reason: "inspector-open" });
     }
 
-    if (this._supportsChangesPanel()) {
-      // Get the Changes front, then call a method on it, which will instantiate
-      // the ChangesActor. We want the ChangesActor to be guaranteed available before
-      // the user makes any changes.
-      this.changesFront = await this.toolbox.target.getFront("changes");
-      this.changesFront.start();
-    }
-
     // Setup the splitter before the sidebar is displayed so, we don't miss any events.
     this.setupSplitter();
 
     // We can display right panel with: tab bar, markup view and breadbrumb. Right after
     // the splitter set the right and left panel sizes, in order to avoid resizing it
     // during load of the inspector.
     this.panelDoc.getElementById("inspector-main-content").style.visibility = "visible";
 
@@ -327,16 +310,25 @@ Inspector.prototype = {
     }, this._handleRejectionIfNotDestroyed);
   },
 
   _getAccessibilityFront: async function() {
     this.accessibilityFront = await this.target.getFront("accessibility");
     return this.accessibilityFront;
   },
 
+  _getChangesFront: async function() {
+    // Get the Changes front, then call a method on it, which will instantiate
+    // the ChangesActor. We want the ChangesActor to be guaranteed available before
+    // the user makes any changes.
+    this.changesFront = await this.toolbox.target.getFront("changes");
+    this.changesFront.start();
+    return this.changesFront;
+  },
+
   _getDefaultSelection: function() {
     // This may throw if the document is still loading and we are
     // refering to a dead about:blank document
     return this._getDefaultNodeForSelection().catch(this._handleRejectionIfNotDestroyed);
   },
 
   _getPageStyle: function() {
     return this.inspector.getPageStyle().then(pageStyle => {
@@ -885,34 +877,29 @@ Inspector.prototype = {
         id: "layoutview",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"),
       },
       {
         id: "computedview",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"),
       },
       {
+        id: "changesview",
+        title: INSPECTOR_L10N.getStr("inspector.sidebar.changesViewTitle"),
+      },
+      {
         id: "fontinspector",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
       },
       {
         id: "animationinspector",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle"),
       },
     ];
 
-    if (this._supportsChangesPanel()) {
-      // Insert Changes as third tab, right after Computed.
-      // TODO: move this inline to `sidebarPanels` above when addressing Bug 1511877.
-      sidebarPanels.splice(2, 0, {
-        id: "changesview",
-        title: INSPECTOR_L10N.getStr("inspector.sidebar.changesViewTitle"),
-      });
-    }
-
     if (Services.prefs.getBoolPref("devtools.inspector.new-rulesview.enabled")) {
       sidebarPanels.push({
         id: "newruleview",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.ruleViewTitle"),
       });
     }
 
     for (const { id, title } of sidebarPanels) {
--- a/devtools/client/inspector/markup/markup-context-menu.js
+++ b/devtools/client/inspector/markup/markup-context-menu.js
@@ -31,16 +31,26 @@ class MarkupContextMenu {
     this.inspector = markup.inspector;
     this.selection = this.inspector.selection;
     this.target = this.inspector.target;
     this.telemetry = this.inspector.telemetry;
     this.toolbox = this.inspector.toolbox;
     this.walker = this.inspector.walker;
   }
 
+  destroy() {
+    this.markup = null;
+    this.inspector = null;
+    this.selection = null;
+    this.target = null;
+    this.telemetry = null;
+    this.toolbox = null;
+    this.walker = null;
+  }
+
   show(event) {
     if (!(event.originalTarget instanceof Element) ||
         event.originalTarget.closest("input[type=text]") ||
         event.originalTarget.closest("input:not([type])") ||
         event.originalTarget.closest("textarea")) {
       return;
     }
 
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -2024,16 +2024,21 @@ MarkupView.prototype = {
     }
 
     this._destroyer = promise.resolve();
 
     this._clearBriefBoxModelTimer();
 
     this._hoveredContainer = null;
 
+    if (this._contextMenu) {
+      this._contextMenu.destroy();
+      this._contextMenu = null;
+    }
+
     if (this._eventDetailsTooltip) {
       this._eventDetailsTooltip.destroy();
       this._eventDetailsTooltip = null;
     }
 
     if (this.htmlEditor) {
       this.htmlEditor.destroy();
       this.htmlEditor = null;
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -861,21 +861,28 @@ void TimeoutManager::RunTimeout(const Ti
       if (timeout->mFiringId != firingId) {
         // If the FiringId does not match, but is still valid, then this is
         // a Timeout for another RunTimeout() on the call stack (such as in
         // the case of nested event loops, for alert() or more likely XHR).
         // Just skip it.
         if (IsValidFiringId(timeout->mFiringId)) {
           MOZ_LOG(gTimeoutLog, LogLevel::Debug,
                   ("Skipping Run%s(TimeoutManager=%p, timeout=%p) since "
-                   "firingId %d is valid (processing firingId %d) - "
-                   "FiringIndex %" PRId64 " (mLastFiringIndex %" PRId64 ")",
+                   "firingId %d is valid (processing firingId %d)"
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+                   " - FiringIndex %" PRId64 " (mLastFiringIndex %" PRId64 ")"
+#endif
+                   ,
                    timeout->mIsInterval ? "Interval" : "Timeout", this,
-                   timeout.get(), timeout->mFiringId, firingId,
-                   timeout->mFiringIndex, mFiringIndex));
+                   timeout.get(), timeout->mFiringId, firingId
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+                   ,
+                   timeout->mFiringIndex, mFiringIndex
+#endif
+                   ));
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
           // The old FiringIndex assumed no recursion; recursion can cause
           // other timers to get fired "in the middle" of a sequence we've
           // already assigned firingindexes to.  Since we're not going to
           // run this timeout now, remove any FiringIndex that was already
           // set.
 
           // Since all timers that have FiringIndexes set *must* be ready
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1378,17 +1378,17 @@ void WebRenderBridgeParent::RemovePipeli
   }
 
   auto it = mAsyncCompositables.find(wr::AsUint64(aPipelineId));
   if (it == mAsyncCompositables.end()) {
     return;
   }
   RefPtr<WebRenderImageHost>& wrHost = it->second;
 
-  wrHost->ClearWrBridge();
+  wrHost->ClearWrBridge(this);
   mAsyncImageManager->RemoveAsyncImagePipeline(aPipelineId, aTxn);
   aTxn.RemovePipeline(aPipelineId);
   mAsyncCompositables.erase(wr::AsUint64(aPipelineId));
   return;
 }
 
 void WebRenderBridgeParent::DeleteImage(const ImageKey& aKey,
                                         wr::TransactionBuilder& aUpdates) {
@@ -2003,17 +2003,17 @@ void WebRenderBridgeParent::ClearResourc
     if (wrTexture) {
       mAsyncImageManager->HoldExternalImage(mPipelineId, wrEpoch, wrTexture);
     }
   }
   mTextureHosts.clear();
   for (const auto& entry : mAsyncCompositables) {
     wr::PipelineId pipelineId = wr::AsPipelineId(entry.first);
     RefPtr<WebRenderImageHost> host = entry.second;
-    host->ClearWrBridge();
+    host->ClearWrBridge(this);
     mAsyncImageManager->RemoveAsyncImagePipeline(pipelineId, txn);
     txn.RemovePipeline(pipelineId);
   }
   mAsyncCompositables.clear();
   for (const auto& entry : mSharedSurfaceIds) {
     mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, entry.second);
   }
   mSharedSurfaceIds.clear();
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -13,16 +13,17 @@
 #include "CompositableHost.h"  // for CompositableHost, ImageCompositeNotificationInfo
 #include "GLContextProvider.h"
 #include "mozilla/layers/CompositableTransactionParent.h"
 #include "mozilla/layers/CompositorVsyncSchedulerOwner.h"
 #include "mozilla/layers/PWebRenderBridgeParent.h"
 #include "mozilla/layers/UiCompositorControllerParent.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "nsTArrayForwardDeclare.h"
 
 namespace mozilla {
 
 namespace gl {
@@ -44,18 +45,20 @@ class CompositorAnimationStorage;
 class CompositorBridgeParentBase;
 class CompositorVsyncScheduler;
 class AsyncImagePipelineManager;
 class WebRenderImageHost;
 
 class WebRenderBridgeParent final : public PWebRenderBridgeParent,
                                     public CompositorVsyncSchedulerOwner,
                                     public CompositableParentManager,
-                                    public layers::FrameRecorder {
+                                    public layers::FrameRecorder,
+                                    public SupportsWeakPtr<WebRenderBridgeParent> {
  public:
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebRenderBridgeParent)
   WebRenderBridgeParent(CompositorBridgeParentBase* aCompositorBridge,
                         const wr::PipelineId& aPipelineId,
                         widget::CompositorWidget* aWidget,
                         CompositorVsyncScheduler* aScheduler,
                         RefPtr<wr::WebRenderAPI>&& aApi,
                         RefPtr<AsyncImagePipelineManager>&& aImageMgr,
                         RefPtr<CompositorAnimationStorage>&& aAnimStorage,
                         TimeDuration aVsyncRate);
--- a/gfx/layers/wr/WebRenderImageHost.cpp
+++ b/gfx/layers/wr/WebRenderImageHost.cpp
@@ -25,17 +25,16 @@ using namespace gfx;
 
 namespace layers {
 
 class ISurfaceAllocator;
 
 WebRenderImageHost::WebRenderImageHost(const TextureInfo& aTextureInfo)
     : CompositableHost(aTextureInfo),
       ImageComposite(),
-      mWrBridge(nullptr),
       mWrBridgeBindings(0) {}
 
 WebRenderImageHost::~WebRenderImageHost() { MOZ_ASSERT(!mWrBridge); }
 
 void WebRenderImageHost::UseTextureHost(
     const nsTArray<TimedTexture>& aTextures) {
   CompositableHost::UseTextureHost(aTextures);
   MOZ_ASSERT(aTextures.Length() >= 1);
@@ -245,19 +244,24 @@ void WebRenderImageHost::SetWrBridge(Web
   // the layer tree. However this should be limited to things such as video
   // which will not be reused across different WebRenderBridgeParent objects.
   MOZ_ASSERT(aWrBridge);
   MOZ_ASSERT(!mWrBridge || mWrBridge == aWrBridge);
   mWrBridge = aWrBridge;
   ++mWrBridgeBindings;
 }
 
-void WebRenderImageHost::ClearWrBridge() {
+void WebRenderImageHost::ClearWrBridge(WebRenderBridgeParent* aWrBridge) {
+  MOZ_ASSERT(aWrBridge);
   MOZ_ASSERT(mWrBridgeBindings > 0);
   --mWrBridgeBindings;
   if (mWrBridgeBindings == 0) {
+    MOZ_ASSERT(aWrBridge == mWrBridge);
+    if (aWrBridge != mWrBridge) {
+      gfxCriticalNote << "WrBridge mismatch happened";
+    }
     SetCurrentTextureHost(nullptr);
     mWrBridge = nullptr;
   }
 }
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/wr/WebRenderImageHost.h
+++ b/gfx/layers/wr/WebRenderImageHost.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_WEBRENDERIMAGEHOST_H
 #define MOZILLA_GFX_WEBRENDERIMAGEHOST_H
 
 #include "CompositableHost.h"               // for CompositableHost
 #include "mozilla/layers/ImageComposite.h"  // for ImageComposite
+#include "mozilla/WeakPtr.h"
 
 namespace mozilla {
 namespace layers {
 
 class WebRenderBridgeParent;
 
 /**
  * ImageHost. Works with ImageClientSingle and ImageClientBuffered
@@ -68,27 +69,27 @@ class WebRenderImageHost : public Compos
   uint32_t GetDroppedFrames() override { return GetDroppedFramesAndReset(); }
 
   virtual WebRenderImageHost* AsWebRenderImageHost() override { return this; }
 
   TextureHost* GetAsTextureHostForComposite();
 
   void SetWrBridge(WebRenderBridgeParent* aWrBridge);
 
-  void ClearWrBridge();
+  void ClearWrBridge(WebRenderBridgeParent* aWrBridge);
 
   TextureHost* GetCurrentTextureHost() { return mCurrentTextureHost; }
 
  protected:
   // ImageComposite
   virtual TimeStamp GetCompositionTime() const override;
 
   void SetCurrentTextureHost(TextureHost* aTexture);
 
-  WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge;
+  WeakPtr<WebRenderBridgeParent> mWrBridge;
 
   uint32_t mWrBridgeBindings;
 
   CompositableTextureHostRef mCurrentTextureHost;
 };
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1394,29 +1394,31 @@ pub extern "C" fn wr_transaction_update_
     let mut properties = DynamicProperties {
         transforms: Vec::new(),
         floats: Vec::new(),
     };
 
     if transform_count > 0 {
         let transform_slice = make_slice(transform_array, transform_count);
 
+        properties.transforms.reserve(transform_slice.len());
         for element in transform_slice.iter() {
             let prop = PropertyValue {
                 key: PropertyBindingKey::new(element.id),
                 value: element.transform.into(),
             };
 
             properties.transforms.push(prop);
         }
     }
 
     if opacity_count > 0 {
         let opacity_slice = make_slice(opacity_array, opacity_count);
 
+        properties.floats.reserve(opacity_slice.len());
         for element in opacity_slice.iter() {
             let prop = PropertyValue {
                 key: PropertyBindingKey::new(element.id),
                 value: element.opacity,
             };
             properties.floats.push(prop);
         }
     }
@@ -1435,17 +1437,17 @@ pub extern "C" fn wr_transaction_append_
     }
 
     let mut properties = DynamicProperties {
         transforms: Vec::new(),
         floats: Vec::new(),
     };
 
     let transform_slice = make_slice(transform_array, transform_count);
-
+    properties.transforms.reserve(transform_slice.len());
     for element in transform_slice.iter() {
         let prop = PropertyValue {
             key: PropertyBindingKey::new(element.id),
             value: element.transform.into(),
         };
 
         properties.transforms.push(prop);
     }
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -307,20 +307,17 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         let main_scroll_root = match main_scroll_root {
             Some(main_scroll_root) => main_scroll_root,
             None => ROOT_SPATIAL_NODE_INDEX,
         };
 
         // Get the list of existing primitives in the main stacking context.
-        let mut old_prim_list = mem::replace(
-            primitives,
-            Vec::new(),
-        );
+        let mut old_prim_list = primitives.take();
 
         // In the simple case, there are no preceding or trailing primitives,
         // because everything is anchored to the root scroll node. Handle
         // this case specially to avoid underflow error in the Some(..)
         // path below.
 
         let preceding_prims;
         let mut remaining_prims;
@@ -404,16 +401,17 @@ impl<'a> DisplayListFlattener<'a> {
                 pic_index: PictureIndex(pic_index)
             },
             ClipChainId::NONE,
             main_scroll_root,
         );
 
         // This contains the tile caching picture, with preceding and
         // trailing primitives outside the main scroll root.
+        primitives.reserve(preceding_prims.len() + trailing_prims.len() + 1);
         primitives.extend(preceding_prims);
         primitives.push(instance);
         primitives.extend(trailing_prims);
     }
 
     /// Find the spatial node that is the scroll root for a given
     /// spatial node.
     fn find_scroll_root(
@@ -2446,25 +2444,20 @@ impl<'a> DisplayListFlattener<'a> {
                 .limit_by(font_instance.render_mode);
             let mut flags = font_instance.flags;
             if let Some(options) = glyph_options {
                 render_mode = render_mode.limit_by(options.render_mode);
                 flags |= options.flags;
             }
 
             let font = FontInstance::new(
-                font_instance.font_key,
-                font_instance.size,
-                *text_color,
-                font_instance.bg_color,
+                Arc::clone(font_instance),
+                (*text_color).into(),
                 render_mode,
                 flags,
-                font_instance.synthetic_italics,
-                font_instance.platform_options,
-                font_instance.variations.clone(),
             );
 
             // TODO(gw): We can do better than a hash lookup here...
             let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
 
             // TODO(gw): It'd be nice not to have to allocate here for creating
             //           the primitive key, when the common case is that the
             //           hash will match and we won't end up creating a new
--- a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
@@ -1,25 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorF, ColorU, DevicePoint};
+use api::{ColorU, DevicePoint};
 use api::{FontInstanceFlags, FontInstancePlatformOptions};
-use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
+use api::{FontKey, FontInstanceKey, FontRenderMode, FontTemplate, FontVariation};
 use api::{GlyphIndex, GlyphDimensions, SyntheticItalics};
 use api::{LayoutPoint, LayoutToWorldTransform, WorldPoint};
 use app_units::Au;
 use euclid::approxeq::ApproxEq;
 use internal_types::ResourceCacheError;
+use wr_malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use platform::font::FontContext;
 use rayon::ThreadPool;
 use std::cmp;
 use std::hash::{Hash, Hasher};
 use std::mem;
+use std::ops::Deref;
 use std::sync::{Arc, Condvar, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 
 #[cfg(feature = "pathfinder")]
 mod pathfinder;
 #[cfg(feature = "pathfinder")]
 use self::pathfinder::create_pathfinder_font_context;
 #[cfg(feature = "pathfinder")]
@@ -159,65 +161,108 @@ impl<'a> From<&'a LayoutToWorldTransform
         FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22)
     }
 }
 
 // Some platforms (i.e. Windows) may have trouble rasterizing glyphs above this size.
 // Ensure glyph sizes are reasonably limited to avoid that scenario.
 pub const FONT_SIZE_LIMIT: f64 = 512.0;
 
-#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd, MallocSizeOf)]
+/// A mutable font instance description.
+///
+/// Performance is sensitive to the size of this structure, so it should only contain
+/// the fields that we need to modify from the original base font instance.
+#[derive(Clone, PartialEq, Eq, Debug, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FontInstance {
-    pub font_key: FontKey,
+    pub base: Arc<BaseFontInstance>,
+    pub transform: FontTransform,
+    pub render_mode: FontRenderMode,
+    pub flags: FontInstanceFlags,
+    pub color: ColorU,
     // The font size is in *device* pixels, not logical pixels.
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
     // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
     //           or something similar to that.
     pub size: Au,
-    pub color: ColorU,
+}
+
+impl Hash for FontInstance {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        // Hash only the base instance's key to avoid the cost of hashing
+        // the rest.
+        self.base.instance_key.hash(state);
+        self.transform.hash(state);
+        self.render_mode.hash(state);
+        self.flags.hash(state);
+        self.color.hash(state);
+        self.size.hash(state);
+    }
+}
+
+/// Immutable description of a font instance requested by the user of the API.
+///
+/// `BaseFontInstance` can be identified by a `FontInstanceKey` so we should
+/// never need to hash it.
+#[derive(Clone, PartialEq, Eq, Debug, Ord, PartialOrd, MallocSizeOf)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BaseFontInstance {
+    pub instance_key: FontInstanceKey,
+    pub font_key: FontKey,
+    pub size: Au,
     pub bg_color: ColorU,
     pub render_mode: FontRenderMode,
     pub flags: FontInstanceFlags,
     pub synthetic_italics: SyntheticItalics,
     #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
     pub platform_options: Option<FontInstancePlatformOptions>,
     pub variations: Vec<FontVariation>,
-    pub transform: FontTransform,
+}
+
+impl Deref for FontInstance {
+    type Target = BaseFontInstance;
+    fn deref(&self) -> &BaseFontInstance {
+        self.base.as_ref()
+    }
+}
+
+impl MallocSizeOf for  FontInstance {
+    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 }
 }
 
 impl FontInstance {
     pub fn new(
-        font_key: FontKey,
-        size: Au,
-        color: ColorF,
-        bg_color: ColorU,
+        base: Arc<BaseFontInstance>,
+        color: ColorU,
         render_mode: FontRenderMode,
         flags: FontInstanceFlags,
-        synthetic_italics: SyntheticItalics,
-        platform_options: Option<FontInstancePlatformOptions>,
-        variations: Vec<FontVariation>,
     ) -> Self {
-        // If a background color is enabled, it only makes sense
-        // for it to be completely opaque.
-        debug_assert!(bg_color.a == 0 || bg_color.a == 255);
-
         FontInstance {
-            font_key,
-            size,
+            transform: FontTransform::identity(),
             color: color.into(),
-            bg_color,
+            size: base.size,
+            base,
             render_mode,
             flags,
-            synthetic_italics,
-            platform_options,
-            variations,
+        }
+    }
+
+    pub fn from_base(
+        base: Arc<BaseFontInstance>,
+    ) -> Self {
+        FontInstance {
             transform: FontTransform::identity(),
+            color: ColorU::new(0, 0, 0, 255),
+            size: base.size,
+            render_mode: base.render_mode,
+            flags: base.flags,
+            base,
         }
     }
 
     pub fn get_alpha_glyph_format(&self) -> GlyphFormat {
         if self.transform.is_identity() { GlyphFormat::Alpha } else { GlyphFormat::TransformedAlpha }
     }
 
     pub fn get_subpixel_glyph_format(&self) -> GlyphFormat {
@@ -709,23 +754,23 @@ mod test_glyph_rasterizer {
         use rayon::ThreadPoolBuilder;
         use std::fs::File;
         use std::io::Read;
         use texture_cache::TextureCache;
         use glyph_cache::GlyphCache;
         use gpu_cache::GpuCache;
         use render_task::{RenderTaskCache, RenderTaskTree, RenderTaskTreeCounters};
         use profiler::TextureCacheProfileCounters;
-        use api::{FontKey, FontTemplate, FontRenderMode,
-                  IdNamespace, ColorF, ColorU, DevicePoint};
+        use api::{FontKey, FontInstanceKey, FontTemplate, FontRenderMode,
+                  IdNamespace, ColorU, DevicePoint};
         use render_backend::FrameId;
         use app_units::Au;
         use thread_profiler::register_thread_with_profiler;
         use std::sync::Arc;
-        use glyph_rasterizer::{FontInstance, GlyphKey, GlyphRasterizer};
+        use glyph_rasterizer::{FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer};
 
         let worker = ThreadPoolBuilder::new()
             .thread_name(|idx|{ format!("WRWorker#{}", idx) })
             .start_handler(move |idx| {
                 register_thread_with_profiler(format!("WRWorker#{}", idx));
             })
             .build();
         let workers = Arc::new(worker.unwrap());
@@ -740,27 +785,28 @@ mod test_glyph_rasterizer {
         let mut font_data = vec![];
         font_file
             .read_to_end(&mut font_data)
             .expect("failed to read font file");
 
         let font_key = FontKey::new(IdNamespace(0), 0);
         glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
 
-        let font = FontInstance::new(
+        let font = FontInstance::from_base(Arc::new(BaseFontInstance {
+            instance_key: FontInstanceKey(IdNamespace(0), 0),
             font_key,
-            Au::from_px(32),
-            ColorF::new(0.0, 0.0, 0.0, 1.0),
-            ColorU::new(0, 0, 0, 0),
-            FontRenderMode::Subpixel,
-            Default::default(),
-            Default::default(),
-            None,
-            Vec::new(),
-        );
+            size: Au::from_px(32),
+            bg_color: ColorU::new(0, 0, 0, 0),
+            render_mode: FontRenderMode::Subpixel,
+            flags: Default::default(),
+            synthetic_italics: Default::default(),
+            platform_options: None,
+            variations: Vec::new(),
+        }));
+
         let subpx_dir = font.get_subpx_dir();
 
         let mut glyph_keys = Vec::with_capacity(200);
         for i in 0 .. 200 {
             glyph_keys.push(GlyphKey::new(
                 i,
                 DevicePoint::zero(),
                 subpx_dir,
--- a/gfx/wr/webrender/src/hit_test.rs
+++ b/gfx/wr/webrender/src/hit_test.rs
@@ -157,32 +157,34 @@ impl HitTester {
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore,
         clip_data_store: &ClipDataStore,
     ) {
         self.spatial_nodes.clear();
         self.clip_chains.clear();
 
+        self.spatial_nodes.reserve(clip_scroll_tree.spatial_nodes.len());
         for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() {
             let index = SpatialNodeIndex::new(index);
 
             // If we haven't already seen a node for this pipeline, record this one as the root
             // node.
             self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
 
             self.spatial_nodes.push(HitTestSpatialNode {
                 pipeline_id: node.pipeline_id,
                 world_content_transform: node.world_content_transform,
                 world_viewport_transform: node.world_viewport_transform,
             });
         }
 
         // For each clip chain node, extract the clip node from the clip
         // data store, and store it inline with the clip chain node.
+        self.clip_chains.reserve(clip_store.clip_chain_nodes.len());
         for node in &clip_store.clip_chain_nodes {
             let clip_node = &clip_data_store[node.handle];
             self.clip_chains.push(HitTestClipChainNode {
                 region: HitTestClipNode::new(node.local_pos, clip_node),
                 spatial_node_index: node.spatial_node_index,
                 parent_clip_chain_id: HitTestClipChainId(node.parent_clip_chain_id.0),
             });
         }
--- a/gfx/wr/webrender/src/intern.rs
+++ b/gfx/wr/webrender/src/intern.rs
@@ -307,18 +307,18 @@ where
 
         handle
     }
 
     /// Retrieve the pending list of updates for an interner
     /// that need to be applied to the data store. Also run
     /// a GC step that removes old entries.
     pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList<S> {
-        let mut updates = mem::replace(&mut self.updates, Vec::new());
-        let data = mem::replace(&mut self.update_data, Vec::new());
+        let mut updates = self.updates.take_and_preallocate();
+        let data = self.update_data.take_and_preallocate();
 
         let free_list = &mut self.free_list;
         let current_epoch = self.current_epoch.0;
 
         // First, run a GC step. Walk through the handles, and
         // if we find any that haven't been used for some time,
         // remove them. If this ever shows up in profiles, we
         // can make the GC step partial (scan only part of the
@@ -336,17 +336,16 @@ where
                     index: handle.index as usize,
                     kind: UpdateKind::Remove,
                 });
                 return false;
             }
 
             true
         });
-
         let updates = UpdateList {
             updates,
             data,
         };
 
         // Begin the next epoch
         self.current_epoch = Epoch(self.current_epoch.0 + 1);
 
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -2261,32 +2261,30 @@ impl PicturePrimitive {
         }
     }
 
     /// Destroy an existing picture. This is called just before
     /// a frame builder is replaced with a newly built scene. It
     /// gives a picture a chance to retain any cached tiles that
     /// may be useful during the next scene build.
     pub fn destroy(
-        mut self,
+        &mut self,
         retained_tiles: &mut RetainedTiles,
         clip_scroll_tree: &ClipScrollTree,
     ) {
         if let Some(tile_cache) = self.tile_cache.take() {
             // Calculate and store positions of the reference
             // primitives for this tile cache.
             build_ref_prims(
                 &tile_cache.reference_prims.ref_prims,
                 &mut retained_tiles.ref_prims,
                 clip_scroll_tree,
             );
 
-            for tile in tile_cache.tiles {
-                retained_tiles.tiles.push(tile);
-            }
+            retained_tiles.tiles.extend(tile_cache.tiles);
         }
     }
 
     // TODO(gw): We have the PictureOptions struct available. We
     //           should move some of the parameter list in this
     //           method to be part of the PictureOptions, and
     //           avoid adding new parameters here.
     pub fn new_image(
--- a/gfx/wr/webrender/src/platform/unix/font.rs
+++ b/gfx/wr/webrender/src/platform/unix/font.rs
@@ -652,17 +652,17 @@ impl FontContext {
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
         let slot = self.load_glyph(font, key);
-        slot.and_then(|(slot, scale)| self.get_glyph_dimensions_impl(slot, font, key, scale, true))
+        slot.and_then(|(slot, scale)| self.get_glyph_dimensions_impl(slot, &font, key, scale, true))
     }
 
     fn choose_bitmap_size(&self, face: FT_Face, requested_size: f64) -> FT_Error {
         let mut best_dist = unsafe { *(*face).available_sizes.offset(0) }.y_ppem as f64 / 64.0 - requested_size;
         let mut best_size = 0;
         let num_fixed_sizes = unsafe { (*face).num_fixed_sizes };
         for i in 1 .. num_fixed_sizes {
             // Distance is positive if strike is larger than desired size,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1695,21 +1695,21 @@ impl PrimitiveStore {
         use print_tree::PrintTree;
         let mut pt = PrintTree::new("picture tree");
         self.pictures[root.0].print(&self.pictures, root, &mut pt);
     }
 
     /// Destroy an existing primitive store. This is called just before
     /// a primitive store is replaced with a newly built scene.
     pub fn destroy(
-        self,
+        mut self,
         retained_tiles: &mut RetainedTiles,
         clip_scroll_tree: &ClipScrollTree,
     ) {
-        for pic in self.pictures {
+        for pic in &mut self.pictures {
             pic.destroy(
                 retained_tiles,
                 clip_scroll_tree,
             );
         }
     }
 
     /// Returns the total count of primitive instances contained in pictures.
--- a/gfx/wr/webrender/src/prim_store/text_run.rs
+++ b/gfx/wr/webrender/src/prim_store/text_run.rs
@@ -343,13 +343,13 @@ impl TextRunPrimitive {
 fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<TextRun>(), 88, "TextRun size changed");
-    assert_eq!(mem::size_of::<TextRunTemplate>(), 104, "TextRunTemplate size changed");
-    assert_eq!(mem::size_of::<TextRunKey>(), 96, "TextRunKey size changed");
-    assert_eq!(mem::size_of::<TextRunPrimitive>(), 104, "TextRunPrimitive size changed");
+    assert_eq!(mem::size_of::<TextRun>(), 56, "TextRun size changed");
+    assert_eq!(mem::size_of::<TextRunTemplate>(), 72, "TextRunTemplate size changed");
+    assert_eq!(mem::size_of::<TextRunKey>(), 64, "TextRunKey size changed");
+    assert_eq!(mem::size_of::<TextRunPrimitive>(), 72, "TextRunPrimitive size changed");
 }
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -21,16 +21,17 @@ use api::channel::{MsgReceiver, MsgSende
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
+use glyph_rasterizer::{FontInstance};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use intern_types;
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use picture::RetainedTiles;
 use prim_store::{PrimitiveScratchBuffer, PrimitiveInstance};
 use prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData};
@@ -46,25 +47,25 @@ use resource_cache::PlainResources;
 use scene::{Scene, SceneProperties};
 use scene_builder::*;
 #[cfg(feature = "serialize")]
 use serde::{Serialize, Deserialize};
 #[cfg(feature = "debugger")]
 use serde_json;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
+use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering};
-use std::mem::replace;
 use std::sync::mpsc::{channel, Sender, Receiver};
 use std::time::{UNIX_EPOCH, SystemTime};
 use std::u32;
 #[cfg(feature = "replay")]
 use tiling::Frame;
 use time::precise_time_ns;
-use util::{Recycler, drain_filter};
+use util::{Recycler, VecHelper, drain_filter};
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone)]
 pub struct DocumentView {
     pub window_size: DeviceIntSize,
     pub inner_rect: DeviceIntRect,
     pub layer: DocumentLayer,
@@ -563,17 +564,17 @@ impl Document {
                 &self.clip_scroll_tree,
                 &self.data_stores.clip,
             ));
             self.hit_tester_is_valid = true;
         }
     }
 
     pub fn updated_pipeline_info(&mut self) -> PipelineInfo {
-        let removed_pipelines = replace(&mut self.removed_pipelines, Vec::new());
+        let removed_pipelines = self.removed_pipelines.take_and_preallocate();
         PipelineInfo {
             epochs: self.scene.pipeline_epochs.clone(),
             removed_pipelines,
         }
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.clip_scroll_tree
@@ -888,28 +889,28 @@ impl RenderBackend {
                             // always forwarded to the scene builder thread to avoid this case.
                             if let Some(tx) = result_tx {
                                 tx.send(SceneSwapResult::Aborted).unwrap();
                             }
                             continue;
                         }
 
                         self.resource_cache.add_rasterized_blob_images(
-                            replace(&mut txn.rasterized_blobs, Vec::new())
+                            txn.rasterized_blobs.take()
                         );
                         if let Some((rasterizer, info)) = txn.blob_rasterizer.take() {
                             self.resource_cache.set_blob_rasterizer(rasterizer, info);
                         }
 
                         self.update_document(
                             txn.document_id,
-                            replace(&mut txn.resource_updates, Vec::new()),
+                            txn.resource_updates.take(),
                             txn.interner_updates.take(),
-                            replace(&mut txn.frame_ops, Vec::new()),
-                            replace(&mut txn.notifications, Vec::new()),
+                            txn.frame_ops.take(),
+                            txn.notifications.take(),
                             txn.render_frame,
                             txn.invalidate_rendered_frame,
                             &mut frame_counter,
                             &mut profile_counters,
                             has_built_scene,
                         );
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
@@ -985,17 +986,18 @@ impl RenderBackend {
                 );
                 self.resource_cache.post_scene_building_update(
                     updates,
                     &mut profile_counters.resources
                 );
             }
             ApiMsg::GetGlyphDimensions(instance_key, glyph_indices, tx) => {
                 let mut glyph_dimensions = Vec::with_capacity(glyph_indices.len());
-                if let Some(font) = self.resource_cache.get_font_instance(instance_key) {
+                if let Some(base) = self.resource_cache.get_font_instance(instance_key) {
+                    let font = FontInstance::from_base(Arc::clone(&base));
                     for glyph_index in &glyph_indices {
                         let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, *glyph_index);
                         glyph_dimensions.push(glyph_dim);
                     }
                 }
                 tx.send(glyph_dimensions).unwrap();
             }
             ApiMsg::GetGlyphIndices(font_key, text, tx) => {
@@ -1244,20 +1246,20 @@ impl RenderBackend {
         }
 
         if !transaction_msg.use_scene_builder_thread &&
             txn.can_skip_scene_builder() &&
             txn.blob_rasterizer.is_none() {
 
             self.update_document(
                 txn.document_id,
-                replace(&mut txn.resource_updates, Vec::new()),
+                txn.resource_updates.take(),
                 None,
-                replace(&mut txn.frame_ops, Vec::new()),
-                replace(&mut txn.notifications, Vec::new()),
+                txn.frame_ops.take(),
+                txn.notifications.take(),
                 txn.render_frame,
                 txn.invalidate_rendered_frame,
                 frame_counter,
                 profile_counters,
                 false
             );
 
             return;
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AddFont, BlobImageResources, AsyncBlobImageRasterizer, ResourceUpdate};
 use api::{BlobImageDescriptor, BlobImageHandler, BlobImageRequest, RasterizedBlobImage};
-use api::{ClearCache, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
+use api::{ClearCache, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DebugFlags, FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
 use api::{ExternalImageData, ExternalImageType, BlobImageResult, BlobImageParams};
 use api::{FontInstanceData, FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering, ImageDirtyRect, DirtyRect};
 use api::{BlobImageKey, BlobDirtyRect, MemoryReport, VoidPtrToSizeFn};
 use api::{TileOffset, TileSize, TileRange, BlobImageData, LayoutIntRect, LayoutIntSize};
 use app_units::Au;
@@ -19,17 +19,17 @@ use capture::ExternalCaptureImage;
 use capture::PlainExternalImage;
 #[cfg(any(feature = "replay", feature = "png"))]
 use capture::CaptureConfig;
 use device::TextureFilter;
 use euclid::{point2, size2};
 use glyph_cache::GlyphCache;
 #[cfg(not(feature = "pathfinder"))]
 use glyph_cache::GlyphCacheEntry;
-use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
+use glyph_rasterizer::{BaseFontInstance, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::UvRectKind;
 use image::{compute_tile_size, compute_tile_range, for_each_tile_in_range};
 use internal_types::{FastHashMap, FastHashSet, TextureSource, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use render_backend::{FrameId, FrameStamp};
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
 use render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
@@ -368,17 +368,17 @@ impl ImageResult {
                 }
             },
             ImageResult::Err(_) => {},
         }
     }
 }
 
 type ImageCache = ResourceClassCache<ImageKey, ImageResult, ()>;
-pub type FontInstanceMap = Arc<RwLock<FastHashMap<FontInstanceKey, FontInstance>>>;
+pub type FontInstanceMap = Arc<RwLock<FastHashMap<FontInstanceKey, Arc<BaseFontInstance>>>>;
 
 #[derive(Default)]
 struct Resources {
     font_templates: FastHashMap<FontKey, FontTemplate>,
     font_instances: FontInstanceMap,
     image_templates: ImageTemplates,
 }
 
@@ -400,17 +400,20 @@ impl BlobImageResources for Resources {
                 platform_options: instance.platform_options,
                 variations: instance.variations.clone(),
             }),
             None => None,
         }
     }
 }
 
-pub type GlyphDimensionsCache = FastHashMap<(FontInstance, GlyphIndex), Option<GlyphDimensions>>;
+// We only use this to report glyph dimensions to the user of the API, so using
+// the font instance key should be enough. If we start using it to cache dimensions
+// for internal font instances we should change the hash key accordingly.
+pub type GlyphDimensionsCache = FastHashMap<(FontInstanceKey, GlyphIndex), Option<GlyphDimensions>>;
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub struct BlobImageRasterizerEpoch(usize);
 
 /// Stores parameters for clearing blob image tiles.
 ///
 /// The clearing is necessary when originally requested tile range exceeds
 /// MAX_TILES_PER_REQUEST. In this case, some tiles are not rasterized by
@@ -774,39 +777,39 @@ impl ResourceCache {
             r.delete_font(font_key);
         }
     }
 
     pub fn add_font_instance(
         &mut self,
         instance_key: FontInstanceKey,
         font_key: FontKey,
-        glyph_size: Au,
+        size: Au,
         options: Option<FontInstanceOptions>,
         platform_options: Option<FontInstancePlatformOptions>,
         variations: Vec<FontVariation>,
     ) {
         let FontInstanceOptions {
             render_mode,
             flags,
             bg_color,
             synthetic_italics,
             ..
         } = options.unwrap_or_default();
-        let instance = FontInstance::new(
+        let instance = Arc::new(BaseFontInstance {
+            instance_key,
             font_key,
-            glyph_size,
-            ColorF::new(0.0, 0.0, 0.0, 1.0),
+            size,
             bg_color,
             render_mode,
             flags,
             synthetic_italics,
             platform_options,
             variations,
-        );
+        });
         self.resources.font_instances
             .write()
             .unwrap()
             .insert(instance_key, instance);
     }
 
     pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) {
         self.resources.font_instances
@@ -817,19 +820,19 @@ impl ResourceCache {
             r.delete_font_instance(instance_key);
         }
     }
 
     pub fn get_font_instances(&self) -> FontInstanceMap {
         self.resources.font_instances.clone()
     }
 
-    pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<FontInstance> {
+    pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<Arc<BaseFontInstance>> {
         let instance_map = self.resources.font_instances.read().unwrap();
-        instance_map.get(&instance_key).cloned()
+        instance_map.get(&instance_key).map(|instance| { Arc::clone(instance) })
     }
 
     pub fn add_image_template(
         &mut self,
         image_key: ImageKey,
         descriptor: ImageDescriptor,
         data: CachedImageData,
         mut tiling: Option<TileSize>,
@@ -1497,17 +1500,17 @@ impl ResourceCache {
         }
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         glyph_index: GlyphIndex,
     ) -> Option<GlyphDimensions> {
-        match self.cached_glyph_dimensions.entry((font.clone(), glyph_index)) {
+        match self.cached_glyph_dimensions.entry((font.instance_key, glyph_index)) {
             Occupied(entry) => *entry.get(),
             Vacant(entry) => *entry.insert(
                 self.glyph_rasterizer
                     .get_glyph_dimensions(font, glyph_index),
             ),
         }
     }
 
@@ -1922,17 +1925,17 @@ struct PlainImageTemplate {
     tiling: Option<TileSize>,
 }
 
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PlainResources {
     font_templates: FastHashMap<FontKey, PlainFontTemplate>,
-    font_instances: FastHashMap<FontInstanceKey, FontInstance>,
+    font_instances: FastHashMap<FontInstanceKey, Arc<BaseFontInstance>>,
     image_templates: FastHashMap<ImageKey, PlainImageTemplate>,
 }
 
 #[cfg(feature = "capture")]
 #[derive(Serialize)]
 pub struct PlainCacheRef<'a> {
     current_frame_id: FrameId,
     glyphs: &'a GlyphCache,
--- a/gfx/wr/webrender/src/texture_cache.rs
+++ b/gfx/wr/webrender/src/texture_cache.rs
@@ -605,17 +605,17 @@ impl TextureCache {
             let entry_handles = mem::replace(
                 doc_data.handles.select(kind),
                 Vec::new(),
             );
 
             for handle in entry_handles {
                 let entry = self.entries.free(handle);
                 entry.evict();
-                self.free(entry);
+                self.free(&entry);
             }
         }
         self.per_doc_data = per_doc_data;
     }
 
     fn clear_standalone(&mut self) {
         debug_assert!(!self.now.is_valid());
         self.clear_kind(EntryKind::Standalone);
@@ -968,17 +968,17 @@ impl TextureCache {
                         entry_frame_id < self.now.frame_id()
                     }
                 }
             };
             if evict {
                 let handle = self.doc_data.handles.select(kind).swap_remove(i);
                 let entry = self.entries.free(handle);
                 entry.evict();
-                self.free(entry);
+                self.free(&entry);
             }
         }
     }
 
     /// Expires old shared entries, if we haven't done so this frame.
     ///
     /// Returns true if any entries were expired.
     fn maybe_expire_old_shared_entries(&mut self, threshold: EvictionThreshold) -> bool {
@@ -987,17 +987,17 @@ impl TextureCache {
         if self.doc_data.last_shared_cache_expiration.frame_id() < self.now.frame_id() {
             self.expire_old_entries(EntryKind::Shared, threshold);
             self.doc_data.last_shared_cache_expiration = self.now;
         }
         self.doc_data.handles.shared.len() != old_len
     }
 
     // Free a cache entry from the standalone list or shared cache.
-    fn free(&mut self, entry: CacheEntry) {
+    fn free(&mut self, entry: &CacheEntry) {
         match entry.details {
             EntryDetails::Standalone => {
                 // This is a standalone texture allocation. Free it directly.
                 self.pending_updates.push_free(entry.texture_id);
             }
             EntryDetails::Picture { layer_index } => {
                 self.picture_texture.slices[layer_index].uv_rect_handle = None;
                 if self.debug_flags.contains(
@@ -1233,17 +1233,17 @@ impl TextureCache {
                             (&mut self.doc_data.handles.shared, &mut self.doc_data.handles.standalone),
                         EntryKind::Picture => unreachable!(),
                         EntryKind::Shared =>
                             (&mut self.doc_data.handles.standalone, &mut self.doc_data.handles.shared),
                     };
                     let idx = from.iter().position(|h| h.weak() == *handle).unwrap();
                     to.push(from.remove(idx));
                 }
-                self.free(old_entry);
+                self.free(&old_entry);
             }
             UpsertResult::Inserted(new_handle) => {
                 *handle = new_handle.weak();
                 self.doc_data.handles.select(new_kind).push(new_handle);
             }
         }
     }
 
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -8,16 +8,17 @@ use euclid::{Point2D, Rect, Size2D, Type
 use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D, TypedScale};
 use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
 use num_traits::Zero;
 use plane_split::{Clipper, Polygon};
 use std::{i32, f32, fmt, ptr};
 use std::borrow::Cow;
 use std::os::raw::c_void;
 use std::sync::Arc;
+use std::mem::replace;
 
 
 // Matches the definition of SK_ScalarNearlyZero in Skia.
 const NEARLY_ZERO: f32 = 1.0 / 4096.0;
 
 /// A typesafe helper that separates new value construction from
 /// vector growing, allowing LLVM to ideally construct the element in place.
 pub struct Allocation<'a, T: 'a> {
@@ -56,16 +57,24 @@ impl<'a, T> VecEntry<'a, T> {
 }
 
 pub trait VecHelper<T> {
     /// Growns the vector by a single entry, returning the allocation.
     fn alloc(&mut self) -> Allocation<T>;
     /// Either returns an existing elemenet, or grows the vector by one.
     /// Doesn't expect indices to be higher than the current length.
     fn entry(&mut self, index: usize) -> VecEntry<T>;
+
+    /// Equivalent to `mem::replace(&mut vec, Vec::new())`
+    fn take(&mut self) -> Self;
+
+    /// Functionally equivalent to `mem::replace(&mut vec, Vec::new())` but tries
+    /// to keep the allocation in the caller if it is empty or replace it with a
+    /// pre-allocated vector.
+    fn take_and_preallocate(&mut self) -> Self;
 }
 
 impl<T> VecHelper<T> for Vec<T> {
     fn alloc(&mut self) -> Allocation<T> {
         let index = self.len();
         if self.capacity() == index {
             self.reserve(1);
         }
@@ -80,16 +89,29 @@ impl<T> VecHelper<T> for Vec<T> {
             VecEntry::Occupied(unsafe {
                 self.get_unchecked_mut(index)
             })
         } else {
             assert_eq!(index, self.len());
             VecEntry::Vacant(self.alloc())
         }
     }
+
+    fn take(&mut self) -> Self {
+        replace(self, Vec::new())
+    }
+
+    fn take_and_preallocate(&mut self) -> Self {
+        let len = self.len();
+        if len == 0 {
+            self.clear();
+            return Vec::new();
+        }
+        replace(self, Vec::with_capacity(len + 8))
+    }
 }
 
 
 // Represents an optimized transform where there is only
 // a scale and translation (which are guaranteed to maintain
 // an axis align rectangle under transformation). The
 // scaling is applied first, followed by the translation.
 // TODO(gw): We should try and incorporate F <-> T units here,
--- a/gfx/wr/webrender_api/src/font.rs
+++ b/gfx/wr/webrender_api/src/font.rs
@@ -343,17 +343,17 @@ impl Default for FontInstancePlatformOpt
         FontInstancePlatformOptions {
             lcd_filter: FontLCDFilter::Default,
             hinting: FontHinting::LCD,
         }
     }
 }
 
 #[repr(C)]
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Ord, PartialOrd)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Ord, PartialOrd, MallocSizeOf)]
 pub struct FontInstanceKey(pub IdNamespace, pub u32);
 
 impl FontInstanceKey {
     pub fn new(namespace: IdNamespace, key: u32) -> FontInstanceKey {
         FontInstanceKey(namespace, key)
     }
 }
 
--- a/js/src/jit-test/tests/wasm/streaming.js
+++ b/js/src/jit-test/tests/wasm/streaming.js
@@ -1,10 +1,12 @@
 // |jit-test| skip-if: !wasmStreamingIsSupported()
 
+load(libdir + "wasm-binary.js");
+
 function testInstantiate(source, importObj, exportName, expectedValue) {
     var result;
     WebAssembly.instantiateStreaming(code, importObj).then(r => { result = r });
     drainJobQueue();
     assertEq(result !== undefined, true);
     assertEq(result.module instanceof WebAssembly.Module, true);
     assertEq(result.instance instanceof WebAssembly.Instance, true);
     assertEq(result.instance.exports[exportName](), expectedValue);
@@ -84,8 +86,11 @@ for (var i = 0; i < 10; i++)
     assertEq(results[i].instance.exports.run(), 5050);
 
 // No code section, but data section:
 var code = wasmTextToBinary('(module (memory (import "js" "mem") 1) (data (i32.const 0) "a"))');
 var mem = new WebAssembly.Memory({initial:1});
 WebAssembly.instantiateStreaming(code, {js:{mem}});
 drainJobQueue();
 assertEq(new Uint8Array(mem.buffer)[0], 97);
+
+// Junk section before code section.
+testFailBoth(moduleWithSections([{name: 100, body: [1, 2, 3]}, bodySection([])]), WebAssembly.CompileError);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1546,16 +1546,27 @@ MOZ_MUST_USE bool BaselineCompilerCodeGe
   if (handler.script()->hasFlag(flag) == value) {
     return emit();
   }
   return true;
 }
 
 template <>
 template <typename F>
+MOZ_MUST_USE bool BaselineCompilerCodeGen::emitTestScriptFlag(
+    JSScript::MutableFlags flag, bool value, const F& emit,
+    Register scratch) {
+  if (handler.script()->hasFlag(flag) == value) {
+    return emit();
+  }
+  return true;
+}
+
+template <>
+template <typename F>
 MOZ_MUST_USE bool BaselineInterpreterCodeGen::emitTestScriptFlag(
     JSScript::ImmutableFlags flag, bool value, const F& emit,
     Register scratch) {
   Label done;
   loadScript(scratch);
   masm.branchTest32(value ? Assembler::Zero : Assembler::NonZero,
                     Address(scratch, JSScript::offsetOfImmutableFlags()),
                     Imm32(uint32_t(flag)), &done);
@@ -1564,16 +1575,36 @@ MOZ_MUST_USE bool BaselineInterpreterCod
       return false;
     }
   }
 
   masm.bind(&done);
   return true;
 }
 
+template <>
+template <typename F>
+MOZ_MUST_USE bool BaselineInterpreterCodeGen::emitTestScriptFlag(
+    JSScript::MutableFlags flag, bool value, const F& emit,
+    Register scratch) {
+  Label done;
+  loadScript(scratch);
+  masm.branchTest32(value ? Assembler::Zero : Assembler::NonZero,
+                    Address(scratch, JSScript::offsetOfMutableFlags()),
+                    Imm32(uint32_t(flag)), &done);
+  {
+    if (!emit()) {
+      return false;
+    }
+  }
+
+  masm.bind(&done);
+  return true;
+}
+
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_GOTO() {
   frame.syncStack(0);
   emitJump();
   return true;
 }
 
 template <typename Handler>
@@ -1717,17 +1748,17 @@ bool BaselineCodeGen<Handler>::emit_JSOP
     return false;
   }
 
   auto incCounter = [this]() {
     masm.inc64(
         AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
     return true;
   };
-  return emitTestScriptFlag(JSScript::ImmutableFlags::TrackRecordReplayProgress,
+  return emitTestScriptFlag(JSScript::MutableFlags::TrackRecordReplayProgress,
                             true, incCounter, R2.scratchReg());
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_VOID() {
   frame.pop();
   frame.push(UndefinedValue());
   return true;
@@ -5850,17 +5881,17 @@ bool BaselineCodeGen<Handler>::emitProlo
   // on a bogus EnvironmentChain value in the frame.
   emitPreInitEnvironmentChain(R1.scratchReg());
 
   auto incCounter = [this]() {
     masm.inc64(
         AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
     return true;
   };
-  if (!emitTestScriptFlag(JSScript::ImmutableFlags::TrackRecordReplayProgress,
+  if (!emitTestScriptFlag(JSScript::MutableFlags::TrackRecordReplayProgress,
                           true, incCounter, R2.scratchReg())) {
     return false;
   }
 
   // Functions with a large number of locals require two stack checks.
   // The VMCall for a fallible stack check can only occur after the
   // env chain has been initialized, as that is required for proper
   // exception handling if the VMCall returns false.  The env chain
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -374,16 +374,20 @@ class BaselineCodeGen {
                                        const F1& ifSet, const F2& ifNotSet,
                                        Register scratch);
 
   // If |script->hasFlag(flag) == value|, execute the code emitted by |emit|.
   template <typename F>
   MOZ_MUST_USE bool emitTestScriptFlag(JSScript::ImmutableFlags flag,
                                        bool value, const F& emit,
                                        Register scratch);
+  template <typename F>
+  MOZ_MUST_USE bool emitTestScriptFlag(JSScript::MutableFlags flag,
+                                       bool value, const F& emit,
+                                       Register scratch);
 
   MOZ_MUST_USE bool emitCheckThis(ValueOperand val, bool reinit = false);
   void emitLoadReturnValue(ValueOperand val);
 
   MOZ_MUST_USE bool emitNextIC();
   MOZ_MUST_USE bool emitInterruptCheck();
   MOZ_MUST_USE bool emitWarmUpCounterIncrement();
   MOZ_MUST_USE bool emitTraceLoggerResume(Register script,
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -178,16 +178,17 @@ class CompileInfo {
       : script_(script),
         fun_(fun),
         osrPc_(osrPc),
         analysisMode_(analysisMode),
         scriptNeedsArgsObj_(scriptNeedsArgsObj),
         hadOverflowBailout_(script->hadOverflowBailout()),
         hadFrequentBailouts_(script->hadFrequentBailouts()),
         mayReadFrameArgsDirectly_(script->mayReadFrameArgsDirectly()),
+        trackRecordReplayProgress_(script->trackRecordReplayProgress()),
         inlineScriptTree_(inlineScriptTree) {
     MOZ_ASSERT_IF(osrPc, JSOp(*osrPc) == JSOP_LOOPENTRY);
 
     // The function here can flow in from anywhere so look up the canonical
     // function to ensure that we do not try to embed a nursery pointer in
     // jit-code. Precisely because it can flow in from anywhere, it's not
     // guaranteed to be non-lazy. Hence, don't access its script!
     if (fun_) {
@@ -238,16 +239,17 @@ class CompileInfo {
       : script_(nullptr),
         fun_(nullptr),
         osrPc_(nullptr),
         analysisMode_(Analysis_None),
         scriptNeedsArgsObj_(false),
         hadOverflowBailout_(false),
         hadFrequentBailouts_(false),
         mayReadFrameArgsDirectly_(false),
+        trackRecordReplayProgress_(false),
         inlineScriptTree_(nullptr),
         needsBodyEnvironmentObject_(false),
         funNeedsSomeEnvironmentObject_(false) {
     nimplicit_ = 0;
     nargs_ = 0;
     nlocals_ = nlocals;
     nstack_ = 1; /* For FunctionCompiler::pushPhiInput/popPhiOutput */
     nslots_ = nlocals_ + nstack_;
@@ -494,16 +496,17 @@ class CompileInfo {
     return true;
   }
 
   // Check previous bailout states to prevent doing the same bailout in the
   // next compilation.
   bool hadOverflowBailout() const { return hadOverflowBailout_; }
   bool hadFrequentBailouts() const { return hadFrequentBailouts_; }
   bool mayReadFrameArgsDirectly() const { return mayReadFrameArgsDirectly_; }
+  bool trackRecordReplayProgress() const { return trackRecordReplayProgress_; }
 
  private:
   unsigned nimplicit_;
   unsigned nargs_;
   unsigned nlocals_;
   unsigned nstack_;
   unsigned nslots_;
   mozilla::Maybe<unsigned> thisSlotForDerivedClassConstructor_;
@@ -518,16 +521,17 @@ class CompileInfo {
   bool scriptNeedsArgsObj_;
 
   // Record the state of previous bailouts in order to prevent compiling the
   // same function identically the next time.
   bool hadOverflowBailout_;
   bool hadFrequentBailouts_;
 
   bool mayReadFrameArgsDirectly_;
+  bool trackRecordReplayProgress_;
 
   InlineScriptTree* inlineScriptTree_;
 
   // Whether a script needs environments within its body. This informs us
   // that the environment chain is not easy to reconstruct.
   bool needsBodyEnvironmentObject_;
   bool funNeedsSomeEnvironmentObject_;
 };
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -116,17 +116,17 @@ bool CodeGeneratorShared::generateProlog
   masm.pushReturnAddress();
 #endif
 
   // If profiling, save the current frame pointer to a per-thread global field.
   if (isProfilerInstrumentationEnabled()) {
     masm.profilerEnterFrame(masm.getStackPointer(), CallTempReg0);
   }
 
-  if (gen->info().script()->trackRecordReplayProgress()) {
+  if (gen->info().trackRecordReplayProgress()) {
     masm.inc64(
         AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
   }
 
   // Ensure that the Ion frame is properly aligned.
   masm.assertStackAlignment(JitStackAlignment, 0);
 
   // Note that this automatically sets MacroAssembler::framePushed().
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -3293,17 +3293,17 @@ static bool ShouldTrackRecordReplayProgr
 
   // Record compile options that get checked at runtime.
   script->setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
   script->setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
   script->setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
   script->setFlag(MutableFlags::HideScriptFromDebugger,
                   options.hideScriptFromDebugger);
 
-  script->setFlag(ImmutableFlags::TrackRecordReplayProgress,
+  script->setFlag(MutableFlags::TrackRecordReplayProgress,
                   ShouldTrackRecordReplayProgress(script));
 
   if (cx->runtime()->lcovOutput().isEnabled()) {
     if (!script->initScriptName(cx)) {
       return nullptr;
     }
   }
 
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1720,25 +1720,21 @@ class JSScript : public js::gc::TenuredC
     HasRest = 1 << 20,
 
     // See comments below.
     ArgsHasVarBinding = 1 << 21,
 
     // Script came from eval().
     IsForEval = 1 << 22,
 
-    // Whether the record/replay execution progress counter (see RecordReplay.h)
-    // should be updated as this script runs.
-    TrackRecordReplayProgress = 1 << 23,
-
     // Whether this is a top-level module script.
-    IsModule = 1 << 24,
+    IsModule = 1 << 23,
 
     // Whether this function needs a call object or named lambda environment.
-    NeedsFunctionEnvironmentObjects = 1 << 25,
+    NeedsFunctionEnvironmentObjects = 1 << 24,
   };
 
  private:
   // Note: don't make this a bitfield! It makes it hard to read these flags
   // from JIT code.
   uint32_t immutableFlags_ = 0;
 
   // Mutable flags typically store information about runtime or deoptimization
@@ -1749,17 +1745,21 @@ class JSScript : public js::gc::TenuredC
     WarnedAboutUndefinedProp = 1 << 0,
 
     // If treatAsRunOnce, whether script has executed.
     HasRunOnce = 1 << 1,
 
     // Script has been reused for a clone.
     HasBeenCloned = 1 << 2,
 
-    // (1 << 3) and (1 << 4) are unused.
+    // Whether the record/replay execution progress counter (see RecordReplay.h)
+    // should be updated as this script runs.
+    TrackRecordReplayProgress = 1 << 3,
+
+    // (1 << 4) is unused.
 
     // Script has an entry in Realm::scriptCountsMap.
     HasScriptCounts = 1 << 5,
 
     // Script has an entry in Realm::debugScriptMap.
     HasDebugScript = 1 << 6,
 
     // (1 << 7) and (1 << 8) are unused.
@@ -1891,16 +1891,17 @@ class JSScript : public js::gc::TenuredC
   // Initialize the Function.prototype script.
   static bool initFunctionPrototype(JSContext* cx, js::HandleScript script,
                                     JS::HandleFunction functionProto);
 
 #ifdef DEBUG
  private:
   // Assert that jump targets are within the code array of the script.
   void assertValidJumpTargets() const;
+ public:
 #endif
 
   // MutableFlags accessors.
 
   MOZ_MUST_USE bool hasFlag(MutableFlags flag) const {
     return mutableFlags_ & uint32_t(flag);
   }
   void setFlag(MutableFlags flag) { mutableFlags_ |= uint32_t(flag); }
@@ -2795,17 +2796,17 @@ class JSScript : public js::gc::TenuredC
     explicit operator bool() const { return script_; }
 
    private:
     void holdScript(JS::HandleFunction fun);
     void dropScript();
   };
 
   bool trackRecordReplayProgress() const {
-    return hasFlag(ImmutableFlags::TrackRecordReplayProgress);
+    return hasFlag(MutableFlags::TrackRecordReplayProgress);
   }
 };
 
 /* If this fails, add/remove padding within JSScript. */
 static_assert(
     sizeof(JSScript) % js::gc::CellAlignBytes == 0,
     "Size of JSScript must be an integral multiple of js::gc::CellAlignBytes");
 
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -699,68 +699,73 @@ static SharedBytes CreateBytecode(const 
 
 SharedModule wasm::CompileStreaming(
     const CompileArgs& args, const Bytes& envBytes, const Bytes& codeBytes,
     const ExclusiveBytesPtr& codeBytesEnd,
     const ExclusiveStreamEndData& exclusiveStreamEnd,
     const Atomic<bool>& cancelled, UniqueChars* error,
     UniqueCharsVector* warnings) {
   CompilerEnvironment compilerEnv(args);
-  Maybe<ModuleEnvironment> env;
+  ModuleEnvironment env(
+      args.gcEnabled, &compilerEnv,
+      args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
 
   {
     Decoder d(envBytes, 0, error, warnings);
 
-    env.emplace(args.gcEnabled, &compilerEnv,
-                args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
-    if (!DecodeModuleEnvironment(d, env.ptr())) {
+    if (!DecodeModuleEnvironment(d, &env)) {
       return nullptr;
     }
 
-    MOZ_ASSERT(d.done());
+    if (!env.codeSection) {
+      d.fail("unknown section before code section");
+      return nullptr;
+    }
+
+    MOZ_RELEASE_ASSERT(env.codeSection->size == codeBytes.length());
+    MOZ_RELEASE_ASSERT(d.done());
   }
 
-  ModuleGenerator mg(args, env.ptr(), &cancelled, error);
+  ModuleGenerator mg(args, &env, &cancelled, error);
   if (!mg.init()) {
     return nullptr;
   }
 
   {
-    MOZ_ASSERT(env->codeSection->size == codeBytes.length());
-    StreamingDecoder d(*env, codeBytes, codeBytesEnd, cancelled, error,
+    StreamingDecoder d(env, codeBytes, codeBytesEnd, cancelled, error,
                        warnings);
 
-    if (!DecodeCodeSection(*env, d, mg)) {
+    if (!DecodeCodeSection(env, d, mg)) {
       return nullptr;
     }
 
-    MOZ_ASSERT(d.done());
+    MOZ_RELEASE_ASSERT(d.done());
   }
 
   {
     auto streamEnd = exclusiveStreamEnd.lock();
     while (!streamEnd->reached) {
       if (cancelled) {
         return nullptr;
       }
       streamEnd.wait();
     }
   }
 
   const StreamEndData& streamEnd = exclusiveStreamEnd.lock();
   const Bytes& tailBytes = *streamEnd.tailBytes;
 
   {
-    Decoder d(tailBytes, env->codeSection->end(), error, warnings);
+    Decoder d(tailBytes, env.codeSection->end(), error, warnings);
 
-    if (!DecodeModuleTail(d, env.ptr())) {
+    if (!DecodeModuleTail(d, &env)) {
       return nullptr;
     }
 
-    MOZ_ASSERT(d.done());
+    MOZ_RELEASE_ASSERT(d.done());
   }
 
   SharedBytes bytecode = CreateBytecode(envBytes, codeBytes, tailBytes, error);
   if (!bytecode) {
     return nullptr;
   }
 
   return mg.finishModule(*bytecode, streamEnd.tier2Listener);
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -139,16 +139,26 @@ class Watchdog {
     }
 
     mWakeup = PR_NewCondVar(mLock);
     if (!mWakeup) {
       MOZ_CRASH("PR_NewCondVar failed.");
     }
 
     {
+      // Make sure the debug service is instantiated before we create the
+      // watchdog thread, since we intentionally try to keep the thread's stack
+      // segment as small as possible. It isn't always large enough to
+      // instantiate a new service, and even when it is, we don't want fault in
+      // extra pages if we can avoid it.
+      nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
+      Unused << dbg;
+    }
+
+    {
       AutoLockWatchdog lock(this);
 
       // Gecko uses thread private for accounting and has to clean up at thread
       // exit. Therefore, even though we don't have a return value from the
       // watchdog, we need to join it on shutdown.
       mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this,
                                 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
                                 PR_JOINABLE_THREAD, kWatchdogStackSize);
--- a/layout/generic/BRFrame.cpp
+++ b/layout/generic/BRFrame.cpp
@@ -40,17 +40,18 @@ class BRFrame final : public nsFrame {
   virtual FrameSearchResult PeekOffsetCharacter(
       bool aForward, int32_t* aOffset,
       PeekOffsetCharacterOptions aOptions =
           PeekOffsetCharacterOptions()) override;
   virtual FrameSearchResult PeekOffsetWord(bool aForward,
                                            bool aWordSelectEatSpace,
                                            bool aIsKeyboardSelect,
                                            int32_t* aOffset,
-                                           PeekWordState* aState) override;
+                                           PeekWordState* aState,
+                                           bool aTrimSpaces) override;
 
   virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
                       const ReflowInput& aReflowInput,
                       nsReflowStatus& aStatus) override;
   virtual void AddInlineMinISize(gfxContext* aRenderingContext,
                                  InlineMinISizeData* aData) override;
   virtual void AddInlinePrefISize(gfxContext* aRenderingContext,
                                   InlinePrefISizeData* aData) override;
@@ -233,17 +234,18 @@ nsIFrame::FrameSearchResult BRFrame::Pee
   // Keep going. The actual line jumping will stop us.
   return CONTINUE;
 }
 
 nsIFrame::FrameSearchResult BRFrame::PeekOffsetWord(bool aForward,
                                                     bool aWordSelectEatSpace,
                                                     bool aIsKeyboardSelect,
                                                     int32_t* aOffset,
-                                                    PeekWordState* aState) {
+                                                    PeekWordState* aState,
+                                                    bool aTrimSpaces) {
   NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
   // Keep going. The actual line jumping will stop us.
   return CONTINUE;
 }
 
 #ifdef ACCESSIBILITY
 a11y::AccType BRFrame::AccessibleType() {
   nsIContent* parent = mContent->GetParent();
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -8142,17 +8142,18 @@ nsresult nsIFrame::PeekOffset(nsPeekOffs
       int32_t offsetAdjustment = 0;
       bool done = false;
       while (!done) {
         bool movingInFrameDirection =
             IsMovingInFrameDirection(current, aPos->mDirection, aPos->mVisual);
 
         done = current->PeekOffsetWord(
                    movingInFrameDirection, wordSelectEatSpace,
-                   aPos->mIsKeyboardSelect, &offset, &state) == FOUND;
+                   aPos->mIsKeyboardSelect, &offset, &state,
+                   aPos->mTrimSpaces) == FOUND;
 
         if (!done) {
           nsIFrame* nextFrame;
           int32_t nextFrameOffset;
           bool jumpedLine, movedOverNonSelectableText;
           result = current->GetFrameFromDirection(
               aPos->mDirection, aPos->mVisual, aPos->mJumpLines,
               aPos->mScrollViewStop, aPos->mForceEditableRegion, &nextFrame,
@@ -8393,17 +8394,18 @@ nsIFrame::FrameSearchResult nsFrame::Pee
   }
   return CONTINUE;
 }
 
 nsIFrame::FrameSearchResult nsFrame::PeekOffsetWord(bool aForward,
                                                     bool aWordSelectEatSpace,
                                                     bool aIsKeyboardSelect,
                                                     int32_t* aOffset,
-                                                    PeekWordState* aState) {
+                                                    PeekWordState* aState,
+                                                    bool aTrimSpaces) {
   NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
   int32_t startOffset = *aOffset;
   // This isn't text, so truncate the context
   aState->mContext.Truncate();
   if (startOffset < 0) startOffset = 1;
   if (aForward == (startOffset == 0)) {
     // We're before the frame and moving forward, or after it and moving
     // backwards. If we're looking for non-whitespace, we found it (without
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -211,17 +211,18 @@ class nsFrame : public nsBox {
   FrameSearchResult PeekOffsetNoAmount(bool aForward,
                                        int32_t* aOffset) override;
   FrameSearchResult PeekOffsetCharacter(
       bool aForward, int32_t* aOffset,
       PeekOffsetCharacterOptions aOptions =
           PeekOffsetCharacterOptions()) override;
   FrameSearchResult PeekOffsetWord(bool aForward, bool aWordSelectEatSpace,
                                    bool aIsKeyboardSelect, int32_t* aOffset,
-                                   PeekWordState* aState) override;
+                                   PeekWordState* aState, bool aTrimSpaces)
+                                   override;
   /**
    * Check whether we should break at a boundary between punctuation and
    * non-punctuation. Only call it at a punctuation boundary
    * (i.e. exactly one of the previous and next characters are punctuation).
    * @param aForward true if we're moving forward in content order
    * @param aPunctAfter true if the next character is punctuation
    * @param aWhitespaceAfter true if the next character is whitespace
    */
--- a/layout/generic/nsFrameSelection.cpp
+++ b/layout/generic/nsFrameSelection.cpp
@@ -106,23 +106,25 @@ static void printRange(nsRange* aDomRang
 
 //#define DEBUG_TABLE_SELECTION 1
 
 nsPeekOffsetStruct::nsPeekOffsetStruct(
     nsSelectionAmount aAmount, nsDirection aDirection, int32_t aStartOffset,
     nsPoint aDesiredPos, bool aJumpLines, bool aScrollViewStop,
     bool aIsKeyboardSelect, bool aVisual, bool aExtend,
     ForceEditableRegion aForceEditableRegion,
-    EWordMovementType aWordMovementType)
+    EWordMovementType aWordMovementType,
+    bool aTrimSpaces)
     : mAmount(aAmount),
       mDirection(aDirection),
       mStartOffset(aStartOffset),
       mDesiredPos(aDesiredPos),
       mWordMovementType(aWordMovementType),
       mJumpLines(aJumpLines),
+      mTrimSpaces(aTrimSpaces),
       mScrollViewStop(aScrollViewStop),
       mIsKeyboardSelect(aIsKeyboardSelect),
       mVisual(aVisual),
       mExtend(aExtend),
       mForceEditableRegion(aForceEditableRegion == ForceEditableRegion::Yes),
       mResultContent(),
       mResultFrame(nullptr),
       mContentOffset(0),
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.h
@@ -67,17 +67,18 @@ struct MOZ_STACK_CLASS nsPeekOffsetStruc
     Yes,
   };
 
   nsPeekOffsetStruct(
       nsSelectionAmount aAmount, nsDirection aDirection, int32_t aStartOffset,
       nsPoint aDesiredPos, bool aJumpLines, bool aScrollViewStop,
       bool aIsKeyboardSelect, bool aVisual, bool aExtend,
       ForceEditableRegion = ForceEditableRegion::No,
-      mozilla::EWordMovementType aWordMovementType = mozilla::eDefaultBehavior);
+      mozilla::EWordMovementType aWordMovementType = mozilla::eDefaultBehavior,
+      bool aTrimSpaces = true);
 
   // Note: Most arguments (input and output) are only used with certain values
   // of mAmount. These values are indicated for each argument below.
   // Arguments with no such indication are used with all values of mAmount.
 
   /*** Input arguments ***/
   // Note: The value of some of the input arguments may be changed upon exit.
 
@@ -114,16 +115,19 @@ struct MOZ_STACK_CLASS nsPeekOffsetStruc
   // platform-based pref "layout.word_select.eat_space_to_next_word"
   mozilla::EWordMovementType mWordMovementType;
 
   // Whether to allow jumping across line boundaries.
   //
   // Used with: eSelectCharacter, eSelectWord.
   bool mJumpLines;
 
+  // mTrimSpaces: Whether we should trim spaces at begin/end of content
+  bool mTrimSpaces;
+
   // Whether to stop when reaching a scroll view boundary.
   //
   // Used with: eSelectCharacter, eSelectWord, eSelectLine.
   bool mScrollViewStop;
 
   // Whether the peeking is done in response to a keyboard action.
   //
   // Used with: eSelectWord.
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -4483,17 +4483,18 @@ class nsIFrame : public nsQueryFrame {
       }
       mAtStart = false;
     }
   };
   virtual FrameSearchResult PeekOffsetWord(bool aForward,
                                            bool aWordSelectEatSpace,
                                            bool aIsKeyboardSelect,
                                            int32_t* aOffset,
-                                           PeekWordState* aState) = 0;
+                                           PeekWordState* aState,
+                                           bool aTrimSpaces) = 0;
 
   /**
    * Search for the first paragraph boundary before or after the given position
    * @param  aPos See description in nsFrameSelection.h. The following fields
    *              are used by this method:
    *              Input: mDirection
    *              Output: mResultContent, mContentOffset
    */
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -2941,19 +2941,19 @@ static uint32_t GetEndOfTrimmedText(cons
     aIterator->AdvanceSkipped(-1);
     if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
       return aIterator->GetSkippedOffset() + 1;
   }
   return aStart;
 }
 
 nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets(
-    const nsTextFragment* aFrag, bool aTrimAfter, bool aPostReflow) const {
+    const nsTextFragment* aFrag, TrimmedOffsetFlags aFlags) const {
   NS_ASSERTION(mTextRun, "Need textrun here");
-  if (aPostReflow) {
+  if (!(aFlags & TrimmedOffsetFlags::kNotPostReflow)) {
     // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
     // to be set correctly.  If our parent wasn't reflowed due to the frame
     // tree being too deep then the return value doesn't matter.
     NS_ASSERTION(
         !(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
             (GetParent()->GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
         "Can only call this on frames that have been reflowed");
     NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
@@ -2961,24 +2961,28 @@ nsTextFrame::TrimmedOffsets nsTextFrame:
   }
 
   TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()};
   const nsStyleText* textStyle = StyleText();
   // Note that pre-line newlines should still allow us to trim spaces
   // for display
   if (textStyle->WhiteSpaceIsSignificant()) return offsets;
 
-  if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) {
+  if (!(aFlags & TrimmedOffsetFlags::kNoTrimBefore) &&
+      ((aFlags & TrimmedOffsetFlags::kNotPostReflow) ||
+       (GetStateBits() & TEXT_START_OF_LINE))) {
     int32_t whitespaceCount =
         GetTrimmableWhitespaceCount(aFrag, offsets.mStart, offsets.mLength, 1);
     offsets.mStart += whitespaceCount;
     offsets.mLength -= whitespaceCount;
   }
 
-  if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) {
+  if (!(aFlags & TrimmedOffsetFlags::kNoTrimAfter) &&
+      ((aFlags & TrimmedOffsetFlags::kNotPostReflow) ||
+       (GetStateBits() & TEXT_END_OF_LINE))) {
     // This treats a trailing 'pre-line' newline as trimmable. That's fine,
     // it's actually what we want since we want whitespace before it to
     // be trimmed.
     int32_t whitespaceCount = GetTrimmableWhitespaceCount(
         aFrag, offsets.GetEnd() - 1, offsets.mLength, -1);
     offsets.mLength -= whitespaceCount;
   }
   return offsets;
@@ -3707,25 +3711,28 @@ void PropertyProvider::GetHyphenationBre
         aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
       }
     }
   }
 }
 
 void PropertyProvider::InitializeForDisplay(bool aTrimAfter) {
   nsTextFrame::TrimmedOffsets trimmed =
-      mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
+      mFrame->GetTrimmedOffsets(mFrag,
+          (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::kDefaultTrimFlags :
+                        nsTextFrame::TrimmedOffsetFlags::kNoTrimAfter));
   mStart.SetOriginalOffset(trimmed.mStart);
   mLength = trimmed.mLength;
   SetupJustificationSpacing(true);
 }
 
 void PropertyProvider::InitializeForMeasure() {
   nsTextFrame::TrimmedOffsets trimmed =
-      mFrame->GetTrimmedOffsets(mFrag, true, false);
+      mFrame->GetTrimmedOffsets(mFrag,
+          nsTextFrame::TrimmedOffsetFlags::kNotPostReflow);
   mStart.SetOriginalOffset(trimmed.mStart);
   mLength = trimmed.mLength;
   SetupJustificationSpacing(false);
 }
 
 void PropertyProvider::SetupJustificationSpacing(bool aPostReflow) {
   MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
 
@@ -3733,17 +3740,19 @@ void PropertyProvider::SetupJustificatio
     return;
   }
 
   gfxSkipCharsIterator start(mStart), end(mStart);
   // We can't just use our mLength here; when InitializeForDisplay is
   // called with false for aTrimAfter, we still shouldn't be assigning
   // justification space to any trailing whitespace.
   nsTextFrame::TrimmedOffsets trimmed =
-      mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
+      mFrame->GetTrimmedOffsets(mFrag,
+          (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::kDefaultTrimFlags :
+                         nsTextFrame::TrimmedOffsetFlags::kNotPostReflow));
   end.AdvanceOriginal(trimmed.mLength);
   gfxSkipCharsIterator realEnd(end);
 
   Range range(uint32_t(start.GetOriginalOffset()),
               uint32_t(end.GetOriginalOffset()));
   nsTArray<JustificationAssignment> assignments;
   JustificationInfo info = ComputeJustification(range, &assignments);
 
@@ -7699,17 +7708,17 @@ nsresult nsTextFrame::GetChildFrameConta
 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward,
                                                             int32_t* aOffset) {
   NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),
                "aOffset out of range");
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun) return CONTINUE_EMPTY;
 
-  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true);
+  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText());
   // Check whether there are nonskipped characters in the trimmmed range
   return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
           iter.ConvertOriginalToSkipped(trimmed.mStart))
              ? FOUND
              : CONTINUE;
 }
 
 /**
@@ -7718,17 +7727,18 @@ nsIFrame::FrameSearchResult nsTextFrame:
  * to see if it's whitespace (as far as selection/caret movement is concerned),
  * or punctuation, or if there is a word break before the cluster. ("Before"
  * is interpreted according to aDirection, so if aDirection is -1, "before"
  * means actually *after* the cluster content.)
  */
 class MOZ_STACK_CLASS ClusterIterator {
  public:
   ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
-                  int32_t aDirection, nsString& aContext);
+                  int32_t aDirection, nsString& aContext,
+                  bool aTrimSpaces = true);
 
   bool NextCluster();
   bool IsWhitespace();
   bool IsPunctuation();
   bool HaveWordBreakBefore() { return mHaveWordBreak; }
 
   // Get the charIndex that corresponds to the "before" side of the current
   // character, according to the direction of iteration: so for a forward
@@ -7822,17 +7832,18 @@ nsIFrame::FrameSearchResult nsTextFrame:
     if (selectStyle == StyleUserSelect::All) {
       return CONTINUE_UNSELECTABLE;
     }
   }
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun) return CONTINUE_EMPTY;
 
-  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false);
+  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(),
+                                             TrimmedOffsetFlags::kNoTrimAfter);
 
   // A negative offset means "end of frame".
   int32_t startOffset =
       GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
 
   if (!aForward) {
     // If at the beginning of the line, look at the previous continuation
     for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
@@ -7942,30 +7953,34 @@ bool ClusterIterator::NextCluster() {
     if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
       mHaveWordBreak = true;
     }
     if (!keepGoing) return true;
   }
 }
 
 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
-                                 int32_t aDirection, nsString& aContext)
+                                 int32_t aDirection, nsString& aContext,
+                                 bool aTrimSpaces)
     : mTextFrame(aTextFrame),
       mDirection(aDirection),
       mCharIndex(-1),
       mHaveWordBreak(false) {
   mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
   if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
     mDirection = 0;  // signal failure
     return;
   }
   mIterator.SetOriginalOffset(aPosition);
 
   mFrag = aTextFrame->GetContent()->GetText();
-  mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true);
+  mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag,
+      aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::kDefaultTrimFlags :
+                    nsTextFrame::TrimmedOffsetFlags::kNoTrimAfter |
+                        nsTextFrame::TrimmedOffsetFlags::kNoTrimBefore);
 
   int32_t textOffset = aTextFrame->GetContentOffset();
   int32_t textLen = aTextFrame->GetContentLength();
   if (!mWordBreaks.AppendElements(textLen + 1)) {
     mDirection = 0;  // signal failure
     return;
   }
   memset(mWordBreaks.Elements(), false, (textLen + 1) * sizeof(bool));
@@ -7993,27 +8008,28 @@ ClusterIterator::ClusterIterator(nsTextF
     mWordBreaks[i] |= wordBreaker->BreakInBetween(
         aContext.get(), indexInText, aContext.get() + indexInText,
         aContext.Length() - indexInText);
   }
 }
 
 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord(
     bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
-    int32_t* aOffset, PeekWordState* aState) {
+    int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
   int32_t contentLength = GetContentLength();
   NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
 
   StyleUserSelect selectStyle;
   IsSelectable(&selectStyle);
   if (selectStyle == StyleUserSelect::All) return CONTINUE_UNSELECTABLE;
 
   int32_t offset =
       GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
-  ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
+  ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext,
+                        aTrimSpaces);
 
   if (!cIter.NextCluster()) return CONTINUE_EMPTY;
 
   do {
     bool isPunctuation = cIter.IsPunctuation();
     bool isWhitespace = cIter.IsWhitespace();
     bool isWordBreakBefore = cIter.HaveWordBreakBefore();
     if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
@@ -9557,17 +9573,17 @@ nsTextFrame::TrimOutput nsTextFrame::Tri
 
   gfxSkipCharsIterator start =
       EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
   NS_ENSURE_TRUE(mTextRun, result);
 
   uint32_t trimmedStart = start.GetSkippedOffset();
 
   const nsTextFragment* frag = mContent->GetText();
-  TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true);
+  TrimmedOffsets trimmed = GetTrimmedOffsets(frag);
   gfxSkipCharsIterator trimmedEndIter = start;
   const nsStyleText* textStyle = StyleText();
   gfxFloat delta = 0;
   uint32_t trimmedEnd =
       trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
 
   if (!(GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) &&
       trimmed.GetEnd() < GetContentEnd()) {
@@ -9763,17 +9779,19 @@ nsIFrame::RenderedText nsTextFrame::GetR
     } else {
       // Weird situation where we have a line layout without a block.
       // No soft breaks occur in this situation.
       trimAfter = true;
     }
 
     // Skip to the start of the text run, past ignored chars at start of line
     TrimmedOffsets trimmedOffsets =
-        textFrame->GetTrimmedOffsets(textFrag, trimAfter);
+        textFrame->GetTrimmedOffsets(textFrag,
+            (trimAfter ? TrimmedOffsetFlags::kDefaultTrimFlags :
+                         TrimmedOffsetFlags::kNoTrimAfter));
     bool trimmedSignificantNewline =
         trimmedOffsets.GetEnd() < GetContentEnd() &&
         HasSignificantTerminalNewline();
     uint32_t skippedToRenderedStringOffset =
         offsetInRenderedString -
         tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
     uint32_t nextOffsetInRenderedString =
         tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
@@ -10102,11 +10120,12 @@ bool nsTextFrame::HasNonSuppressedText()
                       NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) {
     return true;
   }
 
   if (!GetTextRun(nsTextFrame::eInflated)) {
     return false;
   }
 
-  TrimmedOffsets offsets = GetTrimmedOffsets(mContent->GetText(), false);
+  TrimmedOffsets offsets = GetTrimmedOffsets(mContent->GetText(),
+                                             TrimmedOffsetFlags::kNoTrimAfter);
   return offsets.mLength != 0;
 }
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -178,17 +178,18 @@ class nsTextFrame : public nsFrame {
                         SelectionType aSelectionType);
 
   FrameSearchResult PeekOffsetNoAmount(bool aForward, int32_t* aOffset) final;
   FrameSearchResult PeekOffsetCharacter(
       bool aForward, int32_t* aOffset,
       PeekOffsetCharacterOptions aOptions = PeekOffsetCharacterOptions()) final;
   FrameSearchResult PeekOffsetWord(bool aForward, bool aWordSelectEatSpace,
                                    bool aIsKeyboardSelect, int32_t* aOffset,
-                                   PeekWordState* aState) final;
+                                   PeekWordState* aState, bool aTrimSpaces)
+                                   final;
 
   nsresult CheckVisibility(nsPresContext* aContext, int32_t aStartIndex,
                            int32_t aEndIndex, bool aRecurse, bool* aFinished,
                            bool* _retval) final;
 
   // Flags for aSetLengthFlags
   enum { ALLOW_FRAME_CREATION_AND_DESTRUCTION = 0x01 };
 
@@ -568,18 +569,24 @@ class nsTextFrame : public nsFrame {
   // Get the DOM content range mapped by this frame after excluding
   // whitespace subject to start-of-line and end-of-line trimming.
   // The textrun must have been created before calling this.
   struct TrimmedOffsets {
     int32_t mStart;
     int32_t mLength;
     int32_t GetEnd() const { return mStart + mLength; }
   };
-  TrimmedOffsets GetTrimmedOffsets(const nsTextFragment* aFrag, bool aTrimAfter,
-                                   bool aPostReflow = true) const;
+  enum class TrimmedOffsetFlags : uint8_t {
+    kDefaultTrimFlags = 0,
+    kNotPostReflow = 1 << 0,
+    kNoTrimAfter = 1 << 1,
+    kNoTrimBefore = 1 << 2
+  };
+  TrimmedOffsets GetTrimmedOffsets(const nsTextFragment* aFrag,
+      TrimmedOffsetFlags aFlags = TrimmedOffsetFlags::kDefaultTrimFlags) const;
 
   // Similar to Reflow(), but for use from nsLineLayout
   void ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
                   DrawTarget* aDrawTarget, ReflowOutput& aMetrics,
                   nsReflowStatus& aStatus);
 
   bool IsFloatingFirstLetterChild() const;
 
@@ -801,9 +808,11 @@ class nsTextFrame : public nsFrame {
   void UpdateIteratorFromOffset(const PropertyProvider& aProperties,
                                 int32_t& aInOffset,
                                 gfxSkipCharsIterator& aIter);
 
   nsPoint GetPointFromIterator(const gfxSkipCharsIterator& aIter,
                                PropertyProvider& aProperties);
 };
 
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::TrimmedOffsetFlags)
+
 #endif
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -979,17 +979,17 @@ void TextRenderedRun::GetClipEdges(nscoo
   // Get the offset/length of the whole nsTextFrame.
   uint32_t frameOffset = mFrame->GetContentOffset();
   uint32_t frameLength = mFrame->GetContentLength();
 
   // Trim the whole-nsTextFrame offset/length to remove any leading/trailing
   // white space, as the nsTextFrame when painting does not include them when
   // interpreting clip edges.
   nsTextFrame::TrimmedOffsets trimmedOffsets =
-      mFrame->GetTrimmedOffsets(mFrame->GetContent()->GetText(), true);
+      mFrame->GetTrimmedOffsets(mFrame->GetContent()->GetText());
   TrimOffsets(frameOffset, frameLength, trimmedOffsets);
 
   // Convert the trimmed whole-nsTextFrame offset/length into skipped
   // characters.
   Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength);
 
   // Measure the advance width in the text run between the start of
   // frame's content and the start of the rendered run's content,
@@ -1884,17 +1884,17 @@ TextRenderedRun TextRenderedRunIterator:
     baseline = GetBaselinePosition(
         frame, frame->GetTextRun(nsTextFrame::eInflated),
         mFrameIterator.DominantBaseline(), mFontSizeScaleFactor);
 
     // Trim the offset/length to remove any leading/trailing white space.
     uint32_t untrimmedOffset = offset;
     uint32_t untrimmedLength = length;
     nsTextFrame::TrimmedOffsets trimmedOffsets =
-        frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true);
+        frame->GetTrimmedOffsets(frame->GetContent()->GetText());
     TrimOffsets(offset, length, trimmedOffsets);
     charIndex += offset - untrimmedOffset;
 
     // Get the position and rotation of the character that begins this
     // rendered run.
     pt = Root()->mPositions[charIndex].mPosition;
     rotate = Root()->mPositions[charIndex].mAngle;
 
@@ -2366,17 +2366,18 @@ bool CharIterator::IsOriginalCharTrimmed
     // Since we do a lot of trim checking, we cache the trimmed offsets and
     // lengths while we are in the same frame.
     mFrameForTrimCheck = TextFrame();
     uint32_t offset = mFrameForTrimCheck->GetContentOffset();
     uint32_t length = mFrameForTrimCheck->GetContentLength();
     nsIContent* content = mFrameForTrimCheck->GetContent();
     nsTextFrame::TrimmedOffsets trim = mFrameForTrimCheck->GetTrimmedOffsets(
         content->GetText(),
-        /* aTrimAfter */ true, mPostReflow);
+            (mPostReflow ? nsTextFrame::TrimmedOffsetFlags::kDefaultTrimFlags :
+                           nsTextFrame::TrimmedOffsetFlags::kNotPostReflow));
     TrimOffsets(offset, length, trim);
     mTrimmedOffset = offset;
     mTrimmedLength = length;
   }
 
   // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength
   // range and it is not a significant newline character.
   uint32_t index = mSkipCharsIterator.GetOriginalOffset();
@@ -3818,18 +3819,17 @@ nsresult SVGTextFrame::GetSubStringLengt
     const uint32_t untrimmedOffset = frame->GetContentOffset();
     const uint32_t untrimmedLength = frame->GetContentEnd() - untrimmedOffset;
 
     // Trim the offset/length to remove any leading/trailing white space.
     uint32_t trimmedOffset = untrimmedOffset;
     uint32_t trimmedLength = untrimmedLength;
     nsTextFrame::TrimmedOffsets trimmedOffsets =
         frame->GetTrimmedOffsets(frame->GetContent()->GetText(),
-                                 /* aTrimAfter */ true,
-                                 /* aPostReflow */ false);
+            nsTextFrame::TrimmedOffsetFlags::kNotPostReflow);
     TrimOffsets(trimmedOffset, trimmedLength, trimmedOffsets);
 
     textElementCharIndex += trimmedOffset - untrimmedOffset;
 
     if (textElementCharIndex >= charnum + nchars) {
       break;  // we're past the end of the substring
     }
 
@@ -4427,17 +4427,17 @@ void SVGTextFrame::DetermineCharPosition
 
     // Any characters not in a frame, e.g. when display:none.
     for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
       aPositions.AppendElement(position);
     }
 
     // Any white space characters trimmed at the start of the line of text.
     nsTextFrame::TrimmedOffsets trimmedOffsets =
-        frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true);
+        frame->GetTrimmedOffsets(frame->GetContent()->GetText());
     while (it.GetOriginalOffset() < trimmedOffsets.mStart) {
       aPositions.AppendElement(position);
       it.AdvanceOriginal(1);
     }
 
     // If a ligature was started in the previous frame, we should record
     // the ligature's start position, not any partial position.
     while (it.GetOriginalOffset() < frame->GetContentEnd() &&
--- a/testing/web-platform/meta/css/CSS2/stacking-context/opacity-change-twice-stacking-context.html.ini
+++ b/testing/web-platform/meta/css/CSS2/stacking-context/opacity-change-twice-stacking-context.html.ini
@@ -1,5 +1,7 @@
 [opacity-change-twice-stacking-context.html]
+  disabled:
+    if webrender and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1526468
   expected:
     if (os == "mac"): FAIL
     if webrender and (os == "win"): FAIL
     if (os == "android"): "FAIL"
--- a/toolkit/modules/ActorManagerChild.jsm
+++ b/toolkit/modules/ActorManagerChild.jsm
@@ -213,20 +213,22 @@ class SingletonDispatcher extends Dispat
     for (let msg of this.messages.keys()) {
       this.mm.removeMessageListener(msg, this);
     }
     for (let [event, listener, options] of this.listeners) {
       this.mm.removeEventListener(event, listener, options);
     }
 
     for (let actor of this.instances.values()) {
-      try {
-        actor.cleanup();
-      } catch (e) {
-        Cu.reportError(e);
+      if (typeof actor.cleanup == "function") {
+        try {
+          actor.cleanup();
+        } catch (e) {
+          Cu.reportError(e);
+        }
       }
     }
 
     this.listeners = [];
   }
 
   get window() {
     return this._window.get();
--- a/widget/windows/WinUtils.cpp
+++ b/widget/windows/WinUtils.cpp
@@ -1268,31 +1268,30 @@ NS_IMETHODIMP AsyncEncodeAndWriteIcon::R
   MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread.");
 
   // Note that since we're off the main thread we can't use
   // gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()
   RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface(
       mBuffer.get(), mStride, IntSize(mWidth, mHeight),
       SurfaceFormat::B8G8R8A8);
 
-  FILE* file = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "wb");
+  FILE* file = _wfopen(mIconPath.get(), L"wb");
   if (!file) {
     // Maybe the directory doesn't exist; try creating it, then fopen again.
     nsresult rv = NS_ERROR_FAILURE;
     nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
     if (comFile) {
-      // NS_ConvertUTF8toUTF16 utf16path(mIconPath);
       rv = comFile->InitWithPath(mIconPath);
       if (NS_SUCCEEDED(rv)) {
         nsCOMPtr<nsIFile> dirPath;
         comFile->GetParent(getter_AddRefs(dirPath));
         if (dirPath) {
           rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
           if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
-            file = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "wb");
+            file = _wfopen(mIconPath.get(), L"wb");
             if (!file) {
               rv = NS_ERROR_FAILURE;
             }
           }
         }
       }
     }
     if (!file) {