Merge mozilla-central to autoland. CLOSED TREE
authorDorel Luca <dluca@mozilla.com>
Fri, 03 May 2019 16:09:46 +0300
changeset 531289 8b1e5ece60a2bacc0ff172739d2128104d619a56
parent 531288 26a53be19f261e246e1e2ef372395b2c81d772aa (current diff)
parent 531275 c68ff92fe54c75338c3a937bc6c4e615d9b1d45a (diff)
child 531290 beae0db3529d6490795e69d6d6567fb74b95ffe8
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. CLOSED TREE
--- a/browser/components/resistfingerprinting/test/browser/browser_navigator.js
+++ b/browser/components/resistfingerprinting/test/browser/browser_navigator.js
@@ -1,13 +1,15 @@
 /**
  * Bug 1333651 - A test case for making sure the navigator object has been
  *   spoofed/disabled correctly.
  */
 
+"use strict";
+
 const CC = Components.Constructor;
 
 ChromeUtils.defineModuleGetter(this, "AppConstants",
   "resource://gre/modules/AppConstants.jsm");
 
 const TEST_PATH = "http://example.net/browser/browser/" +
                   "components/resistfingerprinting/test/browser/";
 
@@ -15,46 +17,46 @@ var spoofedUserAgentNavigator;
 var spoofedUserAgentHeader;
 
 const SPOOFED_APPNAME = "Netscape";
 
 const SPOOFED_APPVERSION = {
   linux: "5.0 (X11)",
   win: "5.0 (Windows)",
   macosx: "5.0 (Macintosh)",
-  android: "5.0 (Android 6.0)",
+  android: "5.0 (Android 8.1)",
   other: "5.0 (X11)",
 };
 const SPOOFED_PLATFORM = {
   linux: "Linux x86_64",
   win: "Win32",
   macosx: "MacIntel",
   android: "Linux armv7l",
   other: "Linux x86_64",
 };
 const SPOOFED_OSCPU = {
   linux: "Linux x86_64",
-  win: "Windows NT 6.1; Win64; x64",
-  macosx: "Intel Mac OS X 10.13",
+  win: "Windows NT 10.0; Win64; x64",
+  macosx: "Intel Mac OS X 10.14",
   android: "Linux armv7l",
   other: "Linux x86_64",
 };
 const SPOOFED_UA_NAVIGATOR_OS = {
   linux: "X11; Linux x86_64",
-  win: "Windows NT 6.1; Win64; x64",
-  macosx: "Macintosh; Intel Mac OS X 10.13",
-  android: "Android 6.0; Mobile",
+  win: "Windows NT 10.0; Win64; x64",
+  macosx: "Macintosh; Intel Mac OS X 10.14",
+  android: "Android 8.1; Mobile",
   other: "X11; Linux x86_64",
 };
 const SPOOFED_UA_HTTPHEADER_OS = {
-  linux: "Windows NT 6.1",
-  win: "Windows NT 6.1",
-  macosx: "Windows NT 6.1",
-  android: "Android 6.0; Mobile",
-  other: "Windows NT 6.1",
+  linux: "Windows NT 10.0",
+  win: "Windows NT 10.0",
+  macosx: "Windows NT 10.0",
+  android: "Android 8.1; Mobile",
+  other: "Windows NT 10.0",
 };
 const SPOOFED_HW_CONCURRENCY = 2;
 
 const CONST_APPCODENAME = "Mozilla";
 const CONST_PRODUCT     = "Gecko";
 const CONST_PRODUCTSUB  = "20100101";
 const CONST_VENDOR      = "";
 const CONST_VENDORSUB   = "";
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -344,16 +344,23 @@ HangMonitorChild::~HangMonitorChild() {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance == this);
   sInstance = nullptr;
 }
 
 void HangMonitorChild::InterruptCallback() {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
+  // The interrupt callback is triggered at non-deterministic points when
+  // recording/replaying, so don't perform any operations that can interact
+  // with the recording.
+  if (recordreplay::IsRecordingOrReplaying()) {
+    return;
+  }
+
   bool paintWhileInterruptingJS;
   bool paintWhileInterruptingJSForce;
   TabId paintWhileInterruptingJSTab;
   LayersObserverEpoch paintWhileInterruptingJSEpoch;
 
   bool cancelContentJS;
   TabId cancelContentJSTab;
   nsIRemoteTab::NavigationType cancelContentJSNavigationType;
@@ -374,19 +381,17 @@ void HangMonitorChild::InterruptCallback
     cancelContentJSNavigationIndex = mCancelContentJSNavigationIndex;
     cancelContentJSNavigationURI = std::move(mCancelContentJSNavigationURI);
     cancelContentJSEpoch = mCancelContentJSEpoch;
 
     mPaintWhileInterruptingJS = false;
     mCancelContentJS = false;
   }
 
