Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Thu, 06 Sep 2018 12:53:57 +0300
changeset 490780 0c947d96e8f3
parent 490770 cc51965a00a9 (current diff)
parent 490779 a8d5538c240e (diff)
child 490781 54cd02c97ed4
child 490785 c4c0be053c5f
child 490807 c84439e715fd
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
0c947d96e8f3 / 64.0a1 / 20180906100252 / files
nightly linux64
0c947d96e8f3 / 64.0a1 / 20180906100252 / files
nightly mac
0c947d96e8f3 / 64.0a1 / 20180906100252 / files
nightly win32
0c947d96e8f3 / 64.0a1 / 20180906100252 / files
nightly win64
0c947d96e8f3 / 64.0a1 / 20180906100252 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1667,17 +1667,17 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "plane-split"
-version = "0.12.1"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2656,17 +2656,17 @@ dependencies = [
  "dwrote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "plane-split 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "plane-split 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.57.2",
 ]
@@ -2980,17 +2980,17 @@ dependencies = [
 "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
 "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356"
 "checksum petgraph 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7e5234c228fbfa874c86a77f685886127f82e0aef602ad1d48333fcac6ad61"
 "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
 "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
 "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
 "checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
 "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
-"checksum plane-split 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ff3a4fc9e31d70eb6828e9a2d7a401a824d9f281686a39a8fc06f08796edb1bb"
+"checksum plane-split 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "64d766f38b15fe1337bdddfc869ef5c50437323f857aaaadc6490197db80a1b8"
 "checksum podio 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e5422a1ee1bc57cc47ae717b0137314258138f38fd5f3cea083f43a9725383a0"
 "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
 "checksum proc-macro2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "77997c53ae6edd6d187fec07ec41b207063b5ee6f33680e9fa86d405cdd313d4"
 "checksum proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "cccdc7557a98fe98453030f077df7f3a042052fae465bb61d2c2c41435cfd9b6"
 "checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
 "checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4"
 "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
 "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -1132,20 +1132,16 @@ nsCSPContext::SendReports(
   return NS_OK;
 }
 
 nsresult
 nsCSPContext::FireViolationEvent(
   Element* aTriggeringElement,
   const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
 {
-  if (!StaticPrefs::security_csp_enable_violation_events()) {
-    return NS_OK;
-  }
-
   if (mEventListener) {
     nsAutoString json;
     if (aViolationEventInit.ToJSON(json)) {
       mEventListener->OnCSPViolationEvent(json);
     }
   }
 
   // 1. If target is not null, and global is a Window, and target’s
--- a/dom/security/test/csp/test_security_policy_violation_event.html
+++ b/dom/security/test/csp/test_security_policy_violation_event.html
@@ -1,19 +1,15 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <meta http-equiv="Content-Security-Policy" content="img-src 'none'">
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <script>
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({
-  set: [
-    ["security.csp.enable_violation_events", true]
-  ]
-});
+
 document.addEventListener("securitypolicyviolation", (e) => {
   SimpleTest.is(e.blockedURI, "http://mochi.test:8888/foo/bar.jpg", "blockedURI");
   SimpleTest.is(e.violatedDirective, "img-src", "violatedDirective")
   SimpleTest.is(e.originalPolicy, "img-src 'none'", "originalPolicy");
   SimpleTest.finish();
 });
 </script>
 <img src="http://mochi.test:8888/foo/bar.jpg">
--- a/dom/webidl/SecurityPolicyViolationEvent.webidl
+++ b/dom/webidl/SecurityPolicyViolationEvent.webidl
@@ -2,18 +2,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/. */
 
 enum SecurityPolicyViolationEventDisposition
 {
   "enforce", "report"
 };
 
-[Constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict),
- Pref="security.csp.enable_violation_events"]
+[Constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict)]
 interface SecurityPolicyViolationEvent : Event
 {
     readonly attribute DOMString      documentURI;
     readonly attribute DOMString      referrer;
     readonly attribute DOMString      blockedURI;
     readonly attribute DOMString      violatedDirective;
     readonly attribute DOMString      effectiveDirective;
     readonly attribute DOMString      originalPolicy;
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -819,16 +819,26 @@ public:
                      const uint8_t* aInstanceData,
                      uint32_t aInstanceDataLength,
                      const FontVariation* aVariations,
                      uint32_t aNumVariations)
   {
     return nullptr;
   }
 
+  virtual already_AddRefed<ScaledFont>
+    CreateScaledFontFromWRFont(Float aGlyphSize,
+                               const wr::FontInstanceOptions* aOptions,
+                               const wr::FontInstancePlatformOptions* aPlatformOptions,
+                               const FontVariation* aVariations,
+                               uint32_t aNumVariations)
+  {
+    return CreateScaledFont(aGlyphSize, nullptr, 0, aVariations, aNumVariations);
+  }
+
 protected:
   UnscaledFont() {}
 
 private:
   static Atomic<uint32_t> sDeletionCounter;
 };
 
 /** This class is an abstraction of a backend/platform specific font object
--- a/gfx/2d/DrawEventRecorder.cpp
+++ b/gfx/2d/DrawEventRecorder.cpp
@@ -132,17 +132,17 @@ DrawEventRecorderMemory::FlushItem(IntRe
   // destruction events to our stream so we need to do that
   // first.
   DetachResources();
 
   // See moz2d_renderer.rs for a description of the stream format
   WriteElement(mIndex, mOutputStream.mLength);
 
   // write out the fonts into the extra data section
-  mSerializeCallback(mOutputStream, mUnscaledFonts);
+  mSerializeCallback(mOutputStream, mScaledFonts);
   WriteElement(mIndex, mOutputStream.mLength);
 
   WriteElement(mIndex, aRect.x);
   WriteElement(mIndex, aRect.y);
   WriteElement(mIndex, aRect.XMost());
   WriteElement(mIndex, aRect.YMost());
   ClearResources();
 
--- a/gfx/2d/DrawEventRecorder.h
+++ b/gfx/2d/DrawEventRecorder.h
@@ -41,20 +41,19 @@ public:
       auto oldSurface = surface++;
       (*oldSurface)->RemoveUserData(reinterpret_cast<UserDataKey*>(this));
     }
     mStoredFonts.clear();
     mStoredSurfaces.clear();
   }
 
   void ClearResources() {
-    mUnscaledFonts.clear();
     mStoredObjects.clear();
     mStoredFontData.clear();
-    mUnscaledFontMap.clear();
+    mScaledFonts.clear();
   }
 
   template<class S>
   void WriteHeader(S& aStream) {
     WriteElement(aStream, kMagicInt);
     WriteElement(aStream, kMajorRevision);
     WriteElement(aStream, kMinorRevision);
   }
@@ -66,17 +65,20 @@ public:
     mStoredObjects.insert(aObject);
   }
 
   void RemoveStoredObject(const ReferencePtr aObject) {
     mStoredObjects.erase(aObject);
   }
 
   void AddScaledFont(ScaledFont* aFont) {
-    mStoredFonts.insert(aFont);
+    if (mStoredFonts.insert(aFont).second &&
+        WantsExternalFonts()) {
+      mScaledFonts.push_back(aFont);
+    }
   }
 
   void RemoveScaledFont(ScaledFont* aFont) {
     mStoredFonts.erase(aFont);
   }
 
   void AddSourceSurface(SourceSurface* aSurface) {
     mStoredSurfaces.insert(aSurface);
@@ -93,30 +95,16 @@ public:
   void AddStoredFontData(const uint64_t aFontDataKey) {
     mStoredFontData.insert(aFontDataKey);
   }
 
   bool HasStoredFontData(const uint64_t aFontDataKey) {
     return mStoredFontData.find(aFontDataKey) != mStoredFontData.end();
   }
 
-  // Returns the index of the UnscaledFont
-  size_t GetUnscaledFontIndex(UnscaledFont *aFont) {
-    auto i = mUnscaledFontMap.find(aFont);
-    size_t index;
-    if (i == mUnscaledFontMap.end()) {
-      mUnscaledFonts.push_back(aFont);
-      index = mUnscaledFonts.size() - 1;
-      mUnscaledFontMap.insert({{aFont, index}});
-    } else {
-      index = i->second;
-    }
-    return index;
-  }
-
   bool WantsExternalFonts() const { return mExternalFonts; }
 
   void TakeExternalSurfaces(std::vector<RefPtr<SourceSurface>>& aSurfaces)
   {
     aSurfaces = std::move(mExternalSurfaces);
   }
 
   virtual void StoreSourceSurfaceRecording(SourceSurface *aSurface,
@@ -126,19 +114,18 @@ protected:
   void StoreExternalSurfaceRecording(SourceSurface* aSurface,
                                      uint64_t aKey);
 
   virtual void Flush() = 0;
 
   std::unordered_set<const void*> mStoredObjects;
   std::unordered_set<uint64_t> mStoredFontData;
   std::unordered_set<ScaledFont*> mStoredFonts;
+  std::vector<RefPtr<ScaledFont>> mScaledFonts;
   std::unordered_set<SourceSurface*> mStoredSurfaces;
-  std::vector<RefPtr<UnscaledFont>> mUnscaledFonts;
-  std::unordered_map<UnscaledFont*, size_t> mUnscaledFontMap;
   std::vector<RefPtr<SourceSurface>> mExternalSurfaces;
   bool mExternalFonts;
 };
 
 class DrawEventRecorderFile : public DrawEventRecorderPrivate
 {
   using char_type = filesystem::Path::value_type;
 public:
@@ -168,17 +155,17 @@ public:
   void Close();
 
 private:
   void Flush() override;
 
   mozilla::OFStream mOutputStream;
 };
 
-typedef std::function<void(MemStream &aStream, std::vector<RefPtr<UnscaledFont>> &aUnscaledFonts)> SerializeResourcesFn;
+typedef std::function<void(MemStream &aStream, std::vector<RefPtr<ScaledFont>> &aScaledFonts)> SerializeResourcesFn;
 
 // WARNING: This should not be used in its existing state because
 // it is likely to OOM because of large continguous allocations.
 class DrawEventRecorderMemory : public DrawEventRecorderPrivate
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderMemory, override)
 
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -302,47 +302,44 @@ void
 DrawTargetRecording::FillGlyphs(ScaledFont *aFont,
                                 const GlyphBuffer &aBuffer,
                                 const Pattern &aPattern,
                                 const DrawOptions &aOptions)
 {
   EnsurePatternDependenciesStored(aPattern);
 
   UserDataKey* userDataKey = reinterpret_cast<UserDataKey*>(mRecorder.get());
-  if (!aFont->GetUserData(userDataKey)) {
+  if (mRecorder->WantsExternalFonts()) {
+    mRecorder->AddScaledFont(aFont);
+  } else if (!aFont->GetUserData(userDataKey)) {
     UnscaledFont* unscaledFont = aFont->GetUnscaledFont();
-    if (mRecorder->WantsExternalFonts()) {
-      size_t index = mRecorder->GetUnscaledFontIndex(unscaledFont);
-      mRecorder->RecordEvent(RecordedScaledFontCreationByIndex(aFont, index));
-    } else {
-      if (!mRecorder->HasStoredObject(unscaledFont)) {
-	RecordedFontData fontData(unscaledFont);
-	RecordedFontDetails fontDetails;
-	if (fontData.GetFontDetails(fontDetails)) {
-	  // Try to serialise the whole font, just in case this is a web font that
-	  // is not present on the system.
-	  if (!mRecorder->HasStoredFontData(fontDetails.fontDataKey)) {
-	    mRecorder->RecordEvent(fontData);
-	    mRecorder->AddStoredFontData(fontDetails.fontDataKey);
-	  }
-	  mRecorder->RecordEvent(RecordedUnscaledFontCreation(unscaledFont, fontDetails));
-	} else {
-	  // If that fails, record just the font description and try to load it from
-	  // the system on the other side.
-	  RecordedFontDescriptor fontDesc(unscaledFont);
-	  if (fontDesc.IsValid()) {
-	    mRecorder->RecordEvent(fontDesc);
-	  } else {
-	    gfxWarning() << "DrawTargetRecording::FillGlyphs failed to serialise UnscaledFont";
-	  }
-	}
-	mRecorder->AddStoredObject(unscaledFont);
+    if (!mRecorder->HasStoredObject(unscaledFont)) {
+      RecordedFontData fontData(unscaledFont);
+      RecordedFontDetails fontDetails;
+      if (fontData.GetFontDetails(fontDetails)) {
+        // Try to serialise the whole font, just in case this is a web font that
+        // is not present on the system.
+        if (!mRecorder->HasStoredFontData(fontDetails.fontDataKey)) {
+          mRecorder->RecordEvent(fontData);
+          mRecorder->AddStoredFontData(fontDetails.fontDataKey);
+        }
+        mRecorder->RecordEvent(RecordedUnscaledFontCreation(unscaledFont, fontDetails));
+      } else {
+        // If that fails, record just the font description and try to load it from
+        // the system on the other side.
+        RecordedFontDescriptor fontDesc(unscaledFont);
+        if (fontDesc.IsValid()) {
+          mRecorder->RecordEvent(fontDesc);
+        } else {
+          gfxWarning() << "DrawTargetRecording::FillGlyphs failed to serialise UnscaledFont";
+        }
       }
-      mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, unscaledFont));
+      mRecorder->AddStoredObject(unscaledFont);
     }
+    mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, unscaledFont));
     RecordingFontUserData *userData = new RecordingFontUserData;
     userData->refPtr = aFont;
     userData->recorder = mRecorder;
     aFont->AddUserData(userDataKey, userData, &RecordingFontUserDataDestroyFunc);
     userData->recorder->AddScaledFont(aFont);
   }
 
   mRecorder->RecordEvent(RecordedFillGlyphs(this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs));
--- a/gfx/2d/InlineTranslator.h
+++ b/gfx/2d/InlineTranslator.h
@@ -78,22 +78,16 @@ public:
 
   UnscaledFont* LookupUnscaledFont(ReferencePtr aRefPtr) final
   {
     UnscaledFont* result = mUnscaledFonts.GetWeak(aRefPtr);
     MOZ_ASSERT(result);
     return result;
   }
 
-  UnscaledFont* LookupUnscaledFontByIndex(size_t index) final
-  {
-    UnscaledFont* result = mUnscaledFontTable[index];
-    return result;
-  }
-
   NativeFontResource* LookupNativeFontResource(uint64_t aKey) final
   {
     NativeFontResource* result = mNativeFontResources.GetWeak(aKey);
     MOZ_ASSERT(result);
     return result;
   }
 
   void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget *aDT) final
@@ -123,17 +117,16 @@ public:
 
   void AddScaledFont(ReferencePtr aRefPtr, ScaledFont *aScaledFont) final
   {
     mScaledFonts.Put(aRefPtr, aScaledFont);
   }
 
   void AddUnscaledFont(ReferencePtr aRefPtr, UnscaledFont *aUnscaledFont) final
   {
-    mUnscaledFontTable.push_back(aUnscaledFont);
     mUnscaledFonts.Put(aRefPtr, aUnscaledFont);
   }
 
   void AddNativeFontResource(uint64_t aKey,
                              NativeFontResource *aScaledFontResouce) final
   {
     mNativeFontResources.Put(aKey, aScaledFontResouce);
   }
@@ -181,17 +174,16 @@ public:
   mozilla::gfx::DrawTarget* GetReferenceDrawTarget() final { return mBaseDT; }
 
   void* GetFontContext() final { return mFontContext; }
 
 private:
   RefPtr<DrawTarget> mBaseDT;
   void*              mFontContext;
 
-  std::vector<RefPtr<UnscaledFont>> mUnscaledFontTable;
   nsRefPtrHashtable<nsPtrHashKey<void>, DrawTarget> mDrawTargets;
   nsRefPtrHashtable<nsPtrHashKey<void>, Path> mPaths;
   nsRefPtrHashtable<nsPtrHashKey<void>, SourceSurface> mSourceSurfaces;
   nsRefPtrHashtable<nsPtrHashKey<void>, FilterNode> mFilterNodes;
   nsRefPtrHashtable<nsPtrHashKey<void>, GradientStops> mGradientStops;
   nsRefPtrHashtable<nsPtrHashKey<void>, ScaledFont> mScaledFonts;
   nsRefPtrHashtable<nsPtrHashKey<void>, UnscaledFont> mUnscaledFonts;
   nsRefPtrHashtable<nsUint64HashKey, NativeFontResource> mNativeFontResources;
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -129,16 +129,17 @@ enum class LogReason : int {
   TextureCreation,
   InvalidCacheSurface,
   AlphaWithBasicClient,
   UnbalancedClipStack,
   ProcessingError,
   InvalidDrawTarget,
   NativeFontResourceNotFound,
   UnscaledFontNotFound,
+  ScaledFontNotFound,
   InvalidLayerType,
   // End
   MustBeLessThanThis = 101,
 };
 
 struct BasicLogger
 {
   // For efficiency, this method exists and copies the logic of the
--- a/gfx/2d/RecordedEvent.cpp
+++ b/gfx/2d/RecordedEvent.cpp
@@ -84,18 +84,16 @@ RecordedEvent::GetEventName(EventType aT
   case GRADIENTSTOPSCREATION:
     return "GradientStopsCreation";
   case GRADIENTSTOPSDESTRUCTION:
     return "GradientStopsDestruction";
   case SNAPSHOT:
     return "Snapshot";
   case SCALEDFONTCREATION:
     return "ScaledFontCreation";
-  case SCALEDFONTCREATIONBYINDEX:
-    return "ScaledFontCreationByIndex";
   case SCALEDFONTDESTRUCTION:
     return "ScaledFontDestruction";
   case MASKSURFACE:
     return "MaskSurface";
   case FILTERNODESETATTRIBUTE:
     return "SetAttribute";
   case FILTERNODESETINPUT:
     return "SetInput";
--- a/gfx/2d/RecordedEvent.h
+++ b/gfx/2d/RecordedEvent.h
@@ -85,17 +85,16 @@ public:
 
   virtual DrawTarget *LookupDrawTarget(ReferencePtr aRefPtr) = 0;
   virtual Path *LookupPath(ReferencePtr aRefPtr) = 0;
   virtual SourceSurface *LookupSourceSurface(ReferencePtr aRefPtr) = 0;
   virtual FilterNode *LookupFilterNode(ReferencePtr aRefPtr) = 0;
   virtual GradientStops *LookupGradientStops(ReferencePtr aRefPtr) = 0;
   virtual ScaledFont *LookupScaledFont(ReferencePtr aRefPtr) = 0;
   virtual UnscaledFont* LookupUnscaledFont(ReferencePtr aRefPtr) = 0;
-  virtual UnscaledFont* LookupUnscaledFontByIndex(size_t aIndex) { return nullptr; }
   virtual NativeFontResource *LookupNativeFontResource(uint64_t aKey) = 0;
   virtual already_AddRefed<SourceSurface> LookupExternalSurface(uint64_t aKey) { return nullptr; }
   virtual void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget *aDT) = 0;
   virtual void RemoveDrawTarget(ReferencePtr aRefPtr) = 0;
   virtual void AddPath(ReferencePtr aRefPtr, Path *aPath) = 0;
   virtual void RemovePath(ReferencePtr aRefPtr) = 0;
   virtual void AddSourceSurface(ReferencePtr aRefPtr, SourceSurface *aPath) = 0;
   virtual void RemoveSourceSurface(ReferencePtr aRefPtr) = 0;
@@ -239,17 +238,16 @@ public:
     PATHCREATION,
     PATHDESTRUCTION,
     SOURCESURFACECREATION,
     SOURCESURFACEDESTRUCTION,
     GRADIENTSTOPSCREATION,
     GRADIENTSTOPSDESTRUCTION,
     SNAPSHOT,
     SCALEDFONTCREATION,
-    SCALEDFONTCREATIONBYINDEX,
     SCALEDFONTDESTRUCTION,
     MASKSURFACE,
     FILTERNODECREATION,
     FILTERNODEDESTRUCTION,
     DRAWFILTER,
     FILTERNODESETATTRIBUTE,
     FILTERNODESETINPUT,
     CREATESIMILARDRAWTARGET,
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -1197,60 +1197,16 @@ private:
   Float mGlyphSize;
   std::vector<uint8_t> mInstanceData;
   std::vector<FontVariation> mVariations;
 
   template<class S>
   MOZ_IMPLICIT RecordedScaledFontCreation(S &aStream);
 };
 
-class RecordedScaledFontCreationByIndex : public RecordedEventDerived<RecordedScaledFontCreationByIndex> {
-public:
-
-  static void FontInstanceDataProc(const uint8_t* aData, uint32_t aSize,
-                                   const FontVariation* aVariations, uint32_t aNumVariations,
-                                   void* aBaton)
-  {
-    auto recordedScaledFontCreation = static_cast<RecordedScaledFontCreationByIndex*>(aBaton);
-    recordedScaledFontCreation->SetFontInstanceData(aData, aSize, aVariations, aNumVariations);
-  }
-
-  RecordedScaledFontCreationByIndex(ScaledFont* aScaledFont, size_t aUnscaledFontIndex)
-    : RecordedEventDerived(SCALEDFONTCREATIONBYINDEX)
-    , mRefPtr(aScaledFont)
-    , mUnscaledFontIndex(aUnscaledFontIndex)
-    , mGlyphSize(aScaledFont->GetSize())
-  {
-    aScaledFont->GetFontInstanceData(FontInstanceDataProc, this);
-  }
-
-  virtual bool PlayEvent(Translator *aTranslator) const override;
-
-  template<class S> void Record(S &aStream) const;
-  virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const override;
-
-  virtual std::string GetName() const override { return "ScaledFont Creation"; }
-  virtual ReferencePtr GetObjectRef() const override { return mRefPtr; }
-
-  void SetFontInstanceData(const uint8_t *aData, uint32_t aSize,
-                           const FontVariation* aVariations, uint32_t aNumVariations);
-
-private:
-  friend class RecordedEvent;
-
-  ReferencePtr mRefPtr;
-  size_t mUnscaledFontIndex;
-  Float mGlyphSize;
-  std::vector<uint8_t> mInstanceData;
-  std::vector<FontVariation> mVariations;
-
-  template<class S>
-  MOZ_IMPLICIT RecordedScaledFontCreationByIndex(S &aStream);
-};
-
 class RecordedScaledFontDestruction : public RecordedEventDerived<RecordedScaledFontDestruction> {
 public:
   MOZ_IMPLICIT RecordedScaledFontDestruction(ReferencePtr aRefPtr)
     : RecordedEventDerived(SCALEDFONTDESTRUCTION), mRefPtr(aRefPtr)
   {
   }
 
   virtual bool PlayEvent(Translator *aTranslator) const override;
@@ -3280,80 +3236,16 @@ RecordedScaledFontCreation::RecordedScal
   aStream.read((char*)mInstanceData.data(), size);
   size_t numVariations;
   ReadElement(aStream, numVariations);
   mVariations.resize(numVariations);
   aStream.read((char*)mVariations.data(), sizeof(FontVariation) * numVariations);
 }
 
 inline bool
-RecordedScaledFontCreationByIndex::PlayEvent(Translator *aTranslator) const
-{
-  UnscaledFont* unscaledFont = aTranslator->LookupUnscaledFontByIndex(mUnscaledFontIndex);
-  if (!unscaledFont) {
-    gfxDevCrash(LogReason::UnscaledFontNotFound) <<
-      "UnscaledFont lookup failed for key |" << hexa(mUnscaledFontIndex) << "|.";
-    return false;
-  }
-
-  RefPtr<ScaledFont> scaledFont =
-    unscaledFont->CreateScaledFont(mGlyphSize,
-                                   mInstanceData.data(), mInstanceData.size(),
-                                   mVariations.data(), mVariations.size());
-
-  aTranslator->AddScaledFont(mRefPtr, scaledFont);
-  return true;
-}
-
-template<class S>
-void
-RecordedScaledFontCreationByIndex::Record(S &aStream) const
-{
-  WriteElement(aStream, mRefPtr);
-  WriteElement(aStream, mUnscaledFontIndex);
-  WriteElement(aStream, mGlyphSize);
-  WriteElement(aStream, (size_t)mInstanceData.size());
-  aStream.write((char*)mInstanceData.data(), mInstanceData.size());
-  WriteElement(aStream, (size_t)mVariations.size());
-  aStream.write((char*)mVariations.data(), sizeof(FontVariation) * mVariations.size());
-}
-
-inline void
-RecordedScaledFontCreationByIndex::OutputSimpleEventInfo(std::stringstream &aStringStream) const
-{
-  aStringStream << "[" << mRefPtr << "] ScaledFont Created By Index";
-}
-
-inline void
-RecordedScaledFontCreationByIndex::SetFontInstanceData(const uint8_t *aData, uint32_t aSize,
-                                                const FontVariation* aVariations, uint32_t aNumVariations)
-{
-  mInstanceData.assign(aData, aData + aSize);
-  mVariations.assign(aVariations, aVariations + aNumVariations);
-}
-
-template<class S>
-RecordedScaledFontCreationByIndex::RecordedScaledFontCreationByIndex(S &aStream)
-  : RecordedEventDerived(SCALEDFONTCREATIONBYINDEX)
-{
-  ReadElement(aStream, mRefPtr);
-  ReadElement(aStream, mUnscaledFontIndex);
-  ReadElement(aStream, mGlyphSize);
-
-  size_t size;
-  ReadElement(aStream, size);
-  mInstanceData.resize(size);
-  aStream.read((char*)mInstanceData.data(), size);
-  size_t numVariations;
-  ReadElement(aStream, numVariations);
-  mVariations.resize(numVariations);
-  aStream.read((char*)mVariations.data(), sizeof(FontVariation) * numVariations);
-}
-
-inline bool
 RecordedScaledFontDestruction::PlayEvent(Translator *aTranslator) const
 {
   aTranslator->RemoveScaledFont(mRefPtr);
   return true;
 }
 
 template<class S>
 void
@@ -3558,17 +3450,16 @@ RecordedFilterNodeSetInput::OutputSimple
     f(SOURCESURFACECREATION, RecordedSourceSurfaceCreation); \
     f(SOURCESURFACEDESTRUCTION, RecordedSourceSurfaceDestruction); \
     f(FILTERNODECREATION, RecordedFilterNodeCreation); \
     f(FILTERNODEDESTRUCTION, RecordedFilterNodeDestruction); \
     f(GRADIENTSTOPSCREATION, RecordedGradientStopsCreation); \
     f(GRADIENTSTOPSDESTRUCTION, RecordedGradientStopsDestruction); \
     f(SNAPSHOT, RecordedSnapshot); \
     f(SCALEDFONTCREATION, RecordedScaledFontCreation); \
-    f(SCALEDFONTCREATIONBYINDEX, RecordedScaledFontCreationByIndex); \
     f(SCALEDFONTDESTRUCTION, RecordedScaledFontDestruction); \
     f(MASKSURFACE, RecordedMaskSurface); \
     f(FILTERNODESETATTRIBUTE, RecordedFilterNodeSetAttribute); \
     f(FILTERNODESETINPUT, RecordedFilterNodeSetInput); \
     f(CREATESIMILARDRAWTARGET, RecordedCreateSimilarDrawTarget); \
     f(CREATECLIPPEDDRAWTARGET, RecordedCreateClippedDrawTarget); \
     f(FONTDATA, RecordedFontData); \
     f(FONTDESC, RecordedFontDescriptor); \
--- a/gfx/2d/ScaledFontDWrite.cpp
+++ b/gfx/2d/ScaledFontDWrite.cpp
@@ -392,16 +392,37 @@ UnscaledFontDWrite::GetWRFontDescriptor(
   // the data payload.
   uint32_t index = weight | (stretch << 16) | (style << 24);
   aCb(reinterpret_cast<const uint8_t*>(familyName.data()),
       (familyName.size() - 1) * sizeof(WCHAR),
       index, aBaton);
   return true;
 }
 
+ScaledFontDWrite::InstanceData::InstanceData(const wr::FontInstanceOptions* aOptions,
+                                             const wr::FontInstancePlatformOptions* aPlatformOptions)
+  : mUseEmbeddedBitmap(false)
+  , mForceGDIMode(false)
+  , mGamma(2.2f)
+  , mContrast(1.0f)
+{
+  if (aOptions) {
+    if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
+      mUseEmbeddedBitmap = true;
+    }
+    if (aOptions->flags & wr::FontInstanceFlags::FORCE_GDI) {
+      mForceGDIMode = true;
+    }
+  }
+  if (aPlatformOptions) {
+    mGamma = aPlatformOptions->gamma / 100.0f;
+    mContrast = aPlatformOptions->contrast / 100.0f;
+  }
+}
+
 // Helper for ScaledFontDWrite::GetFontInstanceData: if the font has variation
 // axes, get their current values into the aOutput vector.
 static void
 GetVariationsFromFontFace(IDWriteFontFace* aFace,
                           std::vector<FontVariation>* aOutput)
 {
   RefPtr<IDWriteFontFace5> ff5;
   aFace->QueryInterface(__uuidof(IDWriteFontFace5), (void**)getter_AddRefs(ff5));
@@ -536,49 +557,63 @@ UnscaledFontDWrite::CreateScaledFont(Flo
                                      uint32_t aInstanceDataLength,
                                      const FontVariation* aVariations,
                                      uint32_t aNumVariations)
 {
   if (aInstanceDataLength < sizeof(ScaledFontDWrite::InstanceData)) {
     gfxWarning() << "DWrite scaled font instance data is truncated.";
     return nullptr;
   }
+  const ScaledFontDWrite::InstanceData& instanceData =
+    *reinterpret_cast<const ScaledFontDWrite::InstanceData*>(aInstanceData);
 
   IDWriteFontFace* face = mFontFace;
 
   // If variations are required, we create a separate IDWriteFontFace5 with
   // the requested settings applied.
   RefPtr<IDWriteFontFace5> ff5;
   if (aNumVariations) {
     ff5 = CreateFaceWithVariations(mFontFace, aVariations, aNumVariations);
     if (ff5) {
       face = ff5;
     } else {
       gfxWarning() << "Failed to create IDWriteFontFace5 with variations.";
     }
   }
 
-  const ScaledFontDWrite::InstanceData *instanceData =
-    reinterpret_cast<const ScaledFontDWrite::InstanceData*>(aInstanceData);
   RefPtr<ScaledFontBase> scaledFont =
     new ScaledFontDWrite(face, this, aGlyphSize,
-                         instanceData->mUseEmbeddedBitmap,
-                         instanceData->mForceGDIMode,
+                         instanceData.mUseEmbeddedBitmap,
+                         instanceData.mForceGDIMode,
                          nullptr,
-                         instanceData->mGamma,
-                         instanceData->mContrast);
+                         instanceData.mGamma,
+                         instanceData.mContrast);
 
   if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) {
     gfxWarning() << "Unable to create cairo scaled font DWrite font.";
     return nullptr;
   }
 
   return scaledFont.forget();
 }
 
+already_AddRefed<ScaledFont>
+UnscaledFontDWrite::CreateScaledFontFromWRFont(Float aGlyphSize,
+                                               const wr::FontInstanceOptions* aOptions,
+                                               const wr::FontInstancePlatformOptions* aPlatformOptions,
+                                               const FontVariation* aVariations,
+                                               uint32_t aNumVariations)
+{
+  ScaledFontDWrite::InstanceData instanceData(aOptions, aPlatformOptions);
+  return CreateScaledFont(aGlyphSize,
+                          reinterpret_cast<uint8_t*>(&instanceData),
+                          sizeof(instanceData),
+                          aVariations, aNumVariations);
+}
+
 AntialiasMode
 ScaledFontDWrite::GetDefaultAAMode()
 {
   AntialiasMode defaultMode = GetSystemDefaultAAMode();
 
   if (defaultMode == AntialiasMode::GRAY) {
     if (!DoGrayscale(mFontFace, mSize)) {
       defaultMode = AntialiasMode::NONE;
--- a/gfx/2d/ScaledFontDWrite.h
+++ b/gfx/2d/ScaledFontDWrite.h
@@ -97,16 +97,19 @@ private:
   {
     explicit InstanceData(ScaledFontDWrite* aScaledFont)
       : mUseEmbeddedBitmap(aScaledFont->mUseEmbeddedBitmap)
       , mForceGDIMode(aScaledFont->mForceGDIMode)
       , mGamma(aScaledFont->mGamma)
       , mContrast(aScaledFont->mContrast)
     {}
 
+    InstanceData(const wr::FontInstanceOptions* aOptions,
+                 const wr::FontInstancePlatformOptions* aPlatformOptions);
+
     bool mUseEmbeddedBitmap;
     bool mForceGDIMode;
     Float mGamma;
     Float mContrast;
   };
 };
 
 }
--- a/gfx/2d/ScaledFontFontconfig.cpp
+++ b/gfx/2d/ScaledFontFontconfig.cpp
@@ -102,16 +102,79 @@ ScaledFontFontconfig::InstanceData::Inst
         hintstyle = FC_HINT_FULL;
       }
       mHintStyle = hintstyle;
     }
   }
   cairo_font_options_destroy(fontOptions);
 }
 
+ScaledFontFontconfig::InstanceData::InstanceData(const wr::FontInstanceOptions* aOptions,
+                                                 const wr::FontInstancePlatformOptions* aPlatformOptions)
+  : mFlags(HINT_METRICS)
+  , mHintStyle(FC_HINT_FULL)
+  , mSubpixelOrder(FC_RGBA_UNKNOWN)
+  , mLcdFilter(FC_LCD_LEGACY)
+{
+  if (aOptions) {
+    if (aOptions->flags & wr::FontInstanceFlags::FORCE_AUTOHINT) {
+      mFlags |= AUTOHINT;
+    }
+    if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
+      mFlags |= EMBEDDED_BITMAP;
+    }
+    if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
+      mFlags |= EMBOLDEN;
+    }
+    if (aOptions->flags & wr::FontInstanceFlags::VERTICAL_LAYOUT) {
+      mFlags |= VERTICAL_LAYOUT;
+    }
+    if (aOptions->render_mode != wr::FontRenderMode::Mono) {
+      mFlags |= ANTIALIAS;
+      if (aOptions->render_mode == wr::FontRenderMode::Subpixel) {
+        if (aOptions->flags & wr::FontInstanceFlags::SUBPIXEL_BGR) {
+          mSubpixelOrder =
+            aOptions->flags & wr::FontInstanceFlags::LCD_VERTICAL ? FC_RGBA_VBGR : FC_RGBA_BGR;
+        } else {
+          mSubpixelOrder =
+            aOptions->flags & wr::FontInstanceFlags::LCD_VERTICAL ? FC_RGBA_VRGB : FC_RGBA_RGB;
+        }
+      }
+    }
+  }
+  if (aPlatformOptions) {
+    switch (aPlatformOptions->hinting) {
+    case wr::FontHinting::None:
+      mHintStyle = FC_HINT_NONE;
+      break;
+    case wr::FontHinting::Light:
+      mHintStyle = FC_HINT_SLIGHT;
+      break;
+    case wr::FontHinting::Normal:
+      mHintStyle = FC_HINT_MEDIUM;
+      break;
+    default:
+      break;
+    }
+    switch (aPlatformOptions->lcd_filter) {
+    case wr::FontLCDFilter::None:
+      mLcdFilter = FC_LCD_NONE;
+      break;
+    case wr::FontLCDFilter::Default:
+      mLcdFilter = FC_LCD_DEFAULT;
+      break;
+    case wr::FontLCDFilter::Light:
+      mLcdFilter = FC_LCD_LIGHT;
+      break;
+    default:
+      break;
+    }
+  }
+}
+
 void
 ScaledFontFontconfig::InstanceData::SetupPattern(FcPattern* aPattern) const
 {
   if (mFlags & AUTOHINT) {
     FcPatternAddBool(aPattern, FC_AUTOHINT, FcTrue);
   }
   if (mFlags & EMBEDDED_BITMAP) {
     FcPatternAddBool(aPattern, FC_EMBEDDED_BITMAP, FcTrue);
@@ -348,37 +411,16 @@ ScaledFontFontconfig::GetWRFontInstanceO
     if (FcPatternGetFTFace(mPattern, FC_FT_FACE, 0, &face) == FcResultMatch) {
       UnscaledFontFreeType::GetVariationSettingsFromFace(aOutVariations, face);
     }
   }
 
   return true;
 }
 
-already_AddRefed<ScaledFont>
-UnscaledFontFontconfig::CreateScaledFont(Float aGlyphSize,
-                                         const uint8_t* aInstanceData,
-                                         uint32_t aInstanceDataLength,
-                                         const FontVariation* aVariations,
-                                         uint32_t aNumVariations)
-{
-  if (aInstanceDataLength < sizeof(ScaledFontFontconfig::InstanceData)) {
-    gfxWarning() << "Fontconfig scaled font instance data is truncated.";
-    return nullptr;
-  }
-  const ScaledFontFontconfig::InstanceData *instanceData =
-    reinterpret_cast<const ScaledFontFontconfig::InstanceData*>(aInstanceData);
-  RefPtr<ScaledFont> scaledFont =
-    ScaledFontFontconfig::CreateFromInstanceData(*instanceData, this, aGlyphSize,
-                                                 aVariations, aNumVariations,
-                                                 static_cast<NativeFontResourceFontconfig*>(
-                                                   mNativeFontResource.get()));
-  return scaledFont.forget();
-}
-
 static cairo_user_data_key_t sNativeFontResourceKey;
 
 static void
 ReleaseNativeFontResource(void* aData)
 {
   static_cast<NativeFontResource*>(aData)->Release();
 }
 
@@ -386,129 +428,150 @@ static cairo_user_data_key_t sFaceKey;
 
 static void
 ReleaseFace(void* aData)
 {
   Factory::ReleaseFTFace(static_cast<FT_Face>(aData));
 }
 
 already_AddRefed<ScaledFont>
-ScaledFontFontconfig::CreateFromInstanceData(const InstanceData& aInstanceData,
-                                             UnscaledFontFontconfig* aUnscaledFont,
-                                             Float aSize,
-                                             const FontVariation* aVariations,
-                                             uint32_t aNumVariations,
-                                             NativeFontResourceFontconfig* aNativeFontResource)
+UnscaledFontFontconfig::CreateScaledFont(Float aSize,
+                                         const uint8_t* aInstanceData,
+                                         uint32_t aInstanceDataLength,
+                                         const FontVariation* aVariations,
+                                         uint32_t aNumVariations)
 {
+  if (aInstanceDataLength < sizeof(ScaledFontFontconfig::InstanceData)) {
+    gfxWarning() << "Fontconfig scaled font instance data is truncated.";
+    return nullptr;
+  }
+  const ScaledFontFontconfig::InstanceData& instanceData =
+    *reinterpret_cast<const ScaledFontFontconfig::InstanceData*>(aInstanceData);
+
   FcPattern* pattern = FcPatternCreate();
   if (!pattern) {
     gfxWarning() << "Failed initializing Fontconfig pattern for scaled font";
     return nullptr;
   }
-  FT_Face face = aUnscaledFont->GetFace();
+  FT_Face face = GetFace();
+  NativeFontResourceFreeType* nfr = static_cast<NativeFontResourceFreeType*>(mNativeFontResource.get());
   FT_Face varFace = nullptr;
   if (face) {
-    if (aNativeFontResource && aNumVariations > 0) {
-      varFace = aNativeFontResource->CloneFace();
+    if (nfr && aNumVariations > 0) {
+      varFace = nfr->CloneFace();
       if (!varFace) {
         gfxWarning() << "Failed cloning face for variations";
       }
     }
     FcPatternAddFTFace(pattern, FC_FT_FACE, varFace ? varFace : face);
   } else {
-    FcPatternAddString(pattern, FC_FILE, reinterpret_cast<const FcChar8*>(aUnscaledFont->GetFile()));
-    FcPatternAddInteger(pattern, FC_INDEX, aUnscaledFont->GetIndex());
+    FcPatternAddString(pattern, FC_FILE, reinterpret_cast<const FcChar8*>(GetFile()));
+    FcPatternAddInteger(pattern, FC_INDEX, GetIndex());
   }
   FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aSize);
-  aInstanceData.SetupPattern(pattern);
+  instanceData.SetupPattern(pattern);
 
   StackArray<FT_Fixed, 32> coords(aNumVariations);
   for (uint32_t i = 0; i < aNumVariations; i++) {
     coords[i] = std::round(aVariations[i].mValue * 65536.0);
   }
 
   cairo_font_face_t* font = cairo_ft_font_face_create_for_pattern(pattern, coords.data(), aNumVariations);
   if (cairo_font_face_status(font) != CAIRO_STATUS_SUCCESS) {
     gfxWarning() << "Failed creating Cairo font face for Fontconfig pattern";
     FcPatternDestroy(pattern);
     if (varFace) {
       Factory::ReleaseFTFace(varFace);
     }
     return nullptr;
   }
 
-  if (aNativeFontResource) {
+  if (nfr) {
     // Bug 1362117 - Cairo may keep the font face alive after the owning NativeFontResource
     // was freed. To prevent this, we must bind the NativeFontResource to the font face so that
     // it stays alive at least as long as the font face.
-    aNativeFontResource->AddRef();
+    nfr->AddRef();
     cairo_status_t err = CAIRO_STATUS_SUCCESS;
     bool cleanupFace = false;
     if (varFace) {
       err = cairo_font_face_set_user_data(font,
                                           &sFaceKey,
                                           varFace,
                                           ReleaseFace);
     }
     if (err != CAIRO_STATUS_SUCCESS) {
       cleanupFace = true;
     } else {
       err = cairo_font_face_set_user_data(font,
                                           &sNativeFontResourceKey,
-                                          aNativeFontResource,
+                                          nfr,
                                           ReleaseNativeFontResource);
     }
     if (err != CAIRO_STATUS_SUCCESS) {
       gfxWarning() << "Failed binding NativeFontResource to Cairo font face";
       if (varFace && cleanupFace) {
         Factory::ReleaseFTFace(varFace);
       }
-      aNativeFontResource->Release();
+      nfr->Release();
       cairo_font_face_destroy(font);
       FcPatternDestroy(pattern);
       return nullptr;
     }
   }
 
   cairo_matrix_t sizeMatrix;
   cairo_matrix_init(&sizeMatrix, aSize, 0, 0, aSize, 0, 0);
 
   cairo_matrix_t identityMatrix;
   cairo_matrix_init_identity(&identityMatrix);
 
   cairo_font_options_t *fontOptions = cairo_font_options_create();
-  aInstanceData.SetupFontOptions(fontOptions);
+  instanceData.SetupFontOptions(fontOptions);
 
   cairo_scaled_font_t* cairoScaledFont =
     cairo_scaled_font_create(font, &sizeMatrix, &identityMatrix, fontOptions);
 
   cairo_font_options_destroy(fontOptions);
   cairo_font_face_destroy(font);
 
   if (cairo_scaled_font_status(cairoScaledFont) != CAIRO_STATUS_SUCCESS) {
     gfxWarning() << "Failed creating Cairo scaled font for font face";
     FcPatternDestroy(pattern);
     return nullptr;
   }
 
   RefPtr<ScaledFontFontconfig> scaledFont =
-    new ScaledFontFontconfig(cairoScaledFont, pattern, aUnscaledFont, aSize);
+    new ScaledFontFontconfig(cairoScaledFont, pattern, this, aSize);
 
   cairo_scaled_font_destroy(cairoScaledFont);
   FcPatternDestroy(pattern);
 
   // Only apply variations if we have an explicitly cloned face. Otherwise,
   // if the pattern holds the pathname, Cairo will handle setting of variations.
   if (varFace) {
-    UnscaledFontFreeType::ApplyVariationsToFace(aVariations, aNumVariations, varFace);
+    ApplyVariationsToFace(aVariations, aNumVariations, varFace);
   }
 
   return scaledFont.forget();
 }
 
+already_AddRefed<ScaledFont>
+UnscaledFontFontconfig::CreateScaledFontFromWRFont(Float aGlyphSize,
+                                                   const wr::FontInstanceOptions* aOptions,
+                                                   const wr::FontInstancePlatformOptions* aPlatformOptions,
+                                                   const FontVariation* aVariations,
+                                                   uint32_t aNumVariations)
+{
+  ScaledFontFontconfig::InstanceData instanceData(aOptions, aPlatformOptions);
+  return CreateScaledFont(aGlyphSize,
+                          reinterpret_cast<uint8_t*>(&instanceData),
+                          sizeof(instanceData),
+                          aVariations, aNumVariations);
+}
+
 bool
 ScaledFontFontconfig::HasVariationSettings()
 {
   // Check if the FT face has been cloned.
   FT_Face face = nullptr;
   return FcPatternGetFTFace(mPattern, FC_FT_FACE, 0, &face) == FcResultMatch &&
          face && face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS &&
          face != static_cast<UnscaledFontFontconfig*>(mUnscaledFont.get())->GetFace();
--- a/gfx/2d/ScaledFontFontconfig.h
+++ b/gfx/2d/ScaledFontFontconfig.h
@@ -52,33 +52,27 @@ private:
       AUTOHINT        = 1 << 1,
       EMBEDDED_BITMAP = 1 << 2,
       EMBOLDEN        = 1 << 3,
       VERTICAL_LAYOUT = 1 << 4,
       HINT_METRICS    = 1 << 5
     };
 
     InstanceData(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern);
+    InstanceData(const wr::FontInstanceOptions* aOptions,
+                 const wr::FontInstancePlatformOptions* aPlatformOptions);
 
     void SetupPattern(FcPattern* aPattern) const;
     void SetupFontOptions(cairo_font_options_t* aFontOptions) const;
 
     uint8_t mFlags;
     uint8_t mHintStyle;
     uint8_t mSubpixelOrder;
     uint8_t mLcdFilter;
   };
 
-  static already_AddRefed<ScaledFont>
-    CreateFromInstanceData(const InstanceData& aInstanceData,
-                           UnscaledFontFontconfig* aUnscaledFont,
-                           Float aSize,
-                           const FontVariation* aVariations,
-                           uint32_t aNumVariations,
-                           NativeFontResourceFontconfig* aNativeFontResource = nullptr);
-
   FcPattern* mPattern;
 };
 
 }
 }
 
 #endif /* MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ */
