Merge mozilla-central to autoland. a=merge on a CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Sat, 23 Feb 2019 06:25:33 +0200
changeset 518610 28b53210d52239f7ebe715becd6681e0c5ffd707
parent 518609 ed6150abbe586459ae8014b340e9f26e094a9190 (current diff)
parent 518606 6924dd16f7b1e2e6ce71a8eb4cbe330d3e4745dc (diff)
child 518611 1e95c3246660be51aa6529391d4a0c3f85e38dd3
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
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge on a CLOSED TREE
--- 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();
@@ -3515,18 +3516,17 @@ void SVGTextFrame::ReflowSVG() {
     mRect.SetEmpty();
   } else {
     mRect = nsLayoutUtils::RoundGfxRectToAppRect(r.ToThebesRect(),
                                                  AppUnitsPerCSSPixel());
 
     // Due to rounding issues when we have a transform applied, we sometimes
     // don't include an additional row of pixels.  For now, just inflate our
     // covered region.
-    double contextScale = GetContextScale(GetCanvasTM());
-    mRect.Inflate(ceil(presContext->AppUnitsPerDevPixel() / contextScale));
+    mRect.Inflate(ceil(presContext->AppUnitsPerDevPixel() / mLastContextScale));
   }
 
   if (mState & NS_FRAME_FIRST_REFLOW) {
     // Make sure we have our filter property (if any) before calling
     // FinishAndStoreOverflow (subsequent filter changes are handled off
     // nsChangeHint_UpdateEffects):
     SVGObserverUtils::UpdateEffects(this);
   }
@@ -3819,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
     }
 
@@ -4428,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) {