-  // Don't paint from the interrupt callback when recording or replaying, as
-  // the interrupt callback is triggered non-deterministically.
-  if (paintWhileInterruptingJS && !recordreplay::IsRecordingOrReplaying()) {
+  if (paintWhileInterruptingJS) {
     RefPtr<BrowserChild> browserChild =
         BrowserChild::FindBrowserChild(paintWhileInterruptingJSTab);
     if (browserChild) {
       js::AutoAssertNoContentJS nojs(mContext);
       browserChild->PaintWhileInterruptingJS(paintWhileInterruptingJSEpoch,
                                              paintWhileInterruptingJSForce);
     }
   }
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -641,17 +641,17 @@ void APZCTreeManager::SampleForWebRender
     }
 
     ParentLayerPoint layerTranslation =
         apzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing)
             .mTranslation;
     LayoutDeviceToParentLayerScale zoom;
     if (Maybe<uint64_t> zoomAnimationId = apzc->GetZoomAnimationId()) {
       // for now we only support zooming on root content APZCs
-      MOZ_ASSERT(apzc->Metrics().IsRootContent());
+      MOZ_ASSERT(apzc->IsRootContent());
       zoom = apzc->GetCurrentPinchZoomScale(
           AsyncPanZoomController::eForCompositing);
       transforms.AppendElement(wr::ToWrTransformProperty(
           *zoomAnimationId, Matrix4x4::Scaling(zoom.scale, zoom.scale, 1.0f)));
     }
 
     // The positive translation means the painted content is supposed to
     // move down (or to the right), and that corresponds to a reduction in
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -841,20 +841,20 @@ AsyncPanZoomController::AsyncPanZoomCont
       mLastContentPaintMetrics(mLastContentPaintMetadata.GetMetrics()),
       mX(this),
       mY(this),
       mPanDirRestricted(false),
       mPinchLocked(false),
       mPinchEventBuffer(
           TimeDuration::FromMilliseconds(gfxPrefs::APZPinchLockBufferMaxAge())),
       mZoomConstraints(false, false,
-                       Metrics().GetDevPixelsPerCSSPixel() * kViewportMinScale /
-                           ParentLayerToScreenScale(1),
-                       Metrics().GetDevPixelsPerCSSPixel() * kViewportMaxScale /
-                           ParentLayerToScreenScale(1)),
+                       mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() *
+                           kViewportMinScale / ParentLayerToScreenScale(1),
+                       mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() *
+                           kViewportMaxScale / ParentLayerToScreenScale(1)),
       mLastSampleTime(GetFrameTime()),
       mLastCheckerboardReport(GetFrameTime()),
       mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
       mState(NOTHING),
       mNotificationBlockers(0),
       mInputQueue(aInputQueue),
       mPinchPaintTimerSet(false),
       mAPZCId(sAsyncPanZoomControllerCount++),
@@ -905,18 +905,18 @@ void AsyncPanZoomController::Destroy() {
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
   }
   mParent = nullptr;
   mTreeManager = nullptr;
 
   // Only send the release message if the SharedFrameMetrics has been created.
   if (mMetricsSharingController && mSharedFrameMetricsBuffer) {
-    Unused << mMetricsSharingController->StopSharingMetrics(
-        Metrics().GetScrollId(), mAPZCId);
+    Unused << mMetricsSharingController->StopSharingMetrics(GetScrollId(),
+                                                            mAPZCId);
   }
 
   {  // scope the lock
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     mSharedFrameMetricsBuffer = nullptr;
     delete mSharedLock;
     mSharedLock = nullptr;
   }
@@ -1571,16 +1571,17 @@ nsEventStatus AsyncPanZoomController::On
       controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0,
                                      aEvent.modifiers);
     }
   }
 
   SetState(PINCHING);
   mX.SetVelocity(0);
   mY.SetVelocity(0);
+  RecursiveMutexAutoLock lock(mRecursiveMutex);
   mLastZoomFocus =
       aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft();
 
   mPinchEventBuffer.push(aEvent);
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
@@ -1618,26 +1619,25 @@ nsEventStatus AsyncPanZoomController::On
           aEvent.mType, GetGuid(),
           ViewAs<LayoutDevicePixel>(
               aEvent.mCurrentSpan - aEvent.mPreviousSpan,
               PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF),
           aEvent.modifiers);
     }
   }
 