--- a/gfx/2d/UnscaledFontDWrite.h
+++ b/gfx/2d/UnscaledFontDWrite.h
@@ -9,16 +9,18 @@
 
 #include <dwrite.h>
 
 #include "2D.h"
 
 namespace mozilla {
 namespace gfx {
 
+class ScaledFontDWrite;
+
 class UnscaledFontDWrite final : public UnscaledFont
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontDWrite, override)
   UnscaledFontDWrite(const RefPtr<IDWriteFontFace>& aFontFace,
                      const RefPtr<IDWriteFont>& aFont,
                      DWRITE_FONT_SIMULATIONS aSimulations = DWRITE_FONT_SIMULATIONS_NONE,
                      bool aNeedsCairo = false)
@@ -38,16 +40,23 @@ public:
 
   already_AddRefed<ScaledFont>
     CreateScaledFont(Float aGlyphSize,
                      const uint8_t* aInstanceData,
                      uint32_t aInstanceDataLength,
                      const FontVariation* aVariations,
                      uint32_t aNumVariations) override;
 
+  already_AddRefed<ScaledFont>
+    CreateScaledFontFromWRFont(Float aGlyphSize,
+                               const wr::FontInstanceOptions* aOptions,
+                               const wr::FontInstancePlatformOptions* aPlatformOptions,
+                               const FontVariation* aVariations,
+                               uint32_t aNumVariations) override;
+
   bool GetWRFontDescriptor(WRFontDescriptorOutput aCb, void* aBaton) override;
 
 private:
   RefPtr<IDWriteFontFace> mFontFace;
   RefPtr<IDWriteFont> mFont;
   DWRITE_FONT_SIMULATIONS mSimulations;
   bool mNeedsCairo;
 };
--- a/gfx/2d/UnscaledFontFreeType.h
+++ b/gfx/2d/UnscaledFontFreeType.h
@@ -79,17 +79,16 @@ public:
 
 protected:
   FT_Face mFace;
   bool mOwnsFace;
   std::string mFile;
   uint32_t mIndex;
   RefPtr<NativeFontResource> mNativeFontResource;
 
-private:
   friend class ScaledFontFreeType;
   friend class ScaledFontFontconfig;
 
   static void
     GetVariationSettingsFromFace(std::vector<FontVariation>* aVariations,
                                  FT_Face aFace);
 
   static void
@@ -126,16 +125,23 @@ public:
     CreateFromFontDescriptor(const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex);
 
   already_AddRefed<ScaledFont>
     CreateScaledFont(Float aGlyphSize,
                      const uint8_t* aInstanceData,
                      uint32_t aInstanceDataLength,
                      const FontVariation* aVariations,
                      uint32_t aNumVariations) override;
+
+  already_AddRefed<ScaledFont>
+    CreateScaledFontFromWRFont(Float aGlyphSize,
+                               const wr::FontInstanceOptions* aOptions,
+                               const wr::FontInstancePlatformOptions* aPlatformOptions,
+                               const FontVariation* aVariations,
+                               uint32_t aNumVariations) override;
 };
 #endif
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_ */
 
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -280,71 +280,82 @@ WebRenderBridgeChild::PushGlyphs(wr::Dis
                     aBackfaceVisible,
                     aColor,
                     key,
                     aGlyphs,
                     aGlyphOptions);
 }
 
 wr::FontInstanceKey
-WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
+WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont,
+                                              wr::IpcResourceUpdateQueue* aResources)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(aScaledFont);
   MOZ_ASSERT(aScaledFont->CanSerialize());
 
   wr::FontInstanceKey instanceKey = { wr::IdNamespace { 0 }, 0 };
   if (mFontInstanceKeys.Get(aScaledFont, &instanceKey)) {
     return instanceKey;
   }
 
-  wr::IpcResourceUpdateQueue resources(this);
+  Maybe<wr::IpcResourceUpdateQueue> resources =
+    aResources ? Nothing() : Some(wr::IpcResourceUpdateQueue(this));
+  aResources = resources.ptrOr(aResources);
 
-  wr::FontKey fontKey = GetFontKeyForUnscaledFont(aScaledFont->GetUnscaledFont());
+  wr::FontKey fontKey = GetFontKeyForUnscaledFont(aScaledFont->GetUnscaledFont(), aResources);
   wr::FontKey nullKey = { wr::IdNamespace { 0 }, 0};
   if (fontKey == nullKey) {
     return instanceKey;
   }
 
   instanceKey = GetNextFontInstanceKey();
 
   Maybe<wr::FontInstanceOptions> options;
   Maybe<wr::FontInstancePlatformOptions> platformOptions;
   std::vector<FontVariation> variations;
   aScaledFont->GetWRFontInstanceOptions(&options, &platformOptions, &variations);
 
-  resources.AddFontInstance(instanceKey, fontKey, aScaledFont->GetSize(),
-                            options.ptrOr(nullptr), platformOptions.ptrOr(nullptr),
-                            Range<const FontVariation>(variations.data(), variations.size()));
-  UpdateResources(resources);
+  aResources->AddFontInstance(instanceKey, fontKey, aScaledFont->GetSize(),
+                              options.ptrOr(nullptr), platformOptions.ptrOr(nullptr),
+                              Range<const FontVariation>(variations.data(), variations.size()));
+  if (resources.isSome()) {
+    UpdateResources(resources.ref());
+  }
 
   mFontInstanceKeys.Put(aScaledFont, instanceKey);
 
   return instanceKey;
 
 }
 
 wr::FontKey
-WebRenderBridgeChild::GetFontKeyForUnscaledFont(gfx::UnscaledFont* aUnscaled)
+WebRenderBridgeChild::GetFontKeyForUnscaledFont(gfx::UnscaledFont* aUnscaled,
+                                                wr::IpcResourceUpdateQueue* aResources)
 {
   MOZ_ASSERT(!mDestroyed);
 
   wr::FontKey fontKey = { wr::IdNamespace { 0 }, 0};
   if (!mFontKeys.Get(aUnscaled, &fontKey)) {
-    wr::IpcResourceUpdateQueue resources(this);
-    FontFileDataSink sink = { &fontKey, this, &resources };
+    Maybe<wr::IpcResourceUpdateQueue> resources =
+      aResources ? Nothing() : Some(wr::IpcResourceUpdateQueue(this));
+
+    FontFileDataSink sink = { &fontKey, this, resources.ptrOr(aResources) };
     // First try to retrieve a descriptor for the font, as this is much cheaper
     // to send over IPC than the full raw font data. If this is not possible, then
     // and only then fall back to getting the raw font file data. If that fails,
     // then the only thing left to do is signal failure by returning a null font key.
     if (!aUnscaled->GetWRFontDescriptor(WriteFontDescriptor, &sink) &&
         !aUnscaled->GetFontFileData(WriteFontFileData, &sink)) {
       return fontKey;
     }
-    UpdateResources(resources);
+
+    if (resources.isSome()) {
+      UpdateResources(resources.ref());
+    }
 
     mFontKeys.Put(aUnscaled, fontKey);
   }
 
   return fontKey;
 }
 
 void
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -138,19 +138,20 @@ public:
 
   void PushGlyphs(wr::DisplayListBuilder& aBuilder, Range<const wr::GlyphInstance> aGlyphs,
                   gfx::ScaledFont* aFont, const wr::ColorF& aColor,
                   const StackingContextHelper& aSc,
                   const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                   bool aBackfaceVisible,
                   const wr::GlyphOptions* aGlyphOptions = nullptr);
 
-  wr::FontInstanceKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
-  wr::FontKey GetFontKeyForUnscaledFont(gfx::UnscaledFont* aUnscaledFont);
-
+  wr::FontInstanceKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont,
+                                              wr::IpcResourceUpdateQueue* aResources = nullptr);
+  wr::FontKey GetFontKeyForUnscaledFont(gfx::UnscaledFont* aUnscaledFont,
+                                        wr::IpcResourceUpdateQueue* aResources = nullptr);
   void RemoveExpiredFontKeys(wr::IpcResourceUpdateQueue& aResources);
 
   void BeginClearCachedResources();
   void EndClearCachedResources();
 
   void SetWebRenderLayerManager(WebRenderLayerManager* aManager);
 
   ipc::IShmemAllocator* GetShmemAllocator();
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -350,16 +350,17 @@ struct DIGroup
   LayoutDeviceRect mPaintRect;
   int32_t mAppUnitsPerDevPixel;
   gfx::Size mScale;
   FrameMetrics::ViewID mScrollId;
   LayerPoint mResidualOffset;
   LayerIntRect mLayerBounds;
   Maybe<wr::ImageKey> mKey;
   std::vector<RefPtr<SourceSurface>> mExternalSurfaces;
+  std::vector<RefPtr<ScaledFont>> mFonts;
 
   DIGroup()
     : mAppUnitsPerDevPixel(0)
     , mScrollId(FrameMetrics::NULL_SCROLL_ID)
   {
   }
 
   void InvalidateRect(const IntRect& aRect)
@@ -380,16 +381,26 @@ struct DIGroup
     for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
       BlobItemData* data = iter.Get()->GetKey();
       GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey);
       iter.Remove();
       delete data;
     }
   }
 
+  void ClearImageKey(WebRenderLayerManager* aManager, bool aForce = false)
+  {
+    if (mKey) {
+      MOZ_RELEASE_ASSERT(aForce || mInvalidRect.IsEmpty());
+      aManager->AddImageKeyForDiscard(mKey.value());
+      mKey = Nothing();
+    }
+    mFonts.clear();
+  }
+
   static IntRect
   ToDeviceSpace(nsRect aBounds, Matrix& aMatrix, int32_t aAppUnitsPerDevPixel, LayerIntPoint aOffset)
   {
     // RoundedOut can convert empty rectangles to non-empty ones
     // so special case them here
     if (aBounds.IsEmpty()) {
       return IntRect();
     }
@@ -621,44 +632,46 @@ struct DIGroup
       if (mKey) {
         SetBlobImageVisibleArea(aResources, mKey.value(), bounds, mPaintRect);
         PushImage(aBuilder, bounds);
       }
       return;
     }
 
     gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
+    std::vector<RefPtr<ScaledFont>> fonts;
     RefPtr<WebRenderDrawEventRecorder> recorder =
       MakeAndAddRef<WebRenderDrawEventRecorder>(
-        [&](MemStream& aStream, std::vector<RefPtr<UnscaledFont>>& aUnscaledFonts) {
-          size_t count = aUnscaledFonts.size();
+        [&](MemStream& aStream, std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
+          size_t count = aScaledFonts.size();
           aStream.write((const char*)&count, sizeof(count));
-          for (auto unscaled : aUnscaledFonts) {
-            wr::FontKey key = aWrManager->WrBridge()->GetFontKeyForUnscaledFont(unscaled);
-            aStream.write((const char*)&key, sizeof(key));
+          for (auto& scaled : aScaledFonts) {
+            BlobFont font = {
+              aWrManager->WrBridge()->GetFontKeyForScaledFont(scaled, &aResources),
+              scaled
+            };
+            aStream.write((const char*)&font, sizeof(font));
           }
+          fonts = std::move(aScaledFonts);
         });
 
     RefPtr<gfx::DrawTarget> dummyDt =
       gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
 
     RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, dtSize);
     // Setup the gfxContext
     RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
     GP("ctx-offset %f %f\n", bounds.x, bounds.y);
     context->SetMatrix(Matrix::Scaling(mScale.width, mScale.height).PreTranslate(-bounds.x, -bounds.y));
 
     GP("mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height);
 
     bool empty = aStartItem == aEndItem;
     if (empty) {
-      if (mKey) {
-        aWrManager->AddImageKeyForDiscard(mKey.value());
-        mKey = Nothing();
-      }
+      ClearImageKey(aWrManager, true);
       return;
     }
 
     PaintItemRange(aGrouper, aStartItem, aEndItem, context, recorder);
 
     // XXX: set this correctly perhaps using aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).Contains(paintBounds);?
     bool isOpaque = false;
 
@@ -682,16 +695,17 @@ struct DIGroup
       auto bottomRight = mInvalidRect.BottomRight();
       GP("check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y, dtSize.width, dtSize.height);
       MOZ_RELEASE_ASSERT(bottomRight.x <= dtSize.width && bottomRight.y <= dtSize.height);
       GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height);
       if (!aResources.UpdateBlobImage(mKey.value(), descriptor, bytes, ViewAs<ImagePixel>(mInvalidRect))) {
         return;
       }
     }
+    mFonts = std::move(fonts);
     mInvalidRect.SetEmpty();
     SetBlobImageVisibleArea(aResources, mKey.value(), mPaintRect, bounds);
     PushImage(aBuilder, bounds);
     GP("End EndGroup\n\n");
   }
 
   void PushImage(wr::DisplayListBuilder& aBuilder, const LayoutDeviceRect& bounds)
   {
@@ -1017,21 +1031,17 @@ Grouper::ConstructGroups(nsDisplayListBu
           groupData->mFollowingGroup.mAppUnitsPerDevPixel != currentGroup->mAppUnitsPerDevPixel ||
           groupData->mFollowingGroup.mResidualOffset != currentGroup->mResidualOffset) {
         if (groupData->mFollowingGroup.mAppUnitsPerDevPixel != currentGroup->mAppUnitsPerDevPixel) {
           GP("app unit change following: %d %d\n", groupData->mFollowingGroup.mAppUnitsPerDevPixel, currentGroup->mAppUnitsPerDevPixel);
         }
         // The group changed size
         GP("Inner group size change\n");
         groupData->mFollowingGroup.ClearItems();
-        if (groupData->mFollowingGroup.mKey) {
-          MOZ_RELEASE_ASSERT(groupData->mFollowingGroup.mInvalidRect.IsEmpty());
-          aCommandBuilder->mManager->AddImageKeyForDiscard(groupData->mFollowingGroup.mKey.value());
-          groupData->mFollowingGroup.mKey = Nothing();
-        }
+        groupData->mFollowingGroup.ClearImageKey(aCommandBuilder->mManager);
       }
       groupData->mFollowingGroup.mGroupBounds = currentGroup->mGroupBounds;
       groupData->mFollowingGroup.mAppUnitsPerDevPixel = currentGroup->mAppUnitsPerDevPixel;
       groupData->mFollowingGroup.mLayerBounds = currentGroup->mLayerBounds;
       groupData->mFollowingGroup.mScale = currentGroup->mScale;
       groupData->mFollowingGroup.mResidualOffset = currentGroup->mResidualOffset;
       groupData->mFollowingGroup.mPaintRect = currentGroup->mPaintRect;
 
@@ -1158,21 +1168,17 @@ WebRenderCommandBuilder::DoGroupingForDi
     }
     // The bounds have changed so we need to discard the old image and add all
     // the commands again.
     auto p = group.mGroupBounds;
     auto q = groupBounds;
     GP("Bounds change: %d %d %d %d vs %d %d %d %d\n", p.x, p.y, p.width, p.height, q.x, q.y, q.width, q.height);
 
     group.ClearItems();