-  // Only the root APZC is zoomable, and the root APZC is not allowed to have
-  // different x and y scales. If it did, the calculations in this function
-  // would have to be adjusted (as e.g. it would no longer be valid to take
-  // the minimum or maximum of the ratios of the widths and heights of the
-  // page rect and the composition bounds).
-  MOZ_ASSERT(Metrics().IsRootContent());
-  MOZ_ASSERT(Metrics().GetZoom().AreScalesSame());
-
   {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
+    // Only the root APZC is zoomable, and the root APZC is not allowed to have
+    // different x and y scales. If it did, the calculations in this function
+    // would have to be adjusted (as e.g. it would no longer be valid to take
+    // the minimum or maximum of the ratios of the widths and heights of the
+    // page rect and the composition bounds).
+    MOZ_ASSERT(Metrics().IsRootContent());
+    MOZ_ASSERT(Metrics().GetZoom().AreScalesSame());
 
     CSSToParentLayerScale userZoom = Metrics().GetZoom().ToScaleFactor();
     ParentLayerPoint focusPoint =
         aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft();
     CSSPoint cssFocusPoint = focusPoint / Metrics().GetZoom();
 
     ParentLayerPoint focusChange = mLastZoomFocus - focusPoint;
     mLastZoomFocus = focusPoint;
@@ -2337,17 +2337,17 @@ nsEventStatus AsyncPanZoomController::On
              this);
   } else if ((delta.x || delta.y) && !CanScrollWithWheel(delta)) {
     // We can't scroll this apz anymore, so we simply drop the event.
     if (mInputQueue->GetActiveWheelTransaction() &&
         gfxPrefs::MouseScrollTestingEnabled()) {
       if (RefPtr<GeckoContentController> controller =
               GetGeckoContentController()) {
         controller->NotifyMozMouseScrollEvent(
-            Metrics().GetScrollId(), NS_LITERAL_STRING("MozMouseScrollFailed"));
+            GetScrollId(), NS_LITERAL_STRING("MozMouseScrollFailed"));
       }
     }
     return nsEventStatus_eConsumeNoDefault;
   }
 
   MOZ_ASSERT(mInputQueue->GetCurrentWheelBlock());
   AdjustDeltaForAllowedScrollDirections(
       delta, mInputQueue->GetCurrentWheelBlock()->GetAllowedScrollDirections());
@@ -2357,17 +2357,21 @@ nsEventStatus AsyncPanZoomController::On
     return nsEventStatus_eIgnore;
   }
 
   switch (aEvent.mScrollMode) {
     case ScrollWheelInput::SCROLLMODE_INSTANT: {
       // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
       // next snap point. Check for this, and adjust the delta to take into
       // account the snap point.
-      CSSPoint startPosition = Metrics().GetScrollOffset();
+      CSSPoint startPosition;
+      {
+        RecursiveMutexAutoLock lock(mRecursiveMutex);
+        startPosition = Metrics().GetScrollOffset();
+      }
       MaybeAdjustDeltaForScrollSnappingOnWheelInput(aEvent, delta,
                                                     startPosition);
 
       ScreenPoint distance = ToScreenCoordinates(
           ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin);
 
       CancelAnimation();
 
@@ -2450,18 +2454,17 @@ nsEventStatus AsyncPanZoomController::On
 }
 
 void AsyncPanZoomController::NotifyMozMouseScrollEvent(
     const nsString& aString) const {
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
-
-  controller->NotifyMozMouseScrollEvent(Metrics().GetScrollId(), aString);
+  controller->NotifyMozMouseScrollEvent(GetScrollId(), aString);
 }
 
 nsEventStatus AsyncPanZoomController::OnPanMayBegin(
     const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-maybegin in state %d\n", this, mState);
 
   mX.StartTouch(aEvent.mLocalPanStartPoint.x, aEvent.mTime);
   mY.StartTouch(aEvent.mLocalPanStartPoint.y, aEvent.mTime);
@@ -3971,34 +3974,32 @@ bool AsyncPanZoomController::AdvanceAnim
   // smooth. If an animation frame is requested, it is the compositor's
   // responsibility to schedule a composite.
   mAsyncTransformAppliedToContent = false;
   bool requestAnimationFrame = false;
   nsTArray<RefPtr<Runnable>> deferredTasks;
 
   {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
-
     {  // scope lock
       MutexAutoLock lock(mCheckerboardEventLock);
       // Update RendertraceProperty before UpdateAnimation() call, since
       // the UpdateAnimation() updates effective ScrollOffset for next frame
       // if APZFrameDelay is enabled.
       if (mCheckerboardEvent) {
         mCheckerboardEvent->UpdateRendertraceProperty(
             CheckerboardEvent::UserVisible,
             CSSRect(GetEffectiveScrollOffset(
                         AsyncPanZoomController::eForCompositing),
                     Metrics().CalculateCompositedSizeInCssPixels()));
       }
     }
 
     requestAnimationFrame = UpdateAnimation(aSampleTime, &deferredTasks);
   }
-
   // Execute any deferred tasks queued up by mAnimation's Sample() (called by
   // UpdateAnimation()). This needs to be done after the monitor is released
   // since the tasks are allowed to call APZCTreeManager methods which can grab
   // the tree lock.
   for (uint32_t i = 0; i < deferredTasks.Length(); ++i) {
     APZThreadUtils::RunOnControllerThread(deferredTasks[i].forget());
   }
 
@@ -4154,16 +4155,17 @@ AsyncPanZoomController::GetCurrentAsyncT
     AsyncTransformConsumer aMode, AsyncTransformComponents aComponents) const {
   return AsyncTransformComponentMatrix(
              GetCurrentAsyncTransform(aMode, aComponents)) *
          GetOverscrollTransform(aMode);
 }
 
 LayoutDeviceToParentLayerScale AsyncPanZoomController::GetCurrentPinchZoomScale(
     AsyncTransformConsumer aMode) const {
+  RecursiveMutexAutoLock lock(mRecursiveMutex);
   AutoApplyAsyncTestAttributes testAttributeApplier(this);
   CSSToParentLayerScale2D scale = GetEffectiveZoom(aMode);
   // Note that in general the zoom might have different x- and y-scales.
   // However, this function in particular is only used on the WebRender codepath
   // for which the scales should always be the same.
   return scale.ToScaleFactor() / Metrics().GetDevPixelsPerCSSPixel();
 }
 
@@ -4750,17 +4752,17 @@ FrameMetrics& AsyncPanZoomController::Me
 
 const FrameMetrics& AsyncPanZoomController::Metrics() const {
   return mScrollMetadata.GetMetrics();
   ;
 }
 
 const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() const {
   mRecursiveMutex.AssertCurrentThreadIn();
-  return mScrollMetadata.GetMetrics();
+  return Metrics();
   ;
 }
 
 const ScrollMetadata& AsyncPanZoomController::GetScrollMetadata() const {
   mRecursiveMutex.AssertCurrentThreadIn();
   return mScrollMetadata;
 }
 
@@ -4790,29 +4792,29 @@ void AsyncPanZoomController::ZoomToRect(
     // If zooming out is disabled, an empty rect is nonsensical
     // and will produce undesirable scrolling.
     NS_WARNING(
         "ZoomToRect got called with an empty rect and zoom out disabled; "
         "ignoring...");
     return;
   }
 
-  // Only the root APZC is zoomable, and the root APZC is not allowed to have
-  // different x and y scales. If it did, the calculations in this function
-  // would have to be adjusted (as e.g. it would no longer be valid to take
-  // the minimum or maximum of the ratios of the widths and heights of the
-  // page rect and the composition bounds).
-  MOZ_ASSERT(Metrics().IsRootContent());
-  MOZ_ASSERT(Metrics().GetZoom().AreScalesSame());
-
   SetState(ANIMATING_ZOOM);
 
   {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
 
+    // Only the root APZC is zoomable, and the root APZC is not allowed to have
+    // different x and y scales. If it did, the calculations in this function
+    // would have to be adjusted (as e.g. it would no longer be valid to take
+    // the minimum or maximum of the ratios of the widths and heights of the
+    // page rect and the composition bounds).
+    MOZ_ASSERT(Metrics().IsRootContent());
+    MOZ_ASSERT(Metrics().GetZoom().AreScalesSame());
+
     ParentLayerRect compositionBounds = Metrics().GetCompositionBounds();
     CSSRect cssPageRect = Metrics().GetScrollableRect();
     CSSPoint scrollOffset = Metrics().GetScrollOffset();
     CSSToParentLayerScale currentZoom = Metrics().GetZoom().ToScaleFactor();
     CSSToParentLayerScale targetZoom;
 
     // The minimum zoom to prevent over-zoom-out.
     // If the zoom factor is lower than this (i.e. we are zoomed more into the
@@ -5018,16 +5020,17 @@ void AsyncPanZoomController::DispatchSta
                !IsTransformingState(aNewState)) {
 #if defined(MOZ_WIDGET_ANDROID)
       // The Android UI thread only shows overlay UI elements when the content
       // is not being panned or zoomed and it is in a steady state. So the
       // FrameMetrics only need to be updated when the transform ends.
       if (APZCTreeManager* manager = GetApzcTreeManager()) {
         if (AndroidDynamicToolbarAnimator* animator =
                 manager->GetAndroidDynamicToolbarAnimator()) {
+          RecursiveMutexAutoLock lock(mRecursiveMutex);
           animator->UpdateRootFrameMetrics(Metrics());
         }
       }
 #endif
 
       controller->NotifyAPZStateChange(GetGuid(),
                                        APZStateChange::eTransformEnd);
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
@@ -5054,16 +5057,17 @@ void AsyncPanZoomController::UpdateZoomC
            aConstraints.mAllowZoom, aConstraints.mAllowDoubleTapZoom,
            aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale);
   if (IsNaN(aConstraints.mMinZoom.scale) ||
       IsNaN(aConstraints.mMaxZoom.scale)) {
     NS_WARNING("APZC received zoom constraints with NaN values; dropping...");
     return;
   }
 
+  RecursiveMutexAutoLock lock(mRecursiveMutex);
   CSSToParentLayerScale min = Metrics().GetDevPixelsPerCSSPixel() *
                               kViewportMinScale / ParentLayerToScreenScale(1);
   CSSToParentLayerScale max = Metrics().GetDevPixelsPerCSSPixel() *
                               kViewportMaxScale / ParentLayerToScreenScale(1);
 
   // inf float values and other bad cases should be sanitized by the code below.
   mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom;
   mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom;
@@ -5104,16 +5108,17 @@ bool AsyncPanZoomController::HasTreeMana
 
 void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut) const {
   if (aGuidOut) {
     *aGuidOut = GetGuid();
   }
 }
 
 ScrollableLayerGuid AsyncPanZoomController::GetGuid() const {
+  RecursiveMutexAutoLock lock(mRecursiveMutex);
   return ScrollableLayerGuid(mLayersId, Metrics().GetPresShellId(),
                              Metrics().GetScrollId());
 }
 
 void AsyncPanZoomController::UpdateSharedCompositorFrameMetrics() {
   mRecursiveMutex.AssertCurrentThreadIn();
 
   FrameMetrics* frame =
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1404,16 +1404,22 @@ class AsyncPanZoomController {
   // further above due to initialization-order constraints.
 
   RefPtr<AsyncPanZoomController> mParent;
 
   /* ===================================================================
    * The functions and members in this section are used for scrolling,
    * including handing off scroll to another APZC, and overscrolling.
    */
+
+  ScrollableLayerGuid::ViewID GetScrollId() const {
+    RecursiveMutexAutoLock lock(mRecursiveMutex);
+    return Metrics().GetScrollId();
+  }
+
  public:
   ScrollableLayerGuid::ViewID GetScrollHandoffParentId() const {
     return mScrollMetadata.GetScrollParentId();
   }
 
   /**
    * Attempt to scroll in response to a touch-move from |aStartPoint| to
    * |aEndPoint|, which are in our (transformed) screen coordinates.
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js
@@ -23,18 +23,17 @@ add_task(async function test_support_the
   await extension.startup();
 
   let docEl = window.document.documentElement;
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let style = window.getComputedStyle(docEl);
-  Assert.ok(style.backgroundImage.includes("face.png"),
-            `The backgroundImage should use face.png. Actual value is: ${style.backgroundImage}`);
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face.png`);
   Assert.equal(style.backgroundColor, "rgb(" + FRAME_COLOR.join(", ") + ")",
                "Expected correct background color");
   Assert.equal(style.color, "rgb(" + TAB_TEXT_COLOR.join(", ") + ")",
                "Expected correct text color");
 
   await extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
@@ -20,17 +20,17 @@ function validateTheme(backgroundImage, 
   let style = window.getComputedStyle(docEl);
 
   if (isLWT) {
     Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
     Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                  "LWT text color attribute should be set");
   }
 
-  Assert.ok(style.backgroundImage.includes(backgroundImage), "Expected correct background image");
+  checkThemeHeaderImage(window, backgroundImage);
   if (accentColor.startsWith("#")) {
     accentColor = hexToRGB(accentColor);
   }
   if (textColor.startsWith("#")) {
     textColor = hexToRGB(textColor);
   }
   Assert.equal(style.backgroundColor, accentColor, "Expected correct accent color");
   Assert.equal(style.color, textColor, "Expected correct text color");
@@ -70,33 +70,33 @@ add_task(async function test_dynamic_the
     "colors": {
       "frame": ACCENT_COLOR_1,
       "tab_background_text": TEXT_COLOR_1,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
-  validateTheme("image1.png", ACCENT_COLOR_1, TEXT_COLOR_1, true);
+  validateTheme(`moz-extension://${extension.uuid}/image1.png`, ACCENT_COLOR_1, TEXT_COLOR_1, true);
 
   // Check with the LWT aliases (to update on Firefox 69, because the
   // LWT aliases are going to be removed).
   extension.sendMessage("update-theme", {
     "images": {
       "theme_frame": "image2.png",
     },
     "colors": {
       "frame": ACCENT_COLOR_2,
       "tab_background_text": TEXT_COLOR_2,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
-  validateTheme("image2.png", ACCENT_COLOR_2, TEXT_COLOR_2, true);
+  validateTheme(`moz-extension://${extension.uuid}/image2.png`, ACCENT_COLOR_2, TEXT_COLOR_2, true);
 
   extension.sendMessage("reset-theme");
 
   await extension.awaitMessage("theme-reset");
 
   let {backgroundImage, backgroundColor, color} = defaultStyle;
   validateTheme(backgroundImage, backgroundColor, color, false);
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
@@ -23,17 +23,17 @@ add_task(async function test_support_LWT
   let docEl = window.document.documentElement;
   let style = window.getComputedStyle(docEl);
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.ok(docEl.hasAttribute("lwtheme-image"), "LWT image attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
-  Assert.ok(style.backgroundImage.includes("image1.png"), "Expected background image");
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/image1.png`);
   Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(ACCENT_COLOR).join(", ") + ")",
                "Expected correct background color");
   Assert.equal(style.color, "rgb(" + hexToRGB(TEXT_COLOR).join(", ") + ")",
                "Expected correct text color");
 
   await extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
@@ -29,20 +29,19 @@ add_task(async function test_support_bac
   let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let toolboxCS = window.getComputedStyle(toolbox);
   let rootCS = window.getComputedStyle(docEl);
-  let rootBgImage = rootCS.backgroundImage.split(",")[0].trim();
   let bgImage = toolboxCS.backgroundImage.split(",")[0].trim();
-  Assert.ok(rootBgImage.includes("face1.png"),
-            `The backgroundImage should use face1.png. Actual value is: ${rootBgImage}`);
+
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face1.png`);
   Assert.equal(toolboxCS.backgroundImage, Array(3).fill(bgImage).join(", "),
                "The backgroundImage should use face2.png three times.");
   Assert.equal(toolboxCS.backgroundPosition, "0% 0%, 50% 0%, 100% 100%",
                "The backgroundPosition should use the three values provided.");
   Assert.equal(toolboxCS.backgroundRepeat, "no-repeat",
                "The backgroundPosition should use the default value.");
 
   await extension.unload();
@@ -90,20 +89,18 @@ add_task(async function test_support_bac
   let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let rootCS = window.getComputedStyle(docEl);
   let toolboxCS = window.getComputedStyle(toolbox);
-  let bgImage = rootCS.backgroundImage.split(",")[0].trim();
-  Assert.ok(bgImage.includes("face0.png"),
-            `The backgroundImage should use face.png. Actual value is: ${bgImage}`);
-  Assert.equal([1, 2, 3].map(num => bgImage.replace(/face[\d]*/, `face${num}`)).join(", "),
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face0.png`);
+  Assert.equal([1, 2, 3].map(num => `url("moz-extension://${extension.uuid}/face${num}.png")`).join(", "),
                toolboxCS.backgroundImage, "The backgroundImage should use face.png three times.");
   Assert.equal(rootCS.backgroundPosition, "100% 0%",
                "The backgroundPosition should use the default value for root.");
   Assert.equal(toolboxCS.backgroundPosition, "100% 0%",
                "The backgroundPosition should use the default value for navigator-toolbox.");
   Assert.equal(rootCS.backgroundRepeat, "no-repeat",
                "The backgroundRepeat should use the default values for root.");
   Assert.equal(toolboxCS.backgroundRepeat, "repeat-x, repeat-y, repeat",
@@ -141,19 +138,17 @@ add_task(async function test_additional_
   let toolbox = document.querySelector("#navigator-toolbox");
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
 
   let rootCS = window.getComputedStyle(docEl);
   let toolboxCS = window.getComputedStyle(toolbox);
-  let bgImage = rootCS.backgroundImage.split(",")[0];
-  Assert.ok(bgImage.includes("face.png"),
-            `The backgroundImage should use face.png. Actual value is: ${bgImage}`);
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/face.png`);
   Assert.equal("none", toolboxCS.backgroundImage,
                "The backgroundImage should not use face.png.");
   Assert.equal(rootCS.backgroundPosition, "100% 0%",
                "The backgroundPosition should use the default value.");
   Assert.equal(rootCS.backgroundRepeat, "no-repeat",
                "The backgroundPosition should use only one (default) value.");
 
   await extension.unload();
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
@@ -19,28 +19,26 @@ add_task(async function test_multiple_wi
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
   await extension.startup();
 
   let docEl = window.document.documentElement;
-  let style = window.getComputedStyle(docEl);
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
-  Assert.ok(style.backgroundImage.includes("image1.png"), "Expected background image");
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/image1.png`);
 
   // Now we'll open a new window to see if the theme is also applied there.
   let window2 = await BrowserTestUtils.openNewBrowserWindow();
   docEl = window2.document.documentElement;
-  style = window2.getComputedStyle(docEl);
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
                "LWT text color attribute should be set");
-  Assert.ok(style.backgroundImage.includes("image1.png"), "Expected background image");
+  checkThemeHeaderImage(window, `moz-extension://${extension.uuid}/image1.png`);
 
   await BrowserTestUtils.closeWindow(window2);
   await extension.unload();
 });