-    if (group.mKey) {
-      MOZ_RELEASE_ASSERT(group.mInvalidRect.IsEmpty());
-      mManager->AddImageKeyForDiscard(group.mKey.value());
-      group.mKey = Nothing();
-    }
+    group.ClearImageKey(mManager);
   }
 
   FrameMetrics::ViewID scrollId = FrameMetrics::NULL_SCROLL_ID;
   if (const ActiveScrolledRoot* asr = aWrappingItem->GetActiveScrolledRoot()) {
     scrollId = asr->GetViewId();
   }
 
   g.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
@@ -1791,25 +1797,30 @@ WebRenderCommandBuilder::GenerateFallbac
     newGeometry = aItem->AllocateGeometry(aDisplayListBuilder);
     fallbackData->SetGeometry(std::move(newGeometry));
 
     gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK ?
                                                       gfx::SurfaceFormat::A8 : gfx::SurfaceFormat::B8G8R8A8;
     if (useBlobImage) {
       bool snapped;
       bool isOpaque = aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).Contains(paintBounds);
+      std::vector<RefPtr<ScaledFont>> fonts;
 
       RefPtr<WebRenderDrawEventRecorder> recorder =
-        MakeAndAddRef<WebRenderDrawEventRecorder>([&] (MemStream &aStream, std::vector<RefPtr<UnscaledFont>> &aUnscaledFonts) {
-          size_t count = aUnscaledFonts.size();
+        MakeAndAddRef<WebRenderDrawEventRecorder>([&] (MemStream &aStream, std::vector<RefPtr<ScaledFont>> &aScaledFonts) {
+          size_t count = aScaledFonts.size();
           aStream.write((const char*)&count, sizeof(count));
-          for (auto unscaled : aUnscaledFonts) {
-            wr::FontKey key = mManager->WrBridge()->GetFontKeyForUnscaledFont(unscaled);
-            aStream.write((const char*)&key, sizeof(key));
+          for (auto& scaled : aScaledFonts) {
+            BlobFont font = {
+              mManager->WrBridge()->GetFontKeyForScaledFont(scaled, &aResources),
+              scaled
+            };
+            aStream.write((const char*)&font, sizeof(font));
           }
+          fonts = std::move(aScaledFonts);
         });
       RefPtr<gfx::DrawTarget> dummyDt =
         gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
       RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, dtSize.ToUnknownSize());
       if (!fallbackData->mBasicLayerManager) {
         fallbackData->mBasicLayerManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
       }
       bool isInvalidated = PaintItemByDrawTarget(aItem, dt, paintRect, offset, aDisplayListBuilder,
@@ -1821,16 +1832,17 @@ WebRenderCommandBuilder::GenerateFallbac
       if (isInvalidated) {
         Range<uint8_t> bytes((uint8_t *)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
         wr::ImageKey key = mManager->WrBridge()->GetNextImageKey();
         wr::ImageDescriptor descriptor(dtSize.ToUnknownSize(), 0, dt->GetFormat(), isOpaque);
         if (!aResources.AddBlobImage(key, descriptor, bytes)) {
           return nullptr;
         }
         fallbackData->SetKey(key);
+        fallbackData->SetFonts(fonts);
       } else {
         // If there is no invalidation region and we don't have a image key,
         // it means we don't need to push image for the item.
         if (!fallbackData->GetKey().isSome()) {
           return nullptr;
         }
       }
     } else {
@@ -1987,12 +1999,14 @@ WebRenderGroupData::WebRenderGroupData(W
 {
   MOZ_COUNT_CTOR(WebRenderGroupData);
 }
 
 WebRenderGroupData::~WebRenderGroupData()
 {
   MOZ_COUNT_DTOR(WebRenderGroupData);
   GP("Group data destruct\n");
+  mSubGroup.ClearImageKey(mWRManager, true);
+  mFollowingGroup.ClearImageKey(mWRManager, true);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderDrawEventRecorder.h
+++ b/gfx/layers/wr/WebRenderDrawEventRecorder.h
@@ -7,16 +7,22 @@
 #define MOZILLA_LAYERS_WEBRENDERDRAWTARGETRECORDER_H
 
 #include "mozilla/gfx/DrawEventRecorder.h"
 #include "mozilla/gfx/InlineTranslator.h"
 
 namespace mozilla {
 namespace layers {
 
+struct BlobFont
+{
+  wr::FontInstanceKey mFontInstanceKey;
+  gfx::ReferencePtr mScaledFontPtr;
+};
+
 class WebRenderDrawEventRecorder final : public gfx::DrawEventRecorderMemory
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(WebRenderDrawEventRecorder, final)
 
   explicit WebRenderDrawEventRecorder(const gfx::SerializeResourcesFn &aSerialize)
     : DrawEventRecorderMemory(aSerialize)
   { }
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -172,24 +172,26 @@ public:
   nsDisplayItemGeometry* GetGeometry() override;
   void SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry);
   nsRect GetBounds() { return mBounds; }
   void SetBounds(const nsRect& aRect) { mBounds = aRect; }
   void SetInvalid(bool aInvalid) { mInvalid = aInvalid; }
   void SetScale(gfx::Size aScale) { mScale = aScale; }
   gfx::Size GetScale() { return mScale; }
   bool IsInvalid() { return mInvalid; }
+  void SetFonts(const std::vector<RefPtr<gfx::ScaledFont>>& aFonts) { mFonts = aFonts; }
 
   RefPtr<BasicLayerManager> mBasicLayerManager;
   std::vector<RefPtr<gfx::SourceSurface>> mExternalSurfaces;
 protected:
   nsAutoPtr<nsDisplayItemGeometry> mGeometry;
   nsRect mBounds;
   bool mInvalid;
   gfx::Size mScale;
+  std::vector<RefPtr<gfx::ScaledFont>> mFonts;
 };
 
 class WebRenderAnimationData : public WebRenderUserData
 {
 public:
   WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderAnimationData();
 
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -27,17 +27,17 @@ byteorder = "1.0"
 cfg-if = "0.1.2"
 euclid = "0.19"
 fxhash = "0.2.1"
 gleam = "0.6"
 image = { optional = true, version = "0.19" }
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.2"
-plane-split = "0.12.1"
+plane-split = "0.13"
 png = { optional = true, version = "0.12" }
 rayon = "1"
 ron = { optional = true, version = "0.1.7" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
 serde_json = { optional = true, version = "1.0" }
 smallvec = "0.6"
 thread_profiler = "0.1.1"
 time = "0.1"
--- a/gfx/webrender/res/cs_border_segment.glsl
+++ b/gfx/webrender/res/cs_border_segment.glsl
@@ -89,17 +89,17 @@ vec2 get_outer_corner_scale(int segment)
             break;
         case SEGMENT_BOTTOM_RIGHT:
             p = vec2(1.0, 1.0);
             break;
         case SEGMENT_BOTTOM_LEFT:
             p = vec2(0.0, 1.0);
             break;
         default:
-            // Should never get hit
+            // The result is only used for non-default segment cases
             p = vec2(0.0);
             break;
     }
 
     return p;
 }
 
 // NOTE(emilio): If you change this algorithm, do the same change
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_border_solid.glsl
@@ -0,0 +1,223 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,ellipse
+
+// For edges, the colors are the same. For corners, these
+// are the colors of each edge making up the corner.
+flat varying vec4 vColor0;
+flat varying vec4 vColor1;
+
+// A point + tangent defining the line where the edge
+// transition occurs. Used for corners only.
+flat varying vec4 vColorLine;
+
+// x = segment, z = edge axes, w = clip mode
+flat varying ivec4 vConfig;
+
+// xy = Local space position of the clip center.
+// zw = Scale the rect origin by this to get the outer
+// corner from the segment rectangle.
+flat varying vec4 vClipCenter_Sign;
+
+// An outer and inner elliptical radii for border
+// corner clipping.
+flat varying vec4 vClipRadii;
+
+// Reference point for determine edge clip lines.
+flat varying vec4 vEdgeReference;
+
+// Stores widths/2 and widths/3 to save doing this in FS.
+flat varying vec4 vPartialWidths;
+
+// Clipping parameters for dot or dash.
+flat varying vec4 vClipParams1;
+flat varying vec4 vClipParams2;
+
+// Local space position
+varying vec2 vPos;
+
+#define SEGMENT_TOP_LEFT        0
+#define SEGMENT_TOP_RIGHT       1
+#define SEGMENT_BOTTOM_RIGHT    2
+#define SEGMENT_BOTTOM_LEFT     3
+#define SEGMENT_LEFT            4
+#define SEGMENT_TOP             5
+#define SEGMENT_RIGHT           6
+#define SEGMENT_BOTTOM          7
+
+#define CLIP_NONE   0
+#define CLIP_DASH   1
+#define CLIP_DOT    2
+
+#ifdef WR_VERTEX_SHADER
+
+in vec2 aTaskOrigin;
+in vec4 aRect;
+in vec4 aColor0;
+in vec4 aColor1;
+in int aFlags;
+in vec2 aWidths;
+in vec2 aRadii;
+in vec4 aClipParams1;
+in vec4 aClipParams2;
+
+vec2 get_outer_corner_scale(int segment) {
+    vec2 p;
+
+    switch (segment) {
+        case SEGMENT_TOP_LEFT:
+            p = vec2(0.0, 0.0);
+            break;
+        case SEGMENT_TOP_RIGHT:
+            p = vec2(1.0, 0.0);
+            break;
+        case SEGMENT_BOTTOM_RIGHT:
+            p = vec2(1.0, 1.0);
+            break;
+        case SEGMENT_BOTTOM_LEFT:
+            p = vec2(0.0, 1.0);
+            break;
+        default:
+            // The result is only used for non-default segment cases
+            p = vec2(0.0);
+            break;
+    }
+
+    return p;
+}
+
+void main(void) {
+    int segment = aFlags & 0xff;
+    int clip_mode = (aFlags >> 24) & 0xff;
+
+    vec2 outer_scale = get_outer_corner_scale(segment);
+    vec2 outer = outer_scale * aRect.zw;
+    vec2 clip_sign = 1.0 - 2.0 * outer_scale;
+
+    // Set some flags used by the FS to determine the
+    // orientation of the two edges in this corner.
+    ivec2 edge_axis;
+    // Derive the positions for the edge clips, which must be handled
+    // differently between corners and edges.
+    vec2 edge_reference;
+    switch (segment) {
+        case SEGMENT_TOP_LEFT:
+            edge_axis = ivec2(0, 1);
+            edge_reference = outer;
+            break;
+        case SEGMENT_TOP_RIGHT:
+            edge_axis = ivec2(1, 0);
+            edge_reference = vec2(outer.x - aWidths.x, outer.y);
+            break;
+        case SEGMENT_BOTTOM_RIGHT:
+            edge_axis = ivec2(0, 1);
+            edge_reference = outer - aWidths;
+            break;
+        case SEGMENT_BOTTOM_LEFT:
+            edge_axis = ivec2(1, 0);
+            edge_reference = vec2(outer.x, outer.y - aWidths.y);
+            break;
+        case SEGMENT_TOP:
+        case SEGMENT_BOTTOM:
+            edge_axis = ivec2(1, 1);
+            edge_reference = vec2(0.0);
+            break;
+        case SEGMENT_LEFT:
+        case SEGMENT_RIGHT:
+        default:
+            edge_axis = ivec2(0, 0);
+            edge_reference = vec2(0.0);
+            break;
+    }
+
+    vConfig = ivec4(
+        segment,
+        0,
+        edge_axis.x | (edge_axis.y << 16),
+        clip_mode
+    );
+    vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
+    vPos = aRect.zw * aPosition.xy;
+
+    vColor0 = aColor0;
+    vColor1 = aColor1;
+    vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
+    vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
+    vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
+    vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
+    vClipParams1 = aClipParams1;
+    vClipParams2 = aClipParams2;
+
+    // For the case of dot clips, optimize the number of pixels that
+    // are hit to just include the dot itself.
+    // TODO(gw): We should do something similar in the future for
+    //           dash clips!
+    if (clip_mode == CLIP_DOT) {
+        // Expand by a small amount to allow room for AA around
+        // the dot.
+        float expanded_radius = aClipParams1.z + 2.0;
+        vPos = vClipParams1.xy + expanded_radius * (2.0 * aPosition.xy - 1.0);
+        vPos = clamp(vPos, vec2(0.0), aRect.zw);
+    }
+
+    gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    float aa_range = compute_aa_range(vPos);
+    vec4 color0, color1;
+
+    int segment = vConfig.x;
+    ivec2 edge_axis = ivec2(vConfig.z & 0xffff, vConfig.z >> 16);
+    int clip_mode = vConfig.w;
+
+    float mix_factor = 0.0;
+    if (edge_axis.x != edge_axis.y) {
+        float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
+        mix_factor = distance_aa(aa_range, -d_line);
+    }
+
+    // Check if inside corner clip-region
+    vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
+    bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+    float d = -1.0;
+
+    switch (clip_mode) {
+        case CLIP_DOT: {
+            // Set clip distance based or dot position and radius.
+            d = distance(vClipParams1.xy, vPos) - vClipParams1.z;
+            break;
+        }
+        case CLIP_DASH: {
+            // Get SDF for the two line/tangent clip lines,
+            // do SDF subtract to get clip distance.
+            float d0 = distance_to_line(vClipParams1.xy,
+                                        vClipParams1.zw,
+                                        vPos);
+            float d1 = distance_to_line(vClipParams2.xy,
+                                        vClipParams2.zw,
+                                        vPos);
+            d = max(d0, -d1);
+            break;
+        }
+        case CLIP_NONE:
+        default:
+            break;
+    }
+
+    if (in_clip_region) {
+        float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy, aa_range);
+        float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw, aa_range);
+        float d_radii = max(d_radii_a, -d_radii_b);
+        d = max(d, d_radii);
+    }
+
+    float alpha = distance_aa(aa_range, d);
+    vec4 color = mix(vColor0, vColor1, mix_factor);
+    oFragColor = color * alpha;
+}
+#endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -45,18 +45,18 @@ varying vec4 vClipMaskUv;
 #define COLOR_MODE_IMAGE              9
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sPrimitiveHeadersF;
 uniform HIGHP_SAMPLER_FLOAT isampler2D sPrimitiveHeadersI;
 
 // Instanced attributes
 in ivec4 aData;
 
-#define VECS_PER_PRIM_HEADER_F 2
-#define VECS_PER_PRIM_HEADER_I 2
+#define VECS_PER_PRIM_HEADER_F 2U
+#define VECS_PER_PRIM_HEADER_I 2U
 
 struct PrimitiveHeader {
     RectWithSize local_rect;
     RectWithSize local_clip_rect;
     float z;
     int specific_prim_address;
     int render_task_index;
     int clip_task_index;
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -11,32 +11,34 @@ flat varying vec2 vMaskSwizzle;
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
 varying vec4 vUvClip;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 #define VECS_PER_TEXT_RUN           3
+#define GLYPHS_PER_GPU_BLOCK        2U
 
 struct Glyph {
     vec2 offset;
 };
 
 Glyph fetch_glyph(int specific_prim_address,
                   int glyph_index) {
     // Two glyphs are packed in each texel in the GPU cache.
     int glyph_address = specific_prim_address +
                         VECS_PER_TEXT_RUN +
-                        glyph_index / 2;
+                        int(uint(glyph_index) / GLYPHS_PER_GPU_BLOCK);
     vec4 data = fetch_from_resource_cache_1(glyph_address);
     // Select XY or ZW based on glyph index.
     // We use "!= 0" instead of "== 1" here in order to work around a driver
     // bug with equality comparisons on integers.
-    vec2 glyph = mix(data.xy, data.zw, bvec2(glyph_index % 2 != 0));
+    vec2 glyph = mix(data.xy, data.zw,
+                     bvec2(uint(glyph_index) % GLYPHS_PER_GPU_BLOCK != 0U));
 
     return Glyph(glyph);
 }
 
 struct GlyphResource {
     vec4 uv_rect;
     float layer;
     vec2 offset;
--- a/gfx/webrender/res/render_task.glsl
+++ b/gfx/webrender/res/render_task.glsl
@@ -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/. */
 
 
 #ifdef WR_VERTEX_SHADER
-#define VECS_PER_RENDER_TASK        2
+#define VECS_PER_RENDER_TASK        2U
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
 
 struct RenderTaskCommonData {
     RectWithSize task_rect;
     float texture_layer_index;
 };
 
--- a/gfx/webrender/res/resource_cache.glsl
+++ b/gfx/webrender/res/resource_cache.glsl
@@ -9,18 +9,18 @@ uniform HIGHP_SAMPLER_FLOAT sampler2D sR
 // TODO(gw): This is here temporarily while we have
 //           both GPU store and cache. When the GPU
 //           store code is removed, we can change the
 //           PrimitiveInstance instance structure to
 //           use 2x unsigned shorts as vertex attributes
 //           instead of an int, and encode the UV directly
 //           in the vertices.
 ivec2 get_resource_cache_uv(int address) {
-    return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
-                 address / WR_MAX_VERTEX_TEXTURE_WIDTH);
+    return ivec2(uint(address) % WR_MAX_VERTEX_TEXTURE_WIDTH,
+                 uint(address) / WR_MAX_VERTEX_TEXTURE_WIDTH);
 }
 
 vec4[2] fetch_from_resource_cache_2_direct(ivec2 address) {
     return vec4[2](
         TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0))
     );
 }
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -35,17 +35,17 @@
     // Attribute inputs
     in vec3 aPosition;
 
     // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
     // TODO: convert back to a function once the driver issues are resolved, if ever.
     // https://github.com/servo/webrender/pull/623
     // https://github.com/servo/servo/issues/13953
     // Do the division with unsigned ints because that's more efficient with D3D
-    #define get_fetch_uv(i, vpi)  ivec2(int(uint(vpi) * (uint(i) % uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / uint(WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
+    #define get_fetch_uv(i, vpi)  ivec2(int(vpi * (uint(i) % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
 #endif
 
 //======================================================================================
 // Fragment shader attributes and uniforms
 //======================================================================================
 #ifdef WR_FRAGMENT_SHADER
     // Uniform inputs
 
--- a/gfx/webrender/res/transform.glsl
+++ b/gfx/webrender/res/transform.glsl
@@ -1,17 +1,17 @@
 /* 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/. */
 
 flat varying vec4 vTransformBounds;
 
 #ifdef WR_VERTEX_SHADER
 
-#define VECS_PER_TRANSFORM   8
+#define VECS_PER_TRANSFORM   8U
 uniform HIGHP_SAMPLER_FLOAT sampler2D sTransformPalette;
 
 void init_transform_vs(vec4 local_bounds) {
     vTransformBounds = local_bounds;
 }
 
 struct Transform {
     mat4 m;
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -655,18 +655,20 @@ impl AlphaBatchBuilder {
                                 TransformedRectKind::Complex => {
                                     let mut clipper = Clipper::new();
                                     let matrix = transform.m.cast();
                                     let results = clipper.clip_transformed(
                                         Polygon::from_rect(prim_metadata.local_rect.cast(), prim_index.0),
                                         &matrix,
                                         Some(bounding_rect.to_f64()),
                                     );
-                                    for poly in results {
-                                        splitter.add(poly);
+                                    if let Ok(results) = results {
+                                        for poly in results {
+                                            splitter.add(poly);
+                                        }
                                     }
                                 }
                             }
 
                             return;
                         }
 
                         match picture.raster_config {
@@ -1606,16 +1608,17 @@ pub fn resolve_image(
                             image_properties.descriptor.size,
                         ),
                         texture_layer: 0,
                     };
 
                     deferred_resolves.push(DeferredResolve {
                         image_properties,
                         address: gpu_cache.get_address(&cache_handle),
+                        rendering: request.rendering,
                     });
 
                     cache_item
                 }
                 None => {
                     if let Ok(cache_item) = resource_cache.get_cached_image(request) {
                         cache_item
                     } else {
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -26,17 +26,16 @@ use internal_types::{FastHashMap, FastHa
 use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
 use prim_store::{BorderSource, BrushSegment, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
-use scene_builder::{BuiltScene, SceneRequest};
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, iter, mem};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
@@ -1995,41 +1994,16 @@ impl<'a> DisplayListFlattener<'a> {
         )
     }
 
     pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
         self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
     }
 }
 
-pub fn build_scene(config: &FrameBuilderConfig, request: SceneRequest) -> BuiltScene {
-
-    let mut clip_scroll_tree = ClipScrollTree::new();
-    let mut new_scene = Scene::new();
-
-    let frame_builder = DisplayListFlattener::create_frame_builder(
-        FrameBuilder::empty(), // WIP, we're not really recycling anything here, clean this up.
-        &request.scene,
-        &mut clip_scroll_tree,
-        request.font_instances,
-        &request.view,
-        &request.output_pipelines,
-        config,
-        &mut new_scene,
-        request.scene_id,
-    );
-
-    BuiltScene {
-        scene: new_scene,
-        frame_builder,
-        clip_scroll_tree,
-        removed_pipelines: request.removed_pipelines,
-    }
-}
-
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
 struct FlattenedStackingContext {
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
     /// Filters / mix-blend-mode effects
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -206,22 +206,25 @@ impl<F, T> SpaceMapper<F, T> where F: fm
 
 /// For external images, it's not possible to know the
 /// UV coords of the image (or the image data itself)
 /// until the render thread receives the frame and issues
 /// callbacks to the client application. For external
 /// images that are visible, a DeferredResolve is created
 /// that is stored in the frame. This allows the render
 /// thread to iterate this list and update any changed
-/// texture data and update the UV rect.
+/// texture data and update the UV rect. Any filtering
+/// is handled externally for NativeTexture external
+/// images.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct DeferredResolve {
     pub address: GpuCacheAddress,
     pub image_properties: ImageProperties,
+    pub rendering: ImageRendering,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveIndex(pub usize);
 
 impl GpuCacheHandle {
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -12,18 +12,16 @@ use api::{ScrollLocation, ScrollNodeStat
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
-#[cfg(feature = "replay")]
-use display_list_flattener::build_scene;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use renderer::{AsyncPropertySampler, PipelineInfo};
 use resource_cache::ResourceCache;
@@ -65,47 +63,40 @@ impl DocumentView {
         DevicePixelScale::new(
             self.device_pixel_ratio *
             self.page_zoom_factor *
             self.pinch_zoom_factor
         )
     }
 }
 
-struct SceneData {
-    scene: Scene,
-    removed_pipelines: Vec<PipelineId>,
-}
-
 #[derive(Copy, Clone, Hash, PartialEq, PartialOrd, Debug, Eq, Ord)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(pub u32);
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
-    current: SceneData,
-    // The scene with the latest transactions applied, not necessarily built yet.
-    // what we will send to the scene builder.
-    pending: SceneData,
+    scene: Scene,
+
+    // Temporary list of removed pipelines received from the scene builder
+    // thread and forwarded to the renderer.
+    removed_pipelines: Vec<PipelineId>,
 
     view: DocumentView,
 
     /// The ClipScrollTree for this document which tracks SpatialNodes, ClipNodes, and ClipChains.
     /// This is stored here so that we are able to preserve scrolling positions between rendered
     /// frames.
     clip_scroll_tree: ClipScrollTree,
 
     /// The id of the current frame.
     frame_id: FrameId,
 
-    /// A configuration object for the FrameBuilder that we produce.
-    frame_builder_config: FrameBuilderConfig,
-
     // the `Option` here is only to deal with borrow checker
     frame_builder: Option<FrameBuilder>,
     // A set of pipelines that the caller has requested be
     // made available as output textures.
     output_pipelines: FastHashSet<PipelineId>,
     // A helper switch to prevent any frames rendering triggered by scrolling
     // messages between `SetDisplayList` and `GenerateFrame`.
     // If we allow them, then a reftest that scrolls a few layers before generating
@@ -119,79 +110,70 @@ struct Document {
 
     /// Properties that are resolved during frame building and can be changed at any time
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
 }
 
 impl Document {
     pub fn new(
-        frame_builder_config: FrameBuilderConfig,
         window_size: DeviceUintSize,
         layer: DocumentLayer,
         enable_render_on_scroll: bool,
         default_device_pixel_ratio: f32,
     ) -> Self {
         let render_on_scroll = if enable_render_on_scroll {
             Some(false)
         } else {
             None
         };
         Document {
-            current: SceneData {
-                scene: Scene::new(),
-                removed_pipelines: Vec::new(),
-            },
-            pending: SceneData {
-                scene: Scene::new(),
-                removed_pipelines: Vec::new(),
-            },
+            scene: Scene::new(),
+            removed_pipelines: Vec::new(),
             view: DocumentView {
                 window_size,
                 inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), window_size),
                 layer,
                 pan: DeviceIntPoint::zero(),
                 page_zoom_factor: 1.0,
                 pinch_zoom_factor: 1.0,
                 device_pixel_ratio: default_device_pixel_ratio,
             },
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
-            frame_builder_config,
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             render_on_scroll,
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
         }
     }
 
-    fn can_render(&self) -> bool { self.frame_builder.is_some() }
+    fn can_render(&self) -> bool {
+        self.frame_builder.is_some() && self.scene.has_root_pipeline()
+    }
 
     fn has_pixels(&self) -> bool {
         !self.view.window_size.is_empty_or_negative()
     }
 
     fn process_frame_msg(
         &mut self,
         message: FrameMsg,
     ) -> DocumentOps {
         match message {
             FrameMsg::UpdateEpoch(pipeline_id, epoch) => {
-                self.current.scene.update_epoch(pipeline_id, epoch);
-
-                DocumentOps::nop()
+                self.scene.update_epoch(pipeline_id, epoch);
             }
             FrameMsg::EnableFrameOutput(pipeline_id, enable) => {
                 if enable {
                     self.output_pipelines.insert(pipeline_id);
                 } else {
                     self.output_pipelines.remove(&pipeline_id);
                 }
-                DocumentOps::nop()
             }
             FrameMsg::Scroll(delta, cursor) => {
                 profile_scope!("Scroll");
 
                 let mut should_render = true;
                 let node_index = match self.hit_tester {
                     Some(ref hit_tester) => {
                         // Ideally we would call self.scroll_nearest_scrolling_ancestor here, but
@@ -204,131 +186,84 @@ impl Document {
                         None
                     }
                 };
 
                 let should_render =
                     should_render &&
                     self.scroll_nearest_scrolling_ancestor(delta, node_index) &&
                     self.render_on_scroll == Some(true);
-                DocumentOps {
+
+                return DocumentOps {
                     scroll: true,
-                    render: should_render,
-                    composite: should_render,
+                    build_frame: should_render,
+                    render_frame: should_render,
                     ..DocumentOps::nop()
-                }
+                };
             }
             FrameMsg::HitTest(pipeline_id, point, flags, tx) => {
 
                 let result = match self.hit_tester {
                     Some(ref hit_tester) => {
                         hit_tester.hit_test(HitTest::new(pipeline_id, point, flags))
                     }
                     None => HitTestResult { items: Vec::new() },
                 };
 
                 tx.send(result).unwrap();
-                DocumentOps::nop()
             }
             FrameMsg::SetPan(pan) => {
                 self.view.pan = pan;
-                DocumentOps::nop()
             }
             FrameMsg::ScrollNodeWithId(origin, id, clamp) => {
                 profile_scope!("ScrollNodeWithScrollId");
 
                 let should_render = self.scroll_node(origin, id, clamp)
                     && self.render_on_scroll == Some(true);
 
-                DocumentOps {
+                return DocumentOps {
                     scroll: true,
-                    render: should_render,
-                    composite: should_render,
+                    build_frame: should_render,
+                    render_frame: should_render,
                     ..DocumentOps::nop()
-                }
+                };
             }
             FrameMsg::GetScrollNodeState(tx) => {
                 profile_scope!("GetScrollNodeState");
                 tx.send(self.get_scroll_node_state()).unwrap();
-                DocumentOps::nop()
             }
             FrameMsg::UpdateDynamicProperties(property_bindings) => {
                 self.dynamic_properties.set_properties(property_bindings);
-                DocumentOps::nop()
             }
             FrameMsg::AppendDynamicProperties(property_bindings) => {
                 self.dynamic_properties.add_properties(property_bindings);
-                DocumentOps::nop()
             }
         }
+
+        DocumentOps::nop()
     }
 
-    fn forward_transaction_to_scene_builder(
-        &mut self,
-        transaction_msg: TransactionMsg,
-        blobs_to_rasterize: &[ImageKey],
-        document_ops: &DocumentOps,
-        document_id: DocumentId,
-        scene_id: u64,
-        resource_cache: &mut ResourceCache,
-        scene_tx: &Sender<SceneBuilderRequest>,
-    ) {
-        // Do as much of the error handling as possible here before dispatching to
-        // the scene builder thread.
-        let build_scene: bool = document_ops.build
-            && self.pending.scene.root_pipeline_id.map(
-                |id| { self.pending.scene.pipelines.contains_key(&id) }
-            ).unwrap_or(false);
-
-        let scene_request = if build_scene {
-            Some(SceneRequest {
-                scene: self.pending.scene.clone(),
-                removed_pipelines: replace(&mut self.pending.removed_pipelines, Vec::new()),
-                view: self.view.clone(),
-                font_instances: resource_cache.get_font_instances(),
-                output_pipelines: self.output_pipelines.clone(),
-                scene_id,
-            })
-        } else {
-            None
-        };
-
-        let (blob_rasterizer, blob_requests) = resource_cache.create_blob_scene_builder_requests(
-            blobs_to_rasterize
-        );
-
-        scene_tx.send(SceneBuilderRequest::Transaction {
-            scene: scene_request,
-            blob_requests,
-            blob_rasterizer,
-            resource_updates: transaction_msg.resource_updates,
-            frame_ops: transaction_msg.frame_ops,
-            render: transaction_msg.generate_frame,
-            document_id,
-        }).unwrap();
-    }
-
-    fn render(
+    fn build_frame(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
         is_new_scene: bool,
     ) -> RenderedDocument {
         let accumulated_scale_factor = self.view.accumulated_scale_factor();
         let pan = self.view.pan.to_f32() / accumulated_scale_factor;
 
         let frame = {
             let frame_builder = self.frame_builder.as_mut().unwrap();
             let frame = frame_builder.build(
                 resource_cache,
                 gpu_cache,
                 self.frame_id,
                 &mut self.clip_scroll_tree,
-                &self.current.scene.pipelines,
+                &self.scene.pipelines,
                 accumulated_scale_factor,
                 self.view.layer,
                 pan,
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
             );
             self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree));
@@ -337,19 +272,19 @@ impl Document {
 
         RenderedDocument {
             frame,
             is_new_scene,
         }
     }
 
     pub fn updated_pipeline_info(&mut self) -> PipelineInfo {
-        let removed_pipelines = replace(&mut self.current.removed_pipelines, Vec::new());
+        let removed_pipelines = replace(&mut self.removed_pipelines, Vec::new());
         PipelineInfo {
-            epochs: self.current.scene.pipeline_epochs.clone(),
+            epochs: self.scene.pipeline_epochs.clone(),
             removed_pipelines,
         }
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.clip_scroll_tree
             .discard_frame_state_for_pipeline(pipeline_id);
     }
@@ -372,68 +307,44 @@ impl Document {
     ) -> bool {
         self.clip_scroll_tree.scroll_node(origin, id, clamp)
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
-    pub fn new_async_scene_ready(&mut self, mut built_scene: BuiltScene) {
-        self.current.scene = built_scene.scene;
+    pub fn new_async_scene_ready(&mut self, built_scene: BuiltScene) {
+        self.scene = built_scene.scene;
 
         self.frame_builder = Some(built_scene.frame_builder);
-        self.current.removed_pipelines.extend(built_scene.removed_pipelines.drain(..));
 
         let old_scrolling_states = self.clip_scroll_tree.drain();
         self.clip_scroll_tree = built_scene.clip_scroll_tree;
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
 
         // Advance to the next frame.
         self.frame_id.0 += 1;
     }
 }
 
 struct DocumentOps {
     scroll: bool,
-    build: bool,
-    render: bool,
-    composite: bool,
+    build_frame: bool,
+    render_frame: bool,
 }
 
 impl DocumentOps {
     fn nop() -> Self {
         DocumentOps {
             scroll: false,
-            build: false,
-            render: false,
-            composite: false,
-        }
-    }
-
-    fn build() -> Self {
-        DocumentOps {
-            build: true,
-            ..DocumentOps::nop()
+            build_frame: false,
+            render_frame: false,
         }
     }
-
-    fn render() -> Self {
-        DocumentOps {
-            render: true,
-            ..DocumentOps::nop()
-        }
-    }
-
-    fn combine(&mut self, other: Self) {
-        self.scroll = self.scroll || other.scroll;
-        self.build = self.build || other.build;
-        self.render = self.render || other.render;
-        self.composite = self.composite || other.composite;
-    }
 }
 
 /// The unique id for WR resource identification.
 static NEXT_NAMESPACE_ID: AtomicUsize = ATOMIC_USIZE_INIT;
 
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -450,16 +361,17 @@ struct PlainRenderBackend {
 /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
 ///
 /// The render backend operates on its own thread.
 pub struct RenderBackend {
     api_rx: MsgReceiver<ApiMsg>,
     payload_rx: Receiver<Payload>,
     result_tx: Sender<ResultMsg>,
     scene_tx: Sender<SceneBuilderRequest>,
+    low_priority_scene_tx: Sender<SceneBuilderRequest>,
     scene_rx: Receiver<SceneBuilderResult>,
 
     payload_buffer: Vec<Payload>,
 
     default_device_pixel_ratio: f32,
 
     gpu_cache: GpuCache,
     resource_cache: ResourceCache,
@@ -476,16 +388,17 @@ pub struct RenderBackend {
 }
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: Receiver<Payload>,
         result_tx: Sender<ResultMsg>,
         scene_tx: Sender<SceneBuilderRequest>,
+        low_priority_scene_tx: Sender<SceneBuilderRequest>,
         scene_rx: Receiver<SceneBuilderResult>,
         default_device_pixel_ratio: f32,
         resource_cache: ResourceCache,
         notifier: Box<RenderNotifier>,
         frame_config: FrameBuilderConfig,
         recorder: Option<Box<ApiRecordingReceiver>>,
         sampler: Option<Box<AsyncPropertySampler + Send>>,
         enable_render_on_scroll: bool,
@@ -493,16 +406,17 @@ impl RenderBackend {
         // The namespace_id should start from 1.
         NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
 
         RenderBackend {
             api_rx,
             payload_rx,
             result_tx,
             scene_tx,
+            low_priority_scene_tx,
             scene_rx,
             payload_buffer: Vec::new(),
             default_device_pixel_ratio,
             resource_cache,
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             notifier,
@@ -513,43 +427,39 @@ impl RenderBackend {
         }
     }
 
     fn process_scene_msg(
         &mut self,
         document_id: DocumentId,
         message: SceneMsg,
         frame_counter: u32,
+        txn: &mut Transaction,
         ipc_profile_counters: &mut IpcProfileCounters,
-    ) -> DocumentOps {
+    ) {
         let doc = self.documents.get_mut(&document_id).expect("No document?");
 
         match message {
             SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
-                doc.pending.scene.update_epoch(pipeline_id, epoch);
-
-                DocumentOps::nop()
+                txn.epoch_updates.push((pipeline_id, epoch));
             }
             SceneMsg::SetPageZoom(factor) => {
                 doc.view.page_zoom_factor = factor.get();
-                DocumentOps::nop()
             }
             SceneMsg::SetPinchZoom(factor) => {
                 doc.view.pinch_zoom_factor = factor.get();
-                DocumentOps::nop()
             }
             SceneMsg::SetWindowParameters {
                 window_size,
                 inner_rect,
                 device_pixel_ratio,
             } => {
                 doc.view.window_size = window_size;
                 doc.view.inner_rect = inner_rect;
                 doc.view.device_pixel_ratio = device_pixel_ratio;
-                DocumentOps::nop()
             }
             SceneMsg::SetDisplayList {
                 epoch,
                 pipeline_id,
                 background,
                 viewport_size,
                 content_size,
                 list_descriptor,
@@ -583,26 +493,24 @@ impl RenderBackend {
                     doc.discard_frame_state_for_pipeline(pipeline_id);
                 }
 
                 let display_list_len = built_display_list.data().len();
                 let (builder_start_time, builder_finish_time, send_start_time) =
                     built_display_list.times();
                 let display_list_received_time = precise_time_ns();
 
-                {
-                    doc.pending.scene.set_display_list(
-                        pipeline_id,
-                        epoch,
-                        built_display_list,
-                        background,
-                        viewport_size,
-                        content_size,
-                    );
-                }
+                txn.display_list_updates.push(DisplayListUpdate {
+                    built_display_list,
+                    pipeline_id,
+                    epoch,
+                    background,
+                    viewport_size,
+                    content_size,
+                });
 
                 if let Some(ref mut ros) = doc.render_on_scroll {
                     *ros = false; //wait for `GenerateFrame`
                 }
 
                 // Note: this isn't quite right as auxiliary values will be
                 // pulled out somewhere in the prim_store, but aux values are
                 // really simple and cheap to access, so it's not a big deal.
@@ -611,35 +519,26 @@ impl RenderBackend {
                 ipc_profile_counters.set(
                     builder_start_time,
                     builder_finish_time,
                     send_start_time,
                     display_list_received_time,
                     display_list_consumed_time,
                     display_list_len,
                 );
-
-                DocumentOps::build()
             }
             SceneMsg::SetRootPipeline(pipeline_id) => {
                 profile_scope!("SetRootPipeline");
 
-                doc.pending.scene.set_root_pipeline_id(pipeline_id);
-                if doc.pending.scene.pipelines.get(&pipeline_id).is_some() {
-                    DocumentOps::build()
-                } else {
-                    DocumentOps::nop()
-                }
+                txn.set_root_pipeline = Some(pipeline_id);
             }
             SceneMsg::RemovePipeline(pipeline_id) => {
                 profile_scope!("RemovePipeline");
 
-                doc.pending.scene.remove_pipeline(pipeline_id);
-                doc.pending.removed_pipelines.push(pipeline_id);
-                DocumentOps::nop()
+                txn.removed_pipelines.push(pipeline_id);
             }
         }
     }
 
     fn next_namespace_id(&self) -> IdNamespace {
         IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
     }
 
@@ -657,35 +556,26 @@ impl RenderBackend {
             sampler.register();
         }
 
         while keep_going {
             profile_scope!("handle_msg");
 
             while let Ok(msg) = self.scene_rx.try_recv() {
                 match msg {
-                    SceneBuilderResult::Transaction {
-                        document_id,
-                        mut built_scene,
-                        resource_updates,
-                        frame_ops,
-                        render,
-                        result_tx,
-                        rasterized_blobs,
-                        blob_rasterizer,
-                    } => {
-                        let mut ops = DocumentOps::nop();
-                        if let Some(doc) = self.documents.get_mut(&document_id) {
-                            if let Some(mut built_scene) = built_scene.take() {
+                    SceneBuilderResult::Transaction(mut txn, result_tx) => {
+                        let has_built_scene = txn.built_scene.is_some();
+                        if let Some(doc) = self.documents.get_mut(&txn.document_id) {
+
+                            doc.removed_pipelines.append(&mut txn.removed_pipelines);
+
+                            if let Some(mut built_scene) = txn.built_scene.take() {
                                 doc.new_async_scene_ready(built_scene);
-                                // After applying the new scene we need to
-                                // rebuild the hit-tester, so we trigger a render
-                                // step.
-                                ops = DocumentOps::render();
                             }
+
                             if let Some(tx) = result_tx {
                                 let (resume_tx, resume_rx) = channel();
                                 tx.send(SceneSwapResult::Complete(resume_tx)).unwrap();
                                 // Block until the post-swap hook has completed on
                                 // the scene builder thread. We need to do this before
                                 // we can sample from the sampler hook which might happen
                                 // in the update_document call below.
                                 resume_rx.recv().ok();
@@ -695,38 +585,33 @@ impl RenderBackend {
                             // TODO: we might want to just ensure that removed documents are
                             // always forwarded to the scene builder thread to avoid this case.
                             if let Some(tx) = result_tx {
                                 tx.send(SceneSwapResult::Aborted).unwrap();
                             }
                             continue;
                         }
 
-                        let transaction_msg = TransactionMsg {
-                            scene_ops: Vec::new(),
-                            frame_ops,
-                            resource_updates,
-                            generate_frame: render,
-                            use_scene_builder_thread: false,
-                        };
-
-                        self.resource_cache.add_rasterized_blob_images(rasterized_blobs);
-                        if let Some(rasterizer) = blob_rasterizer {
+                        self.resource_cache.add_rasterized_blob_images(
+                            replace(&mut txn.rasterized_blobs, Vec::new())
+                        );
+                        if let Some(rasterizer) = txn.blob_rasterizer.take() {
                             self.resource_cache.set_blob_rasterizer(rasterizer);
                         }
 
-                        if !transaction_msg.is_empty() || ops.render {
+                        if txn.build_frame || !txn.resource_updates.is_empty() || !txn.frame_ops.is_empty() {
                             self.update_document(
-                                document_id,
-                                transaction_msg,
-                                &[],
+                            txn.document_id,
+                                replace(&mut txn.resource_updates, Vec::new()),
+                                replace(&mut txn.frame_ops, Vec::new()),
+                                txn.build_frame,
+                                txn.render_frame,
                                 &mut frame_counter,
                                 &mut profile_counters,
-                                ops,
-                                true,
+                                has_built_scene,
                             );
                         }
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
                         tx.send(()).ok();
                     }
                     SceneBuilderResult::Stopped => {
                         panic!("We haven't sent a Stop yet, how did we get a Stopped back?");
@@ -740,17 +625,17 @@ impl RenderBackend {
                         r.write_msg(frame_counter, &msg);
                     }
                     self.process_api_msg(msg, &mut profile_counters, &mut frame_counter)
                 }
                 Err(..) => { false }
             };
         }
 
-        let _ = self.scene_tx.send(SceneBuilderRequest::Stop);
+        let _ = self.low_priority_scene_tx.send(SceneBuilderRequest::Stop);
         // Ensure we read everything the scene builder is sending us from
         // inflight messages, otherwise the scene builder might panic.
         while let Ok(msg) = self.scene_rx.recv() {
             match msg {
                 SceneBuilderResult::FlushComplete(tx) => {
                     // If somebody's blocked waiting for a flush, how did they
                     // trigger the RB thread to shut down? This shouldn't happen
                     // but handle it gracefully anyway.
@@ -777,17 +662,17 @@ impl RenderBackend {
         frame_counter: &mut u32,
     ) -> bool {
         match msg {
             ApiMsg::WakeUp => {}
             ApiMsg::WakeSceneBuilder => {
                 self.scene_tx.send(SceneBuilderRequest::WakeUp).unwrap();
             }
             ApiMsg::FlushSceneBuilder(tx) => {
-                self.scene_tx.send(SceneBuilderRequest::Flush(tx)).unwrap();
+                self.low_priority_scene_tx.send(SceneBuilderRequest::Flush(tx)).unwrap();
             }
             ApiMsg::UpdateResources(mut updates) => {
                 self.resource_cache.pre_scene_building_update(
                     &mut updates,
                     &mut profile_counters.resources
                 );
                 self.resource_cache.post_scene_building_update(
                     updates,
@@ -812,26 +697,28 @@ impl RenderBackend {
                 }
                 tx.send(glyph_indices).unwrap();
             }
             ApiMsg::CloneApi(sender) => {
                 sender.send(self.next_namespace_id()).unwrap();
             }
             ApiMsg::AddDocument(document_id, initial_size, layer) => {
                 let document = Document::new(
-                    self.frame_config.clone(),
                     initial_size,
                     layer,
                     self.enable_render_on_scroll,
                     self.default_device_pixel_ratio,
                 );
                 self.documents.insert(document_id, document);
             }
             ApiMsg::DeleteDocument(document_id) => {
                 self.documents.remove(&document_id);
+                self.low_priority_scene_tx.send(
+                    SceneBuilderRequest::DeleteDocument(document_id)
+                ).unwrap();
             }
             ApiMsg::ExternalEvent(evt) => {
                 self.notifier.external_event(evt);
             }
             ApiMsg::ClearNamespace(namespace_id) => {
                 self.resource_cache.clear_namespace(namespace_id);
                 let document_ids = self.documents
                     .keys()
@@ -864,22 +751,17 @@ impl RenderBackend {
             ApiMsg::DebugCommand(option) => {
                 let msg = match option {
                     DebugCommand::EnableDualSourceBlending(enable) => {
                         // Set in the config used for any future documents
                         // that are created.
                         self.frame_config
                             .dual_source_blending_is_enabled = enable;
 
-                        // Set for any existing documents.
-                        for (_, doc) in &mut self.documents {
-                            doc.frame_builder_config.dual_source_blending_is_enabled = enable;
-                        }
-
-                        self.scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig(
+                        self.low_priority_scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig(
                             self.frame_config.clone()
                         )).unwrap();
 
                         // We don't want to forward this message to the renderer.
                         return true;
                     }
                     DebugCommand::FetchDocuments => {
                         let json = self.get_docs_for_debugger();
@@ -899,17 +781,17 @@ impl RenderBackend {
                         NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
                         *frame_counter += 1;
 
                         self.load_capture(&root, profile_counters);
 
                         for (id, doc) in &self.documents {
                             let captured = CapturedDocument {
                                 document_id: *id,
-                                root_pipeline_id: doc.current.scene.root_pipeline_id,
+                                root_pipeline_id: doc.scene.root_pipeline_id,
                                 window_size: doc.view.window_size,
                             };
                             tx.send(captured).unwrap();
                         }
                         // Note: we can't pass `LoadCapture` here since it needs to arrive
                         // before the `PublishDocument` messages sent by `load_capture`.
                         return true;
                     }
@@ -920,157 +802,206 @@ impl RenderBackend {
                     _ => ResultMsg::DebugCommand(option),
                 };
                 self.result_tx.send(msg).unwrap();
                 self.notifier.wake_up();
             }
             ApiMsg::ShutDown => {
                 return false;
             }
-            ApiMsg::UpdateDocument(document_id, mut doc_msgs) => {
-                let blob_requests = get_blob_image_updates(&doc_msgs.resource_updates);
-
-                self.resource_cache.pre_scene_building_update(
-                    &mut doc_msgs.resource_updates,
-                    &mut profile_counters.resources,
-                );
-
-                self.update_document(
+            ApiMsg::UpdateDocument(document_id, transaction_msg) => {
+                self.prepare_transaction(
                     document_id,
-                    doc_msgs,
-                    &blob_requests,
+                    transaction_msg,
                     frame_counter,
                     profile_counters,
-                    DocumentOps::nop(),
-                    false,
-                )
+                );
             }
         }
 
         true
     }
 
-    fn update_document(
+    fn prepare_transaction(
         &mut self,
         document_id: DocumentId,
         mut transaction_msg: TransactionMsg,
-        blob_requests: &[ImageKey],
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
-        initial_op: DocumentOps,
-        has_built_scene: bool,
     ) {
-        let mut op = initial_op;
+        let mut txn = Box::new(Transaction {
+            document_id,
+            display_list_updates: Vec::new(),
+            removed_pipelines: Vec::new(),
+            epoch_updates: Vec::new(),
+            request_scene_build: None,
+            blob_rasterizer: None,
+            blob_requests: Vec::new(),
+            resource_updates: transaction_msg.resource_updates,
+            frame_ops: transaction_msg.frame_ops,
+            rasterized_blobs: Vec::new(),
+            set_root_pipeline: None,
+            build_frame: transaction_msg.generate_frame,
+            render_frame: transaction_msg.generate_frame,
+        });
 
-        if !blob_requests.is_empty() {
-            transaction_msg.use_scene_builder_thread = true;
-        }
+        self.resource_cache.pre_scene_building_update(
+            &mut txn.resource_updates,
+            &mut profile_counters.resources,
+        );
 
         for scene_msg in transaction_msg.scene_ops.drain(..) {
             let _timer = profile_counters.total_time.timer();
-            op.combine(
-                self.process_scene_msg(
-                    document_id,
-                    scene_msg,
-                    *frame_counter,
-                    &mut profile_counters.ipc,
-                )
-            );
+            self.process_scene_msg(
+                document_id,
+                scene_msg,
+                *frame_counter,
+                &mut txn,
+                &mut profile_counters.ipc,
+            )
         }
 
-        if !has_built_scene && (op.build || transaction_msg.use_scene_builder_thread) {
-            let scene_id = self.make_unique_scene_id();
-            let doc = self.documents.get_mut(&document_id).unwrap();
+        let blobs_to_rasterize = get_blob_image_updates(&txn.resource_updates);
+        if !blobs_to_rasterize.is_empty() {
+            let (blob_rasterizer, blob_requests) = self.resource_cache
+                .create_blob_scene_builder_requests(&blobs_to_rasterize);
+
+            txn.blob_requests = blob_requests;
+            txn.blob_rasterizer = blob_rasterizer;
+        }
 
-            doc.forward_transaction_to_scene_builder(
-                transaction_msg,
-                blob_requests,
-                &op,
-                document_id,
-                scene_id,
-                &mut self.resource_cache,
-                &self.scene_tx,
+        if !transaction_msg.use_scene_builder_thread && txn.can_skip_scene_builder() {
+            self.update_document(
+                txn.document_id,
+                replace(&mut txn.resource_updates, Vec::new()),
+                replace(&mut txn.frame_ops, Vec::new()),
+                txn.build_frame,
+                txn.render_frame,
+                frame_counter,
+                profile_counters,
+                false
             );
 
             return;
         }
 
+        let scene_id = self.make_unique_scene_id();
+        let doc = self.documents.get_mut(&document_id).unwrap();
+
+        if txn.should_build_scene() {
+            txn.request_scene_build = Some(SceneRequest {
+                view: doc.view.clone(),
+                font_instances: self.resource_cache.get_font_instances(),
+                output_pipelines: doc.output_pipelines.clone(),
+                scene_id,
+            });
+        }
+
+        let tx = if transaction_msg.low_priority {
+            &self.low_priority_scene_tx
+        } else {
+            &self.scene_tx
+        };
+
+        tx.send(SceneBuilderRequest::Transaction(txn)).unwrap();
+    }
+
+    fn update_document(
+        &mut self,
+        document_id: DocumentId,
+        resource_updates: Vec<ResourceUpdate>,
+        mut frame_ops: Vec<FrameMsg>,
+        mut build_frame: bool,
+        mut render_frame: bool,
+        frame_counter: &mut u32,
+        profile_counters: &mut BackendProfileCounters,
+        has_built_scene: bool,
+    ) {
         self.resource_cache.post_scene_building_update(
-            transaction_msg.resource_updates,
+            resource_updates,
             &mut profile_counters.resources,
         );
 
         // If we have a sampler, get more frame ops from it and add them
         // to the transaction. This is a hook to allow the WR user code to
         // fiddle with things after a potentially long scene build, but just
         // before rendering. This is useful for rendering with the latest
         // async transforms.
-        if op.render || transaction_msg.generate_frame {
+        if build_frame {
             if let Some(ref sampler) = self.sampler {
-                transaction_msg.frame_ops.append(&mut sampler.sample());
+                frame_ops.append(&mut sampler.sample());
             }
         }
 
+
         let doc = self.documents.get_mut(&document_id).unwrap();
 
-        for frame_msg in transaction_msg.frame_ops {
+        let mut scroll = false;
+        for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
-            op.combine(doc.process_frame_msg(frame_msg));
-        }
-
-        if doc.dynamic_properties.flush_pending_updates() {
-            op.render = true;
+            let op = doc.process_frame_msg(frame_msg);
+            build_frame |= op.build_frame;
+            render_frame |= op.render_frame;
+            scroll |= op.scroll;
         }
 
-        if transaction_msg.generate_frame {
+        // After applying the new scene we need to
+        // rebuild the hit-tester, so we trigger a frame generation
+        // step.
+        //
+        // TODO: We could avoid some the cost of building the frame by only
+        // building the information required for hit-testing (See #2807).
+        build_frame |= has_built_scene;
+
+        if doc.dynamic_properties.flush_pending_updates() {
+            build_frame = true;
+        }
+
+        if render_frame {
             if let Some(ref mut ros) = doc.render_on_scroll {
                 *ros = true;
             }
-
-            if doc.current.scene.root_pipeline_id.is_some() {
-                op.render = true;
-                op.composite = true;
-            }
         }
 
         if !doc.can_render() {
             // TODO: this happens if we are building the first scene asynchronously and
             // scroll at the same time. we should keep track of the fact that we skipped
             // composition here and do it as soon as we receive the scene.
-            op.render = false;
-            op.composite = false;
+            build_frame = false;
+            render_frame = false;
         }
 
-        debug_assert!(op.render || !op.composite);
+        // If we don't generate a frame it makes no sense to render.
+        debug_assert!(build_frame || !render_frame);
 
-        let mut render_time = None;
-        if op.render && doc.has_pixels() {
+        let mut frame_build_time = None;
+        if build_frame && doc.has_pixels() {
             profile_scope!("generate frame");
 
             *frame_counter += 1;
 
             // borrow ck hack for profile_counters
             let (pending_update, rendered_document) = {
                 let _timer = profile_counters.total_time.timer();
-                let render_start_time = precise_time_ns();
+                let frame_build_start_time = precise_time_ns();
 
-                let rendered_document = doc.render(
+                let rendered_document = doc.build_frame(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
                     has_built_scene,
                 );
 
                 debug!("generated frame for document {:?} with {} passes",
                     document_id, rendered_document.frame.passes.len());
 
                 let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
                 self.result_tx.send(msg).unwrap();
 
-                render_time = Some(precise_time_ns() - render_start_time);
+                frame_build_time = Some(precise_time_ns() - frame_build_start_time);
 
                 let pending_update = self.resource_cache.pending_updates();
                 (pending_update, rendered_document)
             };
 
             let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info());
             self.result_tx.send(msg).unwrap();
 
@@ -1078,27 +1009,27 @@ impl RenderBackend {
             let msg = ResultMsg::PublishDocument(
                 document_id,
                 rendered_document,
                 pending_update,
                 profile_counters.clone()
             );
             self.result_tx.send(msg).unwrap();
             profile_counters.reset();
-        } else if op.render {
+        } else if build_frame {
             // WR-internal optimization to avoid doing a bunch of render work if
             // there's no pixels. We still want to pretend to render and request
-            // a composite to make sure that the callbacks (particularly the
+            // a render to make sure that the callbacks (particularly the
             // new_frame_ready callback below) has the right flags.
             let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info());
             self.result_tx.send(msg).unwrap();
         }
 
-        if transaction_msg.generate_frame {
-            self.notifier.new_frame_ready(document_id, op.scroll, op.composite, render_time);
+        if render_frame {
+            self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time);
         }
     }
 
     #[cfg(not(feature = "debugger"))]
     fn get_docs_for_debugger(&self) -> String {
         String::new()
     }
 
@@ -1144,17 +1075,17 @@ impl RenderBackend {
 
     #[cfg(feature = "debugger")]
     fn get_docs_for_debugger(&self) -> String {
         let mut docs = debug_server::DocumentList::new();
 
         for (_, doc) in &self.documents {
             let mut debug_doc = debug_server::TreeNode::new("document");
 
-            for (_, pipeline) in &doc.current.scene.pipelines {
+            for (_, pipeline) in &doc.scene.pipelines {
                 let mut debug_dl = debug_server::TreeNode::new("display-list");
                 self.traverse_items(&mut pipeline.display_list.iter(), &mut debug_dl);
                 debug_doc.add_child(debug_dl);
             }
 
             docs.add(debug_doc);
         }
 
@@ -1262,20 +1193,20 @@ impl RenderBackend {
             }
         }
         let config = CaptureConfig::new(root, bits);
 
         for (&id, doc) in &mut self.documents {
             debug!("\tdocument {:?}", id);
             if config.bits.contains(CaptureBits::SCENE) {
                 let file_name = format!("scene-{}-{}", (id.0).0, id.1);
-                config.serialize(&doc.current.scene, file_name);
+                config.serialize(&doc.scene, file_name);
             }
             if config.bits.contains(CaptureBits::FRAME) {
-                let rendered_document = doc.render(
+                let rendered_document = doc.build_frame(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
                     true,
                 );
                 //TODO: write down doc's pipeline info?
                 // it has `pipeline_epoch_map`,
                 // which may capture necessary details for some cases.
@@ -1352,79 +1283,81 @@ impl RenderBackend {
             None => GpuCache::new(),
         };
 
         self.documents.clear();
         self.default_device_pixel_ratio = backend.default_device_pixel_ratio;
         self.frame_config = backend.frame_config;
         self.enable_render_on_scroll = backend.enable_render_on_scroll;
 
+        let mut scenes_to_build = Vec::new();
+
         let mut last_scene_id = backend.last_scene_id;
         for (id, view) in backend.documents {
             debug!("\tdocument {:?}", id);
             let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
             let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
                 .expect(&format!("Unable to open {}.ron", scene_name));
 
             let mut doc = Document {
-                current: SceneData {
-                    scene: scene.clone(),
-                    removed_pipelines: Vec::new(),
-                },
-                pending: SceneData {
-                    scene,
-                    removed_pipelines: Vec::new(),
-                },
+                scene: scene.clone(),
+                removed_pipelines: Vec::new(),
                 view: view.clone(),
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
-                frame_builder_config: self.frame_config.clone(),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 render_on_scroll: None,
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
-            let render_doc = match CaptureConfig::deserialize::<Frame, _>(root, frame_name) {
+            let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
+            let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
-                    RenderedDocument { frame, is_new_scene: true }
+
+                    let msg_update = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
+                    self.result_tx.send(msg_update).unwrap();
+
+                    let msg_publish = ResultMsg::PublishDocument(
+                        id,
+                        RenderedDocument { frame, is_new_scene: true },
+                        self.resource_cache.pending_updates(),
+                        profile_counters.clone(),
+                    );
+                    self.result_tx.send(msg_publish).unwrap();
+                    profile_counters.reset();
+
+                    self.notifier.new_frame_ready(id, false, true, None);
+
+                    // We deserialized the state of the frame so we don't want to build
+                    // it (but we do want to update the scene builder's state)
+                    false
                 }
-                None => {
-                    last_scene_id += 1;
-                    let built_scene = build_scene(&self.frame_config, SceneRequest {
-                        scene: doc.pending.scene.clone(),
-                        view,
-                        font_instances: self.resource_cache.get_font_instances(),
-                        output_pipelines: doc.output_pipelines.clone(),
-                        removed_pipelines: Vec::new(),
-                        scene_id: last_scene_id,
-                    });
-                    doc.new_async_scene_ready(built_scene);
-                    doc.render(
-                        &mut self.resource_cache,
-                        &mut self.gpu_cache,
-                        &mut profile_counters.resources,
-                        true,
-                    )
-                }
+                None => true,
             };
 
-            let msg_update = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
-            self.result_tx.send(msg_update).unwrap();
+            last_scene_id += 1;
 
-            let msg_publish = ResultMsg::PublishDocument(
-                id,
-                render_doc,
-                self.resource_cache.pending_updates(),
-                profile_counters.clone(),
-            );
-            self.result_tx.send(msg_publish).unwrap();
-            profile_counters.reset();
+            scenes_to_build.push(LoadScene {
+                document_id: id,
+                scene: doc.scene.clone(),
+                view: view.clone(),
+                config: self.frame_config.clone(),
+                output_pipelines: doc.output_pipelines.clone(),
+                font_instances: self.resource_cache.get_font_instances(),
+                scene_id: last_scene_id,
+                build_frame,
+            });
 
-            self.notifier.new_frame_ready(id, false, true, None);
             self.documents.insert(id, doc);
         }
+
+        if !scenes_to_build.is_empty() {
+            self.low_priority_scene_tx.send(
+                SceneBuilderRequest::LoadScenes(scenes_to_build)
+            ).unwrap();
+        }
     }
 }
 
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -7,16 +7,17 @@
 //! The `webrender::renderer` module provides the interface to webrender, which
 //! is accessible through [`Renderer`][renderer]
 //!
 //! [renderer]: struct.Renderer.html
 
 use api::{BlobImageHandler, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, Epoch, ExternalImageId};
 use api::{ExternalImageType, FontRenderMode, FrameMsg, ImageFormat, PipelineId};
+use api::{ImageRendering};
 use api::{RenderApiSender, RenderNotifier, TexelRect, TextureTarget};
 use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
@@ -38,17 +39,17 @@ use internal_types::{TextureUpdateList, 
 use internal_types::{RenderTargetInfo, SavedTargetIndex};
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters,
                GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use device::query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
-use scene_builder::SceneBuilder;
+use scene_builder::{SceneBuilder, LowPrioritySceneBuilder};
 use shade::Shaders;
 use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
@@ -1708,19 +1709,21 @@ impl Renderer {
                 Arc::new(worker.unwrap())
             });
         let sampler = options.sampler;
         let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_handler = options.blob_image_handler.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
+        let thread_listener_for_lp_scene_builder = thread_listener.clone();
         let scene_builder_hooks = options.scene_builder_hooks;
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
+        let lp_scene_thread_name = format!("WRSceneBuilderLP#{}", options.renderer_id.unwrap_or(0));
         let glyph_rasterizer = GlyphRasterizer::new(workers)?;
 
         let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(
             config,
             api_tx.clone(),
             scene_builder_hooks);
         thread::Builder::new().name(scene_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(scene_thread_name.clone());
@@ -1731,16 +1734,42 @@ impl Renderer {
             let mut scene_builder = scene_builder;
             scene_builder.run();
 
             if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
                 thread_listener.thread_stopped(&scene_thread_name);
             }
         })?;
 
+        let low_priority_scene_tx = if options.support_low_priority_transactions {
+            let (low_priority_scene_tx, low_priority_scene_rx) = channel();
+            let lp_builder = LowPrioritySceneBuilder {
+                rx: low_priority_scene_rx,
+                tx: scene_tx.clone(),
+            };
+
+            thread::Builder::new().name(lp_scene_thread_name.clone()).spawn(move || {
+                register_thread_with_profiler(lp_scene_thread_name.clone());
+                if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder {
+                    thread_listener.thread_started(&lp_scene_thread_name);
+                }
+
+                let mut scene_builder = lp_builder;
+                scene_builder.run();
+
+                if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder {
+                    thread_listener.thread_stopped(&lp_scene_thread_name);
+                }
+            })?;
+
+            low_priority_scene_tx
+        } else {
+            scene_tx.clone()
+        };
+
         thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(rb_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                 thread_listener.thread_started(&rb_thread_name);
             }
 
             let texture_cache = TextureCache::new(max_device_size);
             let resource_cache = ResourceCache::new(
@@ -1749,16 +1778,17 @@ impl Renderer {
                 blob_image_handler,
             );
 
             let mut backend = RenderBackend::new(
                 api_rx,
                 payload_rx_for_backend,
                 result_tx,
                 scene_tx,
+                low_priority_scene_tx,
                 scene_rx,
                 device_pixel_ratio,
                 resource_cache,
                 backend_notifier,
                 config,
                 recorder,
                 sampler,
                 enable_render_on_scroll,
@@ -2596,17 +2626,18 @@ impl Renderer {
                                     rect, layer_index, stride,
                                     &data[offset as usize ..],
                                 )
                             }
                             TextureUpdateSource::External { id, channel_index } => {
                                 let handler = self.external_image_handler
                                     .as_mut()
                                     .expect("Found external image, but no handler set!");
-                                let size = match handler.lock(id, channel_index).source {
+                                // The filter is only relevant for NativeTexture external images.
+                                let size = match handler.lock(id, channel_index, ImageRendering::Auto).source {
                                     ExternalImageSource::RawData(data) => {
                                         uploader.upload(
                                             rect, layer_index, stride,
                                             &data[offset as usize ..],
                                         )
                                     }
                                     ExternalImageSource::Invalid => {
                                         // Create a local buffer to fill the pbo.
@@ -3361,30 +3392,53 @@ impl Renderer {
         for rect in &target.clears {
             self.device.clear_target(Some([0.0, 0.0, 0.0, 0.0]), None, Some(*rect));
         }
 
         // Handle any blits to this texture from child tasks.
         self.handle_blits(&target.blits, render_tasks);
 
         // Draw any borders for this target.
-        if !target.border_segments.is_empty() {
+        if !target.border_segments_solid.is_empty() ||
+           !target.border_segments_complex.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER);
-
+        }
+
+        if !target.border_segments_solid.is_empty() {
+            self.set_blend(true, FramebufferKind::Other);
+            self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
+
+            self.shaders.cs_border_solid.bind(
+                &mut self.device,
+                &projection,
+                &mut self.renderer_errors,
+            );
+
+            self.draw_instanced_batch(
+                &target.border_segments_solid,
+                VertexArrayKind::Border,
+                &BatchTextures::no_texture(),
+                stats,
+            );
+
+            self.set_blend(false, FramebufferKind::Other);
+        }
+
+        if !target.border_segments_complex.is_empty() {
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
 
             self.shaders.cs_border_segment.bind(
                 &mut self.device,
                 &projection,
                 &mut self.renderer_errors,
             );
 
             self.draw_instanced_batch(
-                &target.border_segments,
+                &target.border_segments_complex,
                 VertexArrayKind::Border,
                 &BatchTextures::no_texture(),
                 stats,
             );
 
             self.set_blend(false, FramebufferKind::Other);
         }
 
@@ -3448,17 +3502,18 @@ impl Renderer {
         };
 
         for deferred_resolve in deferred_resolves {
             self.gpu_profile.place_marker("deferred resolve");
             let props = &deferred_resolve.image_properties;
             let ext_image = props
                 .external_image
                 .expect("BUG: Deferred resolves must be external images!");
-            let image = handler.lock(ext_image.id, ext_image.channel_index);
+            // Provide rendering information for NativeTexture external images.
+            let image = handler.lock(ext_image.id, ext_image.channel_index, deferred_resolve.rendering);
             let texture_target = match ext_image.image_type {
                 ExternalImageType::TextureHandle(target) => target,
                 ExternalImageType::Buffer => {
                     panic!("not a suitable image type in update_deferred_resolves()");
                 }
             };
 
             // In order to produce the handle, the external image handler may call into
@@ -4100,18 +4155,18 @@ pub struct ExternalImage<'a> {
 /// external image buffers.
 /// When the the application passes an external image to WR, it should kepp that
 /// external image life time. People could check the epoch id in RenderNotifier
 /// at the client side to make sure that the external image is not used by WR.
 /// Then, do the clean up for that external image.
 pub trait ExternalImageHandler {
     /// Lock the external image. Then, WR could start to read the image content.
     /// The WR client should not change the image content until the unlock()
-    /// call.
-    fn lock(&mut self, key: ExternalImageId, channel_index: u8) -> ExternalImage;
+    /// call. Provide ImageRendering for NativeTexture external images.
+    fn lock(&mut self, key: ExternalImageId, channel_index: u8, rendering: ImageRendering) -> ExternalImage;
     /// Unlock the external image. The WR should not read the image content
     /// after this call.
     fn unlock(&mut self, key: ExternalImageId, channel_index: u8);
 }
 
 /// Allows callers to receive a texture with the contents of a specific
 /// pipeline copied to it. Lock should return the native texture handle
 /// and the size of the texture. Unlock will only be called if the lock()
@@ -4191,16 +4246,17 @@ pub struct RendererOptions {
     pub enable_render_on_scroll: bool,
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
     pub disable_dual_source_blending: bool,
     pub scene_builder_hooks: Option<Box<SceneBuilderHooks + Send>>,
     pub sampler: Option<Box<AsyncPropertySampler + Send>>,
     pub chase_primitive: ChasePrimitive,
+    pub support_low_priority_transactions: bool,
 }
 
 impl Default for RendererOptions {
     fn default() -> Self {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
@@ -4225,16 +4281,17 @@ impl Default for RendererOptions {
             thread_listener: None,
             enable_render_on_scroll: true,
             renderer_id: None,
             cached_programs: None,
             disable_dual_source_blending: false,
             scene_builder_hooks: None,
             sampler: None,
             chase_primitive: ChasePrimitive::Nothing,
+            support_low_priority_transactions: false,
         }
     }
 }
 
 #[cfg(not(feature = "debugger"))]
 pub struct DebugServer;
 
 #[cfg(not(feature = "debugger"))]
@@ -4298,17 +4355,17 @@ enum CapturedExternalImageData {
 
 #[cfg(feature = "replay")]
 struct DummyExternalImageHandler {
     data: FastHashMap<(ExternalImageId, u8), (CapturedExternalImageData, TexelRect)>,
 }
 
 #[cfg(feature = "replay")]
 impl ExternalImageHandler for DummyExternalImageHandler {
-    fn lock(&mut self, key: ExternalImageId, channel_index: u8) -> ExternalImage {
+    fn lock(&mut self, key: ExternalImageId, channel_index: u8, _rendering: ImageRendering) -> ExternalImage {
         let (ref captured_data, ref uv) = self.data[&(key, channel_index)];
         ExternalImage {
             uv: *uv,
             source: match *captured_data {
                 CapturedExternalImageData::NativeTexture(tid) => ExternalImageSource::NativeTexture(tid),
                 CapturedExternalImageData::Buffer(ref arc) => ExternalImageSource::RawData(&*arc),
             }
         }
@@ -4430,17 +4487,18 @@ impl Renderer {
             let mut arc_map = FastHashMap::<*const u8, String>::default();
             let mut tex_map = FastHashMap::<u32, String>::default();
             let handler = self.external_image_handler
                 .as_mut()
                 .expect("Unable to lock the external image handler!");
             for def in &deferred_images {
                 info!("\t{}", def.short_path);
                 let ExternalImageData { id, channel_index, image_type } = def.external;
-                let ext_image = handler.lock(id, channel_index);
+                // The image rendering parameter is irrelevant because no filtering happens during capturing.
+                let ext_image = handler.lock(id, channel_index, ImageRendering::Auto);
                 let (data, short_path) = match ext_image.source {
                     ExternalImageSource::RawData(data) => {
                         let arc_id = arc_map.len() + 1;
                         match arc_map.entry(data.as_ptr()) {
                             Entry::Occupied(e) => {
                                 (None, e.get().clone())
                             }
                             Entry::Vacant(e) => {
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -183,16 +183,24 @@ impl Scene {
         }
         self.pipelines.remove(&pipeline_id);
         self.pipeline_epochs.remove(&pipeline_id);
     }
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         self.pipeline_epochs.insert(pipeline_id, epoch);
     }
+
+    pub fn has_root_pipeline(&self) -> bool {
+        if let Some(ref root_id) = self.root_pipeline_id {
+            return self.pipelines.contains_key(root_id);
+        }
+
+        false
+    }
 }
 
 /// An arbitrary number which we assume opacity is invisible below.
 pub const OPACITY_EPSILON: f32 = 0.001;
 
 pub trait FilterOpHelpers {
     fn is_visible(&self) -> bool;
     fn is_noop(&self) -> bool;
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -1,85 +1,141 @@
 /* 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::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult};
-use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate};
+use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch};
+use api::{BuiltDisplayList, ColorF, LayoutSize};
 use api::channel::MsgSender;
-use display_list_flattener::build_scene;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip_scroll_tree::ClipScrollTree;
-use internal_types::FastHashSet;
+use display_list_flattener::DisplayListFlattener;
+use internal_types::{FastHashMap, FastHashSet};
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
+use std::mem::replace;
 use time::precise_time_ns;
 
+/// Represents the work associated to a transaction before scene building.
+pub struct Transaction {
+    pub document_id: DocumentId,
+    pub display_list_updates: Vec<DisplayListUpdate>,
+    pub removed_pipelines: Vec<PipelineId>,
+    pub epoch_updates: Vec<(PipelineId, Epoch)>,
+    pub request_scene_build: Option<SceneRequest>,
+    pub blob_requests: Vec<BlobImageParams>,
+    pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
+    pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
+    pub resource_updates: Vec<ResourceUpdate>,
+    pub frame_ops: Vec<FrameMsg>,
+    pub set_root_pipeline: Option<PipelineId>,
+    pub build_frame: bool,
+    pub render_frame: bool,
+}
+
+impl Transaction {
+    pub fn can_skip_scene_builder(&self) -> bool {
+        self.request_scene_build.is_none() &&
+            self.display_list_updates.is_empty() &&
+            self.epoch_updates.is_empty() &&
+            self.removed_pipelines.is_empty() &&
+            self.blob_requests.is_empty() &&
+            self.set_root_pipeline.is_none()
+    }
+
+    pub fn should_build_scene(&self) -> bool {
+        !self.display_list_updates.is_empty() ||
+            self.set_root_pipeline.is_some()
+    }
+}
+
+/// Represent the remaining work associated to a transaction after the scene building
+/// phase as well as the result of scene building itself if applicable.
+pub struct BuiltTransaction {
+    pub document_id: DocumentId,
+    pub built_scene: Option<BuiltScene>,
+    pub resource_updates: Vec<ResourceUpdate>,
+    pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
+    pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
+    pub frame_ops: Vec<FrameMsg>,
+    pub removed_pipelines: Vec<PipelineId>,
+    pub scene_build_start_time: u64,
+    pub scene_build_end_time: u64,
+    pub build_frame: bool,
+    pub render_frame: bool,
+}
+
+pub struct DisplayListUpdate {
+    pub pipeline_id: PipelineId,
+    pub epoch: Epoch,
+    pub built_display_list: BuiltDisplayList,
+    pub background: Option<ColorF>,
+    pub viewport_size: LayoutSize,
+    pub content_size: LayoutSize,
+}
+
+/// Contains the render backend data needed to build a scene.
+pub struct SceneRequest {
+    pub view: DocumentView,
+    pub font_instances: FontInstanceMap,
+    pub output_pipelines: FastHashSet<PipelineId>,
+    pub scene_id: u64,
+}
+
+#[cfg(feature = "replay")]
+pub struct LoadScene {
+    pub document_id: DocumentId,
+    pub scene: Scene,
+    pub scene_id: u64,
+    pub output_pipelines: FastHashSet<PipelineId>,
+    pub font_instances: FontInstanceMap,
+    pub view: DocumentView,
+    pub config: FrameBuilderConfig,
+    pub build_frame: bool,
+}
+
+pub struct BuiltScene {
+    pub scene: Scene,
+    pub frame_builder: FrameBuilder,
+    pub clip_scroll_tree: ClipScrollTree,
+}
+
 // Message from render backend to scene builder.
 pub enum SceneBuilderRequest {
-    Transaction {
-        document_id: DocumentId,
-        scene: Option<SceneRequest>,
-        blob_requests: Vec<BlobImageParams>,
-        blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
-        resource_updates: Vec<ResourceUpdate>,
-        frame_ops: Vec<FrameMsg>,
-        render: bool,
-    },
+    Transaction(Box<Transaction>),
+    DeleteDocument(DocumentId),
     WakeUp,
     Flush(MsgSender<()>),
     SetFrameBuilderConfig(FrameBuilderConfig),
-    Stop
+    Stop,
+    #[cfg(feature = "replay")]
+    LoadScenes(Vec<LoadScene>),
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
-    Transaction {
-        document_id: DocumentId,
-        built_scene: Option<BuiltScene>,
-        resource_updates: Vec<ResourceUpdate>,
-        rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
-        blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
-        frame_ops: Vec<FrameMsg>,
-        render: bool,
-        result_tx: Option<Sender<SceneSwapResult>>,
-    },
+    Transaction(Box<BuiltTransaction>, Option<Sender<SceneSwapResult>>),
     FlushComplete(MsgSender<()>),
     Stopped,
 }
 
 // Message from render backend to scene builder to indicate the
 // scene swap was completed. We need a separate channel for this
 // so that they don't get mixed with SceneBuilderRequest messages.
 pub enum SceneSwapResult {
     Complete(Sender<()>),
     Aborted,
 }
 
-/// Contains the render backend data needed to build a scene.
-pub struct SceneRequest {
-    pub scene: Scene,
-    pub view: DocumentView,
-    pub font_instances: FontInstanceMap,
-    pub output_pipelines: FastHashSet<PipelineId>,
-    pub removed_pipelines: Vec<PipelineId>,
-    pub scene_id: u64,
-}
-
-pub struct BuiltScene {
-    pub scene: Scene,
-    pub frame_builder: FrameBuilder,
-    pub clip_scroll_tree: ClipScrollTree,
-    pub removed_pipelines: Vec<PipelineId>,
-}
-
 pub struct SceneBuilder {
+    documents: FastHashMap<DocumentId, Scene>,
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
     api_tx: MsgSender<ApiMsg>,
     config: FrameBuilderConfig,
     hooks: Option<Box<SceneBuilderHooks + Send>>,
 }
 
 impl SceneBuilder {
@@ -87,140 +143,292 @@ impl SceneBuilder {
         config: FrameBuilderConfig,
         api_tx: MsgSender<ApiMsg>,
         hooks: Option<Box<SceneBuilderHooks + Send>>,
     ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>) {
         let (in_tx, in_rx) = channel();
         let (out_tx, out_rx) = channel();
         (
             SceneBuilder {
+                documents: FastHashMap::default(),
                 rx: in_rx,
                 tx: out_tx,
                 api_tx,
                 config,
                 hooks,
             },
             in_tx,
             out_rx,
         )
     }
 
+    /// The scene builder thread's event loop.
     pub fn run(&mut self) {
         if let Some(ref hooks) = self.hooks {
             hooks.register();
         }
 
         loop {
             match self.rx.recv() {
-                Ok(msg) => {
-                    if !self.process_message(msg) {
-                        break;
-                    }
+                Ok(SceneBuilderRequest::WakeUp) => {}
+                Ok(SceneBuilderRequest::Flush(tx)) => {
+                    self.tx.send(SceneBuilderResult::FlushComplete(tx)).unwrap();
+                    let _ = self.api_tx.send(ApiMsg::WakeUp);
+                }
+                Ok(SceneBuilderRequest::Transaction(mut txn)) => {
+                    let built_txn = self.process_transaction(&mut txn);
+                    self.forward_built_transaction(built_txn);
+                }
+                Ok(SceneBuilderRequest::DeleteDocument(document_id)) => {
+                    self.documents.remove(&document_id);
+                }
+                Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => {
+                    self.config = cfg;
+                }
+                #[cfg(feature = "replay")]
+                Ok(SceneBuilderRequest::LoadScenes(msg)) => {
+                    self.load_scenes(msg);
+                }
+                Ok(SceneBuilderRequest::Stop) => {
+                    self.tx.send(SceneBuilderResult::Stopped).unwrap();
+                    // We don't need to send a WakeUp to api_tx because we only
+                    // get the Stop when the RenderBackend loop is exiting.
+                    break;
                 }
                 Err(_) => {
                     break;
                 }
             }
 
             if let Some(ref hooks) = self.hooks {
                 hooks.poke();
             }
         }
 
         if let Some(ref hooks) = self.hooks {
             hooks.deregister();
         }
     }
 
-    fn process_message(&mut self, msg: SceneBuilderRequest) -> bool {
-        match msg {
-            SceneBuilderRequest::WakeUp => {}
-            SceneBuilderRequest::Flush(tx) => {
-                self.tx.send(SceneBuilderResult::FlushComplete(tx)).unwrap();
-                let _ = self.api_tx.send(ApiMsg::WakeUp);
-            }
-            SceneBuilderRequest::Transaction {
-                document_id,
-                scene,
-                blob_requests,
-                mut blob_rasterizer,
-                resource_updates,
-                frame_ops,
-                render,
-            } => {
-                let scenebuild_start_time = precise_time_ns();
-                let built_scene = scene.map(|request|{
-                    build_scene(&self.config, request)
-                });
+    #[cfg(feature = "replay")]
+    fn load_scenes(&mut self, scenes: Vec<LoadScene>) {
+        for item in scenes {
+            self.config = item.config;
+
+            let scene_build_start_time = precise_time_ns();
 
-                let rasterized_blobs = blob_rasterizer.as_mut().map_or(
-                    Vec::new(),
-                    |rasterizer| rasterizer.rasterize(&blob_requests),
+            let mut built_scene = None;
+            if item.scene.has_root_pipeline() {
+                let mut clip_scroll_tree = ClipScrollTree::new();
+                let mut new_scene = Scene::new();
+
+                let frame_builder = DisplayListFlattener::create_frame_builder(
+                    FrameBuilder::empty(),
+                    &item.scene,
+                    &mut clip_scroll_tree,
+                    item.font_instances,
+                    &item.view,
+                    &item.output_pipelines,
+                    &self.config,
+                    &mut new_scene,
+                    item.scene_id,
                 );
 
-                // We only need the pipeline info and the result channel if we
-                // have a hook callback *and* if this transaction actually built
-                // a new scene that is going to get swapped in. In other cases
-                // pipeline_info can be None and we can avoid some overhead from
-                // invoking the hooks and blocking on the channel.
-                let (pipeline_info, result_tx, result_rx) = match (&self.hooks, &built_scene) {
-                    (&Some(ref hooks), &Some(ref built)) => {
-                        let info = PipelineInfo {
-                            epochs: built.scene.pipeline_epochs.clone(),
-                            removed_pipelines: built.removed_pipelines.clone(),
-                        };
-                        let (tx, rx) = channel();
+                built_scene = Some(BuiltScene {
+                    scene: new_scene,
+                    frame_builder,
+                    clip_scroll_tree,
+                });
+            }
+
+            self.documents.insert(item.document_id, item.scene);
 
-                        let scenebuild_time = precise_time_ns() - scenebuild_start_time;
-                        hooks.pre_scene_swap(scenebuild_time);
+            let txn = Box::new(BuiltTransaction {
+                document_id: item.document_id,
+                build_frame: true,
+                render_frame: item.build_frame,
+                built_scene,
+                resource_updates: Vec::new(),
+                rasterized_blobs: Vec::new(),
+                blob_rasterizer: None,
+                frame_ops: Vec::new(),
+                removed_pipelines: Vec::new(),
+                scene_build_start_time,
+                scene_build_end_time: precise_time_ns(),
+            });
 
-                        (Some(info), Some(tx), Some(rx))
-                    }
-                    _ => (None, None, None),
-                };
+            self.forward_built_transaction(txn);
+        }
+    }
+
+    /// Do the bulk of the work of the scene builder thread.
+    fn process_transaction(&mut self, txn: &mut Transaction) -> Box<BuiltTransaction> {
+
+        let scene_build_start_time = precise_time_ns();
+
+        let scene = self.documents.entry(txn.document_id).or_insert(Scene::new());
 
-                let sceneswap_start_time = precise_time_ns();
-                let has_resources_updates = !resource_updates.is_empty();
-                self.tx.send(SceneBuilderResult::Transaction {
-                    document_id,
-                    built_scene,
-                    resource_updates,
-                    rasterized_blobs,
-                    blob_rasterizer,
-                    frame_ops,
-                    render,
-                    result_tx,
-                }).unwrap();
+        for update in txn.display_list_updates.drain(..) {
+            scene.set_display_list(
+                update.pipeline_id,
+                update.epoch,
+                update.built_display_list,
+                update.background,
+                update.viewport_size,
+                update.content_size,
+            );
+        }
 
-                let _ = self.api_tx.send(ApiMsg::WakeUp);
+        for &(pipeline_id, epoch) in &txn.epoch_updates {
+            scene.update_epoch(pipeline_id, epoch);
+        }
+
+        if let Some(id) = txn.set_root_pipeline {
+            scene.set_root_pipeline_id(id);
+        }
+
+        for pipeline_id in &txn.removed_pipelines {
+            scene.remove_pipeline(*pipeline_id)
+        }
 
-                if let Some(pipeline_info) = pipeline_info {
-                    // Block until the swap is done, then invoke the hook.
-                    let swap_result = result_rx.unwrap().recv();
-                    let sceneswap_time = precise_time_ns() - sceneswap_start_time;
-                    self.hooks.as_ref().unwrap().post_scene_swap(pipeline_info, sceneswap_time);
-                    // Once the hook is done, allow the RB thread to resume
-                    match swap_result {
-                        Ok(SceneSwapResult::Complete(resume_tx)) => {
-                            resume_tx.send(()).ok();
-                        },
-                        _ => (),
-                    };
-                } else if has_resources_updates {
-                    if let &Some(ref hooks) = &self.hooks {
-                        hooks.post_resource_update();
-                    }
-                }
-            }
-            SceneBuilderRequest::Stop => {
-                self.tx.send(SceneBuilderResult::Stopped).unwrap();
-                // We don't need to send a WakeUp to api_tx because we only
-                // get the Stop when the RenderBackend loop is exiting.
-                return false;
-            }
-            SceneBuilderRequest::SetFrameBuilderConfig(cfg) => {
-                self.config = cfg;
+        let mut built_scene = None;
+        if scene.has_root_pipeline() {
+            if let Some(request) = txn.request_scene_build.take() {
+                let mut clip_scroll_tree = ClipScrollTree::new();
+                let mut new_scene = Scene::new();
+
+                let frame_builder = DisplayListFlattener::create_frame_builder(
+                    FrameBuilder::empty(),
+                    &scene,
+                    &mut clip_scroll_tree,
+                    request.font_instances,
+                    &request.view,
+                    &request.output_pipelines,
+                    &self.config,
+                    &mut new_scene,
+                    request.scene_id,
+                );
+
+                built_scene = Some(BuiltScene {
+                    scene: new_scene,
+                    frame_builder,
+                    clip_scroll_tree,
+                });
             }
         }
 
-        true
+        let blob_requests = replace(&mut txn.blob_requests, Vec::new());
+        let mut rasterized_blobs = txn.blob_rasterizer.as_mut().map_or(
+            Vec::new(),
+            |rasterizer| rasterizer.rasterize(&blob_requests),
+        );
+        rasterized_blobs.append(&mut txn.rasterized_blobs);
+
+        Box::new(BuiltTransaction {
+            document_id: txn.document_id,
+            build_frame: txn.build_frame || built_scene.is_some(),
+            render_frame: txn.render_frame,
+            built_scene,
+            rasterized_blobs,
+            resource_updates: replace(&mut txn.resource_updates, Vec::new()),
+            blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
+            frame_ops: replace(&mut txn.frame_ops, Vec::new()),
+            removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()),
+            scene_build_start_time,
+            scene_build_end_time: precise_time_ns(),
+        })
+    }
+
+    /// Send the result of process_transaction back to the render backend.
+    fn forward_built_transaction(&mut self, txn: Box<BuiltTransaction>) {
+        // We only need the pipeline info and the result channel if we
+        // have a hook callback *and* if this transaction actually built
+        // a new scene that is going to get swapped in. In other cases
+        // pipeline_info can be None and we can avoid some overhead from
+        // invoking the hooks and blocking on the channel.
+        let (pipeline_info, result_tx, result_rx) = match (&self.hooks, &txn.built_scene) {
+            (&Some(ref hooks), &Some(ref built)) => {
+                let info = PipelineInfo {
+                    epochs: built.scene.pipeline_epochs.clone(),
+                    removed_pipelines: txn.removed_pipelines.clone(),
+                };
+                let (tx, rx) = channel();
+
+                hooks.pre_scene_swap(txn.scene_build_end_time - txn.scene_build_start_time);
+
+                (Some(info), Some(tx), Some(rx))
+            }
+            _ => (None, None, None),
+        };
+
+        let scene_swap_start_time = precise_time_ns();
+        let has_resources_updates = !txn.resource_updates.is_empty();
+
+        self.tx.send(SceneBuilderResult::Transaction(txn, result_tx)).unwrap();
+
+        let _ = self.api_tx.send(ApiMsg::WakeUp);
+
+        if let Some(pipeline_info) = pipeline_info {
+            // Block until the swap is done, then invoke the hook.
+            let swap_result = result_rx.unwrap().recv();
+            let scene_swap_time = precise_time_ns() - scene_swap_start_time;
+            self.hooks.as_ref().unwrap().post_scene_swap(pipeline_info, scene_swap_time);
+            // Once the hook is done, allow the RB thread to resume
+            match swap_result {
+                Ok(SceneSwapResult::Complete(resume_tx)) => {
+                    resume_tx.send(()).ok();
+                },
+                _ => (),
+            };
+        } else if has_resources_updates {
+            if let &Some(ref hooks) = &self.hooks {
+                hooks.post_resource_update();
+            }
+        }
     }
 }
+
+/// A scene builder thread which executes expensive operations such as blob rasterization
+/// with a lower priority than the normal scene builder thread.
+///
+/// After rasterizing blobs, the secene building request is forwarded to the normal scene
+/// builder where the FrameBuilder is generated.
+pub struct LowPrioritySceneBuilder {
+    pub rx: Receiver<SceneBuilderRequest>,
+    pub tx: Sender<SceneBuilderRequest>,
+}
+
+impl LowPrioritySceneBuilder {
+    pub fn run(&mut self) {
+        loop {
+            match self.rx.recv() {
+                Ok(SceneBuilderRequest::Transaction(txn)) => {
+                    let txn = self.process_transaction(txn);
+                    self.tx.send(SceneBuilderRequest::Transaction(txn)).unwrap();
+                }
+                Ok(SceneBuilderRequest::DeleteDocument(document_id)) => {
+                    self.tx.send(SceneBuilderRequest::DeleteDocument(document_id)).unwrap();
+                }
+                Ok(SceneBuilderRequest::Stop) => {
+                    self.tx.send(SceneBuilderRequest::Stop).unwrap();
+                    break;
+                }
+                Ok(other) => {
+                    self.tx.send(other).unwrap();
+                }
+                Err(_) => {
+                    break;
+                }
+            }
+        }
+    }
+
+    fn process_transaction(&mut self, mut txn: Box<Transaction>) -> Box<Transaction> {
+        let blob_requests = replace(&mut txn.blob_requests, Vec::new());
+        let mut more_rasterized_blobs = txn.blob_rasterizer.as_mut().map_or(
+            Vec::new(),
+            |rasterizer| rasterizer.rasterize(&blob_requests),
+        );
+        txn.rasterized_blobs.append(&mut more_rasterized_blobs);
+
+        txn
+    }
+}
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -347,17 +347,17 @@ impl TextShader {
 
 fn create_prim_shader(
     name: &'static str,
     device: &mut Device,
     features: &[&'static str],
     vertex_format: VertexArrayKind,
 ) -> Result<Program, ShaderError> {
     let mut prefix = format!(
-        "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}\n",
+        "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}U\n",
         MAX_VERTEX_TEXTURE_WIDTH
     );
 
     for feature in features {
         prefix.push_str(&format!("#define WR_FEATURE_{}\n", feature));
     }
 
     debug!("PrimShader {}", name);
@@ -393,17 +393,17 @@ fn create_prim_shader(
         );
     }
 
     program
 }
 
 fn create_clip_shader(name: &'static str, device: &mut Device) -> Result<Program, ShaderError> {
     let prefix = format!(
-        "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}\n
+        "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}U\n
         #define WR_FEATURE_TRANSFORM\n",
         MAX_VERTEX_TEXTURE_WIDTH
     );
 
     debug!("ClipShader {}", name);
 
     let program = device.create_program(name, &prefix, &desc::CLIP);
 
@@ -428,16 +428,17 @@ fn create_clip_shader(name: &'static str
 
 pub struct Shaders {
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
     pub cs_blur_a8: LazilyCompiledShader,
     pub cs_blur_rgba8: LazilyCompiledShader,
     pub cs_border_segment: LazilyCompiledShader,
+    pub cs_border_solid: LazilyCompiledShader,
 
     // Brush shaders
     brush_solid: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
     brush_radial_gradient: BrushShader,
@@ -658,32 +659,41 @@ impl Shaders {
         let cs_border_segment = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Border),
             "cs_border_segment",
              &[],
              device,
              options.precache_shaders,
         )?;
 
+        let cs_border_solid = LazilyCompiledShader::new(
+            ShaderKind::Cache(VertexArrayKind::Border),
+            "cs_border_solid",
+            &[],
+            device,
+            options.precache_shaders,
+        )?;
+
         let ps_split_composite = LazilyCompiledShader::new(
             ShaderKind::Primitive,
             "ps_split_composite",
             &[],
             device,
             options.precache_shaders,
         )?;
 
         if let Some(vao) = dummy_vao {
             device.delete_custom_vao(vao);
         }
 
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             cs_border_segment,
+            cs_border_solid,
             brush_solid,
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
             cs_clip_rectangle,
@@ -771,12 +781,13 @@ impl Shaders {
                 shader.deinit(device);
             }
         }
         for shader in self.brush_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
+        self.cs_border_solid.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.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::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
-use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat, LayoutRect};
-use api::{MixBlendMode, PipelineId};
+use api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
+use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat};
+use api::{LayoutRect, MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
 use clip_scroll_tree::SpatialNodeIndex;
 use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, TransformData, TransformPalette};
@@ -22,16 +22,18 @@ use render_task::{BlitSource, RenderTask
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
 use webrender_api::{DevicePixel, FontRenderMode};
 
 const MIN_TARGET_SIZE: u32 = 2048;
+const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
+const STYLE_MASK: i32 = 0x00FF_FF00;
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub scroll_frame_index: SpatialNodeIndex,
     pub prim_index: PrimitiveIndex,
     pub frame_rect: LayoutRect,
 }
 
@@ -613,28 +615,30 @@ impl RenderTarget for AlphaRenderTarget 
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCacheRenderTarget {
     pub target_kind: RenderTargetKind,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub blits: Vec<BlitJob>,
     pub glyphs: Vec<GlyphJob>,
-    pub border_segments: Vec<BorderInstance>,
+    pub border_segments_complex: Vec<BorderInstance>,
+    pub border_segments_solid: Vec<BorderInstance>,
     pub clears: Vec<DeviceIntRect>,
 }
 
 impl TextureCacheRenderTarget {
     fn new(target_kind: RenderTargetKind) -> Self {
         TextureCacheRenderTarget {
             target_kind,
             horizontal_blurs: vec![],
             blits: vec![],
             glyphs: vec![],
-            border_segments: vec![],
+            border_segments_complex: vec![],
+            border_segments_solid: vec![],
             clears: vec![],
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
@@ -679,18 +683,23 @@ impl TextureCacheRenderTarget {
                 // TODO(gw): It may be better to store the task origin in
                 //           the render task data instead of per instance.
                 let task_origin = target_rect.0.origin.to_f32();
                 for instance in &mut task_info.instances {
                     instance.task_origin = task_origin;
                 }
 
                 let instances = mem::replace(&mut task_info.instances, Vec::new());
-
-                self.border_segments.extend(instances);
+                for instance in instances {
+                    if instance.flags & STYLE_MASK == STYLE_SOLID {
+                        self.border_segments_solid.push(instance);
+                    } else {
+                        self.border_segments_complex.push(instance);
+                    }
+                }
             }
             RenderTaskKind::Glyph(ref mut task_info) => {
                 self.add_glyph_task(task_info, target_rect.0)
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -456,22 +456,30 @@ pub fn project_rect<F, T>(
         transform.transform_point2d_homogeneous(&rect.bottom_left()),
         transform.transform_point2d_homogeneous(&rect.bottom_right()),
     ];
 
     // Note: we only do the full frustum collision when the polygon approaches the camera plane.
     // Otherwise, it will be clamped to the screen bounds anyway.
     if homogens.iter().any(|h| h.w <= 0.0) {
         let mut clipper = Clipper::new();
-        clipper.add_frustum(
+        let polygon = Polygon::from_rect(*rect, 1);
+
+        let planes = match Clipper::frustum_planes(
             transform,
             Some(*bounds),
-        );
+        ) {
+            Ok(planes) => planes,
+            Err(..) => return None,
+        };
 
-        let polygon = Polygon::from_rect(*rect, 1);
+        for plane in planes {
+            clipper.add(plane);
+        }
+
         let results = clipper.clip(polygon);
         if results.is_empty() {
             return None
         }
 
         Some(TypedRect::from_points(results
             .into_iter()
             // filter out parts behind the view plane
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -11,17 +11,17 @@ use mozangle::shaders::{BuiltInResources
 const FRAGMENT_SHADER: u32 = 0x8B30;
 const VERTEX_SHADER: u32 = 0x8B31;
 
 struct Shader {
     name: &'static str,
     features: &'static [&'static str],
 }
 
-const SHADER_PREFIX: &str = "#define WR_MAX_VERTEX_TEXTURE_WIDTH 1024\n";
+const SHADER_PREFIX: &str = "#define WR_MAX_VERTEX_TEXTURE_WIDTH 1024U\n";
 
 const BRUSH_FEATURES: &[&str] = &["", "ALPHA_PASS"];
 const CLIP_FEATURES: &[&str] = &["TRANSFORM"];
 const CACHE_FEATURES: &[&str] = &[""];
 const GRADIENT_FEATURES: &[&str] = &[ "", "DITHERING", "ALPHA_PASS", "DITHERING,ALPHA_PASS" ];
 const PRIM_FEATURES: &[&str] = &[""];
 
 const SHADERS: &[Shader] = &[
@@ -46,16 +46,20 @@ const SHADERS: &[Shader] = &[
     Shader {
         name: "cs_blur",
         features: &[ "ALPHA_TARGET", "COLOR_TARGET" ],
     },
     Shader {
         name: "cs_border_segment",
         features: CACHE_FEATURES,
     },
+    Shader {
+        name: "cs_border_solid",
+        features: CACHE_FEATURES,
+    },
     // Prim shaders
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_text_run",
         features: &[ "", "GLYPH_TRANSFORM" ],
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -51,27 +51,30 @@ pub struct Transaction {
     // Resource updates are applied after scene building.
     pub resource_updates: Vec<ResourceUpdate>,
 
     // If true the transaction is piped through the scene building thread, if false
     // it will be applied directly on the render backend.
     use_scene_builder_thread: bool,
 
     generate_frame: bool,
+
+    low_priority: bool,
 }
 
 impl Transaction {
     pub fn new() -> Self {
         Transaction {
             scene_ops: Vec::new(),
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
             payloads: Vec::new(),
             use_scene_builder_thread: true,
             generate_frame: false,
+            low_priority: false,
         }
     }
 
     // TODO: better name?
     pub fn skip_scene_builder(&mut self) {
         self.use_scene_builder_thread = false;
     }
 
@@ -251,16 +254,17 @@ impl Transaction {
     fn finalize(self) -> (TransactionMsg, Vec<Payload>) {
         (
             TransactionMsg {
                 scene_ops: self.scene_ops,
                 frame_ops: self.frame_ops,
                 resource_updates: self.resource_updates,
                 use_scene_builder_thread: self.use_scene_builder_thread,
                 generate_frame: self.generate_frame,
+                low_priority: self.low_priority,
             },
             self.payloads,
         )
     }
 
     pub fn add_image(
         &mut self,
         key: ImageKey,
@@ -332,16 +336,28 @@ impl Transaction {
                 variations,
             }));
     }
 
     pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
         self.resource_updates.push(ResourceUpdate::DeleteFontInstance(key));
     }
 
+    // A hint that this transaction can be processed at a lower priority. High-
+    // priority transactions can jump ahead of regular-priority transactions,
+    // but both high- and regular-priority transactions are processed in order
+    // relative to other transactions of the same priority.
+    pub fn set_low_priority(&mut self, low_priority: bool) {
+        self.low_priority = low_priority;
+    }
+
+    pub fn is_low_priority(&self) -> bool {
+        self.low_priority
+    }
+
     pub fn merge(&mut self, mut other: Vec<ResourceUpdate>) {
         self.resource_updates.append(&mut other);
     }
 
     pub fn clear(&mut self) {
         self.resource_updates.clear()
     }
 }
@@ -349,16 +365,17 @@ impl Transaction {
 /// Represents a transaction in the format sent through the channel.
 #[derive(Clone, Deserialize, Serialize)]
 pub struct TransactionMsg {
     pub scene_ops: Vec<SceneMsg>,
     pub frame_ops: Vec<FrameMsg>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub generate_frame: bool,
     pub use_scene_builder_thread: bool,
+    pub low_priority: bool,
 }
 
 impl TransactionMsg {
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
             self.resource_updates.is_empty()
@@ -367,26 +384,28 @@ impl TransactionMsg {
     // TODO: We only need this for a few RenderApi methods which we should remove.
     fn frame_message(msg: FrameMsg) -> Self {
         TransactionMsg {
             scene_ops: Vec::new(),
             frame_ops: vec![msg],
             resource_updates: Vec::new(),
             generate_frame: false,
             use_scene_builder_thread: false,
+            low_priority: false,
         }
     }
 
     fn scene_message(msg: SceneMsg) -> Self {
         TransactionMsg {
             scene_ops: vec![msg],
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
             generate_frame: false,
             use_scene_builder_thread: false,
+            low_priority: false,
         }
     }
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddImage {
     pub key: ImageKey,
     pub descriptor: ImageDescriptor,
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -21,28 +21,29 @@
 #include "mozilla/gfx/UnscaledFontMac.h"
 #elif defined(XP_WIN)
 #include "mozilla/gfx/UnscaledFontDWrite.h"
 #else
 #include "mozilla/gfx/UnscaledFontFreeType.h"
 #endif
 
 namespace std {
-  template <>
-    struct hash<mozilla::wr::FontKey>{
-      public :
-        size_t operator()(const mozilla::wr::FontKey &key ) const
-        {
-          return hash<size_t>()(mozilla::wr::AsUint64(key));
-        }
-    };
+  template <> struct hash<mozilla::wr::FontKey> {
+    size_t operator()(const mozilla::wr::FontKey& key) const {
+      return hash<size_t>()(mozilla::wr::AsUint64(key));
+    }
+  };
+
+  template <> struct hash<mozilla::wr::FontInstanceKey> {
+    size_t operator()(const mozilla::wr::FontInstanceKey& key) const {
+      return hash<size_t>()(mozilla::wr::AsUint64(key));
+    }
+  };
 };
 
-
-
 namespace mozilla {
 
 using namespace gfx;
 
 namespace wr {
 
 struct FontTemplate {
   const uint8_t* mData;
@@ -60,18 +61,34 @@ struct FontTemplate {
 
   ~FontTemplate() {
     if (mVec) {
       wr_dec_ref_arc(mVec);
     }
   }
 };
 
+struct FontInstanceData {
+  WrFontKey mFontKey;
+  float mSize;
+  Maybe<FontInstanceOptions> mOptions;
+  Maybe<FontInstancePlatformOptions> mPlatformOptions;
+  UniquePtr<FontVariation[]> mVariations;
+  size_t mNumVariations;
+  RefPtr<ScaledFont> mScaledFont;
+
+  FontInstanceData()
+    : mSize(0)
+    , mNumVariations(0)
+  {}
+};
+
 StaticMutex sFontDataTableLock;
-std::unordered_map<FontKey, FontTemplate> sFontDataTable;
+std::unordered_map<WrFontKey, FontTemplate> sFontDataTable;
+std::unordered_map<WrFontInstanceKey, FontInstanceData> sBlobFontTable;
 
 // Fixed-size ring buffer logging font deletion events to aid debugging.
 static struct FontDeleteLog {
   static const size_t MAX_ENTRIES = 256;
 
   uint64_t mEntries[MAX_ENTRIES] = { 0 };
   size_t mNextEntry = 0;
 
@@ -113,24 +130,32 @@ static struct FontDeleteLog {
     return "unknown font";
   }
 } sFontDeleteLog;
 
 void
 ClearAllBlobImageResources() {
   StaticMutexAutoLock lock(sFontDataTableLock);
   sFontDeleteLog.AddAll();
+  sBlobFontTable.clear();
   sFontDataTable.clear();
 }
 
 extern "C" {
 void
 ClearBlobImageResources(WrIdNamespace aNamespace) {
   StaticMutexAutoLock lock(sFontDataTableLock);
   sFontDeleteLog.Add(aNamespace);
+  for (auto i = sBlobFontTable.begin(); i != sBlobFontTable.end();) {
+    if (i->first.mNamespace == aNamespace) {
+      i = sBlobFontTable.erase(i);
+    } else {
+      i++;
+    }
+  }
   for (auto i = sFontDataTable.begin(); i != sFontDataTable.end();) {
     if (i->first.mNamespace == aNamespace) {
       i = sFontDataTable.erase(i);
     } else {
       i++;
     }
   }
 }
@@ -170,25 +195,62 @@ void
 DeleteFontData(WrFontKey aKey) {
   StaticMutexAutoLock lock(sFontDataTableLock);
   sFontDeleteLog.Add(aKey);
   auto i = sFontDataTable.find(aKey);
   if (i != sFontDataTable.end()) {
     sFontDataTable.erase(i);
   }
 }
+
+void
+AddBlobFont(WrFontInstanceKey aInstanceKey,
+            WrFontKey aFontKey,
+            float aSize,
+            const FontInstanceOptions* aOptions,
+            const FontInstancePlatformOptions* aPlatformOptions,
+            const FontVariation* aVariations,
+            size_t aNumVariations)
+{
+  StaticMutexAutoLock lock(sFontDataTableLock);
+  auto i = sBlobFontTable.find(aInstanceKey);
+  if (i == sBlobFontTable.end()) {
+    FontInstanceData& font = sBlobFontTable[aInstanceKey];
+    font.mFontKey = aFontKey;
+    font.mSize = aSize;
+    if (aOptions) {
+      font.mOptions = Some(*aOptions);
+    }
+    if (aPlatformOptions) {
+      font.mPlatformOptions = Some(*aPlatformOptions);
+    }
+    if (aNumVariations) {
+      font.mVariations.reset(new FontVariation[aNumVariations]);
+      PodCopy(font.mVariations.get(), aVariations, aNumVariations);
+    }
+  }
 }
 
-RefPtr<UnscaledFont>
-GetUnscaledFont(Translator *aTranslator, wr::FontKey key) {
+void
+DeleteBlobFont(WrFontInstanceKey aKey)
+{
   StaticMutexAutoLock lock(sFontDataTableLock);
-  auto i = sFontDataTable.find(key);
+  auto i = sBlobFontTable.find(aKey);
+  if (i != sBlobFontTable.end()) {
+    sBlobFontTable.erase(i);
+  }
+}
+
+static RefPtr<UnscaledFont>
+GetUnscaledFont(Translator* aTranslator, WrFontKey aKey)
+{
+  auto i = sFontDataTable.find(aKey);
   if (i == sFontDataTable.end()) {
-    gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to get UnscaledFont entry for FontKey " << key.mHandle
-                                                 << " because " << sFontDeleteLog.Find(key);
+    gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to get UnscaledFont entry for FontKey " << aKey.mHandle
+                                                 << " because " << sFontDeleteLog.Find(aKey);
     return nullptr;
   }
   FontTemplate &data = i->second;
   if (data.mUnscaledFont) {
     return data.mUnscaledFont;
   }
   MOZ_ASSERT(data.mData);
   FontType type =
@@ -203,29 +265,59 @@ GetUnscaledFont(Translator *aTranslator,
 #endif
   // makes a copy of the data
   RefPtr<NativeFontResource> fontResource = Factory::CreateNativeFontResource((uint8_t*)data.mData, data.mSize,
                                                                               aTranslator->GetReferenceDrawTarget()->GetBackendType(),
                                                                               type,
                                                                               aTranslator->GetFontContext());
   RefPtr<UnscaledFont> unscaledFont;
   if (!fontResource) {
-    gfxDevCrash(LogReason::NativeFontResourceNotFound) << "Failed to create NativeFontResource for FontKey " << key.mHandle;
+    gfxDevCrash(LogReason::NativeFontResourceNotFound) << "Failed to create NativeFontResource for FontKey " << aKey.mHandle;
   } else {
     // Instance data is only needed for GDI fonts which webrender does not
     // support.
     unscaledFont = fontResource->CreateUnscaledFont(data.mIndex, nullptr, 0);
     if (!unscaledFont) {
-      gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to create UnscaledFont for FontKey " << key.mHandle;
+      gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to create UnscaledFont for FontKey " << aKey.mHandle;
     }
   }
   data.mUnscaledFont = unscaledFont;
   return unscaledFont;
 }
 
+static RefPtr<ScaledFont>
+GetScaledFont(Translator* aTranslator, WrFontInstanceKey aKey)
+{
+  StaticMutexAutoLock lock(sFontDataTableLock);
+  auto i = sBlobFontTable.find(aKey);
+  if (i == sBlobFontTable.end()) {
+    gfxDevCrash(LogReason::ScaledFontNotFound) << "Failed to get ScaledFont entry for FontInstanceKey " << aKey.mHandle;
+    return nullptr;
+  }
+  FontInstanceData &data = i->second;
+  if (data.mScaledFont) {
+    return data.mScaledFont;
+  }
+  RefPtr<UnscaledFont> unscaled = GetUnscaledFont(aTranslator, data.mFontKey);
+  if (!unscaled) {
+    return nullptr;
+  }
+  RefPtr<ScaledFont> scaled =
+    unscaled->CreateScaledFontFromWRFont(data.mSize,
+                                         data.mOptions.ptrOr(nullptr),
+                                         data.mPlatformOptions.ptrOr(nullptr),
+                                         data.mVariations.get(),
+                                         data.mNumVariations);
+  if (!scaled) {
+    gfxDevCrash(LogReason::ScaledFontNotFound) << "Failed to create ScaledFont for FontKey " << aKey.mHandle;
+  }
+  data.mScaledFont = scaled;
+  return data.mScaledFont;
+}
+
 static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
                                 gfx::IntSize aSize,
                                 gfx::SurfaceFormat aFormat,
                                 const uint16_t *aTileSize,
                                 const mozilla::wr::TileOffset *aTileOffset,
                                 const mozilla::wr::DeviceUintRect *aDirtyRect,
                                 Range<uint8_t> aOutput)
 {
@@ -324,20 +416,22 @@ static bool Moz2DRenderCallback(const Ra
       offset = extra_end;
       continue;
     }
 
     layers::WebRenderTranslator translator(dt);
 
     size_t count = *(size_t*)(aBlob.begin().get() + end);
     for (size_t i = 0; i < count; i++) {
-      wr::FontKey key = *(wr::FontKey*)(aBlob.begin() + end + sizeof(count) + sizeof(wr::FontKey)*i).get();
-      RefPtr<UnscaledFont> font = GetUnscaledFont(&translator, key);
-      translator.AddUnscaledFont(0, font);
+      layers::BlobFont blobFont =
+        *(layers::BlobFont*)(aBlob.begin() + end + sizeof(count) + sizeof(layers::BlobFont)*i).get();
+      RefPtr<ScaledFont> scaledFont = GetScaledFont(&translator, blobFont.mFontInstanceKey);
+      translator.AddScaledFont(blobFont.mScaledFontPtr, scaledFont);
     }
+
     Range<const uint8_t> blob(aBlob.begin() + offset, aBlob.begin() + end);
     ret = translator.TranslateRecording((char*)blob.begin().get(), blob.length());
     MOZ_RELEASE_ASSERT(ret);
     offset = extra_end;
   }
 
 #if 0
   dt->SetTransform(gfx::Matrix());
@@ -356,21 +450,16 @@ static bool Moz2DRenderCallback(const Ra
   char filename[40];
   sprintf(filename, "out%d.png", i++);
   gfxUtils::WriteAsPNG(dt, filename);
 #endif
 
   return ret;
 }
 
-} // namespace
-} // namespace
-
-extern "C" {
-
 bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
                         uint32_t width, uint32_t height,
                         mozilla::wr::ImageFormat aFormat,
                         const uint16_t *aTileSize,
                         const mozilla::wr::TileOffset *aTileOffset,
                         const mozilla::wr::DeviceUintRect *aDirtyRect,
                         mozilla::wr::MutByteSlice output)
 {
@@ -380,9 +469,11 @@ bool wr_moz2d_render_cb(const mozilla::w
                                           aTileSize,
                                           aTileOffset,
                                           aDirtyRect,
                                           mozilla::wr::MutByteSliceToRange(output));
 }
 
 } // extern
 
+} // namespace
+} // namespace
 
--- a/gfx/webrender_bindings/RenderBufferTextureHost.cpp
+++ b/gfx/webrender_bindings/RenderBufferTextureHost.cpp
@@ -44,17 +44,19 @@ RenderBufferTextureHost::RenderBufferTex
 }
 
 RenderBufferTextureHost::~RenderBufferTextureHost()
 {
   MOZ_COUNT_DTOR_INHERITED(RenderBufferTextureHost, RenderTextureHost);
 }
 
 wr::WrExternalImage
-RenderBufferTextureHost::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
+RenderBufferTextureHost::Lock(uint8_t aChannelIndex,
+                              gl::GLContext* aGL,
+                              wr::ImageRendering aRendering)
 {
   if (!mLocked) {
     if (!GetBuffer()) {
       // We hit some problems to get the shmem.
       return InvalidToWrExternalImage();
     }
     if (mFormat != gfx::SurfaceFormat::YUV) {
       mSurface = gfx::Factory::CreateWrappingDataSourceSurface(GetBuffer(),
--- a/gfx/webrender_bindings/RenderBufferTextureHost.h
+++ b/gfx/webrender_bindings/RenderBufferTextureHost.h
@@ -13,17 +13,19 @@ namespace mozilla {
 namespace wr {
 
 class RenderBufferTextureHost final : public RenderTextureHost
 {
 public:
   RenderBufferTextureHost(uint8_t* aBuffer,
                           const layers::BufferDescriptor& aDescriptor);
 
-  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex,
+                           gl::GLContext* aGL,
+                           wr::ImageRendering aRendering) override;
   void Unlock() override;
 
   class RenderBufferData
   {
   public:
     RenderBufferData(uint8_t* aData, size_t aBufferSize)
       : mData(aData)
       , mBufferSize(aBufferSize)
--- a/gfx/webrender_bindings/RenderD3D11TextureHostOGL.cpp
+++ b/gfx/webrender_bindings/RenderD3D11TextureHostOGL.cpp
@@ -36,19 +36,37 @@ RenderDXGITextureHostOGL::RenderDXGIText
 
 RenderDXGITextureHostOGL::~RenderDXGITextureHostOGL()
 {
   MOZ_COUNT_DTOR_INHERITED(RenderDXGITextureHostOGL, RenderTextureHostOGL);
   DeleteTextureHandle();
 }
 
 bool
-RenderDXGITextureHostOGL::EnsureLockable()
+RenderDXGITextureHostOGL::EnsureLockable(wr::ImageRendering aRendering)
 {
   if (mTextureHandle[0]) {
+    // Update filter if filter was changed.
+    if (IsFilterUpdateNecessary(aRendering)) {
+      ActivateBindAndTexParameteri(mGL,
+                                   LOCAL_GL_TEXTURE0,
+                                   LOCAL_GL_TEXTURE_EXTERNAL_OES,
+                                   mTextureHandle[0],
+                                   aRendering);
+      // Cache new rendering filter.
+      mCachedRendering = aRendering;
+      // NV12 uses two handles.
+      if (mFormat == gfx::SurfaceFormat::NV12) {
+        ActivateBindAndTexParameteri(mGL,
+                                     LOCAL_GL_TEXTURE1,
+                                     LOCAL_GL_TEXTURE_EXTERNAL_OES,
+                                     mTextureHandle[1],
+                                     aRendering);
+      }
+    }
     return true;
   }
 
   auto* egl = gl::GLLibraryEGL::Get();
 
   // We use EGLStream to get the converted gl handle from d3d texture. The
   // NV_stream_consumer_gltexture_yuv and ANGLE_stream_producer_d3d_texture
   // could support nv12 and rgb d3d texture format.
@@ -81,20 +99,23 @@ RenderDXGITextureHostOGL::EnsureLockable
   // Create the EGLStream.
   mStream = egl->fCreateStreamKHR(egl->Display(), nullptr);
   MOZ_ASSERT(mStream);
 
   if (mFormat != gfx::SurfaceFormat::NV12) {
     // The non-nv12 format.
 
     mGL->fGenTextures(1, mTextureHandle);
-    mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureHandle[0]);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
-
+    ActivateBindAndTexParameteri(mGL,
+                                 LOCAL_GL_TEXTURE0,
+                                 LOCAL_GL_TEXTURE_EXTERNAL_OES,
+                                 mTextureHandle[0],
+                                 aRendering);
+    // Cache new rendering filter.
+    mCachedRendering = aRendering;
     MOZ_ALWAYS_TRUE(egl->fStreamConsumerGLTextureExternalAttribsNV(egl->Display(), mStream, nullptr));
     MOZ_ALWAYS_TRUE(egl->fCreateStreamProducerD3DTextureANGLE(egl->Display(), mStream, nullptr));
   } else {
     // The nv12 format.
 
     // Setup the NV12 stream consumer/producer.
     EGLAttrib consumerAttributes[] = {
         LOCAL_EGL_COLOR_BUFFER_TYPE,
@@ -103,47 +124,55 @@ RenderDXGITextureHostOGL::EnsureLockable
         2,
         LOCAL_EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
         0,
         LOCAL_EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
         1,
         LOCAL_EGL_NONE,
     };
     mGL->fGenTextures(2, mTextureHandle);
-    mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureHandle[0]);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
-    mGL->fActiveTexture(LOCAL_GL_TEXTURE1);
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureHandle[1]);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
+    ActivateBindAndTexParameteri(mGL,
+                                 LOCAL_GL_TEXTURE0,
+                                 LOCAL_GL_TEXTURE_EXTERNAL_OES,
+                                 mTextureHandle[0],
+                                 aRendering);
+    ActivateBindAndTexParameteri(mGL,
+                                 LOCAL_GL_TEXTURE1,
+                                 LOCAL_GL_TEXTURE_EXTERNAL_OES,
+                                 mTextureHandle[1],
+                                 aRendering);
+    // Cache new rendering filter.
+    mCachedRendering = aRendering;
     MOZ_ALWAYS_TRUE(egl->fStreamConsumerGLTextureExternalAttribsNV(egl->Display(), mStream, consumerAttributes));
     MOZ_ALWAYS_TRUE(egl->fCreateStreamProducerD3DTextureANGLE(egl->Display(), mStream, nullptr));
   }
 
   // Insert the d3d texture.
   MOZ_ALWAYS_TRUE(egl->fStreamPostD3DTextureANGLE(egl->Display(), mStream, (void*)mTexture.get(), nullptr));
 
   // Now, we could get the gl handle from the stream.
   egl->fStreamConsumerAcquireKHR(egl->Display(), mStream);
   MOZ_ASSERT(egl->fGetError() == LOCAL_EGL_SUCCESS);
 
   return true;
 }
 
 wr::WrExternalImage
-RenderDXGITextureHostOGL::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
+RenderDXGITextureHostOGL::Lock(uint8_t aChannelIndex,
+                               gl::GLContext* aGL,
+                               wr::ImageRendering aRendering)
 {
   if (mGL.get() != aGL) {
     // Release the texture handle in the previous gl context.
     DeleteTextureHandle();
     mGL = aGL;
     mGL->MakeCurrent();
   }
 
-  if (!EnsureLockable()) {
+  if (!EnsureLockable(aRendering)) {
     return InvalidToWrExternalImage();
   }
 
   if (!mLocked) {
     if (mKeyedMutex) {
       HRESULT hr = mKeyedMutex->AcquireSync(0, 10000);
       if (hr != S_OK) {
         gfxCriticalError() << "RenderDXGITextureHostOGL AcquireSync timeout, hr=" << gfx::hexa(hr);
@@ -249,19 +278,31 @@ RenderDXGIYCbCrTextureHostOGL::RenderDXG
 
 RenderDXGIYCbCrTextureHostOGL::~RenderDXGIYCbCrTextureHostOGL()
 {
   MOZ_COUNT_DTOR_INHERITED(RenderDXGIYCbCrTextureHostOGL, RenderTextureHostOGL);
   DeleteTextureHandle();
 }
 
 bool
-RenderDXGIYCbCrTextureHostOGL::EnsureLockable()
+RenderDXGIYCbCrTextureHostOGL::EnsureLockable(wr::ImageRendering aRendering)
 {
   if (mTextureHandles[0]) {
+    // Update filter if filter was changed.
+    if (IsFilterUpdateNecessary(aRendering)) {
+      for (int i = 0; i < 3; ++i) {
+        ActivateBindAndTexParameteri(mGL,
+                                     LOCAL_GL_TEXTURE0 + i,
+                                     LOCAL_GL_TEXTURE_EXTERNAL_OES,
+                                     mTextureHandles[i],
+                                     aRendering);
+        // Cache new rendering filter.
+        mCachedRendering = aRendering;
+      }
+    }
     return true;
   }
 
   auto* egl = gl::GLLibraryEGL::Get();
 
   // The eglCreatePbufferFromClientBuffer doesn't support R8 format, so we
   // use EGLStream to get the converted gl handle from d3d R8 texture.
 
@@ -293,19 +334,23 @@ RenderDXGIYCbCrTextureHostOGL::EnsureLoc
   }
 
   for (int i = 0; i < 3; ++i) {
     mTextures[i]->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mKeyedMutexs[i]));
   }
 
   mGL->fGenTextures(3, mTextureHandles);
   for (int i = 0; i < 3; ++i) {
-    mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureHandles[i]);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL_OES, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
+    ActivateBindAndTexParameteri(mGL,
+                                 LOCAL_GL_TEXTURE0 + i,
+                                 LOCAL_GL_TEXTURE_EXTERNAL_OES,
+                                 mTextureHandles[i],
+                                 aRendering);
+    // Cache new rendering filter.
+    mCachedRendering = aRendering;
 
     // Create the EGLStream.
     mStreams[i] = egl->fCreateStreamKHR(egl->Display(), nullptr);
     MOZ_ASSERT(mStreams[i]);
 
     MOZ_ALWAYS_TRUE(egl->fStreamConsumerGLTextureExternalAttribsNV(egl->Display(), mStreams[i], nullptr));
     MOZ_ALWAYS_TRUE(egl->fCreateStreamProducerD3DTextureANGLE(egl->Display(), mStreams[i], nullptr));
 
@@ -316,26 +361,28 @@ RenderDXGIYCbCrTextureHostOGL::EnsureLoc
     egl->fStreamConsumerAcquireKHR(egl->Display(), mStreams[i]);
     MOZ_ASSERT(egl->fGetError() == LOCAL_EGL_SUCCESS);
   }
 
   return true;
 }
 
 wr::WrExternalImage
-RenderDXGIYCbCrTextureHostOGL::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
+RenderDXGIYCbCrTextureHostOGL::Lock(uint8_t aChannelIndex,
+                                    gl::GLContext* aGL,
+                                    wr::ImageRendering aRendering)
 {
   if (mGL.get() != aGL) {
     // Release the texture handle in the previous gl context.
     DeleteTextureHandle();
     mGL = aGL;
     mGL->MakeCurrent();
   }
 
-  if (!EnsureLockable()) {
+  if (!EnsureLockable(aRendering)) {
     return InvalidToWrExternalImage();
   }
 
   if (!mLocked) {
     if (mKeyedMutexs[0]) {
       for (const auto& mutex : mKeyedMutexs) {
         HRESULT hr = mutex->AcquireSync(0, 10000);
         if (hr != S_OK) {
--- a/gfx/webrender_bindings/RenderD3D11TextureHostOGL.h
+++ b/gfx/webrender_bindings/RenderD3D11TextureHostOGL.h
@@ -19,27 +19,29 @@ namespace wr {
 
 class RenderDXGITextureHostOGL final : public RenderTextureHostOGL
 {
 public:
   explicit RenderDXGITextureHostOGL(WindowsHandle aHandle,
                                     gfx::SurfaceFormat aFormat,
                                     gfx::IntSize aSize);
 
-  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex,
+                           gl::GLContext* aGL,
+                           wr::ImageRendering aRendering) override;
   void Unlock() override;
   void ClearCachedResources() override;
 
   virtual gfx::IntSize GetSize(uint8_t aChannelIndex) const;
   virtual GLuint GetGLHandle(uint8_t aChannelIndex) const;
 
 private:
   virtual ~RenderDXGITextureHostOGL();
 
-  bool EnsureLockable();
+  bool EnsureLockable(wr::ImageRendering aRendering);
 
   void DeleteTextureHandle();
 
   RefPtr<gl::GLContext> mGL;
 
   WindowsHandle mHandle;
   RefPtr<ID3D11Texture2D> mTexture;
   RefPtr<IDXGIKeyedMutex> mKeyedMutex;
@@ -59,27 +61,29 @@ private:
 
 class RenderDXGIYCbCrTextureHostOGL final : public RenderTextureHostOGL
 {
 public:
   explicit RenderDXGIYCbCrTextureHostOGL(WindowsHandle (&aHandles)[3],
                                          gfx::IntSize aSize,
                                          gfx::IntSize aSizeCbCr);
 
-  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex,
+                           gl::GLContext* aGL,
+                           wr::ImageRendering aRendering) override;
   virtual void Unlock() override;
   void ClearCachedResources() override;
 
   virtual gfx::IntSize GetSize(uint8_t aChannelIndex) const;
   virtual GLuint GetGLHandle(uint8_t aChannelIndex) const;
 
 private:
   virtual ~RenderDXGIYCbCrTextureHostOGL();
 
-  bool EnsureLockable();
+  bool EnsureLockable(wr::ImageRendering aRendering);
 
   void DeleteTextureHandle();
 
   RefPtr<gl::GLContext> mGL;
 
   WindowsHandle mHandles[3];
   RefPtr<ID3D11Texture2D> mTextures[3];
   RefPtr<IDXGIKeyedMutex> mKeyedMutexs[3];
--- a/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.cpp
+++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.cpp
@@ -9,23 +9,26 @@
 #include "GLContextCGL.h"
 #include "mozilla/gfx/Logging.h"
 #include "ScopedGLHelpers.h"
 
 namespace mozilla {
 namespace wr {
 
 static CGLError
-CreateTextureForPlane(uint8_t aPlaneID, gl::GLContext* aGL, MacIOSurface* aSurface, GLuint* aTexture)
+CreateTextureForPlane(uint8_t aPlaneID,
+                      gl::GLContext* aGL,
+                      MacIOSurface* aSurface,
+                      GLuint* aTexture,
+                      wr::ImageRendering aRendering)
 {
   MOZ_ASSERT(aGL && aSurface && aTexture);
 
   aGL->fGenTextures(1, aTexture);
-  aGL->fActiveTexture(LOCAL_GL_TEXTURE0);
-  aGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, *aTexture);
+  ActivateBindAndTexParameteri(aGL, LOCAL_GL_TEXTURE0, LOCAL_GL_TEXTURE_RECTANGLE_ARB,  *aTexture, aRendering);
   aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
   aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
 
   CGLError result = kCGLNoError;
   gfx::SurfaceFormat readFormat = gfx::SurfaceFormat::UNKNOWN;
   result = aSurface->CGLTexImageIOSurface2D(aGL,
                                             gl::GLContextCGL::Cast(aGL)->GetCGLContext(),
                                             aPlaneID,
@@ -68,37 +71,56 @@ RenderMacIOSurfaceTextureHostOGL::GetSiz
   if (!mSurface) {
     return gfx::IntSize();
   }
   return gfx::IntSize(mSurface->GetDevicePixelWidth(aChannelIndex),
                       mSurface->GetDevicePixelHeight(aChannelIndex));
 }
 
 wr::WrExternalImage
-RenderMacIOSurfaceTextureHostOGL::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
+RenderMacIOSurfaceTextureHostOGL::Lock(uint8_t aChannelIndex,
+                                       gl::GLContext* aGL,
+                                       wr::ImageRendering aRendering)
 {
   if (mGL.get() != aGL) {
     // release the texture handle in the previous gl context
     DeleteTextureHandle();
     mGL = aGL;
     mGL->MakeCurrent();
   }
 
   if (!mSurface || !mGL || !mGL->MakeCurrent()) {
     return InvalidToWrExternalImage();
   }
 
   if (!mTextureHandles[0]) {
     MOZ_ASSERT(gl::GLContextCGL::Cast(mGL.get())->GetCGLContext());
 
+    mCachedRendering = aRendering;
     // The result of GetPlaneCount() is 0 for single plane format, but it will
     // be 2 if the format has 2 planar data.
-    CreateTextureForPlane(0, mGL, mSurface, &(mTextureHandles[0]));
+    CreateTextureForPlane(0, mGL, mSurface, &(mTextureHandles[0]), aRendering);
     for (size_t i = 1; i < mSurface->GetPlaneCount(); ++i) {
-      CreateTextureForPlane(i, mGL, mSurface, &(mTextureHandles[i]));
+      CreateTextureForPlane(i, mGL, mSurface, &(mTextureHandles[i]), aRendering);
+    }
+    // update filter if filter was changed
+  } else if(IsFilterUpdateNecessary(aRendering)) {
+    ActivateBindAndTexParameteri(aGL,
+                                 LOCAL_GL_TEXTURE0,
+                                 LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+                                 mTextureHandles[0],
+                                 aRendering);
+    // Cache new rendering filter.
+    mCachedRendering = aRendering;
+    for (size_t i = 1; i < mSurface->GetPlaneCount(); ++i) {
+      ActivateBindAndTexParameteri(aGL,
+                                   LOCAL_GL_TEXTURE0,
+                                   LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+                                   mTextureHandles[i],
+                                   aRendering);
     }
   }
 
   gfx::IntSize size = GetSize(aChannelIndex);
   return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0, 0,
                                         size.width, size.height);
 }
 
--- a/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.h
+++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.h
@@ -19,17 +19,19 @@ class SurfaceDescriptorMacIOSurface;
 
 namespace wr {
 
 class RenderMacIOSurfaceTextureHostOGL final : public RenderTextureHostOGL
 {
 public:
   explicit RenderMacIOSurfaceTextureHostOGL(MacIOSurface* aSurface);
 
-  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex,
+                           gl::GLContext* aGL,
+                           wr::ImageRendering aRendering) override;
   void Unlock() override;
 
   virtual gfx::IntSize GetSize(uint8_t aChannelIndex) const override;
   virtual GLuint GetGLHandle(uint8_t aChannelIndex) const override;
 
 private:
   virtual ~RenderMacIOSurfaceTextureHostOGL();
   void DeleteTextureHandle();
--- a/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp
@@ -23,17 +23,19 @@ RenderSharedSurfaceTextureHost::RenderSh
 }
 
 RenderSharedSurfaceTextureHost::~RenderSharedSurfaceTextureHost()
 {
   MOZ_COUNT_DTOR_INHERITED(RenderSharedSurfaceTextureHost, RenderTextureHost);
 }
 
 wr::WrExternalImage
-RenderSharedSurfaceTextureHost::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
+RenderSharedSurfaceTextureHost::Lock(uint8_t aChannelIndex,
+                                     gl::GLContext* aGL,
+                                     wr::ImageRendering aRendering)
 {
   if (!mLocked) {
     if (NS_WARN_IF(!mSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE,
                                   &mMap))) {
       return InvalidToWrExternalImage();
     }
     mLocked = true;
   }
--- a/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h
@@ -21,17 +21,19 @@ namespace wr {
  * into the render texture cache by wrapping an existing surface wrapper. These
  * surfaces are backed by BGRA/X shared memory buffers.
  */
 class RenderSharedSurfaceTextureHost final : public RenderTextureHost
 {
 public:
   explicit RenderSharedSurfaceTextureHost(gfx::SourceSurfaceSharedDataWrapper* aSurface);
 
-  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex,
+                           gl::GLContext* aGL,
+                           wr::ImageRendering aRendering) override;
   void Unlock() override;
 
 private:
   ~RenderSharedSurfaceTextureHost() override;
 
   RefPtr<gfx::SourceSurfaceSharedDataWrapper> mSurface;
   gfx::DataSourceSurface::MappedSurface mMap;
   bool mLocked;
--- a/gfx/webrender_bindings/RenderTextureHost.cpp
+++ b/gfx/webrender_bindings/RenderTextureHost.cpp
@@ -5,21 +5,48 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RenderTextureHost.h"
 #include "RenderThread.h"
 
 namespace mozilla {
 namespace wr {
 
+void
+ActivateBindAndTexParameteri(gl::GLContext* aGL,
+                             GLenum aActiveTexture,
+                             GLenum aBindTarget,
+                             GLuint aBindTexture,
+                             wr::ImageRendering aRendering)
+{
+  aGL->fActiveTexture(aActiveTexture);
+  aGL->fBindTexture(aBindTarget, aBindTexture);
+  aGL->fTexParameteri(aBindTarget,
+                      LOCAL_GL_TEXTURE_MIN_FILTER,
+                      aRendering == wr::ImageRendering::Pixelated
+                        ? LOCAL_GL_NEAREST
+                        : LOCAL_GL_LINEAR);
+  aGL->fTexParameteri(aBindTarget,
+                      LOCAL_GL_TEXTURE_MAG_FILTER,
+                      aRendering == wr::ImageRendering::Pixelated
+                        ? LOCAL_GL_NEAREST
+                        : LOCAL_GL_LINEAR);
+}
+
 RenderTextureHost::RenderTextureHost()
 {
   MOZ_COUNT_CTOR(RenderTextureHost);
 }
 
 RenderTextureHost::~RenderTextureHost()
 {
   MOZ_ASSERT(RenderThread::IsInRenderThread());
   MOZ_COUNT_DTOR(RenderTextureHost);
 }
 
+bool
+RenderTextureHost::IsFilterUpdateNecessary(wr::ImageRendering aRendering)
+{
+  return mCachedRendering != aRendering;
+}
+
 } // namespace wr
 } // namespace mozilla
--- a/gfx/webrender_bindings/RenderTextureHost.h
+++ b/gfx/webrender_bindings/RenderTextureHost.h
@@ -2,45 +2,61 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_RENDERTEXTUREHOST_H
 #define MOZILLA_GFX_RENDERTEXTUREHOST_H
 
+#include "GLConsts.h"
 #include "nsISupportsImpl.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/layers/LayersSurfaces.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/webrender/webrender_ffi.h" // for wr::ImageRendering
 
 namespace mozilla {
 
 namespace gl {
 class GLContext;
 }
 
 namespace wr {
 
 class RenderBufferTextureHost;
 class RenderTextureHostOGL;
 class RenderTextureHostWrapper;
 
+void
+ActivateBindAndTexParameteri(gl::GLContext* aGL,
+                             GLenum aActiveTexture,
+                             GLenum aBindTarget,
+                             GLuint aBindTexture,
+                             wr::ImageRendering aRendering);
+
 class RenderTextureHost
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RenderTextureHost)
 
 public:
   RenderTextureHost();
 
-  virtual wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) = 0;
+  virtual wr::WrExternalImage Lock(uint8_t aChannelIndex,
+                                   gl::GLContext* aGL,
+                                   wr::ImageRendering aRendering) = 0;
   virtual void Unlock() = 0;
   virtual void ClearCachedResources() {}
 
   virtual RenderTextureHostWrapper* AsRenderTextureHostWrapper() { return nullptr; }
+
 protected:
   virtual ~RenderTextureHost();
+
+  bool IsFilterUpdateNecessary(wr::ImageRendering aRendering);
+
+  wr::ImageRendering mCachedRendering;
 };
 
 } // namespace wr
 } // namespace mozilla
 
 #endif // MOZILLA_GFX_RENDERTEXTUREHOST_H
--- a/gfx/webrender_bindings/RenderTextureHostWrapper.cpp
+++ b/gfx/webrender_bindings/RenderTextureHostWrapper.cpp
@@ -22,25 +22,27 @@ RenderTextureHostWrapper::RenderTextureH
 }
 
 RenderTextureHostWrapper::~RenderTextureHostWrapper()
 {
   MOZ_COUNT_DTOR_INHERITED(RenderTextureHostWrapper, RenderTextureHost);
 }
 
 wr::WrExternalImage
-RenderTextureHostWrapper::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
+RenderTextureHostWrapper::Lock(uint8_t aChannelIndex,
+                               gl::GLContext* aGL,
+                               wr::ImageRendering aRendering)
 {
   if (!mTextureHost) {
     MOZ_ASSERT_UNREACHABLE("unexpected to happen");
     return InvalidToWrExternalImage();
   }
 
   mLocked = true;
-  return mTextureHost->Lock(aChannelIndex, aGL);
+  return mTextureHost->Lock(aChannelIndex, aGL, aRendering);
 }
 
 void
 RenderTextureHostWrapper::Unlock()
 {
   if (mTextureHost) {
     mTextureHost->Unlock();
   }
--- a/gfx/webrender_bindings/RenderTextureHostWrapper.h
+++ b/gfx/webrender_bindings/RenderTextureHostWrapper.h
@@ -13,17 +13,19 @@ namespace mozilla {
 
 namespace wr {
 
 class RenderTextureHostWrapper final : public RenderTextureHost
 {
 public:
   explicit RenderTextureHostWrapper();
 
-  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex,
+                           gl::GLContext* aGL,
+                           wr::ImageRendering aRendering) override;
   void Unlock() override;
   void ClearCachedResources() override;
 
   RenderTextureHostWrapper* AsRenderTextureHostWrapper() override { return this; }
 
   void UpdateRenderTextureHost(RenderTextureHost* aTextureHost);
   bool IsInited() { return mInited; }
 
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -13,26 +13,30 @@
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/webrender/RenderCompositor.h"
 #include "mozilla/webrender/RenderTextureHost.h"
 #include "mozilla/widget/CompositorWidget.h"
 
 namespace mozilla {
 namespace wr {
 
-wr::WrExternalImage LockExternalImage(void* aObj, wr::WrExternalImageId aId, uint8_t aChannelIndex)
+wr::WrExternalImage
+LockExternalImage(void* aObj,
+                  wr::WrExternalImageId aId,
+                  uint8_t aChannelIndex,
+                  wr::ImageRendering aRendering)
 {
   RendererOGL* renderer = reinterpret_cast<RendererOGL*>(aObj);
   RenderTextureHost* texture = renderer->GetRenderTexture(aId);
   MOZ_ASSERT(texture);
   if (!texture) {
     gfxCriticalNote << "Failed to lock ExternalImage for extId:" << AsUint64(aId);
     return InvalidToWrExternalImage();
   }
-  return texture->Lock(aChannelIndex, renderer->gl());
+  return texture->Lock(aChannelIndex, renderer->gl(), aRendering);
 }
 
 void UnlockExternalImage(void* aObj, wr::WrExternalImageId aId, uint8_t aChannelIndex)
 {
   RendererOGL* renderer = reinterpret_cast<RendererOGL*>(aObj);
   RenderTextureHost* texture = renderer->GetRenderTexture(aId);
   MOZ_ASSERT(texture);
   if (!texture) {
--- a/gfx/webrender_bindings/RendererOGL.h
+++ b/gfx/webrender_bindings/RendererOGL.h
@@ -38,17 +38,20 @@ class RenderTextureHost;
 
 /// Owns the WebRender renderer and GL context.
 ///
 /// There is one renderer per window, all owned by the render thread.
 /// This class is a similar abstraction to CompositorOGL except that it is used
 /// on the render thread instead of the compositor thread.
 class RendererOGL
 {
-  friend wr::WrExternalImage LockExternalImage(void* aObj, wr::WrExternalImageId aId, uint8_t aChannelIndex);
+  friend wr::WrExternalImage LockExternalImage(void* aObj,
+                                               wr::WrExternalImageId aId,
+                                               uint8_t aChannelIndex,
+                                               wr::ImageRendering);
   friend void UnlockExternalImage(void* aObj, wr::WrExternalImageId aId, uint8_t aChannelIndex);
 
 public:
   wr::WrExternalImageHandler GetExternalImageHandler();
 
   /// This can be called on the render thread only.
   void Update();
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-d89e290c57aab76c45d8016975240cf762354e39
+46af5bf17978a97f0ab2a899a0d785d58c140a0a
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -68,17 +68,17 @@ pub type WrIdNamespace = IdNamespace;
 /// cbindgen:field-names=[mNamespace, mHandle]
 type WrPipelineId = PipelineId;
 /// cbindgen:field-names=[mNamespace, mHandle]
 /// cbindgen:derive-neq=true
 type WrImageKey = ImageKey;
 /// cbindgen:field-names=[mNamespace, mHandle]
 pub type WrFontKey = FontKey;
 /// cbindgen:field-names=[mNamespace, mHandle]
-type WrFontInstanceKey = FontInstanceKey;
+pub type WrFontInstanceKey = FontInstanceKey;
 /// cbindgen:field-names=[mNamespace, mHandle]
 type WrYuvColorSpace = YuvColorSpace;
 
 fn make_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
     if ptr.is_null() {
         &[]
     } else {
         unsafe { slice::from_raw_parts(ptr, len) }
@@ -339,33 +339,34 @@ struct WrExternalImage {
     u1: f32,
     v1: f32,
 
     // external image buffer
     buff: *const u8,
     size: usize,
 }
 
-type LockExternalImageCallback = unsafe extern "C" fn(*mut c_void, WrExternalImageId, u8) -> WrExternalImage;
+type LockExternalImageCallback = unsafe extern "C" fn(*mut c_void, WrExternalImageId, u8, ImageRendering) -> WrExternalImage;
 type UnlockExternalImageCallback = unsafe extern "C" fn(*mut c_void, WrExternalImageId, u8);
 
 #[repr(C)]
 pub struct WrExternalImageHandler {
     external_image_obj: *mut c_void,
     lock_func: LockExternalImageCallback,
     unlock_func: UnlockExternalImageCallback,
 }
 
 impl ExternalImageHandler for WrExternalImageHandler {
     fn lock(&mut self,
             id: ExternalImageId,
-            channel_index: u8)
+            channel_index: u8,
+            rendering: ImageRendering)
             -> ExternalImage {
 
-        let image = unsafe { (self.lock_func)(self.external_image_obj, id.into(), channel_index) };
+        let image = unsafe { (self.lock_func)(self.external_image_obj, id.into(), channel_index, rendering) };
         ExternalImage {
             uv: TexelRect::new(image.u0, image.v0, image.u1, image.v1),
             source: match image.image_type {
                 WrExternalImageType::NativeTexture => ExternalImageSource::NativeTexture(image.handle),
                 WrExternalImageType::RawData => ExternalImageSource::RawData(make_slice(image.buff, image.size)),
                 WrExternalImageType::Invalid => ExternalImageSource::Invalid,
             },
         }
--- a/gfx/webrender_bindings/src/moz2d_renderer.rs
+++ b/gfx/webrender_bindings/src/moz2d_renderer.rs
@@ -77,17 +77,17 @@ impl<'a> BufReader<'a> {
     }
 
     fn read<T>(&mut self) -> T {
         let ret = convert_from_bytes(&self.buf[self.pos..]);
         self.pos += mem::size_of::<T>();
         ret
     }
 
-    fn read_font_key(&mut self) -> FontKey {
+    fn read_blob_font(&mut self) -> BlobFont {
         self.read()
     }
 
     fn read_usize(&mut self) -> usize {
         self.read()
     }
 
     fn has_more(&self) -> bool {
@@ -328,16 +328,21 @@ fn merge_blob_images(old_buf: &[u8], new
 
     assert!(old_reader.cache.is_empty());
 
     let result = result.finish();
     check_result(&result);
     result
 }
 
+#[repr(C)]
+struct BlobFont {
+    font_instance_key: FontInstanceKey,
+    scaled_font_ptr: u64,
+}
 
 struct Moz2dBlobRasterizer {
     workers: Arc<ThreadPool>,
     blob_commands: HashMap<ImageKey, (Arc<BlobImageData>, Option<TileSize>)>,
 }
 
 impl AsyncBlobImageRasterizer for Moz2dBlobRasterizer {
 
@@ -432,17 +437,18 @@ impl BlobImageHandler for Moz2dBlobImage
             blob_commands: self.blob_commands.clone(),
         })
     }
 
     fn delete_font(&mut self, font: FontKey) {
         unsafe { DeleteFontData(font); }
     }
 
-    fn delete_font_instance(&mut self, _key: FontInstanceKey) {
+    fn delete_font_instance(&mut self, key: FontInstanceKey) {
+        unsafe { DeleteBlobFont(key); }
     }
 
     fn clear_namespace(&mut self, namespace: IdNamespace) {
         unsafe { ClearBlobImageResources(namespace); }
     }
 
     fn prepare_resources(
         &mut self,
@@ -452,23 +458,33 @@ impl BlobImageHandler for Moz2dBlobImage
         for params in requests {
             let commands = &self.blob_commands[&params.request.key];
             let blob = Arc::clone(&commands.0);
             self.prepare_request(&blob, resources);
         }
     }
 }
 
-use bindings::{WrFontKey, WrIdNamespace};
+use bindings::{WrFontKey, WrFontInstanceKey, WrIdNamespace};
 
 #[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &Arc<Vec> to an extern function
 extern "C" {
     fn AddFontData(key: WrFontKey, data: *const u8, size: usize, index: u32, vec: &ArcVecU8);
     fn AddNativeFontHandle(key: WrFontKey, handle: *mut c_void, index: u32);
     fn DeleteFontData(key: WrFontKey);
+    fn AddBlobFont(
+        instance_key: WrFontInstanceKey,
+        font_key: WrFontKey,
+        size: f32,
+        options: *const FontInstanceOptions,
+        platform_options: *const FontInstancePlatformOptions,
+        variations: *const FontVariation,
+        num_variations: usize,
+    );
+    fn DeleteBlobFont(key: WrFontInstanceKey);
     fn ClearBlobImageResources(namespace: WrIdNamespace);
 }
 
 impl Moz2dBlobImageHandler {
     pub fn new(workers: Arc<ThreadPool>) -> Self {
         Moz2dBlobImageHandler {
             blob_commands: HashMap::new(),
             workers: workers,
@@ -490,34 +506,66 @@ impl Moz2dBlobImageHandler {
         }
 
         #[cfg(not(any(target_os = "macos", target_os = "windows")))]
         fn process_native_font_handle(key: FontKey, handle: &NativeFontHandle) {
             let cstr = CString::new(handle.pathname.clone()).unwrap();
             unsafe { AddNativeFontHandle(key, cstr.as_ptr() as *mut c_void, handle.index) };
         }
 
-        fn process_fonts(mut extra_data: BufReader, resources: &BlobImageResources) {
+        fn process_fonts(
+            mut extra_data: BufReader,
+            resources: &BlobImageResources,
+            unscaled_fonts: &mut Vec<FontKey>,
+            scaled_fonts: &mut Vec<FontInstanceKey>,
+        ) {
             let font_count = extra_data.read_usize();
             for _ in 0..font_count {
-                let key = extra_data.read_font_key();
-                let template = resources.get_font_data(key);
-                match template {
-                    &FontTemplate::Raw(ref data, ref index) => {
-                        unsafe { AddFontData(key, data.as_ptr(), data.len(), *index, data); }
+                let font = extra_data.read_blob_font();
+                if scaled_fonts.contains(&font.font_instance_key) {
+                    continue;
+                }
+                scaled_fonts.push(font.font_instance_key);
+                if let Some(instance) = resources.get_font_instance_data(font.font_instance_key) {
+                    if !unscaled_fonts.contains(&instance.font_key) {
+                        unscaled_fonts.push(instance.font_key);
+                        let template = resources.get_font_data(instance.font_key);
+                        match template {
+                            &FontTemplate::Raw(ref data, ref index) => {
+                                unsafe { AddFontData(instance.font_key, data.as_ptr(), data.len(), *index, data); }
+                            }
+                            &FontTemplate::Native(ref handle) => {
+                                process_native_font_handle(instance.font_key, handle);
+                            }
+                        }
                     }
-                    &FontTemplate::Native(ref handle) => {
-                        process_native_font_handle(key, handle);
+                    unsafe {
+                        AddBlobFont(
+                            font.font_instance_key,
+                            instance.font_key,
+                            instance.size.to_f32_px(),
+                            option_to_nullable(&instance.options),
+                            option_to_nullable(&instance.platform_options),
+                            instance.variations.as_ptr(),
+                            instance.variations.len(),
+                        );
                     }
                 }
-                resources.get_font_data(key);
             }
         }
+
         {
             let mut index = BlobReader::new(blob);
+            let mut unscaled_fonts = Vec::new();
+            let mut scaled_fonts = Vec::new();
             while index.reader.pos < index.reader.buf.len() {
                 let e  = index.read_entry();
-                process_fonts(BufReader::new(&blob[e.end..e.extra_end]), resources);
+                process_fonts(
+                    BufReader::new(&blob[e.end..e.extra_end]),
+                    resources,
+                    &mut unscaled_fonts,
+                    &mut scaled_fonts,
+                );
             }
         }
     }
 }
 
--- a/gfx/webrender_bindings/webrender_ffi.h
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -46,31 +46,33 @@ struct FontInstanceFlags {
     return *this;
   }
 
   FontInstanceFlags& operator|=(uint32_t aBits) {
     bits |= aBits;
     return *this;
   }
 
-  FontInstanceFlags operator|(uint32_t aBits) {
+  FontInstanceFlags operator|(uint32_t aBits) const {
     FontInstanceFlags flags = { bits | aBits };
     return flags;
   }
 
   FontInstanceFlags& operator&=(uint32_t aBits) {
     bits &= aBits;
     return *this;
   }
 
-  FontInstanceFlags operator&(uint32_t aBits) {
+  FontInstanceFlags operator&(uint32_t aBits) const {
     FontInstanceFlags flags = { bits & aBits };
     return flags;
   }
 
+  MOZ_IMPLICIT operator bool() const { return bits != 0; }
+
   enum : uint32_t {
     SYNTHETIC_BOLD    = 1 << 1,
     EMBEDDED_BITMAPS  = 1 << 2,
     SUBPIXEL_BGR      = 1 << 3,
     TRANSPOSE         = 1 << 4,
     FLIP_X            = 1 << 5,
     FLIP_Y            = 1 << 6,
     SUBPIXEL_POSITION = 1 << 7,
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -1,13 +1,13 @@
 /* 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/. */
 
-/* Generated with cbindgen:0.6.2 */
+/* Generated with cbindgen:0.6.3 */
 
 /* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
  * To generate this file:
  *   1. Get the latest cbindgen using `cargo install --force cbindgen`
  *      a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
  *   2. Run `rustup run nightly cbindgen toolkit/library/rust/ --lockfile Cargo.lock --crate webrender_bindings -o gfx/webrender_bindings/webrender_ffi_generated.h`
  */
 
@@ -232,22 +232,17 @@ struct NormalizedCoordinates;
 
 // The renderer is responsible for submitting to the GPU the work prepared by the
 // RenderBackend.
 struct Renderer;
 
 // Offset in number of tiles.
 struct Tiles;
 
-// A Transaction is a group of commands to apply atomically to a document.
-//
-// This mechanism ensures that:
-// - no other message can be interleaved between two commands that need to be applied together.
-// - no redundant work is performed if two commands in the same transaction cause the scene or
-// the frame to be rebuilt.
+// Represents the work associated to a transaction before scene building.
 struct Transaction;
 
 // The default unit.
 struct UnknownUnit;
 
 template<typename T>
 struct Vec;
 
@@ -849,17 +844,17 @@ struct WrExternalImage {
 struct WrExternalImageId {
   uint64_t mHandle;
 
   bool operator==(const WrExternalImageId& aOther) const {
     return mHandle == aOther.mHandle;
   }
 };
 
-using LockExternalImageCallback = WrExternalImage(*)(void*, WrExternalImageId, uint8_t);
+using LockExternalImageCallback = WrExternalImage(*)(void*, WrExternalImageId, uint8_t, ImageRendering);
 
 using UnlockExternalImageCallback = void(*)(void*, WrExternalImageId, uint8_t);
 
 struct WrExternalImageHandler {
   void *external_image_obj;
   LockExternalImageCallback lock_func;
   UnlockExternalImageCallback unlock_func;
 
--- a/layout/reftests/canvas/reftest.list
+++ b/layout/reftests/canvas/reftest.list
@@ -1,14 +1,14 @@
 == default-size.html default-size-ref.html
 fuzzy-if(Android,0-8,0-1000) == size-1.html size-1-ref.html
 
 == empty-transaction-1.html empty-transaction-1-ref.html
 
-fails-if(webrender&&winWidget) == image-rendering-test.html image-rendering-ref.html
+== image-rendering-test.html image-rendering-ref.html
 == image-shadow.html image-shadow-ref.html
 
 asserts-if(cocoaWidget,0-2) == size-change-1.html size-change-1-ref.html
 
 random-if(cocoaWidget) == subpixel-1.html about:blank # see bug 1192616, re-enable once we're off the pandaboards
 
 != text-ltr-left.html text-blank.html
 != text-ltr-right.html text-blank.html
--- a/layout/reftests/w3c-css/submitted/images3/reftest.list
+++ b/layout/reftests/w3c-css/submitted/images3/reftest.list
@@ -1,35 +1,35 @@
 # Tests for 'object-fit' / 'object-position' with a PNG image
-fails-if(webrender&&winWidget) == object-fit-fill-png-001c.html object-fit-fill-png-001-ref.html
+== object-fit-fill-png-001c.html object-fit-fill-png-001-ref.html
 == object-fit-fill-png-001e.html object-fit-fill-png-001-ref.html
 == object-fit-fill-png-001i.html object-fit-fill-png-001-ref.html
 == object-fit-fill-png-001o.html object-fit-fill-png-001-ref.html
 == object-fit-fill-png-001p.html object-fit-fill-png-001-ref.html
-fails-if(webrender&&winWidget) == object-fit-fill-png-002c.html object-fit-fill-png-002-ref.html
+== object-fit-fill-png-002c.html object-fit-fill-png-002-ref.html
 == object-fit-fill-png-002e.html object-fit-fill-png-002-ref.html
 == object-fit-fill-png-002i.html object-fit-fill-png-002-ref.html
 == object-fit-fill-png-002o.html object-fit-fill-png-002-ref.html
 == object-fit-fill-png-002p.html object-fit-fill-png-002-ref.html
-fails-if(webrender&&winWidget) == object-fit-contain-png-001c.html object-fit-contain-png-001-ref.html
+== object-fit-contain-png-001c.html object-fit-contain-png-001-ref.html
 == object-fit-contain-png-001e.html object-fit-contain-png-001-ref.html
 == object-fit-contain-png-001i.html object-fit-contain-png-001-ref.html
 == object-fit-contain-png-001o.html object-fit-contain-png-001-ref.html
 == object-fit-contain-png-001p.html object-fit-contain-png-001-ref.html
-fails-if(webrender&&winWidget) == object-fit-contain-png-002c.html object-fit-contain-png-002-ref.html
+== object-fit-contain-png-002c.html object-fit-contain-png-002-ref.html
 == object-fit-contain-png-002e.html object-fit-contain-png-002-ref.html
 == object-fit-contain-png-002i.html object-fit-contain-png-002-ref.html
 == object-fit-contain-png-002o.html object-fit-contain-png-002-ref.html
 == object-fit-contain-png-002p.html object-fit-contain-png-002-ref.html
-fails-if(webrender&&winWidget) == object-fit-cover-png-001c.html object-fit-cover-png-001-ref.html
+== object-fit-cover-png-001c.html object-fit-cover-png-001-ref.html
 == object-fit-cover-png-001e.html object-fit-cover-png-001-ref.html
 == object-fit-cover-png-001i.html object-fit-cover-png-001-ref.html
 == object-fit-cover-png-001o.html object-fit-cover-png-001-ref.html
 == object-fit-cover-png-001p.html object-fit-cover-png-001-ref.html
-fails-if(webrender&&winWidget) == object-fit-cover-png-002c.html object-fit-cover-png-002-ref.html
+== object-fit-cover-png-002c.html object-fit-cover-png-002-ref.html
 == object-fit-cover-png-002e.html object-fit-cover-png-002-ref.html
 == object-fit-cover-png-002i.html object-fit-cover-png-002-ref.html
 == object-fit-cover-png-002o.html object-fit-cover-png-002-ref.html
 == object-fit-cover-png-002p.html object-fit-cover-png-002-ref.html
 == object-fit-none-png-001c.html object-fit-none-png-001-ref.html
 == object-fit-none-png-001e.html object-fit-none-png-001-ref.html
 == object-fit-none-png-001i.html object-fit-none-png-001-ref.html
 == object-fit-none-png-001o.html object-fit-none-png-001-ref.html
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1324,22 +1324,16 @@ VARCACHE_PREF(
 
 VARCACHE_PREF(
   "security.csp.enableStrictDynamic",
    security_csp_enableStrictDynamic,
   bool, true
 )
 
 VARCACHE_PREF(
-  "security.csp.enable_violation_events",
-   security_csp_enable_violation_events,
-  bool, true
-)
-
-VARCACHE_PREF(
   "security.csp.reporting.script-sample.max-length",
    security_csp_reporting_script_sample_max_length,
   int32_t, 40
 )
 
 //---------------------------------------------------------------------------
 // View source prefs
 //---------------------------------------------------------------------------
--- a/other-licenses/snappy/moz.build
+++ b/other-licenses/snappy/moz.build
@@ -23,8 +23,11 @@ AllowCompilerWarnings()
 FINAL_LIBRARY = 'xul'
 
 # Suppress warnings in third-party code.
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += [
         '-Wno-sign-compare',
         '-Wno-unused-function'
     ]
+
+if CONFIG['TARGET_ENDIANNESS'] == 'big':
+    DEFINES['IS_BIG_ENDIAN'] = 1
--- a/testing/web-platform/meta/content-security-policy/__dir__.ini
+++ b/testing/web-platform/meta/content-security-policy/__dir__.ini
@@ -1,1 +0,0 @@
-prefs: [security.csp.enable_violation_events:true]
--- a/third_party/rust/plane-split/.cargo-checksum.json
+++ b/third_party/rust/plane-split/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{".travis.yml":"b76d49f66f842c652d40825c67791352364a6b6bbb7d8d1009f2ac79eb413e66","Cargo.toml":"9bffe8481e8672f72a85e3844627b35874b2f357ea7e7d91856268ce03f898a7","LICENSE":"b946744aeda89b467929585fe8eeb5461847695220c1b168fb375d8abd4ea3d0","README.md":"a65ed5c817c867fe23bc2029f34baea4a645a07dd5d101a0027e796d2923be58","benches/split.rs":"632a011dfc6d8235dea853785061b7bbfe0362eb85b91b3b01fbf77a7f1c7f26","src/bsp.rs":"60a306ecb7032a57bc5a7b7f094cb9669f7cd112894f85e6e2fc501a608a9404","src/clip.rs":"d1171933bea98c68440869d341d6f2ecdd672b2acb0a4408a011b7ef1c44b266","src/lib.rs":"3e9fb055066623b08be1abc06d0fd30f838d840ad13a6acab76d117f0c8bf5ff","src/polygon.rs":"c30e17aa88714b490f1cc9b7a9a388b37bc9af919ce1983e42f8b94555847118","tests/clip.rs":"0745faa4cb679c5eef4103a26a8714d6feb37ffd00b6c3dafad5937ec5392f10","tests/main.rs":"e299b33390fc486b45685eaef3af4fc67793b114dc0e63c873022dc1530ab672","tests/split.rs":"0eb1afb1f26cdecd5fffbf32d57e889f8f69254c0a57eecb8ccbbdf38efcdf27"},"package":"ff3a4fc9e31d70eb6828e9a2d7a401a824d9f281686a39a8fc06f08796edb1bb"}
\ No newline at end of file
+{"files":{".travis.yml":"b76d49f66f842c652d40825c67791352364a6b6bbb7d8d1009f2ac79eb413e66","Cargo.toml":"91ca8efc8aeb123545f2dace7bebe3cd2d14bfba3d7f09a242cf88fdd04ee951","LICENSE":"b946744aeda89b467929585fe8eeb5461847695220c1b168fb375d8abd4ea3d0","README.md":"a65ed5c817c867fe23bc2029f34baea4a645a07dd5d101a0027e796d2923be58","benches/split.rs":"632a011dfc6d8235dea853785061b7bbfe0362eb85b91b3b01fbf77a7f1c7f26","src/bsp.rs":"60a306ecb7032a57bc5a7b7f094cb9669f7cd112894f85e6e2fc501a608a9404","src/clip.rs":"838cee6106d240581d1cfcba5d47b67f99a1360748ce7c56531dc4582121fc34","src/lib.rs":"a9ce93011a0b0702a1df2a342aeeb9982a3c8a819b20fefa284686ee0fa04d08","src/polygon.rs":"8c2509698441582d66ca86c35ca6fa924aeaf891a2bb1fc6b08bb311d11ae738","tests/clip.rs":"3335364fd6849697d3919084be5dce6c49acb31d8c53adc93a4cd05ee2ea93a9","tests/main.rs":"ed807b036c475ab99973124c31a9932da51e77e7e580cdc182e98a96af6be941","tests/split.rs":"0eb1afb1f26cdecd5fffbf32d57e889f8f69254c0a57eecb8ccbbdf38efcdf27"},"package":"64d766f38b15fe1337bdddfc869ef5c50437323f857aaaadc6490197db80a1b8"}
\ No newline at end of file
--- a/third_party/rust/plane-split/Cargo.toml
+++ b/third_party/rust/plane-split/Cargo.toml
@@ -7,17 +7,17 @@
 #
 # If you believe there's an error in this file please file an
 # issue against the rust-lang/cargo repository. If you're
 # editing this file be aware that the upstream Cargo.toml
 # will likely look very different (and much more reasonable)
 
 [package]
 name = "plane-split"
-version = "0.12.1"
+version = "0.13.0"
 authors = ["Dzmitry Malyshau <kvark@mozilla.com>"]
 description = "Plane splitting"
 documentation = "https://docs.rs/plane-split"
 keywords = ["geometry", "math"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/plane-split"
 [dependencies.binary-space-partition]
 version = "0.1.2"
--- a/third_party/rust/plane-split/src/clip.rs
+++ b/third_party/rust/plane-split/src/clip.rs
@@ -1,9 +1,9 @@
-use {Intersection, Plane, Polygon};
+use {Intersection, NegativeHemisphereError, Plane, Polygon};
 
 use euclid::{Trig, TypedRect, TypedScale, TypedTransform3D, TypedVector3D};
 use euclid::approxeq::ApproxEq;
 use num_traits::{Float, One, Zero};
 
 use std::{fmt, mem, ops};
 
 
@@ -31,52 +31,65 @@ impl<
         }
     }
 
     /// Reset the clipper internals, but preserve the allocation.
     pub fn reset(&mut self) {
         self.clips.clear();
     }
 
-    /// Add a set of planes that define the frustum for a given transformation.
-    pub fn add_frustum<V>(
-        &mut self,
+    /// Extract the clipping planes that define the frustum for a given transformation.
+    pub fn frustum_planes<V>(
         t: &TypedTransform3D<T, U, V>,
         bounds: Option<TypedRect<T, V>>,
-    ) {
-        //Note: this is not the near plane, but the positive hemisphere
-        // in homogeneous space.
+    ) -> Result<impl Iterator<Item = Plane<T, U>>, NegativeHemisphereError> {
         let mw = TypedVector3D::new(t.m14, t.m24, t.m34);
-        self.clips.extend(Plane::from_unnormalized(mw, t.m44));
+        let plane_positive = Plane::from_unnormalized(mw, t.m44)?;
+
+        let bounds_iter_maybe = match bounds {
+            Some(bounds) => {
+                let mx = TypedVector3D::new(t.m11, t.m21, t.m31);
+                let left = bounds.origin.x;
+                let plane_left = Plane::from_unnormalized(
+                    mx - mw * TypedScale::new(left),
+                    t.m41 - t.m44 * left,
+                )?;
+                let right = bounds.origin.x + bounds.size.width;
+                let plane_right = Plane::from_unnormalized(
+                    mw * TypedScale::new(right) - mx,
+                    t.m44 * right - t.m41,
+                )?;
 
-        if let Some(bounds) = bounds {
-            let mx = TypedVector3D::new(t.m11, t.m21, t.m31);
-            let left = bounds.origin.x;
-            self.clips.extend(Plane::from_unnormalized(
-                mx - mw * TypedScale::new(left),
-                t.m41 - t.m44 * left,
-            ));
-            let right = bounds.origin.x + bounds.size.width;
-            self.clips.extend(Plane::from_unnormalized(
-                mw * TypedScale::new(right) - mx,
-                t.m44 * right - t.m41,
-            ));
+                let my = TypedVector3D::new(t.m12, t.m22, t.m32);
+                let top = bounds.origin.y;
+                let plane_top = Plane::from_unnormalized(
+                    my - mw * TypedScale::new(top),
+                    t.m42 - t.m44 * top,
+                )?;
+                let bottom = bounds.origin.y + bounds.size.height;
+                let plane_bottom = Plane::from_unnormalized(
+                    mw * TypedScale::new(bottom) - my,
+                    t.m44 * bottom - t.m42,
+                )?;
 
-            let my = TypedVector3D::new(t.m12, t.m22, t.m32);
-            let top = bounds.origin.y;
-            self.clips.extend(Plane::from_unnormalized(
-                my - mw * TypedScale::new(top),
-                t.m42 - t.m44 * top,
-            ));
-            let bottom = bounds.origin.y + bounds.size.height;
-            self.clips.extend(Plane::from_unnormalized(
-                mw * TypedScale::new(bottom) - my,
-                t.m44 * bottom - t.m42,
-            ));
-        }
+                Some(plane_left
+                    .into_iter()
+                    .chain(plane_right)
+                    .chain(plane_top)
+                    .chain(plane_bottom)
+                )
+            }
+            None => None,
+        };
+
+        Ok(bounds_iter_maybe
+            .into_iter()
+            .flat_map(|pi| pi)
+            .chain(plane_positive)
+        )
     }
 
     /// Add a clipping plane to the list. The plane will clip everything behind it,
     /// where the direction is set by the plane normal.
     pub fn add(&mut self, plane: Plane<T, U>) {
         self.clips.push(plane);
     }
 
@@ -107,30 +120,35 @@ impl<
             }
         }
 
         &self.results
     }
 
     /// Clip the primitive with the frustum of the specified transformation,
     /// returning a sequence of polygons in the transformed space.
+    /// Returns None if the transformation can't be frustum clipped.
     pub fn clip_transformed<'a, V>(
         &'a mut self,
         polygon: Polygon<T, U>,
         transform: &'a TypedTransform3D<T, U, V>,
         bounds: Option<TypedRect<T, V>>,
-    ) -> impl 'a + Iterator<Item = Polygon<T, V>>
+    ) -> Result<impl 'a + Iterator<Item = Polygon<T, V>>, NegativeHemisphereError>
     where
         T: Trig,
         V: 'a + fmt::Debug,
     {
-        let num_planes = if bounds.is_some() {5} else {1};
-        self.add_frustum(transform, bounds);
+        let planes = Self::frustum_planes(transform, bounds)?;
+
+        let old_count = self.clips.len();
+        self.clips.extend(planes);
         self.clip(polygon);
         // remove the frustum planes
-        for _ in 0 .. num_planes {
+        while self.clips.len() > old_count {
             self.clips.pop();
         }
-        self.results
+
+        let polys = self.results
             .drain(..)
-            .flat_map(move |poly| poly.transform(transform))
+            .flat_map(move |poly| poly.transform(transform));
+        Ok(polys)
     }
 }
--- a/third_party/rust/plane-split/src/lib.rs
+++ b/third_party/rust/plane-split/src/lib.rs
@@ -87,33 +87,44 @@ impl<T: Clone, U> Clone for Plane<T, U> 
     fn clone(&self) -> Self {
         Plane {
             normal: self.normal.clone(),
             offset: self.offset.clone(),
         }
     }
 }
 
+/// An error returned when everything would end up projected
+/// to the negative hemisphere (W <= 0.0);
+#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
+pub struct NegativeHemisphereError;
+
 impl<
     T: Copy + Zero + One + Float + ApproxEq<T> +
         ops::Sub<T, Output=T> + ops::Add<T, Output=T> +
         ops::Mul<T, Output=T> + ops::Div<T, Output=T>,
     U,
 > Plane<T, U> {
     /// Construct a new plane from unnormalized equation.
-    pub fn from_unnormalized(normal: TypedVector3D<T, U>, offset: T) -> Option<Self> {
+    pub fn from_unnormalized(
+        normal: TypedVector3D<T, U>, offset: T
+    ) -> Result<Option<Self>, NegativeHemisphereError> {
         let square_len = normal.square_length();
-        if square_len < T::approx_epsilon() {
-            None
+        if square_len < T::approx_epsilon() * T::approx_epsilon() {
+            if offset > T::zero() {
+                Ok(None)
+            } else {
+                Err(NegativeHemisphereError)
+            }
         } else {
             let kf = T::one() / square_len.sqrt();
-            Some(Plane {
+            Ok(Some(Plane {
                 normal: normal * TypedScale::new(kf),
                 offset: offset * kf,
-            })
+            }))
         }
     }
 
     /// Check if this plane contains another one.
     pub fn contains(&self, other: &Self) -> bool {
         //TODO: actually check for inside/outside
         self.normal == other.normal && self.offset == other.offset
     }
--- a/third_party/rust/plane-split/src/polygon.rs
+++ b/third_party/rust/plane-split/src/polygon.rs
@@ -114,30 +114,20 @@ impl<T: Clone, U> Clone for Polygon<T, U
 impl<T, U> Polygon<T, U> where
     T: Copy + fmt::Debug + ApproxEq<T> +
         ops::Sub<T, Output=T> + ops::Add<T, Output=T> +
         ops::Mul<T, Output=T> + ops::Div<T, Output=T> +
         Zero + One + Float,
     U: fmt::Debug,
 {
     /// Construct a polygon from points that are already transformed.
-    #[deprecated(since = "0.12.1", note = "Use try_from_points instead")]
+    /// Return None if the polygon doesn't contain any space.
     pub fn from_points(
         points: [TypedPoint3D<T, U>; 4],
         anchor: usize,
-    ) -> Self {
-        Self::try_from_points(points, anchor).unwrap()
-    }
-
-    /// Construct a polygon from points that are already transformed.
-    /// Return None if the polygon doesn't contain any space.
-    /// This method will be removed in `from_points` in the next breaking release.
-    pub fn try_from_points(
-        points: [TypedPoint3D<T, U>; 4],
-        anchor: usize,
     ) -> Option<Self> {
         let edge1 = points[1] - points[0];
         let edge2 = points[2] - points[0];
         let edge3 = points[3] - points[0];
         let edge4 = points[3] - points[1];
 
         if edge2.square_length() < T::epsilon() || edge4.square_length() < T::epsilon() {
             return None
@@ -174,17 +164,17 @@ impl<T, U> Polygon<T, U> where
         Self::from_points(
             [
                 rect.origin.to_3d(),
                 rect.top_right().to_3d(),
                 rect.bottom_right().to_3d(),
                 rect.bottom_left().to_3d(),
             ],
             anchor,
-        )
+        ).unwrap()
     }
 
     /// Construct a polygon from a rectangle with 3D transform.
     pub fn from_transformed_rect<V>(
         rect: TypedRect<T, V>,
         transform: TypedTransform3D<T, V, U>,
         anchor: usize,
     ) -> Option<Self>
@@ -196,17 +186,17 @@ impl<T, U> Polygon<T, U> where
             transform.transform_point3d(&rect.top_right().to_3d())?,
             transform.transform_point3d(&rect.bottom_right().to_3d())?,
             transform.transform_point3d(&rect.bottom_left().to_3d())?,
         ];
 
         //Note: this code path could be more efficient if we had inverse-transpose
         //let n4 = transform.transform_point4d(&TypedPoint4D::new(T::zero(), T::zero(), T::one(), T::zero()));
         //let normal = TypedPoint3D::new(n4.x, n4.y, n4.z);
-        Self::try_from_points(points, anchor)
+        Self::from_points(points, anchor)
     }
 
     /// Bring a point into the local coordinate space, returning
     /// the 2D normalized coordinates.
     pub fn untransform_point(&self, point: TypedPoint3D<T, U>) -> Point2D<T> {
         //debug_assert!(self.contains(point));
         // get axises and target vector
         let a = self.points[1] - self.points[0];
@@ -238,17 +228,17 @@ impl<T, U> Polygon<T, U> where
             let mut homo = transform.transform_point3d_homogeneous(point);
             homo.w = homo.w.max(T::approx_epsilon());
             *out = homo.to_point3d()?;
         }
 
         //Note: this code path could be more efficient if we had inverse-transpose
         //let n4 = transform.transform_point4d(&TypedPoint4D::new(T::zero(), T::zero(), T::one(), T::zero()));
         //let normal = TypedPoint3D::new(n4.x, n4.y, n4.z);
-        Polygon::try_from_points(points, self.anchor)
+        Polygon::from_points(points, self.anchor)
     }
 
     /// Check if all the points are indeed placed on the plane defined by
     /// the normal and offset, and the winding order is consistent.
     pub fn is_valid(&self) -> bool {
         let is_planar = self.points
             .iter()
             .all(|p| is_zero(self.plane.signed_distance_to(p)));
--- a/third_party/rust/plane-split/tests/clip.rs
+++ b/third_party/rust/plane-split/tests/clip.rs
@@ -5,97 +5,120 @@ use euclid::{point3, rect, vec3};
 use euclid::{Angle, TypedRect, TypedTransform3D};
 use plane_split::{Clipper, Plane, Polygon};
 
 use std::f32::consts::FRAC_PI_4;
 
 
 #[test]
 fn clip_in() {
-    let plane: Plane<f32, ()> = Plane::from_unnormalized(vec3(1.0, 0.0, 1.0), 20.0).unwrap();
+    let plane: Plane<f32, ()> = Plane::from_unnormalized(vec3(1.0, 0.0, 1.0), 20.0).unwrap().unwrap();
     let mut clipper = Clipper::new();
     clipper.add(plane);
 
-    let poly = Polygon::from_points([
-        point3(-10.0, -10.0, 0.0),
-        point3(10.0, -10.0, 0.0),
-        point3(10.0, 10.0, 0.0),
-        point3(-10.0, 10.0, 0.0),
-    ], 0);
+    let poly = Polygon::from_points(
+        [
+            point3(-10.0, -10.0, 0.0),
+            point3(10.0, -10.0, 0.0),
+            point3(10.0, 10.0, 0.0),
+            point3(-10.0, 10.0, 0.0),
+        ],
+        0,
+    ).unwrap();
 
     let results = clipper.clip(poly.clone());
     assert_eq!(results[0], poly);
     assert_eq!(results.len(), 1);
 }
 
 #[test]
 fn clip_out() {
-    let plane: Plane<f32, ()> = Plane::from_unnormalized(vec3(1.0, 0.0, 1.0), -20.0).unwrap();
+    let plane: Plane<f32, ()> = Plane::from_unnormalized(vec3(1.0, 0.0, 1.0), -20.0).unwrap().unwrap();
     let mut clipper = Clipper::new();
     clipper.add(plane);
 
-    let poly = Polygon::from_points([
-        point3(-10.0, -10.0, 0.0),
-        point3(10.0, -10.0, 0.0),
-        point3(10.0, 10.0, 0.0),
-        point3(-10.0, 10.0, 0.0),
-    ], 0);
+    let poly = Polygon::from_points(
+        [
+            point3(-10.0, -10.0, 0.0),
+            point3(10.0, -10.0, 0.0),
+            point3(10.0, 10.0, 0.0),
+            point3(-10.0, 10.0, 0.0),
+        ],
+        0,
+    ).unwrap();
 
     let results = clipper.clip(poly);
     assert!(results.is_empty());
 }
 
 #[test]
 fn clip_parallel() {
     let plane: Plane<f32, ()> = Plane {
         normal: vec3(0.0, 0.0, 1.0),
         offset: 0.0,
     };
     let mut clipper = Clipper::new();
     clipper.add(plane);
 
-    let poly = Polygon::from_points([
-        point3(-10.0, -10.0, 0.0),
-        point3(10.0, -10.0, 0.0),
-        point3(10.0, 10.0, 0.0),
-        point3(-10.0, 10.0, 0.0),
-    ], 0);
+    let poly = Polygon::from_points(
+        [
+            point3(-10.0, -10.0, 0.0),
+            point3(10.0, -10.0, 0.0),
+            point3(10.0, 10.0, 0.0),
+            point3(-10.0, 10.0, 0.0),
+        ],
+        0,
+    ).unwrap();
 
     let results = clipper.clip(poly);
     assert!(results.is_empty());
 }
 
 #[test]
 fn clip_repeat() {
-    let plane: Plane<f32, ()> = Plane::from_unnormalized(vec3(1.0, 0.0, 1.0), 0.0).unwrap();
+    let plane: Plane<f32, ()> = Plane::from_unnormalized(vec3(1.0, 0.0, 1.0), 0.0).unwrap().unwrap();
     let mut clipper = Clipper::new();
     clipper.add(plane.clone());
     clipper.add(plane.clone());
 
-    let poly = Polygon::from_points([
-        point3(-10.0, -10.0, 0.0),
-        point3(10.0, -10.0, 0.0),
-        point3(10.0, 10.0, 0.0),
-        point3(-10.0, 10.0, 0.0),
-    ], 0);
+    let poly = Polygon::from_points(
+        [
+            point3(-10.0, -10.0, 0.0),
+            point3(10.0, -10.0, 0.0),
+            point3(10.0, 10.0, 0.0),
+            point3(-10.0, 10.0, 0.0),
+        ],
+        0,
+    ).unwrap();
 
     let results = clipper.clip(poly);
     assert_eq!(results.len(), 1);
     assert!(plane.signed_distance_sum_to(&results[0]) > 0.0);
 }
 
 #[test]
 fn clip_transformed() {
     let t_rot: TypedTransform3D<f32, (), ()> =
         TypedTransform3D::create_rotation(0.0, 1.0, 0.0, Angle::radians(-FRAC_PI_4));
     let t_div: TypedTransform3D<f32, (), ()> =
         TypedTransform3D::create_perspective(5.0);
     let transform = t_rot.post_mul(&t_div);
 
-    let poly_rect: TypedRect<f32, ()> = rect(-10.0, -10.0, 20.0, 20.0);
-    let polygon = Polygon::from_rect(poly_rect, 0);
+    let polygon = Polygon::from_rect(rect(-10.0, -10.0, 20.0, 20.0), 0);
     let bounds: TypedRect<f32, ()> = rect(-1.0, -1.0, 2.0, 2.0);
 
     let mut clipper = Clipper::new();
     let results = clipper.clip_transformed(polygon, &transform, Some(bounds));
     // iterating enforces the transformation checks/unwraps
-    assert_ne!(0, results.count());
+    assert_ne!(0, results.unwrap().count());
 }
+
+#[test]
+fn clip_badly_transformed() {
+    let mut tx = TypedTransform3D::<f32, (), ()>::identity();
+    tx.m14 = -0.0000001;
+    tx.m44 = 0.0;
+
+    let mut clipper = Clipper::new();
+    let polygon = Polygon::from_rect(rect(-10.0, -10.0, 20.0, 20.0), 0);
+    let results = clipper.clip_transformed(polygon, &tx, None);
+    assert!(results.is_err());
+}
--- a/third_party/rust/plane-split/tests/main.rs
+++ b/third_party/rust/plane-split/tests/main.rs
@@ -1,14 +1,14 @@
 extern crate euclid;
 extern crate plane_split;
 
 use euclid::{Angle, TypedRect, TypedSize2D, TypedTransform3D, point2, point3, vec3};
 use euclid::approxeq::ApproxEq;
-use plane_split::{Intersection, Line, LineProjection, Plane, Polygon};
+use plane_split::{Intersection, Line, LineProjection, NegativeHemisphereError, Plane, Polygon};
 
 
 #[test]
 fn line_proj_bounds() {
     assert_eq!((-5i8, 4), LineProjection { markers: [-5i8, 1, 4, 2] }.get_bounds());
     assert_eq!((1f32, 4.0), LineProjection { markers: [4f32, 3.0, 2.0, 1.0] }.get_bounds());
 }
 
@@ -55,17 +55,17 @@ fn valid() {
         },
         anchor: 0,
     };
     assert!(poly_c.is_valid());
 }
 
 #[test]
 fn empty() {
-    let poly = Polygon::<f32, ()>::try_from_points(
+    let poly = Polygon::<f32, ()>::from_points(
         [
             point3(0.0, 0.0, 1.0),
             point3(0.0, 0.0, 1.0),
             point3(0.0, 0.00001, 1.0),
             point3(1.0, 0.0, 0.0),
         ],
         1,
     );
@@ -257,16 +257,22 @@ fn split() {
     test_cut(&poly, 2, Line {
         origin: point3(0.5, 1.0, 0.0),
         dir: vec3(-0.5f32.sqrt(), 0.0, 0.5f32.sqrt()),
     });
 }
 
 #[test]
 fn plane_unnormalized() {
-    let mut plane: Option<Plane<f32, ()>> = Plane::from_unnormalized(vec3(0.0, 0.0, 0.0), 1.0);
-    assert_eq!(plane, None);
+    let zero_vec = vec3(0.0000001, 0.0, 0.0);
+    let mut plane: Result<Option<Plane<f32, ()>>, _> = Plane::from_unnormalized(zero_vec, 1.0);
+    assert_eq!(plane, Ok(None));
+    plane = Plane::from_unnormalized(zero_vec, 0.0);
+    assert_eq!(plane, Err(NegativeHemisphereError));
+    plane = Plane::from_unnormalized(zero_vec, -0.5);
+    assert_eq!(plane, Err(NegativeHemisphereError));
+
     plane = Plane::from_unnormalized(vec3(-3.0, 4.0, 0.0), 2.0);
-    assert_eq!(plane, Some(Plane {
+    assert_eq!(plane, Ok(Some(Plane {
         normal: vec3(-3.0/5.0, 4.0/5.0, 0.0),
         offset: 2.0/5.0,
-    }));
+    })));
 }