--- a/toolkit/components/extensions/test/browser/head.js
+++ b/toolkit/components/extensions/test/browser/head.js
@@ -1,11 +1,11 @@
 /* exported ACCENT_COLOR, BACKGROUND, ENCODED_IMAGE_DATA, FRAME_COLOR, TAB_TEXT_COLOR,
    TEXT_COLOR, TAB_BACKGROUND_TEXT_COLOR, imageBufferFromDataURI, hexToCSS, hexToRGB, testBorderColor,
-   waitForTransition */
+   waitForTransition, checkThemeHeaderImage */
 
 "use strict";
 
 const BACKGROUND = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0" +
   "DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
 const ENCODED_IMAGE_DATA = "iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0h" +
   "STQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAdhwAAHYcBj+XxZQAAB5dJREFUSMd" +
   "91vmTlEcZB/Bvd7/vO+/ce83O3gfLDUsC4VgIghBUEo2GM9GCFTaQBEISA1qIEVNQ4aggJDGIgAGTlFUKKcqKQpVHaQyny7FrCMiywp4ze+/Mzs67M/P" +
@@ -77,8 +77,25 @@ function testBorderColor(element, expect
                "Element right border color should be set.");
   Assert.equal(computedStyle.borderTopColor,
                hexToCSS(expected),
                "Element top border color should be set.");
   Assert.equal(computedStyle.borderBottomColor,
                hexToCSS(expected),
                "Element bottom border color should be set.");
 }
+
+function checkThemeHeaderImage(window, expectedURL) {
+  const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
+
+  let root = window.document.documentElement;
+  if (expectedURL === "none") {
+    Assert.equal(window.getComputedStyle(root).backgroundImage,
+                 "none", "Should have no background image");
+  } else {
+    Assert.equal(window.getComputedStyle(root).backgroundImage,
+                 "-moz-element(#lwt-header-image)",
+                 "Should have -moz-element background image");
+
+    Assert.equal(LightweightThemeManager.themeData.theme.headerImage.src,
+                 expectedURL, "Theme image has expected source");
+  }
+}
--- a/toolkit/components/resistfingerprinting/nsRFPService.h
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -15,30 +15,30 @@
 #include "nsDataHashtable.h"
 #include "nsString.h"
 
 // Defines regarding spoofed values of Navigator object. These spoofed values
 // are returned when 'privacy.resistFingerprinting' is true.
 // We decided to give different spoofed values according to the platform. The
 // reason is that it is easy to detect the real platform. So there is no benefit
 // for hiding the platform: it only brings breakages, like keyboard shortcuts
-// won't work in MAC OS if we spoof it as a window platform.
+// won't work in macOS if we spoof it as a Windows platform.
 #ifdef XP_WIN
-#  define SPOOFED_UA_OS "Windows NT 6.1; Win64; x64"
+#  define SPOOFED_UA_OS "Windows NT 10.0; Win64; x64"
 #  define SPOOFED_APPVERSION "5.0 (Windows)"
-#  define SPOOFED_OSCPU "Windows NT 6.1; Win64; x64"
+#  define SPOOFED_OSCPU "Windows NT 10.0; Win64; x64"
 #  define SPOOFED_PLATFORM "Win32"
 #elif defined(XP_MACOSX)
-#  define SPOOFED_UA_OS "Macintosh; Intel Mac OS X 10.13"
+#  define SPOOFED_UA_OS "Macintosh; Intel Mac OS X 10.14"
 #  define SPOOFED_APPVERSION "5.0 (Macintosh)"
-#  define SPOOFED_OSCPU "Intel Mac OS X 10.13"
+#  define SPOOFED_OSCPU "Intel Mac OS X 10.14"
 #  define SPOOFED_PLATFORM "MacIntel"
 #elif defined(MOZ_WIDGET_ANDROID)
-#  define SPOOFED_UA_OS "Android 6.0; Mobile"
-#  define SPOOFED_APPVERSION "5.0 (Android 6.0)"
+#  define SPOOFED_UA_OS "Android 8.1; Mobile"
+#  define SPOOFED_APPVERSION "5.0 (Android 8.1)"
 #  define SPOOFED_OSCPU "Linux armv7l"
 #  define SPOOFED_PLATFORM "Linux armv7l"
 #else
 // For Linux and other platforms, like BSDs, SunOS and etc, we will use Linux
 // platform.
 #  define SPOOFED_UA_OS "X11; Linux x86_64"
 #  define SPOOFED_APPVERSION "5.0 (X11)"
 #  define SPOOFED_OSCPU "Linux x86_64"
@@ -51,17 +51,17 @@
 
 #define SPOOFED_POINTER_INTERFACE MouseEvent_Binding::MOZ_SOURCE_MOUSE
 
 // For the HTTP User-Agent header, we use a simpler set of spoofed values
 // that do not reveal the specific desktop platform.
 #if defined(MOZ_WIDGET_ANDROID)
 #  define SPOOFED_HTTP_UA_OS "Android 6.0; Mobile"
 #else
-#  define SPOOFED_HTTP_UA_OS "Windows NT 6.1"
+#  define SPOOFED_HTTP_UA_OS "Windows NT 10.0"
 #endif
 
 // Forward declare LRUCache, defined in nsRFPService.cpp
 class LRUCache;
 
 namespace mozilla {
 
 enum KeyboardLang { EN = 0x01 };
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -215,17 +215,25 @@ LightweightThemeConsumer.prototype = {
     }
 
     for (let icon of ICONS) {
       let value = theme.icons ? theme.icons[`--${icon}-icon`] : null;
       _setImage(root, active, `--${icon}-icon`, value);
     }
 
     this._setExperiment(active, themeData.experiment, theme.experimental);
-    _setImage(root, active, "--lwt-header-image", theme.headerURL);
+
+    if (theme.headerImage) {
+      this._doc.mozSetImageElement("lwt-header-image", theme.headerImage);
+      root.style.setProperty("--lwt-header-image", "-moz-element(#lwt-header-image)");
+    } else {
+      this._doc.mozSetImageElement("lwt-header-image", null);
+      root.style.removeProperty("--lwt-header-image");
+    }
+
     _setImage(root, active, "--lwt-additional-images", theme.additionalBackgrounds);
     _setProperties(root, active, theme);
 
     if (theme.id != DEFAULT_THEME_ID || this.darkMode) {
       root.setAttribute("lwtheme", "true");
     } else {
       root.removeAttribute("lwtheme");
       root.removeAttribute("lwthemetextcolor");
--- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm
+++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm
@@ -1,24 +1,49 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["LightweightThemeManager"];
 
-// Holds optional fallback theme data that will be returned when no data for an
-// active theme can be found. This the case for WebExtension Themes, for example.
+const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "hiddenWindow", () => {
+  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+  let browser = Services.appShell.createWindowlessBrowser(true);
+  let principal = Services.scriptSecurityManager.getSystemPrincipal();
+  browser.docShell.createAboutBlankContentViewer(principal);
+
+  Services.obs.addObserver(() => { browser.close(); }, "xpcom-will-shutdown");
+
+  return browser.document.ownerGlobal;
+});
+
+if (AppConstants.DEBUG) {
+  void hiddenWindow;
+}
+
 var _fallbackThemeData = null;
 
 var LightweightThemeManager = {
   set fallbackThemeData(data) {
     if (data && Object.getOwnPropertyNames(data).length) {
       _fallbackThemeData = Object.assign({}, data);
+
+      for (let key of ["theme", "darkTheme"]) {
+        let data = _fallbackThemeData[key] || null;
+        if (data && typeof data.headerURL === "string") {
+          data.headerImage = new hiddenWindow.Image();
+          data.headerImage.src = data.headerURL;
+        }
+      }
     } else {
       _fallbackThemeData = null;
     }
   },
 
   /*
    * Returns the currently active theme, taking the fallback theme into account
    * if we'd be using the default theme otherwise.