Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Tue, 20 Nov 2018 23:44:09 +0200
changeset 503772 8991d660f20e
parent 503691 117bb70216d3 (current diff)
parent 503771 52a798ad6583 (diff)
child 503809 ee4915caaa4f
child 503844 772005147068
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
8991d660f20e / 65.0a1 / 20181120220133 / files
nightly linux64
8991d660f20e / 65.0a1 / 20181120220133 / files
nightly mac
8991d660f20e / 65.0a1 / 20181120220133 / files
nightly win32
8991d660f20e / 65.0a1 / 20181120220133 / files
nightly win64
8991d660f20e / 65.0a1 / 20181120220133 / 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
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
testing/web-platform/meta/css/selectors/invalidation/matches.html.ini
testing/web-platform/meta/webrtc/RTCQuicStream.https.html.ini
testing/web-platform/meta/webrtc/RTCQuicTransport.https.html.ini
testing/web-platform/tests/css/selectors/invalidation/matches.html
testing/web-platform/tests/webrtc/RTCQuicStream-helper.js
testing/web-platform/tests/webrtc/RTCQuicStream.https.html
testing/web-platform/tests/webrtc/RTCQuicTransport-helper.js
testing/web-platform/tests/webrtc/RTCQuicTransport.https.html
--- a/devtools/client/inspector/markup/test/browser_markup_display_node_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_display_node_01.js
@@ -28,16 +28,18 @@ const TEST_URI = `
   <div id="flex">Flex</div>
   <div id="block">Block</div>
   <span>HELLO WORLD</span>
 `;
 
 add_task(async function() {
   info("Enable subgrid in order to see the subgrid display type.");
   await pushPref("layout.css.grid-template-subgrid-value.enabled", true);
+  info("Enable the flexbox highlighter to get the interactive flex display badge.");
+  await pushPref("devtools.inspector.flexboxHighlighter.enabled", true);
 
   const {inspector} = await openInspectorForURL("data:text/html;charset=utf-8," +
     encodeURIComponent(TEST_URI));
 
   info("Check the display node is shown and the value of #grid.");
   await selectNode("#grid", inspector);
   const gridContainer = await getContainerForSelector("#grid", inspector);
   const gridDisplayNode = gridContainer.elt.querySelector(
--- a/devtools/client/inspector/markup/test/browser_markup_display_node_02.js
+++ b/devtools/client/inspector/markup/test/browser_markup_display_node_02.js
@@ -93,16 +93,19 @@ const TEST_DATA = [
     after: {
       textContent: "flex",
       visible: true,
     },
   },
 ];
 
 add_task(async function() {
+  info("Enable the flexbox highlighter to get the interactive flex display badge.");
+  await pushPref("devtools.inspector.flexboxHighlighter.enabled", true);
+
   const {inspector, testActor} = await openInspectorForURL(
     "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
 
   for (const data of TEST_DATA) {
     info("Running test case: " + data.desc);
     await runTestData(inspector, testActor, data);
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -32,17 +32,17 @@ add_task(async function() {
 
   const selectedRequest = getSelectedRequest(store.getState());
   is(selectedRequest, requestItem, "Proper request is selected");
 
   const EXPECTED_REQUEST_HEADERS = [
     `${method} ${SIMPLE_URL} ${httpVersion}`,
     "Host: example.com",
     "User-Agent: " + navigator.userAgent + "",
-    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
     "Accept-Language: " + navigator.languages.join(",") + ";q=0.5",
     "Accept-Encoding: gzip, deflate",
     "Connection: keep-alive",
     "Upgrade-Insecure-Requests: 1",
     "Pragma: no-cache",
     "Cache-Control: no-cache",
   ].join("\n");
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7560,22 +7560,16 @@ nsDocShell::CreateAboutBlankContentViewe
 }
 
 NS_IMETHODIMP
 nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal)
 {
   return CreateAboutBlankContentViewer(aPrincipal, nullptr);
 }
 
-NS_IMETHODIMP
-nsDocShell::ForceCreateAboutBlankContentViewer(nsIPrincipal* aPrincipal)
-{
-  return CreateAboutBlankContentViewer(aPrincipal, nullptr, true, false);
-}
-
 bool
 nsDocShell::CanSavePresentation(uint32_t aLoadType,
                                 nsIRequest* aNewRequest,
                                 nsIDocument* aNewDocument)
 {
   if (!mOSHE) {
     return false;  // no entry to save into
   }
@@ -9097,27 +9091,16 @@ private:
  * mLoadType and mLSHE will have their original values.
  */
 bool
 nsDocShell::JustStartedNetworkLoad()
 {
   return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel();
 }
 
-nsresult
-nsDocShell::CreatePrincipalFromReferrer(nsIURI* aReferrer,
-                                        nsIPrincipal** aResult)
-{
-  nsCOMPtr<nsIPrincipal> prin =
-    BasePrincipal::CreateCodebasePrincipal(aReferrer, mOriginAttributes);
-  prin.forget(aResult);
-
-  return *aResult ? NS_OK : NS_ERROR_FAILURE;
-}
-
 NS_IMETHODIMP
 nsDocShell::InternalLoad(nsIURI* aURI,
                          nsIURI* aOriginalURI,
                          Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
                          bool aKeepResultPrincipalURIIfSet,
                          bool aLoadReplace,
                          nsIURI* aReferrer,
                          uint32_t aReferrerPolicy,
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -627,19 +627,16 @@ private: // member functions
   // Get the principal that we'll set on the channel if we're inheriting. If
   // aConsiderCurrentDocument is true, we try to use the current document if
   // at all possible. If that fails, we fall back on the parent document.
   // If that fails too, we force creation of a content viewer and use the
   // resulting principal. If aConsiderCurrentDocument is false, we just look
   // at the parent.
   nsIPrincipal* GetInheritedPrincipal(bool aConsiderCurrentDocument);
 
-  nsresult CreatePrincipalFromReferrer(nsIURI* aReferrer,
-                                       nsIPrincipal** aResult);
-
   /**
    * Helper function that determines if channel is an HTTP POST.
    *
    * @param aChannel
    *        The channel to test
    *
    * @return True iff channel is an HTTP post.
    */
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -715,23 +715,16 @@ interface nsIDocShell : nsIDocShellTreeI
 
   /**
    * Create a new about:blank document and content viewer.
    * @param aPrincipal the principal to use for the new document.
    */
   void createAboutBlankContentViewer(in nsIPrincipal aPrincipal);
 
   /**
-   * Like createAboutBlankContentViewer, but don't check for permit unload.
-   * Only used by special session history operation.
-   * @param aPrincipal the principal to use for the new document.
-   */
-  [noscript] void forceCreateAboutBlankContentViewer(in nsIPrincipal aPrincipal);
-
-  /**
    * Upon getting, returns the canonical encoding label of the document
    * currently loaded into this docshell.
    *
    * Upon setting, sets forcedCharset for compatibility with legacy callers.
    */
   attribute ACString charset;
 
   /**
--- a/dom/fetch/FetchConsumer.cpp
+++ b/dom/fetch/FetchConsumer.cpp
@@ -471,63 +471,68 @@ namespace {
 
 template <class Derived>
 class FileCreationHandler final : public PromiseNativeHandler
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   static void
-  Create(Promise* aPromise, FetchBodyConsumer<Derived>* aConsumer)
+  Create(Promise* aPromise, FetchBodyConsumer<Derived>* aConsumer,
+         ThreadSafeWorkerRef* aWorkerRef)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aPromise);
 
-    RefPtr<FileCreationHandler> handler = new FileCreationHandler<Derived>(aConsumer);
+    RefPtr<FileCreationHandler> handler =
+      new FileCreationHandler<Derived>(aConsumer, aWorkerRef);
     aPromise->AppendNativeHandler(handler);
   }
 
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
     AssertIsOnMainThread();
 
     if (NS_WARN_IF(!aValue.isObject())) {
-      mConsumer->OnBlobResult(nullptr);
+      mConsumer->OnBlobResult(nullptr, mWorkerRef);
       return;
     }
 
     RefPtr<Blob> blob;
     if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
-      mConsumer->OnBlobResult(nullptr);
+      mConsumer->OnBlobResult(nullptr, mWorkerRef);
       return;
     }
 
-    mConsumer->OnBlobResult(blob);
+    mConsumer->OnBlobResult(blob, mWorkerRef);
   }
 
   void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
     AssertIsOnMainThread();
 
-    mConsumer->OnBlobResult(nullptr);
+    mConsumer->OnBlobResult(nullptr, mWorkerRef);
   }
 
 private:
-  explicit FileCreationHandler<Derived>(FetchBodyConsumer<Derived>* aConsumer)
+  FileCreationHandler<Derived>(FetchBodyConsumer<Derived>* aConsumer,
+                               ThreadSafeWorkerRef* aWorkerRef)
     : mConsumer(aConsumer)
+    , mWorkerRef(aWorkerRef)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aConsumer);
   }
 
   ~FileCreationHandler() = default;
 
   RefPtr<FetchBodyConsumer<Derived>> mConsumer;
+  RefPtr<ThreadSafeWorkerRef> mWorkerRef;
 };
 
 template <class Derived>
 NS_IMPL_ADDREF(FileCreationHandler<Derived>)
 template <class Derived>
 NS_IMPL_RELEASE(FileCreationHandler<Derived>)
 template <class Derived>
 NS_INTERFACE_MAP_BEGIN(FileCreationHandler<Derived>)
@@ -599,17 +604,17 @@ FetchBodyConsumer<Derived>::BeginConsume
     // then just consume that URI's blob instance.
     if (!mBodyBlobURISpec.IsEmpty()) {
       RefPtr<BlobImpl> blobImpl;
       rv = NS_GetBlobForBlobURISpec(mBodyBlobURISpec, getter_AddRefs(blobImpl));
       if (NS_WARN_IF(NS_FAILED(rv)) || !blobImpl) {
         return;
       }
       autoReject.DontFail();
-      ContinueConsumeBlobBody(blobImpl);
+      DispatchContinueConsumeBlobBody(blobImpl, aWorkerRef);
       return;
     }
 
     // If we're trying to consume a blob, and the request was for a local
     // file, then generate and return a File blob.
     nsCOMPtr<nsIFile> file;
     rv = GetBodyLocalFile(getter_AddRefs(file));
     if (!NS_WARN_IF(NS_FAILED(rv)) && file) {
@@ -618,18 +623,18 @@ FetchBodyConsumer<Derived>::BeginConsume
 
       ErrorResult error;
       RefPtr<Promise> promise =
         FileCreatorHelper::CreateFile(mGlobal, file, bag, true, error);
       if (NS_WARN_IF(error.Failed())) {
         return;
       }
 
-      FileCreationHandler<Derived>::Create(promise, this);
       autoReject.DontFail();
+      FileCreationHandler<Derived>::Create(promise, this, aWorkerRef);
       return;
     }
   }
 
   nsCOMPtr<nsIInputStreamPump> pump;
   nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump),
                                       mBodyStream.forget(), 0, 0, false,
                                       mMainThreadEventTarget);
@@ -683,29 +688,52 @@ FetchBodyConsumer<Derived>::BeginConsume
  * network transfer completes in BeginConsumeBodyRunnable or its local File has
  * been wrapped by FileCreationHandler). The blob is sent to the target thread
  * and ContinueConsumeBody is called.
  */
 template <class Derived>
 void
 FetchBodyConsumer<Derived>::OnBlobResult(Blob* aBlob, ThreadSafeWorkerRef* aWorkerRef)
 {
-  MOZ_ASSERT(aBlob);
+  AssertIsOnMainThread();
+
+  DispatchContinueConsumeBlobBody(aBlob ? aBlob->Impl() : nullptr, aWorkerRef);
+}
+
+template <class Derived>
+void
+FetchBodyConsumer<Derived>::DispatchContinueConsumeBlobBody(BlobImpl* aBlobImpl,
+                                                            ThreadSafeWorkerRef* aWorkerRef)
+{
+  AssertIsOnMainThread();
 
   // Main-thread.
   if (!aWorkerRef) {
-    ContinueConsumeBlobBody(aBlob->Impl());
+    if (aBlobImpl) {
+      ContinueConsumeBlobBody(aBlobImpl);
+    } else {
+      ContinueConsumeBody(NS_ERROR_DOM_ABORT_ERR, 0, nullptr);
+    }
     return;
   }
 
   // Web Worker.
-  {
+  if (aBlobImpl) {
     RefPtr<ContinueConsumeBlobBodyRunnable<Derived>> r =
       new ContinueConsumeBlobBodyRunnable<Derived>(this, aWorkerRef->Private(),
-                                                   aBlob->Impl());
+                                                   aBlobImpl);
+
+    if (r->Dispatch()) {
+      return;
+    }
+  } else {
+    RefPtr<ContinueConsumeBodyRunnable<Derived>> r =
+      new ContinueConsumeBodyRunnable<Derived>(this, aWorkerRef->Private(),
+                                               NS_ERROR_DOM_ABORT_ERR, 0,
+                                               nullptr);
 
     if (r->Dispatch()) {
       return;
     }
   }
 
   // The worker is shutting down. Let's use a control runnable to complete the
   // shutting down procedure.
--- a/dom/fetch/FetchConsumer.h
+++ b/dom/fetch/FetchConsumer.h
@@ -56,16 +56,20 @@ public:
   void
   ContinueConsumeBody(nsresult aStatus, uint32_t aLength, uint8_t* aResult,
                       bool aShuttingDown = false);
 
   void
   ContinueConsumeBlobBody(BlobImpl* aBlobImpl, bool aShuttingDown = false);
 
   void
+  DispatchContinueConsumeBlobBody(BlobImpl* aBlobImpl,
+                                  ThreadSafeWorkerRef* aWorkerRef);
+
+  void
   ShutDownMainThreadConsuming();
 
   void
   NullifyConsumeBodyPump()
   {
     mShuttingDown = true;
     mConsumeBodyPump = nullptr;
   }
--- a/dom/fetch/FetchStreamReader.cpp
+++ b/dom/fetch/FetchStreamReader.cpp
@@ -94,21 +94,25 @@ FetchStreamReader::Create(JSContext* aCx
 FetchStreamReader::FetchStreamReader(nsIGlobalObject* aGlobal)
   : mGlobal(aGlobal)
   , mOwningEventTarget(mGlobal->EventTargetFor(TaskCategory::Other))
   , mBufferRemaining(0)
   , mBufferOffset(0)
   , mStreamClosed(false)
 {
   MOZ_ASSERT(aGlobal);
+
+  mozilla::HoldJSObjects(this);
 }
 
 FetchStreamReader::~FetchStreamReader()
 {
   CloseAndRelease(nullptr, NS_BASE_STREAM_CLOSED);
+
+  mozilla::DropJSObjects(this);
 }
 
 // If a context is provided, an attempt will be made to cancel the reader.  The
 // only situation where we don't expect to have a context is when closure is
 // being triggered from the destructor or the WorkerRef is notifying.  If
 // we're at the destructor, it's far too late to cancel anything.  And if the
 // WorkerRef is being notified, the global is going away, so there's also
 // no need to do further JS work.
--- a/dom/file/tests/mochitest.ini
+++ b/dom/file/tests/mochitest.ini
@@ -38,8 +38,10 @@ skip-if = (toolkit == 'android') || (ver
 [test_fileapi_slice_memFile_1.html]
 skip-if = (toolkit == 'android') # Android: Bug 775227
 [test_fileapi_slice_memFile_2.html]
 skip-if = (toolkit == 'android') # Android: Bug 775227
 [test_fileapi_slice_image.html]
 skip-if = (toolkit == 'android') # Android: Bug 775227
 [test_mozfiledataurl.html]
 skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug1507893.html]
+support-files = worker_bug1507893.js
new file mode 100644
--- /dev/null
+++ b/dom/file/tests/test_bug1507893.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Blob URLs fetched in workers</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <script>
+
+SimpleTest.waitForExplicitFinish();
+
+// Let's be positive.
+Promise.resolve()
+
+// Create a file.
+.then(_ => {
+  return new Promise(resolve => {
+    let openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
+    let opener = SpecialPowers.loadChromeScript(openerURL);
+
+    opener.addMessageListener("files.opened", files => {
+      resolve(files[0]);
+    });
+
+    opener.sendAsyncMessage("files.open", [ "I am the blob content" ]);
+  })
+})
+
+// Just a couple of checks
+.then(file => {
+  ok(file instanceof File, "We want a file");
+  ok(file.size > 0, "We have content");
+  return file;
+})
+
+// Let's create a blobURL
+.then(file => URL.createObjectURL(file))
+
+// Let's send it to a worker.
+.then(url => {
+  return new Promise(resolve => {
+    let w = new Worker('worker_bug1507893.js');
+    w.onmessage = e => {
+     resolve(e.data);
+    };
+    w.postMessage(url);
+  });
+})
+
+// Let's check the worker's output
+.then(blob => {
+  ok(blob instanceof File, "The worker sends us a blob");
+  ok(blob.size > 0, "We have data");
+})
+
+// All done.
+.then(SimpleTest.finish);
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/file/tests/worker_bug1507893.js
@@ -0,0 +1,5 @@
+onmessage = e => {
+  fetch(e.data)
+  .then(r => r.blob())
+  .then(blob => postMessage(blob));
+}
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -590,17 +590,17 @@ double MediaDecoder::GetCurrentTime() {
   return mLogicalPosition;
 }
 
 void MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata) {
   MOZ_ASSERT(NS_IsMainThread());
   AbstractThread::AutoEnter context(AbstractMainThread());
   GetOwner()->RemoveMediaTracks();
   MetadataLoaded(MakeUnique<MediaInfo>(*aMetadata.mInfo),
-                 UniquePtr<MetadataTags>(aMetadata.mTags.forget()),
+                 UniquePtr<MetadataTags>(std::move(aMetadata.mTags)),
                  MediaDecoderEventVisibility::Observable);
   FirstFrameLoaded(std::move(aMetadata.mInfo),
                    MediaDecoderEventVisibility::Observable);
 }
 
 void MediaDecoder::MetadataLoaded(
     UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags,
     MediaDecoderEventVisibility aEventVisibility) {
--- a/dom/media/MediaMetadataManager.h
+++ b/dom/media/MediaMetadataManager.h
@@ -21,33 +21,34 @@ class TimedMetadata;
 typedef MediaEventProducerExc<TimedMetadata> TimedMetadataEventProducer;
 typedef MediaEventSourceExc<TimedMetadata> TimedMetadataEventSource;
 
 // A struct that contains the metadata of a media, and the time at which those
 // metadata should start to be reported.
 class TimedMetadata : public LinkedListElement<TimedMetadata> {
  public:
   TimedMetadata(const media::TimeUnit& aPublishTime,
-                nsAutoPtr<MetadataTags>&& aTags, nsAutoPtr<MediaInfo>&& aInfo)
-      : mPublishTime(aPublishTime),
-        mTags(std::move(aTags)),
-        mInfo(std::move(aInfo)) {}
+                UniquePtr<MetadataTags>&& aTags,
+                nsAutoPtr<MediaInfo>&& aInfo)
+    : mPublishTime(aPublishTime)
+    , mTags(std::move(aTags))
+    , mInfo(std::move(aInfo)) {}
 
   // Define our move constructor because we don't want to move the members of
   // LinkedListElement to change the list.
   TimedMetadata(TimedMetadata&& aOther)
       : mPublishTime(aOther.mPublishTime),
         mTags(std::move(aOther.mTags)),
         mInfo(std::move(aOther.mInfo)) {}
 
   // The time, in microseconds, at which those metadata should be available.
   media::TimeUnit mPublishTime;
   // The metadata. The ownership is transfered to the element when dispatching
   // to the main threads.
-  nsAutoPtr<MetadataTags> mTags;
+  UniquePtr<MetadataTags> mTags;
   // The media info, including the info of audio tracks and video tracks.
   // The ownership is transfered to MediaDecoder when dispatching to the
   // main thread.
   nsAutoPtr<MediaInfo> mInfo;
 };
 
 // This class encapsulate the logic to give the metadata from the reader to
 // the content, at the right time.
--- a/dom/media/flac/FlacDemuxer.cpp
+++ b/dom/media/flac/FlacDemuxer.cpp
@@ -422,17 +422,17 @@ class FrameParser {
     return mParser.DecodeHeaderBlock(aPacket, aLength).isOk();
   }
 
   bool HasFullMetadata() const { return mParser.HasFullMetadata(); }
 
   AudioInfo Info() const { return mParser.mInfo; }
 
   // Return a hash table with tag metadata.
-  MetadataTags* GetTags() const { return mParser.GetTags(); }
+  UniquePtr<MetadataTags> GetTags() const { return mParser.GetTags(); }
 
  private:
   bool GetNextFrame(MediaResourceIndex& aResource) {
     while (mNextFrame.FindNext(aResource)) {
       // Move our offset slightly, so that we don't find the same frame at the
       // next FindNext call.
       aResource.Seek(SEEK_CUR, mNextFrame.Header().Size());
       if (mFrame.IsValid() &&
@@ -639,17 +639,17 @@ bool FlacTrackDemuxer::Init() {
 
   return true;
 }
 
 UniquePtr<TrackInfo> FlacTrackDemuxer::GetInfo() const {
   if (mParser->Info().IsValid()) {
     // We have a proper metadata header.
     UniquePtr<TrackInfo> info = mParser->Info().Clone();
-    nsAutoPtr<MetadataTags> tags(mParser->GetTags());
+    UniquePtr<MetadataTags> tags(mParser->GetTags());
     if (tags) {
       for (auto iter = tags->Iter(); !iter.Done(); iter.Next()) {
         info->mTags.AppendElement(MetadataTag(iter.Key(), iter.Data()));
       }
     }
     return info;
   } else if (mParser->FirstFrame().Info().IsValid()) {
     // Use the first frame header.
--- a/dom/media/flac/FlacFrameParser.cpp
+++ b/dom/media/flac/FlacFrameParser.cpp
@@ -218,24 +218,22 @@ Result<bool, nsresult> FlacFrameParser::
     MOZ_TRY_VAR(blockType, br.ReadU8());
     blockType &= 0x7f;
     return blockType == FLAC_METADATA_TYPE_STREAMINFO;
   }
   char type = aPacket[0] & 0x7f;
   return type >= 1 && type <= 6;
 }
 
-MetadataTags* FlacFrameParser::GetTags() const {
+UniquePtr<MetadataTags> FlacFrameParser::GetTags() const {
   if (!mParser) {
     return nullptr;
   }
 
-  MetadataTags* tags;
-
-  tags = new MetadataTags;
+  auto tags = MakeUnique<MetadataTags>();
   for (uint32_t i = 0; i < mParser->mTags.Length(); i++) {
     OggCodecState::AddVorbisComment(tags, mParser->mTags[i].Data(),
                                     mParser->mTags[i].Length());
   }
 
   return tags;
 }
 
--- a/dom/media/flac/FlacFrameParser.h
+++ b/dom/media/flac/FlacFrameParser.h
@@ -44,17 +44,17 @@ class FlacFrameParser {
   Result<Ok, nsresult> DecodeHeaderBlock(const uint8_t* aPacket,
                                          size_t aLength);
   bool HasFullMetadata() const { return mFullMetadata; }
   // Return the duration in frames found in the block. -1 if error
   // such as invalid packet.
   int64_t BlockDuration(const uint8_t* aPacket, size_t aLength) const;
 
   // Return a hash table with tag metadata.
-  MetadataTags* GetTags() const;
+  UniquePtr<MetadataTags> GetTags() const;
 
   AudioInfo mInfo;
 
  private:
   bool ReconstructFlacGranulepos(void);
   Maybe<uint32_t> mNumHeaders;
   uint32_t mMinBlockSize;
   uint32_t mMaxBlockSize;
--- a/dom/media/ogg/OggCodecState.cpp
+++ b/dom/media/ogg/OggCodecState.cpp
@@ -90,17 +90,18 @@ bool OggCodecState::IsValidVorbisTagName
   for (uint32_t i = 0; i < length; i++) {
     if (data[i] < 0x20 || data[i] > 0x7D || data[i] == '=') {
       return false;
     }
   }
   return true;
 }
 
-bool OggCodecState::AddVorbisComment(MetadataTags* aTags, const char* aComment,
+bool OggCodecState::AddVorbisComment(UniquePtr<MetadataTags>& aTags,
+                                     const char* aComment,
                                      uint32_t aLength) {
   const char* div = (const char*)memchr(aComment, '=', aLength);
   if (!div) {
     LOG(LogLevel::Debug, ("Skipping comment: no separator"));
     return false;
   }
   nsCString key = nsCString(aComment, div - aComment);
   if (!IsValidVorbisTagName(key)) {
@@ -707,21 +708,20 @@ bool VorbisState::IsHeader(ogg_packet* a
   // The first byte in each Vorbis header packet is either 0x01, 0x03, or 0x05,
   // i.e. the first bit is odd. Audio data packets have their first bit as 0x0.
   // Any packet with its first bit set cannot be a data packet, it's a
   // (possibly invalid) header packet.
   // See: http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2.1
   return aPacket->bytes > 0 ? (aPacket->packet[0] & 0x1) : false;
 }
 
-MetadataTags* VorbisState::GetTags() {
-  MetadataTags* tags;
+UniquePtr<MetadataTags> VorbisState::GetTags() {
   NS_ASSERTION(mComment.user_comments, "no vorbis comment strings!");
   NS_ASSERTION(mComment.comment_lengths, "no vorbis comment lengths!");
-  tags = new MetadataTags;
+  auto tags = MakeUnique<MetadataTags>();
   for (int i = 0; i < mComment.comments; i++) {
     AddVorbisComment(tags, mComment.user_comments[i],
                      mComment.comment_lengths[i]);
   }
   return tags;
 }
 
 nsresult VorbisState::PageIn(ogg_page* aPage) {
@@ -962,20 +962,18 @@ bool OpusState::DecodeHeader(OggPacketPt
       // Put it back on the queue so we can decode it.
       mPackets.PushFront(std::move(aPacket));
       break;
   }
   return true;
 }
 
 /* Construct and return a tags hashmap from our internal array */
-MetadataTags* OpusState::GetTags() {
-  MetadataTags* tags;
-
-  tags = new MetadataTags;
+UniquePtr<MetadataTags> OpusState::GetTags() {
+  auto tags = MakeUnique<MetadataTags>();
   for (uint32_t i = 0; i < mParser->mTags.Length(); i++) {
     AddVorbisComment(tags, mParser->mTags[i].Data(),
                      mParser->mTags[i].Length());
   }
 
   return tags;
 }
 
@@ -1223,17 +1221,19 @@ nsresult FlacState::PageIn(ogg_page* aPa
       mPackets.Append(std::move(packet));
     }
     mUnstamped.Clear();
   }
   return NS_OK;
 }
 
 // Return a hash table with tag metadata.
-MetadataTags* FlacState::GetTags() { return mParser.GetTags(); }
+UniquePtr<MetadataTags> FlacState::GetTags() {
+  return mParser.GetTags();
+}
 
 const TrackInfo* FlacState::GetInfo() const { return &mParser.mInfo; }
 
 bool FlacState::ReconstructFlacGranulepos(void) {
   NS_ASSERTION(mUnstamped.Length() > 0, "Must have unstamped packets");
   auto& last = mUnstamped.LastElement();
   NS_ASSERTION(last->e_o_s || last->granulepos > 0,
                "Must know last granulepos!");
--- a/dom/media/ogg/OggCodecState.h
+++ b/dom/media/ogg/OggCodecState.h
@@ -113,17 +113,17 @@ class OggCodecState {
   // to determine if the last header has been read.
   // This function takes ownership of the packet and is responsible for
   // releasing it or queuing it for later processing.
   virtual bool DecodeHeader(OggPacketPtr aPacket) {
     return (mDoneReadingHeaders = true);
   }
 
   // Build a hash table with tag metadata parsed from the stream.
-  virtual MetadataTags* GetTags() { return nullptr; }
+  virtual UniquePtr<MetadataTags> GetTags() { return nullptr; }
 
   // Returns the end time that a granulepos represents.
   virtual int64_t Time(int64_t granulepos) { return -1; }
 
   // Returns the start time that a granulepos represents.
   virtual int64_t StartTime(int64_t granulepos) { return -1; }
 
   // Returns the duration of the given packet, if it can be determined.
@@ -236,20 +236,21 @@ class OggCodecState {
   }
 
   // Validation utility for vorbis-style tag names.
   static bool IsValidVorbisTagName(nsCString& aName);
 
   // Utility method to parse and add a vorbis-style comment
   // to a metadata hash table. Most Ogg-encapsulated codecs
   // use the vorbis comment format for metadata.
-  static bool AddVorbisComment(MetadataTags* aTags, const char* aComment,
+  static bool AddVorbisComment(UniquePtr<MetadataTags>& aTags,
+                               const char* aComment,
                                uint32_t aLength);
 
- protected:
+protected:
   // Constructs a new OggCodecState. aActive denotes whether the stream is
   // active. For streams of unsupported or unknown types, aActive should be
   // false.
   OggCodecState(ogg_page* aBosPage, bool aActive);
 
   // Deallocates all packets stored in mUnstamped, and clears the array.
   void ClearUnstamped();
 
@@ -284,17 +285,17 @@ class VorbisState : public OggCodecState
   int64_t PacketDuration(ogg_packet* aPacket) override;
   bool Init() override;
   nsresult Reset() override;
   bool IsHeader(ogg_packet* aPacket) override;
   nsresult PageIn(ogg_page* aPage) override;
   const TrackInfo* GetInfo() const override { return &mInfo; }
 
   // Return a hash table with tag metadata.
-  MetadataTags* GetTags() override;
+  UniquePtr<MetadataTags> GetTags() override;
 
  private:
   AudioInfo mInfo;
   vorbis_info mVorbisInfo;
   vorbis_comment mComment;
   vorbis_dsp_state mDsp;
   vorbis_block mBlock;
   OggPacketQueue mHeaders;
@@ -403,17 +404,17 @@ class OpusState : public OggCodecState {
   nsresult PageIn(ogg_page* aPage) override;
   already_AddRefed<MediaRawData> PacketOutAsMediaRawData() override;
   const TrackInfo* GetInfo() const override { return &mInfo; }
 
   // Returns the end time that a granulepos represents.
   static int64_t Time(int aPreSkip, int64_t aGranulepos);
 
   // Construct and return a table of tags from the metadata header.
-  MetadataTags* GetTags() override;
+  UniquePtr<MetadataTags> GetTags() override;
 
  private:
   nsAutoPtr<OpusParser> mParser;
   OpusMSDecoder* mDecoder;
 
   // Granule position (end sample) of the last decoded Opus packet. This is
   // used to calculate the amount we should trim from the last packet.
   int64_t mPrevPacketGranulepos;
@@ -577,17 +578,17 @@ class FlacState : public OggCodecState {
   CodecType GetType() override { return TYPE_FLAC; }
   bool DecodeHeader(OggPacketPtr aPacket) override;
   int64_t Time(int64_t granulepos) override;
   int64_t PacketDuration(ogg_packet* aPacket) override;
   bool IsHeader(ogg_packet* aPacket) override;
   nsresult PageIn(ogg_page* aPage) override;
 
   // Return a hash table with tag metadata.
-  MetadataTags* GetTags() override;
+  UniquePtr<MetadataTags> GetTags() override;
 
   const TrackInfo* GetInfo() const override;
 
  private:
   bool ReconstructFlacGranulepos(void);
 
   FlacFrameParser mParser;
 };
--- a/dom/media/ogg/OggDemuxer.cpp
+++ b/dom/media/ogg/OggDemuxer.cpp
@@ -387,22 +387,22 @@ void OggDemuxer::SetupMediaTracksInfo(co
             true);
       }
       FillTags(isAudio ? static_cast<TrackInfo*>(&mInfo.mAudio) : &mInfo.mVideo,
                primeState->GetTags());
     }
   }
 }
 
-void OggDemuxer::FillTags(TrackInfo* aInfo, MetadataTags* aTags) {
+void OggDemuxer::FillTags(TrackInfo* aInfo, UniquePtr<MetadataTags>&& aTags) {
   if (!aTags) {
     return;
   }
-  nsAutoPtr<MetadataTags> tags(aTags);
-  for (auto iter = aTags->Iter(); !iter.Done(); iter.Next()) {
+  UniquePtr<MetadataTags> tags(std::move(aTags));
+  for (auto iter = tags->Iter(); !iter.Done(); iter.Next()) {
     aInfo->mTags.AppendElement(MetadataTag(iter.Key(), iter.Data()));
   }
 }
 
 nsresult OggDemuxer::ReadMetadata() {
   OGG_DEBUG("OggDemuxer::ReadMetadata called!");
 
   // We read packets until all bitstreams have read all their header packets.
@@ -562,17 +562,17 @@ void OggDemuxer::SetChained() {
   }
 }
 
 bool OggDemuxer::ReadOggChain(const media::TimeUnit& aLastEndTime) {
   bool chained = false;
   OpusState* newOpusState = nullptr;
   VorbisState* newVorbisState = nullptr;
   FlacState* newFlacState = nullptr;
-  nsAutoPtr<MetadataTags> tags;
+  UniquePtr<MetadataTags> tags;
 
   if (HasVideo() || HasSkeleton() || !HasAudio()) {
     return false;
   }
 
   ogg_page page;
   if (!ReadOggPage(TrackInfo::kAudioTrack, &page) || !ogg_page_bos(&page)) {
     // Chaining is only supported for audio only ogg files.
--- a/dom/media/ogg/OggDemuxer.h
+++ b/dom/media/ogg/OggDemuxer.h
@@ -189,17 +189,17 @@ class OggDemuxer : public MediaDataDemux
   // Fills aTracks with the serial numbers of each active stream, for use by
   // various SkeletonState functions.
   void BuildSerialList(nsTArray<uint32_t>& aTracks);
 
   // Setup target bitstreams for decoding.
   void SetupTarget(OggCodecState** aSavedState, OggCodecState* aNewState);
   void SetupTargetSkeleton();
   void SetupMediaTracksInfo(const nsTArray<uint32_t>& aSerials);
-  void FillTags(TrackInfo* aInfo, MetadataTags* aTags);
+  void FillTags(TrackInfo* aInfo, UniquePtr<MetadataTags>&& aTags);
 
   // Compute an ogg page's checksum
   ogg_uint32_t GetPageChecksum(ogg_page* aPage);
 
   // Get the end time of aEndOffset. This is the playback position we'd reach
   // after playback finished at aEndOffset.
   int64_t RangeEndTime(TrackInfo::TrackType aType, int64_t aEndOffset);
 
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -281,25 +281,25 @@ public:
   virtual bool IsInactiveLayerManager() { return false; }
 
   /**
    * Start a new transaction. Nested transactions are not allowed so
    * there must be no transaction currently in progress.
    * This transaction will update the state of the window from which
    * this LayerManager was obtained.
    */
-  virtual bool BeginTransaction() = 0;
+  virtual bool BeginTransaction(const nsCString& aURL = nsCString()) = 0;
   /**
    * Start a new transaction. Nested transactions are not allowed so
    * there must be no transaction currently in progress.
    * This transaction will render the contents of the layer tree to
    * the given target context. The rendering will be complete when
    * EndTransaction returns.
    */
-  virtual bool BeginTransactionWithTarget(gfxContext* aTarget) = 0;
+  virtual bool BeginTransactionWithTarget(gfxContext* aTarget, const nsCString& aURL = nsCString()) = 0;
 
   enum EndTransactionFlags {
     END_DEFAULT = 0,
     END_NO_IMMEDIATE_REDRAW = 1 << 0,  // Do not perform the drawing phase
     END_NO_COMPOSITE = 1 << 1, // Do not composite after drawing painted layer contents.
     END_NO_REMOTE_COMPOSITE = 1 << 2 // Do not schedule a composition with a remote Compositor, if one exists.
   };
 
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -341,25 +341,25 @@ BasicLayerManager::SetDefaultTarget(gfxC
 
 void
 BasicLayerManager::SetDefaultTargetConfiguration(BufferMode aDoubleBuffering, ScreenRotation aRotation)
 {
   mDoubleBuffering = aDoubleBuffering;
 }
 
 bool
-BasicLayerManager::BeginTransaction()
+BasicLayerManager::BeginTransaction(const nsCString &aURL)
 {
   mInTransaction = true;
   mUsingDefaultTarget = true;
-  return BeginTransactionWithTarget(mDefaultTarget);
+  return BeginTransactionWithTarget(mDefaultTarget, aURL);
 }
 
 bool
-BasicLayerManager::BeginTransactionWithTarget(gfxContext* aTarget)
+BasicLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, const nsCString &aURL)
 {
   mInTransaction = true;
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   MOZ_LAYERS_LOG(("[----- BeginTransaction"));
   Log();
 #endif
 
@@ -786,20 +786,18 @@ InstallLayerClipPreserves3D(gfxContext* 
   Matrix transform;
   if (!transform3d.CanDraw2D(&transform)) {
     gfxDevCrash(LogReason::CannotDraw3D) << "GFX: We should not have a 3D transform that CanDraw2D() is false!";
   }
   Matrix oldTransform = aTarget->CurrentMatrix();
   transform *= oldTransform;
   aTarget->SetMatrix(transform);
 
-  aTarget->NewPath();
-  aTarget->SnappedRectangle(gfxRect(clipRect->X(), clipRect->Y(),
-                                    clipRect->Width(), clipRect->Height()));
-  aTarget->Clip();
+  aTarget->SnappedClip(gfxRect(clipRect->X(), clipRect->Y(),
+                               clipRect->Width(), clipRect->Height()));
 
   aTarget->SetMatrix(oldTransform);
 }
 
 void
 BasicLayerManager::PaintLayer(gfxContext* aTarget,
                               Layer* aLayer,
                               DrawPaintedLayerCallback aCallback,
@@ -942,19 +940,17 @@ BasicLayerManager::PaintLayer(gfxContext
     if (xformSurf) {
       aTarget->SetPattern(
         new gfxPattern(xformSurf,
                        Matrix::Translation(xformBounds.TopLeft())));
 
       // Azure doesn't support EXTEND_NONE, so to avoid extending the edges
       // of the source surface out to the current clip region, clip to
       // the rectangle of the result surface now.
-      aTarget->NewPath();
-      aTarget->SnappedRectangle(ThebesRect(xformBounds));
-      aTarget->Clip();
+      aTarget->SnappedClip(ThebesRect(xformBounds));
       FlushGroup(paintLayerContext, needsClipToVisibleRegion);
     }
   }
 }
 
 void
 BasicLayerManager::ClearCachedResources(Layer* aSubtree)
 {
--- a/gfx/layers/basic/BasicLayers.h
+++ b/gfx/layers/basic/BasicLayers.h
@@ -94,18 +94,18 @@ public:
   gfxContext* GetDefaultTarget() { return mDefaultTarget; }
 
   nsIWidget* GetRetainerWidget() { return mWidget; }
   void ClearRetainerWidget() { mWidget = nullptr; }
 
   virtual bool IsWidgetLayerManager() override { return mWidget != nullptr; }
   virtual bool IsInactiveLayerManager() override { return mType == BLM_INACTIVE; }
 
-  virtual bool BeginTransaction() override;
-  virtual bool BeginTransactionWithTarget(gfxContext* aTarget) override;
+  virtual bool BeginTransaction(const nsCString &aURL = nsCString()) override;
+  virtual bool BeginTransactionWithTarget(gfxContext* aTarget, const nsCString &aURL = nsCString()) override;
   virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override;
   virtual void EndTransaction(DrawPaintedLayerCallback aCallback,
                               void* aCallbackData,
                               EndTransactionFlags aFlags = END_DEFAULT) override;
   void AbortTransaction();
 
   virtual void SetRoot(Layer* aLayer) override;
 
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -173,17 +173,17 @@ ClientLayerManager::MutatedSimple(Layer*
 already_AddRefed<ReadbackLayer>
 ClientLayerManager::CreateReadbackLayer()
 {
   RefPtr<ReadbackLayer> layer = new ClientReadbackLayer(this);
   return layer.forget();
 }
 
 bool
-ClientLayerManager::BeginTransactionWithTarget(gfxContext* aTarget)
+ClientLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, const nsCString &aURL)
 {
 #ifdef MOZ_DUMP_PAINTING
   // When we are dump painting, we expect to be able to read the contents of
   // compositable clients from previous paints inside this layer transaction
   // before we flush async paints in EndTransactionInternal.
   // So to work around this flush async paints now.
   if (gfxEnv::DumpPaint()) {
     FlushAsyncPaints();
@@ -193,16 +193,17 @@ ClientLayerManager::BeginTransactionWith
   MOZ_ASSERT(mForwarder, "ClientLayerManager::BeginTransaction without forwarder");
   if (!mForwarder->IPCOpen()) {
     gfxCriticalNote << "ClientLayerManager::BeginTransaction with IPC channel down. GPU process may have died.";
     return false;
   }
 
   mInTransaction = true;
   mTransactionStart = TimeStamp::Now();
+  mURL = aURL;
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   MOZ_LAYERS_LOG(("[----- BeginTransaction"));
   Log();
 #endif
 
   NS_ASSERTION(!InTransaction(), "Nested transactions not allowed");
   mPhase = PHASE_CONSTRUCTION;
@@ -257,19 +258,19 @@ ClientLayerManager::BeginTransactionWith
     if (gfxPrefs::APZTestLoggingEnabled()) {
       mApzTestData.StartNewPaint(mPaintSequenceNumber);
     }
   }
   return true;
 }
 
 bool
-ClientLayerManager::BeginTransaction()
+ClientLayerManager::BeginTransaction(const nsCString &aURL)
 {
-  return BeginTransactionWithTarget(nullptr);
+  return BeginTransactionWithTarget(nullptr, aURL);
 }
 
 bool
 ClientLayerManager::EndTransactionInternal(DrawPaintedLayerCallback aCallback,
                                            void* aCallbackData,
                                            EndTransactionFlags)
 {
   // This just causes the compositor to check whether the GPU is done with its
@@ -391,17 +392,17 @@ ClientLayerManager::EndTransaction(DrawP
 
   if (mRepeatTransaction) {
     mRepeatTransaction = false;
     mIsRepeatTransaction = true;
 
     // BeginTransaction will reset the transaction start time, but we
     // would like to keep the original time for telemetry purposes.
     TimeStamp transactionStart = mTransactionStart;
-    if (BeginTransaction()) {
+    if (BeginTransaction(mURL)) {
       mTransactionStart = transactionStart;
       ClientLayerManager::EndTransaction(aCallback, aCallbackData, aFlags);
     }
 
     mIsRepeatTransaction = false;
   } else {
     MakeSnapshotIfRequired();
   }
@@ -727,17 +728,17 @@ ClientLayerManager::ForwardTransaction(b
     mForwarder->SendPaintTime(mLatestTransactionId, mLastPaintTime);
   }
 
   // forward this transaction's changeset to our LayerManagerComposite
   bool sent = false;
   bool ok = mForwarder->EndTransaction(
     mRegionToClear, mLatestTransactionId, aScheduleComposite,
     mPaintSequenceNumber, mIsRepeatTransaction,
-    refreshStart, mTransactionStart,
+    refreshStart, mTransactionStart, mURL,
     &sent);
   if (ok) {
     if (sent) {
       mNeedsComposite = false;
     }
   } else if (HasShadowManager()) {
     NS_WARNING("failed to forward Layers transaction");
   }
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -76,18 +76,18 @@ public:
     return this;
   }
 
   TabGroup* GetTabGroup();
 
   virtual int32_t GetMaxTextureSize() const override;
 
   virtual void SetDefaultTargetConfiguration(BufferMode aDoubleBuffering, ScreenRotation aRotation);
-  virtual bool BeginTransactionWithTarget(gfxContext* aTarget) override;
-  virtual bool BeginTransaction() override;
+  virtual bool BeginTransactionWithTarget(gfxContext* aTarget, const nsCString &aURL) override;
+  virtual bool BeginTransaction(const nsCString &aURL) override;
   virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override;
   virtual void EndTransaction(DrawPaintedLayerCallback aCallback,
                               void* aCallbackData,
                               EndTransactionFlags aFlags = END_DEFAULT) override;
 
   virtual LayersBackend GetBackendType() override { return LayersBackend::LAYERS_CLIENT; }
   virtual LayersBackend GetCompositorBackendType() override
   {
@@ -331,16 +331,17 @@ private:
   // An incrementing sequence number for paints.
   // Incremented in BeginTransaction(), but not for repeat transactions.
   uint32_t mPaintSequenceNumber;
 
   APZTestData mApzTestData;
 
   RefPtr<ShadowLayerForwarder> mForwarder;
   mozilla::TimeStamp mTransactionStart;
+  nsCString mURL;
 
   nsTArray<DidCompositeObserver*> mDidCompositeObservers;
 
   RefPtr<MemoryPressureObserver> mMemoryPressureObserver;
 };
 
 class ClientLayer : public ShadowableLayer
 {
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -199,17 +199,17 @@ bool
 LayerManagerComposite::AreComponentAlphaLayersEnabled()
 {
   return mCompositor->GetBackendType() != LayersBackend::LAYERS_BASIC &&
          mCompositor->SupportsEffect(EffectTypes::COMPONENT_ALPHA) &&
          LayerManager::AreComponentAlphaLayersEnabled();
 }
 
 bool
-LayerManagerComposite::BeginTransaction()
+LayerManagerComposite::BeginTransaction(const nsCString &aURL)
 {
   mInTransaction = true;
 
   if (!mCompositor->Ready()) {
     return false;
   }
 
   mIsCompositorReady = true;
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -74,17 +74,17 @@ static const int kVisualWarningDuration 
 // An implementation of LayerManager that acts as a pair with ClientLayerManager
 // and is mirrored across IPDL. This gets managed/updated by LayerTransactionParent.
 class HostLayerManager : public LayerManager
 {
 public:
   HostLayerManager();
   ~HostLayerManager();
 
-  virtual bool BeginTransactionWithTarget(gfxContext* aTarget) override
+  virtual bool BeginTransactionWithTarget(gfxContext* aTarget, const nsCString &aURL) override
   {
     MOZ_CRASH("GFX: Use BeginTransactionWithDrawTarget");
   }
 
   virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override
   {
     MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)");
     return false;
@@ -277,17 +277,17 @@ public:
    */
   virtual LayerManagerComposite* AsLayerManagerComposite() override
   {
     return this;
   }
 
   void UpdateRenderBounds(const gfx::IntRect& aRect) override;
 
-  virtual bool BeginTransaction() override;
+  virtual bool BeginTransaction(const nsCString &aURL) override;
   void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget,
                                       const gfx::IntRect& aRect) override;
   void EndTransaction(const TimeStamp& aTimeStamp,
                       EndTransactionFlags aFlags = END_DEFAULT) override;
   virtual void EndTransaction(DrawPaintedLayerCallback aCallback,
                               void* aCallbackData,
                               EndTransactionFlags aFlags = END_DEFAULT) override
   {
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -548,16 +548,22 @@ CompositorBridgeChild::RecvDidComposite(
 
   for (size_t i = 0; i < texturePools.Length(); i++) {
     texturePools[i]->ReturnDeferredClients();
   }
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+CompositorBridgeChild::RecvNotifyFrameStats(nsTArray<FrameStats>&& aFrameStats)
+{
+  gfxPlatform::GetPlatform()->NotifyFrameStats(std::move(aFrameStats));
+  return IPC_OK();
+}
 
 void
 CompositorBridgeChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   if (aWhy == AbnormalShutdown) {
     // If the parent side runs into a problem then the actor will be destroyed.
     // There is nothing we can do in the child side, here sets mCanSend as false.
     gfxCriticalNote << "Receive IPC close with reason=AbnormalShutdown";
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -87,16 +87,19 @@ public:
 
   virtual mozilla::ipc::IPCResult
   RecvDidComposite(const LayersId& aId,
                    const TransactionId& aTransactionId,
                    const TimeStamp& aCompositeStart,
                    const TimeStamp& aCompositeEnd) override;
 
   virtual mozilla::ipc::IPCResult
+  RecvNotifyFrameStats(nsTArray<FrameStats>&& aFrameStats) override;
+
+  virtual mozilla::ipc::IPCResult
   RecvInvalidateLayers(const LayersId& aLayersId) override;
 
   virtual mozilla::ipc::IPCResult
   RecvUpdatePluginConfigurations(const LayoutDeviceIntPoint& aContentOffset,
                                  const LayoutDeviceIntRegion& aVisibleRegion,
                                  nsTArray<PluginWindowData>&& aPlugins) override;
 
   virtual mozilla::ipc::IPCResult
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -760,17 +760,17 @@ CompositorBridgeParent::PauseComposition
 
     TimeStamp now = TimeStamp::Now();
     if (mCompositor) {
       mCompositor->Pause();
       DidComposite(now, now);
     } else if (mWrBridge) {
       mWrBridge->Pause();
       NotifyPipelineRendered(mWrBridge->PipelineId(), mWrBridge->GetCurrentEpoch(),
-                             now, now);
+                             now, now, now);
     }
   }
 
   // if anyone's waiting to make sure that composition really got paused, tell them
   lock.NotifyAll();
 }
 
 void
@@ -1050,20 +1050,21 @@ CompositorBridgeParent::CompositeToTarge
   // to local pages, hide every plugin associated with the window.
   if (!hasRemoteContent && gfxVars::BrowserTabsRemoteAutostart() &&
       mCachedPluginData.Length()) {
     Unused << SendHideAllPlugins(GetWidget()->GetWidgetKey());
     mCachedPluginData.Clear();
   }
 #endif
 
+  nsCString none;
   if (aTarget) {
     mLayerManager->BeginTransactionWithDrawTarget(aTarget, *aRect);
   } else {
-    mLayerManager->BeginTransaction();
+    mLayerManager->BeginTransaction(none);
   }
 
   SetShadowProperties(mLayerManager->GetRoot());
 
   if (mForceCompositionTask && !mOverrideComposeReadiness) {
     if (mCompositionManager->ReadyForCompose()) {
       mForceCompositionTask->Cancel();
       mForceCompositionTask = nullptr;
@@ -1843,17 +1844,17 @@ CompositorBridgeParent::RecvAdoptChild(c
     api = api->Clone();
     wr::Epoch newEpoch = childWrBridge->UpdateWebRender(mWrBridge->CompositorScheduler(),
                                    api,
                                    mWrBridge->AsyncImageManager(),
                                    GetAnimationStorage(),
                                    mWrBridge->GetTextureFactoryIdentifier());
     // Pretend we composited, since parent CompositorBridgeParent was replaced.
     TimeStamp now = TimeStamp::Now();
-    NotifyPipelineRendered(childWrBridge->PipelineId(), newEpoch, now, now);
+    NotifyPipelineRendered(childWrBridge->PipelineId(), newEpoch, now, now, now);
   }
 
   if (oldApzUpdater) {
     // We don't support moving a child from an APZ-enabled compositor to a
     // APZ-disabled compositor. The mOptions assertion above should already
     // ensure this, since APZ-ness is one of the things in mOptions. Note
     // however it is possible for mApzUpdater to be non-null here with
     // oldApzUpdater null, because the child may not have been previously
@@ -2203,51 +2204,58 @@ CompositorBridgeParent::DidComposite(Tim
     mPendingTransaction = TransactionId{0};
   }
 }
 
 void
 CompositorBridgeParent::NotifyPipelineRendered(const wr::PipelineId& aPipelineId,
                                                const wr::Epoch& aEpoch,
                                                TimeStamp& aCompositeStart,
+                                               TimeStamp& aRenderStart,
                                                TimeStamp& aCompositeEnd,
                                                wr::RendererStats* aStats)
 {
   if (!mWrBridge || !mAsyncImageManager) {
     return;
   }
 
+  nsTArray<FrameStats> stats;
+
   RefPtr<UiCompositorControllerParent> uiController =
     UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeID);
 
   if (mWrBridge->PipelineId() == aPipelineId) {
     mWrBridge->RemoveEpochDataPriorTo(aEpoch);
 
     if (!mPaused) {
-      TransactionId transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeEnd, uiController);
+      TransactionId transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeStart, aRenderStart, aCompositeEnd, uiController);
       Unused << SendDidComposite(LayersId{0}, transactionId, aCompositeStart, aCompositeEnd);
 
       nsTArray<ImageCompositeNotificationInfo> notifications;
       mWrBridge->ExtractImageCompositeNotifications(&notifications);
       if (!notifications.IsEmpty()) {
         Unused << ImageBridgeParent::NotifyImageComposites(notifications);
       }
     }
     return;
   }
 
   auto wrBridge = mAsyncImageManager->GetWrBridge(aPipelineId);
   if (wrBridge && wrBridge->GetCompositorBridge()) {
       MOZ_ASSERT(!wrBridge->IsRootWebRenderBridgeParent());
       wrBridge->RemoveEpochDataPriorTo(aEpoch);
       if (!mPaused) {
-        TransactionId transactionId = wrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeEnd, uiController, aStats);
+        TransactionId transactionId = wrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeStart, aRenderStart, aCompositeEnd, uiController, aStats, &stats);
         Unused << wrBridge->GetCompositorBridge()->SendDidComposite(wrBridge->GetLayersId(), transactionId, aCompositeStart, aCompositeEnd);
       }
   }
+
+  if (!stats.IsEmpty()) {
+    Unused << SendNotifyFrameStats(stats);
+  }
 }
 
 RefPtr<AsyncImagePipelineManager>
 CompositorBridgeParent::GetAsyncImagePipelineManager() const
 {
   return mAsyncImageManager;
 }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -279,16 +279,17 @@ public:
 
   bool IsSameProcess() const override;
 
   void NotifyWebRenderError(wr::WebRenderError aError);
   void NotifyWebRenderContextPurge();
   void NotifyPipelineRendered(const wr::PipelineId& aPipelineId,
                               const wr::Epoch& aEpoch,
                               TimeStamp& aCompositeStart,
+                              TimeStamp& aRenderStart,
                               TimeStamp& aCompositeEnd,
                               wr::RendererStats* aStats = nullptr);
   RefPtr<AsyncImagePipelineManager> GetAsyncImagePipelineManager() const;
 
   PCompositorWidgetParent* AllocPCompositorWidgetParent(const CompositorWidgetInitData& aInitData) override;
   bool DeallocPCompositorWidgetParent(PCompositorWidgetParent* aActor) override;
 
   void ObserveLayersUpdate(LayersId aLayersId, LayersObserverEpoch aEpoch, bool aActive) override { }
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -288,17 +288,16 @@ CrossProcessCompositorBridgeParent::Recv
                                                                  const base::ProcessId& pid,
                                                                  CompositorOptions* aOptions)
 {
   // This can only be called from the browser process, as the mapping
   // ensures proper window ownership of layer trees.
   return IPC_FAIL_NO_REASON(this);
 }
 
-
 mozilla::ipc::IPCResult
 CrossProcessCompositorBridgeParent::RecvCheckContentOnlyTDR(const uint32_t& sequenceNum,
                                                             bool* isContentOnlyTDR)
 {
   *isContentOnlyTDR = false;
 #ifdef XP_WIN
   ContentDeviceData compositor;
 
@@ -382,17 +381,17 @@ CrossProcessCompositorBridgeParent::Shad
     profiler_add_marker_for_thread(profiler_current_thread_id(), "CONTENT_FULL_PAINT_TIME", MakeUnique<ContentBuildPayload>(aInfo.transactionStart(),
                                                                                                                        endTime));
   }
 #endif
   Telemetry::Accumulate(Telemetry::CONTENT_FULL_PAINT_TIME,
                         static_cast<uint32_t>((endTime - aInfo.transactionStart()).ToMilliseconds()));
 
 
-  aLayerTree->SetPendingTransactionId(aInfo.id(), aInfo.refreshStart(), aInfo.transactionStart(), aInfo.fwdTime());
+  aLayerTree->SetPendingTransactionId(aInfo.id(), aInfo.refreshStart(), aInfo.transactionStart(), aInfo.url(), aInfo.fwdTime());
 }
 
 void
 CrossProcessCompositorBridgeParent::DidComposite(
   LayersId aId,
   TimeStamp& aCompositeStart,
   TimeStamp& aCompositeEnd)
 {
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -184,17 +184,18 @@ LayerTransactionParent::RecvUpdate(const
   }
 
   // This ensures that destroy operations are always processed. It is not safe
   // to early-return from RecvUpdate without doing so.
   AutoLayerTransactionParentAsyncMessageSender autoAsyncMessageSender(this, &aInfo.toDestroy());
 
   {
     AutoResolveRefLayers resolve(mCompositorBridge->GetCompositionManager(this));
-    mLayerManager->BeginTransaction();
+    nsCString none;
+    mLayerManager->BeginTransaction(none);
   }
 
   // Not all edits require an update to the hit testing tree.
   mUpdateHitTestingTree = false;
 
   for (EditArray::index_type i = 0; i < aInfo.cset().Length(); ++i) {
     const Edit& edit = const_cast<Edit&>(aInfo.cset()[i]);
 
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -73,21 +73,23 @@ public:
   void DeallocShmem(ipc::Shmem& aShmem) override;
 
   bool IsSameProcess() const override;
 
   const TransactionId& GetPendingTransactionId() { return mPendingTransaction; }
   void SetPendingTransactionId(TransactionId aId,
                                const TimeStamp& aRefreshStartTime,
                                const TimeStamp& aTxnStartTime,
+                               const nsCString& aURL,
                                const TimeStamp& aFwdTime)
   {
     mPendingTransaction = aId;
     mRefreshStartTime = aRefreshStartTime;
     mTxnStartTime = aTxnStartTime;
+    mTxnURL = aURL;
     mFwdTime = aFwdTime;
   }
   TransactionId FlushTransactionId(TimeStamp& aCompositeEnd);
 
   // CompositableParentManager
   void SendAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessage) override;
 
   void SendPendingAsyncMessages() override;
@@ -193,16 +195,17 @@ private:
   LayersObserverEpoch mParentEpoch;
 
   TimeDuration mVsyncRate;
 
   TransactionId mPendingTransaction;
   TimeStamp mRefreshStartTime;
   TimeStamp mTxnStartTime;
   TimeStamp mFwdTime;
+  nsCString mTxnURL;
 
   // When the widget/frame/browser stuff in this process begins its
   // destruction process, we need to Disconnect() all the currently
   // live shadow layers, because some of them might be orphaned from
   // the layer tree.  This happens in Destroy() above.  After we
   // Destroy() ourself, there's a window in which that information
   // hasn't yet propagated back to the child side and it might still
   // send us layer transactions.  We want to ignore those transactions
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -549,16 +549,17 @@ struct TransactionInfo
   PluginWindowData[] plugins;
   bool isFirstPaint;
   FocusTarget focusTarget;
   bool scheduleComposite;
   uint32_t paintSequenceNumber;
   bool isRepeatTransaction;
   TimeStamp refreshStart;
   TimeStamp transactionStart;
+  nsCString url;
   TimeStamp fwdTime;
 };
 
 union MaybeTransform {
   Matrix4x4;
   void_t;
 };
 
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -43,16 +43,32 @@ using base::ProcessId from "base/process
 using mozilla::wr::MaybeExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::WebRenderError from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h";
 
 namespace mozilla {
 namespace layers {
 
+struct FrameStats {
+  TransactionId id;
+  TimeStamp compositeStart;
+  TimeStamp renderStart;
+  TimeStamp compositeEnd;
+  int32_t contentFrameTime;
+  double resourceUploadTime;
+  double gpuCacheUploadTime;
+  TimeStamp transactionStart;
+  TimeStamp refreshStart;
+  TimeStamp fwdTime;
+  TimeStamp sceneBuiltTime;
+  uint32_t skippedComposites;
+  nsCString url;
+};
+
 
 /**
  * The PCompositorBridge protocol is a top-level protocol for the compositor.
  * There is an instance of the protocol for each compositor, plus one for each
  * content process. In other words:
  * - There is a CompositorBridgeParent/CompositorBridgeChild pair created
  *   for each "top level browser window", which has its own compositor. The
  *   CompositorBridgeChild instance lives in the UI process, and the
@@ -95,16 +111,18 @@ child:
   // The compositor completed a layers transaction. id is the layers id
   // of the child layer tree that was composited (or 0 when notifying
   // the root layer tree).
   // transactionId is the id of the transaction before this composite, or 0
   // if there was no transaction since the last composite.
   async DidComposite(LayersId id, TransactionId transactionId,
                      TimeStamp compositeStart, TimeStamp compositeEnd);
 
+  async NotifyFrameStats(FrameStats[] aFrameStats);
+
   /**
    * Parent informs the child that the graphics objects are ready for
    * compositing.  This usually means that the graphics objects (textures
    * and the like) are available on the GPU.  This is used for chrome UI.
    * @see RequestNotifyAfterRemotePaint
    * @see PBrowser
    */
   async RemotePaintIsReady();
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -42,21 +42,21 @@ parent:
   async NewCompositable(CompositableHandle handle, TextureInfo info);
   async ReleaseCompositable(CompositableHandle compositable);
 
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async SetDisplayList(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId,
                        LayoutSize aContentSize, ByteBuf aDL, BuiltDisplayListDescriptor aDLDesc,
                        WebRenderScrollData aScrollData,
                        OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems,
-                       IdNamespace aIdNamespace, bool containsSVGGroup, TimeStamp refreshStartTime, TimeStamp txnStartTime, TimeStamp fwdTime);
+                       IdNamespace aIdNamespace, bool containsSVGGroup, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
   async EmptyTransaction(FocusTarget focusTarget, ScrollUpdatesMap scrollUpdates, uint32_t aPaintSequenceNumber,
                          WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId,
                          OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems,
-                         IdNamespace aIdNamespace, TimeStamp refreshStartTime, TimeStamp txnStartTime, TimeStamp fwdTime);
+                         IdNamespace aIdNamespace, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
   async SetFocusTarget(FocusTarget focusTarget);
   async UpdateResources(OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems, bool scheduleComposite);
   async ParentCommands(WebRenderParentCommand[] commands);
   sync GetSnapshot(PTexture texture);
   async SetLayersObserverEpoch(LayersObserverEpoch childEpoch);
   async ClearCachedResources();
   // Schedule a composite if one isn't already scheduled.
   async ScheduleComposite();
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -600,16 +600,17 @@ ShadowLayerForwarder::SendPaintTime(Tran
 bool
 ShadowLayerForwarder::EndTransaction(const nsIntRegion& aRegionToClear,
                                      TransactionId aId,
                                      bool aScheduleComposite,
                                      uint32_t aPaintSequenceNumber,
                                      bool aIsRepeatTransaction,
                                      const mozilla::TimeStamp& aRefreshStart,
                                      const mozilla::TimeStamp& aTransactionStart,
+                                     const nsCString& aURL,
                                      bool* aSent)
 {
   *aSent = false;
 
   TransactionInfo info;
 
   MOZ_ASSERT(IPCOpen(), "no manager to forward to");
   if (!IPCOpen()) {
@@ -742,16 +743,17 @@ ShadowLayerForwarder::EndTransaction(con
   info.plugins() = mPluginWindowData;
   info.isFirstPaint() = mIsFirstPaint;
   info.focusTarget() = mFocusTarget;
   info.scheduleComposite() = aScheduleComposite;
   info.paintSequenceNumber() = aPaintSequenceNumber;
   info.isRepeatTransaction() = aIsRepeatTransaction;
   info.refreshStart() = aRefreshStart;
   info.transactionStart() = aTransactionStart;
+  info.url() = aURL;
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   info.fwdTime() = TimeStamp::Now();
 #endif
 
   TargetConfig targetConfig(mTxn->mTargetBounds,
                             mTxn->mTargetRotation,
                             mTxn->mTargetOrientation,
                             aRegionToClear);
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -254,16 +254,17 @@ public:
    */
   bool EndTransaction(const nsIntRegion& aRegionToClear,
                       TransactionId aId,
                       bool aScheduleComposite,
                       uint32_t aPaintSequenceNumber,
                       bool aIsRepeatTransaction,
                       const mozilla::TimeStamp& aRefreshStart,
                       const mozilla::TimeStamp& aTransactionStart,
+                      const nsCString& aURL,
                       bool* aSent);
 
   /**
    * Set an actor through which layer updates will be pushed.
    */
   void SetShadowManager(PLayerTransactionChild* aShadowManager);
 
   /**
--- a/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
+++ b/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
@@ -183,17 +183,17 @@ LayerManagerMLGPU::GetBackendType()
 
 void
 LayerManagerMLGPU::SetRoot(Layer* aLayer)
 {
   mRoot = aLayer;
 }
 
 bool
-LayerManagerMLGPU::BeginTransaction()
+LayerManagerMLGPU::BeginTransaction(const nsCString &aURL)
 {
   MOZ_ASSERT(!mTarget);
   return true;
 }
 
 void
 LayerManagerMLGPU::BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget,
                                                   const gfx::IntRect& aRect)
--- a/gfx/layers/mlgpu/LayerManagerMLGPU.h
+++ b/gfx/layers/mlgpu/LayerManagerMLGPU.h
@@ -32,17 +32,17 @@ class LayerManagerMLGPU final : public H
 public:
   explicit LayerManagerMLGPU(widget::CompositorWidget* aWidget);
   ~LayerManagerMLGPU();
 
   bool Initialize();
   void Destroy() override;
 
   // LayerManager methods
-  bool BeginTransaction() override;
+  bool BeginTransaction(const nsCString &aURL) override;
   void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, const gfx::IntRect& aRect) override;
   void SetRoot(Layer* aLayer) override;
   already_AddRefed<PaintedLayer> CreatePaintedLayer() override;
   already_AddRefed<ContainerLayer> CreateContainerLayer() override;
   already_AddRefed<ImageLayer> CreateImageLayer() override;
   already_AddRefed<ColorLayer> CreateColorLayer() override;
   already_AddRefed<CanvasLayer> CreateCanvasLayer() override;
   already_AddRefed<RefLayer> CreateRefLayer() override;
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -121,76 +121,72 @@ void
 WebRenderBridgeChild::EndTransaction(const wr::LayoutSize& aContentSize,
                                      wr::BuiltDisplayList& aDL,
                                      wr::IpcResourceUpdateQueue& aResources,
                                      const gfx::IntSize& aSize,
                                      TransactionId aTransactionId,
                                      const WebRenderScrollData& aScrollData,
                                      bool aContainsSVGGroup,
                                      const mozilla::TimeStamp& aRefreshStartTime,
-                                     const mozilla::TimeStamp& aTxnStartTime)
+                                     const mozilla::TimeStamp& aTxnStartTime,
+                                     const nsCString& aTxnURL)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
   ByteBuf dlData(aDL.dl.inner.data, aDL.dl.inner.length, aDL.dl.inner.capacity);
   aDL.dl.inner.capacity = 0;
   aDL.dl.inner.data = nullptr;
 
-  TimeStamp fwdTime;
-#if defined(ENABLE_FRAME_LATENCY_LOG)
-  fwdTime = TimeStamp::Now();
-#endif
+  TimeStamp fwdTime = TimeStamp::Now();
 
   nsTArray<OpUpdateResource> resourceUpdates;
   nsTArray<RefCountedShmem> smallShmems;
   nsTArray<ipc::Shmem> largeShmems;
   aResources.Flush(resourceUpdates, smallShmems, largeShmems);
 
   this->SendSetDisplayList(aSize, mParentCommands, mDestroyedActors,
                            GetFwdTransactionId(), aTransactionId,
                            aContentSize, dlData, aDL.dl_desc, aScrollData,
                            resourceUpdates, smallShmems, largeShmems,
-                           mIdNamespace, aContainsSVGGroup, aRefreshStartTime, aTxnStartTime, fwdTime);
+                           mIdNamespace, aContainsSVGGroup, aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime);
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void
 WebRenderBridgeChild::EndEmptyTransaction(const FocusTarget& aFocusTarget,
                                           const ScrollUpdatesMap& aUpdates,
                                           Maybe<wr::IpcResourceUpdateQueue>& aResources,
                                           uint32_t aPaintSequenceNumber,
                                           TransactionId aTransactionId,
                                           const mozilla::TimeStamp& aRefreshStartTime,
-                                          const mozilla::TimeStamp& aTxnStartTime)
+                                          const mozilla::TimeStamp& aTxnStartTime,
+                                          const nsCString& aTxnURL)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
-  TimeStamp fwdTime;
-#if defined(ENABLE_FRAME_LATENCY_LOG)
-  fwdTime = TimeStamp::Now();
-#endif
+  TimeStamp fwdTime = TimeStamp::Now();
 
   nsTArray<OpUpdateResource> resourceUpdates;
   nsTArray<RefCountedShmem> smallShmems;
   nsTArray<ipc::Shmem> largeShmems;
   if (aResources) {
     aResources->Flush(resourceUpdates, smallShmems, largeShmems);
     aResources.reset();
   }
 
   this->SendEmptyTransaction(aFocusTarget, aUpdates, aPaintSequenceNumber,
                              mParentCommands, mDestroyedActors,
                              GetFwdTransactionId(), aTransactionId,
                              resourceUpdates, smallShmems, largeShmems,
-                             mIdNamespace, aRefreshStartTime, aTxnStartTime, fwdTime);
+                             mIdNamespace, aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime);
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void
 WebRenderBridgeChild::ProcessWebRenderParentCommands()
 {
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -72,24 +72,26 @@ public:
   void EndTransaction(const wr::LayoutSize& aContentSize,
                       wr::BuiltDisplayList& dl,
                       wr::IpcResourceUpdateQueue& aResources,
                       const gfx::IntSize& aSize,
                       TransactionId aTransactionId,
                       const WebRenderScrollData& aScrollData,
                       bool aContainsSVGroup,
                       const mozilla::TimeStamp& aRefreshStartTime,
-                      const mozilla::TimeStamp& aTxnStartTime);
+                      const mozilla::TimeStamp& aTxnStartTime,
+                      const nsCString& aTxtURL);
   void EndEmptyTransaction(const FocusTarget& aFocusTarget,
                            const ScrollUpdatesMap& aUpdates,
                            Maybe<wr::IpcResourceUpdateQueue>& aResources,
                            uint32_t aPaintSequenceNumber,
                            TransactionId aTransactionId,
                            const mozilla::TimeStamp& aRefreshStartTime,
-                           const mozilla::TimeStamp& aTxnStartTime);
+                           const mozilla::TimeStamp& aTxnStartTime,
+                           const nsCString& aTxtURL);
   void ProcessWebRenderParentCommands();
 
   CompositorBridgeChild* GetCompositorBridgeChild();
 
   wr::PipelineId GetPipeline() { return mPipelineId; }
 
   // KnowsCompositor
   TextureForwarder* GetTextureForwarder() override;
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -194,24 +194,28 @@ protected:
   RefPtr<CompositorBridgeParentBase> mBridge;
   LayersId mLayersId;
   LayersObserverEpoch mObserverEpoch;
   bool mIsActive;
 };
 
 class SceneBuiltNotification: public wr::NotificationHandler {
 public:
-  explicit SceneBuiltNotification(TimeStamp aTxnStartTime)
-  : mTxnStartTime(aTxnStartTime)
+  explicit SceneBuiltNotification(WebRenderBridgeParent* aParent, wr::Epoch aEpoch, TimeStamp aTxnStartTime)
+  : mParent(aParent)
+  , mEpoch(aEpoch)
+  , mTxnStartTime(aTxnStartTime)
   {}
 
   virtual void Notify(wr::Checkpoint) override {
     auto startTime = this->mTxnStartTime;
+    RefPtr<WebRenderBridgeParent> parent = mParent;
+    wr::Epoch epoch = mEpoch;
     CompositorThreadHolder::Loop()->PostTask(
-      NS_NewRunnableFunction("SceneBuiltNotificationRunnable", [startTime]() {
+      NS_NewRunnableFunction("SceneBuiltNotificationRunnable", [parent, epoch, startTime]() {
         auto endTime = TimeStamp::Now();
 #ifdef MOZ_GECKO_PROFILER
         if (profiler_is_active()) {
           class ContentFullPaintPayload : public ProfilerMarkerPayload
           {
           public:
             ContentFullPaintPayload(const mozilla::TimeStamp& aStartTime,
                                     const mozilla::TimeStamp& aEndTime)
@@ -231,19 +235,22 @@ public:
 
           profiler_add_marker_for_thread(profiler_current_thread_id(),
                                          "CONTENT_FULL_PAINT_TIME",
                                          MakeUnique<ContentFullPaintPayload>(startTime, endTime));
         }
 #endif
         Telemetry::Accumulate(Telemetry::CONTENT_FULL_PAINT_TIME,
                               static_cast<uint32_t>((endTime - startTime).ToMilliseconds()));
+        parent->NotifySceneBuiltForEpoch(epoch, endTime);
       }));
   }
 protected:
+  RefPtr<WebRenderBridgeParent> mParent;
+  wr::Epoch mEpoch;
   TimeStamp mTxnStartTime;
 };
 
 
 class WebRenderBridgeParent::ScheduleSharedSurfaceRelease final
   : public wr::NotificationHandler
 {
 public:
@@ -896,16 +903,17 @@ WebRenderBridgeParent::RecvSetDisplayLis
                                           const WebRenderScrollData& aScrollData,
                                           nsTArray<OpUpdateResource>&& aResourceUpdates,
                                           nsTArray<RefCountedShmem>&& aSmallShmems,
                                           nsTArray<ipc::Shmem>&& aLargeShmems,
                                           const wr::IdNamespace& aIdNamespace,
                                           const bool& aContainsSVGGroup,
                                           const TimeStamp& aRefreshStartTime,
                                           const TimeStamp& aTxnStartTime,
+                                          const nsCString& aTxnURL,
                                           const TimeStamp& aFwdTime)
 {
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
     }
     return IPC_OK();
   }
@@ -980,38 +988,40 @@ WebRenderBridgeParent::RecvSetDisplayLis
           true
         )
       );
     }
 
     txn.Notify(
       wr::Checkpoint::SceneBuilt,
       MakeUnique<SceneBuiltNotification>(
+        this,
+        wrEpoch,
         aTxnStartTime
       )
     );
 
     mApi->SendTransaction(txn);
 
     // We will schedule generating a frame after the scene
     // build is done, so we don't need to do it here.
   } else if (observeLayersUpdate) {
     mCompositorBridge->ObserveLayersUpdate(GetLayersId(), mChildLayersObserverEpoch, true);
   }
 
   HoldPendingTransactionId(wrEpoch, aTransactionId, aContainsSVGGroup,
-                           aRefreshStartTime, aTxnStartTime, aFwdTime, mIsFirstPaint);
+                           aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime, mIsFirstPaint);
   mIsFirstPaint = false;
 
   if (!validTransaction) {
     // Pretend we composited since someone is wating for this event,
     // though DisplayList was not pushed to webrender.
     if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
       TimeStamp now = TimeStamp::Now();
-      cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, now, now);
+      cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, now, now, now);
     }
   }
 
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
   return IPC_OK();
 }
 
@@ -1024,16 +1034,17 @@ WebRenderBridgeParent::RecvEmptyTransact
                                             const uint64_t& aFwdTransactionId,
                                             const TransactionId& aTransactionId,
                                             nsTArray<OpUpdateResource>&& aResourceUpdates,
                                             nsTArray<RefCountedShmem>&& aSmallShmems,
                                             nsTArray<ipc::Shmem>&& aLargeShmems,
                                             const wr::IdNamespace& aIdNamespace,
                                             const TimeStamp& aRefreshStartTime,
                                             const TimeStamp& aTxnStartTime,
+                                            const nsCString& aTxnURL,
                                             const TimeStamp& aFwdTime)
 {
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
     }
     return IPC_OK();
   }
@@ -1107,29 +1118,30 @@ WebRenderBridgeParent::RecvEmptyTransact
 
   // Only register a value for CONTENT_FRAME_TIME telemetry if we actually drew
   // something. It is for consistency with disabling WebRender.
   HoldPendingTransactionId(mWrEpoch,
                            aTransactionId,
                            false,
                            aRefreshStartTime,
                            aTxnStartTime,
+                           aTxnURL,
                            aFwdTime,
                            /* aIsFirstPaint */false,
                            /* aUseForTelemetry */scheduleComposite);
 
   if (scheduleComposite) {
     ScheduleGenerateFrame();
   } else if (sendDidComposite) {
     // The only thing in the pending transaction id queue should be the entry
     // we just added, and now we're going to pretend we rendered it
     MOZ_ASSERT(mPendingTransactionIds.size() == 1);
     if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
       TimeStamp now = TimeStamp::Now();
-      cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, now, now);
+      cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, now, now, now);
     }
   }
 
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
   return IPC_OK();
 }
 
@@ -1747,16 +1759,24 @@ WebRenderBridgeParent::CompositeToTarget
     mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
   if (wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
     // Render thread is busy, try next time.
     mCompositorScheduler->ScheduleComposition();
     mPreviousFrameTimeStamp = TimeStamp();
+
+    // Record that we skipped presenting a frame for
+    // all pending transactions that have finished scene building.
+    for (auto& id : mPendingTransactionIds) {
+      if (id.mSceneBuiltTime) {
+        id.mSkippedComposites++;
+      }
+    }
     return;
   }
   MaybeGenerateFrame(/* aForceGenerateFrame */ false);
 }
 
 TimeDuration
 WebRenderBridgeParent::GetVsyncInterval() const
 {
@@ -1832,45 +1852,62 @@ WebRenderBridgeParent::MaybeGenerateFram
 }
 
 void
 WebRenderBridgeParent::HoldPendingTransactionId(const wr::Epoch& aWrEpoch,
                                                 TransactionId aTransactionId,
                                                 bool aContainsSVGGroup,
                                                 const TimeStamp& aRefreshStartTime,
                                                 const TimeStamp& aTxnStartTime,
+                                                const nsCString& aTxnURL,
                                                 const TimeStamp& aFwdTime,
                                                 const bool aIsFirstPaint,
                                                 const bool aUseForTelemetry)
 {
   MOZ_ASSERT(aTransactionId > LastPendingTransactionId());
-  mPendingTransactionIds.push(PendingTransactionId(aWrEpoch,
-                                                   aTransactionId,
-                                                   aContainsSVGGroup,
-                                                   aRefreshStartTime,
-                                                   aTxnStartTime,
-                                                   aFwdTime,
-                                                   aIsFirstPaint,
-                                                   aUseForTelemetry));
+  mPendingTransactionIds.push_back(PendingTransactionId(aWrEpoch,
+                                                        aTransactionId,
+                                                        aContainsSVGGroup,
+                                                        aRefreshStartTime,
+                                                        aTxnStartTime,
+                                                        aTxnURL,
+                                                        aFwdTime,
+                                                        aIsFirstPaint,
+                                                        aUseForTelemetry));
 }
 
 TransactionId
 WebRenderBridgeParent::LastPendingTransactionId()
 {
   TransactionId id{0};
   if (!mPendingTransactionIds.empty()) {
     id = mPendingTransactionIds.back().mId;
   }
   return id;
 }
 
+void
+WebRenderBridgeParent::NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime)
+{
+  for (auto& id : mPendingTransactionIds) {
+    if (id.mEpoch.mHandle == aEpoch.mHandle) {
+      id.mSceneBuiltTime = aEndTime;
+      break;
+    }
+  }
+}
+
 TransactionId
-WebRenderBridgeParent::FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime,
+WebRenderBridgeParent::FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch,
+                                                   const TimeStamp& aCompositeStartTime,
+                                                   const TimeStamp& aRenderStartTime,
+                                                   const TimeStamp& aEndTime,
                                                    UiCompositorControllerParent* aUiController,
-                                                   wr::RendererStats* aStats)
+                                                   wr::RendererStats* aStats,
+                                                   nsTArray<FrameStats>* aOutputStats)
 {
   TransactionId id{0};
   while (!mPendingTransactionIds.empty()) {
     const auto& transactionId = mPendingTransactionIds.front();
 
     if (aEpoch.mHandle < transactionId.mEpoch.mHandle) {
       break;
     }
@@ -1886,21 +1923,37 @@ WebRenderBridgeParent::FlushTransactionI
           public:
             ContentFramePayload(const mozilla::TimeStamp& aStartTime, const mozilla::TimeStamp& aEndTime)
               : ProfilerMarkerPayload(aStartTime, aEndTime)
             {}
             virtual void StreamPayload(SpliceableJSONWriter& aWriter, const TimeStamp& aProcessStartTime, UniqueStacks& aUniqueStacks) override {
               StreamCommonProps("CONTENT_FRAME_TIME", aWriter, aProcessStartTime, aUniqueStacks);
             }
         };
-        profiler_add_marker_for_thread(profiler_current_thread_id(), "CONTENT_FRAME_TIME", MakeUnique<ContentFramePayload>(mPendingTransactionIds.front().mTxnStartTime,
+        profiler_add_marker_for_thread(profiler_current_thread_id(), "CONTENT_FRAME_TIME", MakeUnique<ContentFramePayload>(transactionId.mTxnStartTime,
                                                                                                                            aEndTime));
       }
 #endif
 
+      if (fracLatencyNorm > 200) {
+        aOutputStats->AppendElement(FrameStats(transactionId.mId,
+                                               aCompositeStartTime,
+                                               aRenderStartTime,
+                                               aEndTime,
+                                               fracLatencyNorm,
+                                               aStats ? (double(aStats->resource_upload_time) / 1000000.0) : 0.0,
+                                               aStats ? (double(aStats->gpu_cache_upload_time) / 1000000.0) : 0.0,
+                                               transactionId.mTxnStartTime,
+                                               transactionId.mRefreshStartTime,
+                                               transactionId.mFwdTime,
+                                               transactionId.mSceneBuiltTime,
+                                               transactionId.mSkippedComposites,
+                                               transactionId.mTxnURL));
+      }
+
       Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME, fracLatencyNorm);
       if (fracLatencyNorm > 200) {
         wr::RenderThread::Get()->NotifySlowFrame(mApi->GetId());
       }
       if (transactionId.mContainsSVGGroup) {
         Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITH_SVG, fracLatencyNorm);
       }
 
@@ -1930,17 +1983,17 @@ WebRenderBridgeParent::FlushTransactionI
     }
 #endif
 
     if (aUiController && transactionId.mIsFirstPaint) {
       aUiController->NotifyFirstPaint();
     }
 
     id = transactionId.mId;
-    mPendingTransactionIds.pop();
+    mPendingTransactionIds.pop_front();
   }
   return id;
 }
 
 LayersId
 WebRenderBridgeParent::GetLayersId() const
 {
   return wr::AsLayersId(mPipelineId);
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -93,30 +93,32 @@ public:
                                              const WebRenderScrollData& aScrollData,
                                              nsTArray<OpUpdateResource>&& aResourceUpdates,
                                              nsTArray<RefCountedShmem>&& aSmallShmems,
                                              nsTArray<ipc::Shmem>&& aLargeShmems,
                                              const wr::IdNamespace& aIdNamespace,
                                              const bool& aContainsSVGGroup,
                                              const TimeStamp& aRefreshStartTime,
                                              const TimeStamp& aTxnStartTime,
+                                             const nsCString& aTxnURL,
                                              const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvEmptyTransaction(const FocusTarget& aFocusTarget,
                                                const ScrollUpdatesMap& aUpdates,
                                                const uint32_t& aPaintSequenceNumber,
                                                InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                                InfallibleTArray<OpDestroy>&& aToDestroy,
                                                const uint64_t& aFwdTransactionId,
                                                const TransactionId& aTransactionId,
                                                nsTArray<OpUpdateResource>&& aResourceUpdates,
                                                nsTArray<RefCountedShmem>&& aSmallShmems,
                                                nsTArray<ipc::Shmem>&& aLargeShmems,
                                                const wr::IdNamespace& aIdNamespace,
                                                const TimeStamp& aRefreshStartTime,
                                                const TimeStamp& aTxnStartTime,
+                                               const nsCString& aTxnURL,
                                                const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvSetFocusTarget(const FocusTarget& aFocusTarget) override;
   mozilla::ipc::IPCResult RecvParentCommands(nsTArray<WebRenderParentCommand>&& commands) override;
   mozilla::ipc::IPCResult RecvGetSnapshot(PTextureParent* aTexture) override;
 
   mozilla::ipc::IPCResult RecvSetLayersObserverEpoch(const LayersObserverEpoch& aChildEpoch) override;
 
   mozilla::ipc::IPCResult RecvClearCachedResources() override;
@@ -160,23 +162,29 @@ public:
   void SendPendingAsyncMessages() override;
   void SetAboutToSendAsyncMessages() override;
 
   void HoldPendingTransactionId(const wr::Epoch& aWrEpoch,
                                 TransactionId aTransactionId,
                                 bool aContainsSVGGroup,
                                 const TimeStamp& aRefreshStartTime,
                                 const TimeStamp& aTxnStartTime,
+                                const nsCString& aTxnURL,
                                 const TimeStamp& aFwdTime,
                                 const bool aIsFirstPaint,
                                 const bool aUseForTelemetry = true);
   TransactionId LastPendingTransactionId();
-  TransactionId FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime,
+  TransactionId FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch,
+                                            const TimeStamp& aCompositeStartTime,
+                                            const TimeStamp& aRenderStartTime,
+                                            const TimeStamp& aEndTime,
                                             UiCompositorControllerParent* aUiController,
-                                            wr::RendererStats* aStats = nullptr);
+                                            wr::RendererStats* aStats = nullptr,
+                                            nsTArray<FrameStats>* aOutputStats = nullptr);
+  void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime);
 
   TextureFactoryIdentifier GetTextureFactoryIdentifier();
 
   void ExtractImageCompositeNotifications(nsTArray<ImageCompositeNotificationInfo>* aNotifications);
 
   wr::Epoch GetCurrentEpoch() const { return mWrEpoch; }
   wr::IdNamespace GetIdNamespace()
   {
@@ -296,33 +304,39 @@ private:
 
 private:
   struct PendingTransactionId {
     PendingTransactionId(const wr::Epoch& aEpoch,
                          TransactionId aId,
                          bool aContainsSVGGroup,
                          const TimeStamp& aRefreshStartTime,
                          const TimeStamp& aTxnStartTime,
+                         const nsCString& aTxnURL,
                          const TimeStamp& aFwdTime,
                          const bool aIsFirstPaint,
                          const bool aUseForTelemetry)
       : mEpoch(aEpoch)
       , mId(aId)
       , mRefreshStartTime(aRefreshStartTime)
       , mTxnStartTime(aTxnStartTime)
+      , mTxnURL(aTxnURL)
       , mFwdTime(aFwdTime)
+      , mSkippedComposites(0)
       , mContainsSVGGroup(aContainsSVGGroup)
       , mIsFirstPaint(aIsFirstPaint)
       , mUseForTelemetry(aUseForTelemetry)
     {}
     wr::Epoch mEpoch;
     TransactionId mId;
     TimeStamp mRefreshStartTime;
     TimeStamp mTxnStartTime;
+    nsCString mTxnURL;
     TimeStamp mFwdTime;
+    TimeStamp mSceneBuiltTime;
+    uint32_t mSkippedComposites;
     bool mContainsSVGGroup;
     bool mIsFirstPaint;
     bool mUseForTelemetry;
   };
 
   struct CompositorAnimationIdsForEpoch {
     CompositorAnimationIdsForEpoch(const wr::Epoch& aEpoch, InfallibleTArray<uint64_t>&& aIds)
       : mEpoch(aEpoch)
@@ -352,17 +366,17 @@ private:
   TimeStamp mPreviousFrameTimeStamp;
   // These fields keep track of the latest layer observer epoch values in the child and the
   // parent. mChildLayersObserverEpoch is the latest epoch value received from the child.
   // mParentLayersObserverEpoch is the latest epoch value that we have told TabParent about
   // (via ObserveLayerUpdate).
   LayersObserverEpoch mChildLayersObserverEpoch;
   LayersObserverEpoch mParentLayersObserverEpoch;
 
-  std::queue<PendingTransactionId> mPendingTransactionIds;
+  std::deque<PendingTransactionId> mPendingTransactionIds;
   std::queue<CompositorAnimationIdsForEpoch> mCompositorAnimationsToDelete;
   wr::Epoch mWrEpoch;
   wr::IdNamespace mIdNamespace;
 
   bool mPaused;
   bool mDestroyed;
   bool mReceivedDisplayList;
   bool mIsFirstPaint;
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -1733,17 +1733,18 @@ PaintByLayer(nsDisplayItem* aItem,
   if (aManager->GetRoot()) {
     props = LayerProperties::CloneFrom(aManager->GetRoot());
   }
   FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
   layerBuilder->Init(aDisplayListBuilder, aManager, nullptr, true);
   layerBuilder->DidBeginRetainedLayerTransaction(aManager);
 
   aManager->SetDefaultTarget(aContext);
-  aManager->BeginTransactionWithTarget(aContext);
+  nsCString none;
+  aManager->BeginTransactionWithTarget(aContext, none);
   bool isInvalidated = false;
 
   ContainerLayerParameters param(aScale.width, aScale.height);
   RefPtr<Layer> root = aItem->BuildLayer(aDisplayListBuilder, aManager, param);
 
   if (root) {
     aManager->SetRoot(root);
     layerBuilder->WillEndTransaction();
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -171,31 +171,32 @@ WebRenderLayerManager::StopFrameTimeReco
 {
   CompositorBridgeChild* renderer = GetCompositorBridgeChild();
   if (renderer) {
     renderer->SendStopFrameTimeRecording(aStartIndex, &aFrameIntervals);
   }
 }
 
 bool
-WebRenderLayerManager::BeginTransactionWithTarget(gfxContext* aTarget)
+WebRenderLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, const nsCString &aURL)
 {
   mTarget = aTarget;
-  return BeginTransaction();
+  return BeginTransaction(aURL);
 }
 
 bool
-WebRenderLayerManager::BeginTransaction()
+WebRenderLayerManager::BeginTransaction(const nsCString &aURL)
 {
   if (!WrBridge()->IPCOpen()) {
     gfxCriticalNote << "IPC Channel is already torn down unexpectedly\n";
     return false;
   }
 
   mTransactionStart = TimeStamp::Now();
+  mURL = aURL;
 
   // Increment the paint sequence number even if test logging isn't
   // enabled in this process; it may be enabled in the parent process,
   // and the parent process expects unique sequence numbers.
   ++mPaintSequenceNumber;
   if (gfxPrefs::APZTestLoggingEnabled()) {
     mApzTestData.StartNewPaint(mPaintSequenceNumber);
   }
@@ -230,30 +231,35 @@ WebRenderLayerManager::EndEmptyTransacti
     return true;
   }
 
   LayoutDeviceIntSize size = mWidget->GetClientSize();
   WrBridge()->BeginTransaction();
 
   mWebRenderCommandBuilder.EmptyTransaction();
 
+  // Get the time of when the refresh driver start its tick (if available), otherwise
+  // use the time of when LayerManager::BeginTransaction was called.
   TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
+  if (!refreshStart) {
+    refreshStart = mTransactionStart;
+  }
 
   // Skip the synchronization for buffer since we also skip the painting during
   // device-reset status.
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (WrBridge()->GetSyncObject() &&
         WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
       WrBridge()->GetSyncObject()->Synchronize();
     }
   }
 
   WrBridge()->EndEmptyTransaction(mFocusTarget, mPendingScrollUpdates,
       mAsyncResourceUpdates, mPaintSequenceNumber, mLatestTransactionId,
-      refreshStart, mTransactionStart);
+      refreshStart, mTransactionStart, mURL);
   ClearPendingScrollInfoUpdate();
 
   mTransactionStart = TimeStamp();
 
   MakeSnapshotIfRequired(size);
   return true;
 }
 
@@ -336,17 +342,23 @@ WebRenderLayerManager::EndTransactionWit
     mScrollData.SetPaintSequenceNumber(mPaintSequenceNumber);
   }
   // Since we're sending a full mScrollData that will include the new scroll
   // offsets, and we can throw away the pending scroll updates we had kept for
   // an empty transaction.
   ClearPendingScrollInfoUpdate();
 
   mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true);
+
+  // Get the time of when the refresh driver start its tick (if available), otherwise
+  // use the time of when LayerManager::BeginTransaction was called.
   TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
+  if (!refreshStart) {
+    refreshStart = mTransactionStart;
+  }
 
   if (mAsyncResourceUpdates) {
     if (resourceUpdates.IsEmpty()) {
       resourceUpdates = std::move(mAsyncResourceUpdates.ref());
     } else {
       // If we can't just swap the queue, we need to take the slow path and
       // send the update as a separate message. We don't need to schedule a
       // composite however because that will happen with EndTransaction.
@@ -375,17 +387,17 @@ WebRenderLayerManager::EndTransactionWit
   wr::BuiltDisplayList dl;
   builder.Finalize(contentSize, dl);
   mLastDisplayListSize = dl.dl.inner.capacity;
 
   {
     AUTO_PROFILER_TRACING("Paint", "ForwardDPTransaction");
     WrBridge()->EndTransaction(contentSize, dl, resourceUpdates, size.ToUnknownSize(),
                                mLatestTransactionId, mScrollData, containsSVGGroup,
-                               refreshStart, mTransactionStart);
+                               refreshStart, mTransactionStart, mURL);
   }
 
   mTransactionStart = TimeStamp();
 
   MakeSnapshotIfRequired(size);
   mNeedsComposite = false;
 }
 
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -63,18 +63,18 @@ protected:
 public:
   virtual KnowsCompositor* AsKnowsCompositor() override;
   WebRenderLayerManager* AsWebRenderLayerManager() override { return this; }
   virtual CompositorBridgeChild* GetCompositorBridgeChild() override;
 
   // WebRender can handle images larger than the max texture size via tiling.
   virtual int32_t GetMaxTextureSize() const override { return INT32_MAX; }
 
-  virtual bool BeginTransactionWithTarget(gfxContext* aTarget) override;
-  virtual bool BeginTransaction() override;
+  virtual bool BeginTransactionWithTarget(gfxContext* aTarget, const nsCString &aURL) override;
+  virtual bool BeginTransaction(const nsCString &aURL) override;
   virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override;
   void EndTransactionWithoutLayer(nsDisplayList* aDisplayList,
                                   nsDisplayListBuilder* aDisplayListBuilder,
                                   const nsTArray<wr::WrFilterOp>& aFilters = nsTArray<wr::WrFilterOp>(),
                                   WebRenderBackgroundData* aBackground = nullptr);
   virtual void EndTransaction(DrawPaintedLayerCallback aCallback,
                               void* aCallbackData,
                               EndTransactionFlags aFlags = END_DEFAULT) override;
@@ -216,16 +216,17 @@ private:
   RefPtr<gfxContext> mTarget;
 
   // See equivalent field in ClientLayerManager
   uint32_t mPaintSequenceNumber;
   // See equivalent field in ClientLayerManager
   APZTestData mApzTestData;
 
   TimeStamp mTransactionStart;
+  nsCString mURL;
   WebRenderCommandBuilder mWebRenderCommandBuilder;
 
   size_t mLastDisplayListSize;
 
   Maybe<wr::IpcResourceUpdateQueue> mAsyncResourceUpdates;
 };
 
 } // namespace layers
--- a/gfx/tests/gtest/TestLayers.cpp
+++ b/gfx/tests/gtest/TestLayers.cpp
@@ -60,29 +60,29 @@ public:
 
   virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) { return false; }
   virtual already_AddRefed<ContainerLayer> CreateContainerLayer() {
     RefPtr<ContainerLayer> layer = new TestContainerLayer(this);
     return layer.forget();
   }
   virtual void GetBackendName(nsAString& aName) {}
   virtual LayersBackend GetBackendType() { return LayersBackend::LAYERS_BASIC; }
-  virtual bool BeginTransaction() { return true; }
+  virtual bool BeginTransaction(const nsCString& = nsCString()) { return true; }
   virtual already_AddRefed<ImageLayer> CreateImageLayer() {
     MOZ_CRASH("Not implemented.");
   }
   virtual already_AddRefed<PaintedLayer> CreatePaintedLayer() {
     RefPtr<PaintedLayer> layer = new TestPaintedLayer(this);
     return layer.forget();
   }
   virtual already_AddRefed<ColorLayer> CreateColorLayer() {
     MOZ_CRASH("Not implemented.");
   }
   virtual void SetRoot(Layer* aLayer) {}
-  virtual bool BeginTransactionWithTarget(gfxContext* aTarget) { return true; }
+  virtual bool BeginTransactionWithTarget(gfxContext* aTarget, const nsCString& = nsCString()) { return true; }
   virtual already_AddRefed<CanvasLayer> CreateCanvasLayer() {
     MOZ_CRASH("Not implemented.");
   }
   virtual void EndTransaction(DrawPaintedLayerCallback aCallback,
                               void* aCallbackData,
                               EndTransactionFlags aFlags = END_DEFAULT) {}
   virtual int32_t GetMaxTextureSize() const { return 0; }
 };
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -296,16 +296,35 @@ gfxContext::Rectangle(const gfxRect& rec
 
   mPathBuilder->MoveTo(rec.TopLeft());
   mPathBuilder->LineTo(rec.TopRight());
   mPathBuilder->LineTo(rec.BottomRight());
   mPathBuilder->LineTo(rec.BottomLeft());
   mPathBuilder->Close();
 }
 
+void
+gfxContext::SnappedClip(const gfxRect& rect)
+{
+  Rect rec = ToRect(rect);
+
+  gfxRect newRect(rect);
+  if (UserToDevicePixelSnapped(newRect, true)) {
+    gfxMatrix mat = ThebesMatrix(mTransform);
+    if (mat.Invert()) {
+      // We need the user space rect.
+      rec = ToRect(mat.TransformBounds(newRect));
+    } else {
+      rec = Rect();
+    }
+  }
+
+  Clip(rec);
+}
+
 // transform stuff
 void
 gfxContext::Multiply(const gfxMatrix& matrix)
 {
   CURRENTSTATE_CHANGED()
   ChangeTransform(ToMatrix(matrix) * mTransform);
 }
 
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -369,16 +369,17 @@ public:
     void Clip();
 
     /**
      * Helper functions that will create a rect path and call Clip().
      * Any current path will be destroyed by these functions!
      */
     void Clip(const Rect& rect);
     void Clip(const gfxRect& rect); // will clip to a rect
+    void SnappedClip(const gfxRect& rect); // snap rect and clip to the result
     void Clip(Path* aPath);
 
     void PopClip();
 
     enum ClipExtentsSpace {
         eUserSpace = 0,
         eDeviceSpace = 1,
     };
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -5,27 +5,29 @@
 
 #include "mozilla/FontPropertyTypes.h"
 #include "mozilla/RDDProcessManager.h"
 #include "mozilla/image/ImageMemoryReporter.h"
 #include "mozilla/layers/CompositorManagerChild.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/ISurfaceAllocator.h"     // for GfxMemoryImageReporter
+#include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/layers/PaintThread.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/gfx/GraphicsMessages.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
+#include "mozilla/IntegerPrintfMacros.h"
 
 #include "mozilla/Logging.h"
 #include "mozilla/Services.h"
 #include "nsAppDirectoryServiceDefs.h"
 
 #include "gfxCrashReporterUtils.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
@@ -485,16 +487,17 @@ gfxPlatform::OnMemoryPressure(layers::Me
     }
 }
 
 gfxPlatform::gfxPlatform()
   : mHasVariationFontSupport(false)
   , mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
   , mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
   , mTilesInfoCollector(this, &gfxPlatform::GetTilesSupportInfo)
+  , mFrameStatsCollector(this, &gfxPlatform::GetFrameStats)
   , mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
   , mScreenDepth(0)
 {
     mAllowDownloadableFonts = UNINITIALIZED_VALUE;
     mFallbackUsesCmaps = UNINITIALIZED_VALUE;
 
     mWordCacheCharLimit = UNINITIALIZED_VALUE;
     mWordCacheMaxEntries = UNINITIALIZED_VALUE;
@@ -3151,16 +3154,64 @@ gfxPlatform::GetTilesSupportInfo(mozilla
     return;
   }
 
   IntSize tileSize = gfxVars::TileSize();
   aObj.DefineProperty("TileHeight", tileSize.height);
   aObj.DefineProperty("TileWidth", tileSize.width);
 }
 
+void
+gfxPlatform::GetFrameStats(mozilla::widget::InfoObject& aObj)
+{
+  uint32_t i = 0;
+  for (FrameStats& f : mFrameStats) {
+    nsPrintfCString name("Slow Frame #%02u", ++i);
+
+    nsPrintfCString value("Frame %" PRIu64 "(%s) CONTENT_FRAME_TIME %d - Transaction start %f, main-thread time %f, full paint time %f, Skipped composites %u, Composite start %f, Resource upload time %f, GPU cache upload time %f, Render time %f, Composite time %f",
+                          f.id().mId,
+                          f.url().get(),
+                          f.contentFrameTime(),
+                          (f.transactionStart() - f.refreshStart()).ToMilliseconds(),
+                          (f.fwdTime() - f.transactionStart()).ToMilliseconds(),
+                          f.sceneBuiltTime() ? (f.sceneBuiltTime() - f.transactionStart()).ToMilliseconds() : 0.0,
+                          f.skippedComposites(),
+                          (f.compositeStart() - f.refreshStart()).ToMilliseconds(),
+                          f.resourceUploadTime(),
+                          f.gpuCacheUploadTime(),
+                          (f.compositeEnd() - f.renderStart()).ToMilliseconds(),
+                          (f.compositeEnd() - f.compositeStart()).ToMilliseconds());
+    aObj.DefineProperty(name.get(), value.get());
+  }
+}
+
+class FrameStatsComparator
+{
+public:
+  bool Equals(const FrameStats& aA, const FrameStats& aB) const {
+    return aA.contentFrameTime() == aB.contentFrameTime();
+  }
+  // Reverse the condition here since we want the array sorted largest to smallest.
+  bool LessThan(const FrameStats& aA, const FrameStats& aB) const {
+    return aA.contentFrameTime() > aB.contentFrameTime();
+  }
+};
+
+void
+gfxPlatform::NotifyFrameStats(nsTArray<FrameStats>&& aFrameStats)
+{
+  FrameStatsComparator comp;
+  for (FrameStats& f : aFrameStats) {
+    mFrameStats.InsertElementSorted(f, comp);
+  }
+  if (mFrameStats.Length() > 10) {
+    mFrameStats.SetLength(10);
+  }
+}
+
 /*static*/ bool
 gfxPlatform::AsyncPanZoomEnabled()
 {
 #if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_UIKIT)
   // For XUL applications (everything but Firefox on Android)
   // we only want to use APZ when E10S is enabled. If
   // we ever get input events off the main thread we can consider relaxing
   // this requirement.
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -42,16 +42,19 @@ class nsIObserver;
 class SRGBOverrideObserver;
 class gfxTextPerfMetrics;
 typedef struct FT_LibraryRec_ *FT_Library;
 
 namespace mozilla {
 namespace gl {
 class SkiaGLGlue;
 } // namespace gl
+namespace layers {
+class FrameStats;
+}
 namespace gfx {
 class DrawTarget;
 class SourceSurface;
 class DataSourceSurface;
 class ScaledFont;
 class DrawEventRecorder;
 class VsyncSource;
 class ContentDeviceData;
@@ -294,16 +297,17 @@ public:
     virtual bool AllowOpenGLCanvas();
     virtual void InitializeSkiaCacheLimits();
 
     static bool AsyncPanZoomEnabled();
 
     virtual void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj);
     void GetApzSupportInfo(mozilla::widget::InfoObject& aObj);
     void GetTilesSupportInfo(mozilla::widget::InfoObject& aObj);
+    void GetFrameStats(mozilla::widget::InfoObject& aObj);
 
     // Get the default content backend that will be used with the default
     // compositor. If the compositor is known when calling this function,
     // GetContentBackendFor() should be called instead.
     mozilla::gfx::BackendType GetDefaultContentBackend() const {
       return mContentBackend;
     }
 
@@ -743,16 +747,18 @@ public:
       return mHasNativeColrFontSupport;
     }
 
     // you probably want to use gfxVars::UseWebRender() instead of this
     static bool WebRenderPrefEnabled();
     // you probably want to use gfxVars::UseWebRender() instead of this
     static bool WebRenderEnvvarEnabled();
 
+    void NotifyFrameStats(nsTArray<mozilla::layers::FrameStats>&& aFrameStats);
+
     virtual void
     OnMemoryPressure(mozilla::layers::MemoryPressureReason aWhy) override;
 protected:
     gfxPlatform();
     virtual ~gfxPlatform();
 
     virtual bool HasBattery() {
       return true;
@@ -908,16 +914,19 @@ private:
     // The backend to use when we need it not to be accelerated.
     mozilla::gfx::BackendType mSoftwareBackend;
     // Bitmask of backend types we can use to render content
     uint32_t mContentBackendBitmask;
 
     mozilla::widget::GfxInfoCollector<gfxPlatform> mAzureCanvasBackendCollector;
     mozilla::widget::GfxInfoCollector<gfxPlatform> mApzSupportCollector;
     mozilla::widget::GfxInfoCollector<gfxPlatform> mTilesInfoCollector;
+    mozilla::widget::GfxInfoCollector<gfxPlatform> mFrameStatsCollector;
+
+    nsTArray<mozilla::layers::FrameStats> mFrameStats;
 
     RefPtr<mozilla::gfx::DrawEventRecorder> mRecorder;
     RefPtr<mozilla::gl::SkiaGLGlue> mSkiaGlue;
 
     // Backend that we are compositing with. NONE, if no compositor has been
     // created yet.
     mozilla::layers::LayersBackend mCompositorBackend;
 
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -556,22 +556,20 @@ struct MOZ_STACK_CLASS BufferAlphaColor 
 
     }
 
     ~BufferAlphaColor() {}
 
     void PushSolidColor(const gfxRect& aBounds, const Color& aAlphaColor, uint32_t appsPerDevUnit)
     {
         mContext->Save();
-        mContext->NewPath();
-        mContext->SnappedRectangle(gfxRect(aBounds.X() / appsPerDevUnit,
+        mContext->SnappedClip(gfxRect(aBounds.X() / appsPerDevUnit,
                     aBounds.Y() / appsPerDevUnit,
                     aBounds.Width() / appsPerDevUnit,
                     aBounds.Height() / appsPerDevUnit));
-        mContext->Clip();
         mContext->SetColor(Color(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b));
         mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a);
     }
 
     void PopAlpha()
     {
         // pop the text, using the color alpha as the opacity
         mContext->PopGroupAndBlend();
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -337,17 +337,18 @@ RenderThread::RunEvent(wr::WindowId aWin
 
   aEvent->Run(*this, aWindowId);
   aEvent = nullptr;
 }
 
 static void
 NotifyDidRender(layers::CompositorBridgeParent* aBridge,
                 RefPtr<WebRenderPipelineInfo> aInfo,
-                TimeStamp aStart,
+                TimeStamp aCompositeStart,
+                TimeStamp aRenderStart,
                 TimeStamp aEnd,
                 bool aRender,
                 RendererStats aStats)
 {
   if (aRender && aBridge->GetWrBridge()) {
     // We call this here to mimic the behavior in LayerManagerComposite, as to
     // not change what Talos measures. That is, we do not record an empty frame
     // as a frame.
@@ -355,17 +356,18 @@ NotifyDidRender(layers::CompositorBridge
   }
 
   auto info = aInfo->Raw();
 
   for (uintptr_t i = 0; i < info.epochs.length; i++) {
     aBridge->NotifyPipelineRendered(
         info.epochs.data[i].pipeline_id,
         info.epochs.data[i].epoch,
-        aStart,
+        aCompositeStart,
+        aRenderStart,
         aEnd,
         &aStats);
   }
 }
 
 void
 RenderThread::UpdateAndRender(wr::WindowId aWindowId,
                               const TimeStamp& aStartTime,
@@ -379,16 +381,18 @@ RenderThread::UpdateAndRender(wr::Window
   MOZ_ASSERT(aRender || aReadbackBuffer.isNothing());
 
   auto it = mRenderers.find(aWindowId);
   MOZ_ASSERT(it != mRenderers.end());
   if (it == mRenderers.end()) {
     return;
   }
 
+  TimeStamp start = TimeStamp::Now();
+
   auto& renderer = it->second;
   bool rendered = false;
   RendererStats stats = { 0 };
   if (aRender) {
     rendered = renderer->UpdateAndRender(aReadbackSize, aReadbackBuffer, aHadSlowFrame, &stats);
   } else {
     renderer->Update();
   }
@@ -398,17 +402,17 @@ RenderThread::UpdateAndRender(wr::Window
   TimeStamp end = TimeStamp::Now();
   auto info = renderer->FlushPipelineInfo();
 
   layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
     "NotifyDidRenderRunnable",
     &NotifyDidRender,
     renderer->GetCompositorBridge(),
     info,
-    aStartTime, end,
+    aStartTime, start, end,
     aRender,
     stats
   ));
 
   if (rendered) {
     // Wait for GPU after posting NotifyDidRender, since the wait is not
     // necessary for the NotifyDidRender.
     // The wait is necessary for Textures recycling of AsyncImagePipelineManager
@@ -565,16 +569,17 @@ RenderThread::FrameRenderingComplete(wr:
   WindowInfo* info = it->second;
   MOZ_ASSERT(info->mPendingCount > 0);
   MOZ_ASSERT(info->mRenderingCount > 0);
   if (info->mPendingCount <= 0) {
     return;
   }
   info->mPendingCount--;
   info->mRenderingCount--;
+
   // The start time is from WebRenderBridgeParent::CompositeToTarget. From that
   // point until now (when the frame is finally pushed to the screen) is
   // equivalent to the COMPOSITE_TIME metric in the non-WR codepath.
   mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::COMPOSITE_TIME,
                                           info->mStartTimes.front());
   info->mStartTimes.pop();
 }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testTimeout7.js
@@ -0,0 +1,15 @@
+// |jit-test| exitstatus: 6; skip-if: helperThreadCount() == 0
+
+runOffThreadScript(offThreadCompileScript(`
+    function asmModule() {
+        "use asm";
+        function f() {
+            while(1) {
+            }
+        }
+        return f;
+    }
+`));
+timeout(1);
+asmModule()();
+assertEq(true, false);
--- a/js/src/tests/jstests.py
+++ b/js/src/tests/jstests.py
@@ -198,16 +198,18 @@ def parse_args():
                          help='Do not show the progress bar.')
     output_og.add_option('--tinderbox', dest='format', action='store_const',
                          const='automation',
                          help='Use automation-parseable output format.')
     output_og.add_option('--format', dest='format', default='none',
                          type='choice', choices=['automation', 'none'],
                          help='Output format. Either automation or none'
                          ' (default %default).')
+    output_og.add_option('--log-wptreport', dest='wptreport', action='store',
+                         help='Path to write a Web Platform Tests report (wptreport)')
     op.add_option_group(output_og)
 
     special_og = OptionGroup(op, "Special",
                              "Special modes that do not run tests.")
     special_og.add_option('--make-manifests', metavar='BASE_TEST_PATH',
                           help='Generate reftest manifest files.')
     op.add_option_group(special_og)
     options, args = op.parse_args()
--- a/js/src/tests/lib/results.py
+++ b/js/src/tests/lib/results.py
@@ -52,20 +52,21 @@ class NullTestOutput:
 
 class TestResult:
     PASS = 'PASS'
     FAIL = 'FAIL'
     CRASH = 'CRASH'
 
     """Classified result from a test run."""
 
-    def __init__(self, test, result, results):
+    def __init__(self, test, result, results, wpt_results=None):
         self.test = test
         self.result = result
         self.results = results
+        self.wpt_results = wpt_results  # Only used for wpt tests.
 
     @classmethod
     def from_wpt_output(cls, output):
         """Parse the output from a web-platform test that uses testharness.js.
         (The output is written to stdout in js/src/tests/testharnessreport.js.)
         """
         from wptrunner.executors.base import testharness_result_converter
 
@@ -92,17 +93,19 @@ class TestResult:
             else:
                 harness_status = "ERROR"
                 harness_message = "No harness output found"
                 tests = []
         stdout.append("Harness status: %s (%s)" % (harness_status, harness_message))
 
         result = cls.PASS
         results = []
-        if harness_status != output.test.wpt.expected():
+        subtests = []
+        expected_harness_status = output.test.wpt.expected()
+        if harness_status != expected_harness_status:
             if harness_status == "CRASH":
                 result = cls.CRASH
             else:
                 result = cls.FAIL
         else:
             for test in tests:
                 test_output = "Subtest \"%s\": " % (test.name,)
                 expected = output.test.wpt.expected(test.name)
@@ -110,22 +113,35 @@ class TestResult:
                     test_result = (cls.PASS, "")
                     test_output += "as expected: %s" % (test.status,)
                 else:
                     test_result = (cls.FAIL, test.message)
                     result = cls.FAIL
                     test_output += "expected %s, found %s" % (expected, test.status)
                     if test.message:
                         test_output += " (with message: \"%s\")" % (test.message,)
+                subtests.append({
+                    "test": output.test.wpt.id,
+                    "subtest": test.name,
+                    "status": test.status,
+                    "expected": expected,
+                })
                 results.append(test_result)
                 stdout.append(test_output)
 
         output.out = "\n".join(stdout) + "\n"
 
-        return cls(output.test, result, results)
+        wpt_results = {
+            "name": output.test.wpt.id,
+            "status": harness_status,
+            "expected": expected_harness_status,
+            "subtests": subtests,
+        }
+
+        return cls(output.test, result, results, wpt_results)
 
     @classmethod
     def from_output(cls, output):
         test = output.test
         result = None          # str:      overall result, see class-level variables
         results = []           # (str,str) list: subtest results (pass/fail, message)
 
         if test.wpt:
@@ -184,16 +200,25 @@ class TestDuration:
 class ResultsSink:
     def __init__(self, testsuite, options, testcount):
         self.options = options
         self.fp = options.output_fp
         if self.options.format == 'automation':
             self.slog = TestLogger(testsuite)
             self.slog.suite_start()
 
+        self.wptreport = None
+        if self.options.wptreport:
+            try:
+                from .wptreport import WptreportHandler
+                self.wptreport = WptreportHandler(self.options.wptreport)
+                self.wptreport.suite_start()
+            except ImportError:
+                pass
+
         self.groups = {}
         self.output_dict = {}
         self.counts = {'PASS': 0, 'FAIL': 0, 'TIMEOUT': 0, 'SKIP': 0}
         self.slow_tests = []
         self.n = 0
 
         if options.hide_progress:
             self.pb = NullProgressBar()
@@ -215,16 +240,20 @@ class ResultsSink:
             if self.options.format == 'automation':
                 self.print_automation_result(
                     'TEST-KNOWN-FAIL', output.test, time=output.dt,
                     skip=True)
             self.counts['SKIP'] += 1
             self.n += 1
         else:
             result = TestResult.from_output(output)
+
+            if self.wptreport is not None and result.wpt_results:
+                self.wptreport.test(result.wpt_results, output.dt)
+
             tup = (result.result, result.test.expect, result.test.random)
             dev_label = self.LABELS[tup][1]
 
             if self.options.check_output:
                 if output.test.path in self.output_dict.keys():
                     if self.output_dict[output.test.path] != output:
                         self.counts['FAIL'] += 1
                         self.print_automation_result(
@@ -302,16 +331,19 @@ class ResultsSink:
 
     def finish(self, completed):
         self.pb.finish(completed)
         if self.options.format == 'automation':
             self.slog.suite_end()
         else:
             self.list(completed)
 
+        if self.wptreport is not None:
+            self.wptreport.suite_end()
+
     # Conceptually, this maps (test result x test expection) to text labels.
     #      key   is (result, expect, random)
     #      value is (automation label, dev test category)
     LABELS = {
         (TestResult.CRASH, False, False): ('TEST-UNEXPECTED-FAIL',               'REGRESSIONS'),
         (TestResult.CRASH, False, True):  ('TEST-UNEXPECTED-FAIL',               'REGRESSIONS'),
         (TestResult.CRASH, True,  False): ('TEST-UNEXPECTED-FAIL',               'REGRESSIONS'),
         (TestResult.CRASH, True,  True):  ('TEST-UNEXPECTED-FAIL',               'REGRESSIONS'),
new file mode 100644
--- /dev/null
+++ b/js/src/tests/lib/wptreport.py
@@ -0,0 +1,76 @@
+# 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/.
+
+# Integration between the jstests harness and `WptreportFormatter`.
+#
+# `WptreportFormatter` uses the data format specified in
+# <https://firefox-source-docs.mozilla.org/mozbase/mozlog.html>.
+
+from time import time
+
+from wptrunner.formatters import WptreportFormatter
+
+
+class WptreportHandler(object):
+    def __init__(self, out):
+        """
+        Initialize the WptreportHandler handler.
+
+        :param str out: path to a file to write output to.
+        """
+        self.out = out
+        self.formatter = WptreportFormatter()
+
+    def suite_start(self):
+        """
+        Produce the "suite_start" message at the present time.
+        """
+        self.formatter.suite_start({
+            "time": time(),
+        })
+
+    def suite_end(self):
+        """
+        Produce the "suite_end" message at the present time and write the
+        results to the file path given in the constructor.
+        """
+        result = self.formatter.suite_end({
+            "time": time(),
+        })
+        with open(self.out, "w") as fp:
+            fp.write(result)
+
+    def test(self, result, duration):
+        """
+        Produce the "test_start", "test_status" and "test_end" messages, as
+        appropriate.
+
+        :param dict result: a dictionary with the test results. It should
+                            include the following keys:
+                            * "name": the ID of the test;
+                            * "status": the actual status of the whole test;
+                            * "expected": the expected status of the whole test;
+                            * "subtests": a list of dicts with keys "test",
+                              "subtest", "status" and "expected".
+        :param float duration: the runtime of the test
+        """
+        testname = result["name"]
+
+        end_time = time()
+        start_time = end_time - duration
+
+        self.formatter.test_start({
+            "test": testname,
+            "time": start_time,
+        })
+
+        for result in result["subtests"]:
+            self.formatter.test_status(result)
+
+        self.formatter.test_end({
+            "test": testname,
+            "time": end_time,
+            "status": result["status"],
+            "expected": result["expected"],
+        })
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -6630,16 +6630,22 @@ GetImports(JSContext* cx, const AsmJSMet
 static bool
 TryInstantiate(JSContext* cx, CallArgs args, const Module& module, const AsmJSMetadata& metadata,
                MutableHandleWasmInstanceObject instanceObj, MutableHandleObject exportObj)
 {
     HandleValue globalVal = args.get(0);
     HandleValue importVal = args.get(1);
     HandleValue bufferVal = args.get(2);
 
+    // Re-check HasCompilerSupport(cx) since this varies per-thread and
+    // 'module' may have been produced on a parser thread.
+    if (!HasCompilerSupport(cx)) {
+        return LinkFail(cx, "no compiler support");
+    }
+
     RootedArrayBufferObjectMaybeShared buffer(cx);
     RootedWasmMemoryObject memory(cx);
     if (module.metadata().usesMemory()) {
         if (!CheckBuffer(cx, metadata, bufferVal, &buffer)) {
             return false;
         }
 
         memory = WasmMemoryObject::create(cx, buffer, nullptr);
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -1280,17 +1280,17 @@ Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
                     WasmTableObjectVector& tableImports,
                     HandleWasmMemoryObject memoryImport,
                     HandleValVector globalImportValues,
                     WasmGlobalObjectVector& globalObjs,
                     HandleObject instanceProto,
                     MutableHandleWasmInstanceObject instance) const
 {
-    MOZ_RELEASE_ASSERT(metadata().isAsmJS() || cx->wasmHaveSignalHandlers);
+    MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers);
 
     if (!instantiateFunctions(cx, funcImports)) {
         return false;
     }
 
     RootedWasmMemoryObject memory(cx, memoryImport);
     if (!instantiateMemory(cx, &memory)) {
         return false;
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -6238,25 +6238,26 @@ nsIPresShell::RecordShadowStyleChange(Sh
   ApplicableStylesChanged();
 }
 
 void
 PresShell::Paint(nsView*         aViewToPaint,
                  const nsRegion& aDirtyRegion,
                  uint32_t        aFlags)
 {
-#ifdef MOZ_GECKO_PROFILER
+  nsCString url;
   nsIURI* uri = mDocument->GetDocumentURI();
   nsIDocument* contentRoot = GetPrimaryContentDocument();
   if (contentRoot) {
     uri = contentRoot->GetDocumentURI();
   }
+  url = uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A");
+#ifdef MOZ_GECKO_PROFILER
   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
-    "PresShell::Paint", GRAPHICS,
-    uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A"));
+    "PresShell::Paint", GRAPHICS, url);
 #endif
 
   Maybe<js::AutoAssertNoContentJS> nojs;
 
   // On Android, Flash can call into content JS during painting, so we can't
   // assert there. However, we don't rely on this assertion on Android because
   // we don't paint while JS is running.
 #if !defined(MOZ_WIDGET_ANDROID)
@@ -6299,17 +6300,17 @@ PresShell::Paint(nsView*         aViewTo
   // is debatable. For now we'll do it because B2G relied on first paint
   // to configure the viewport and we only want to do that when we have
   // real content to paint. See Bug 798245
   if (mIsFirstPaint && !mPaintingSuppressed) {
     layerManager->SetIsFirstPaint();
     mIsFirstPaint = false;
   }
 
-  if (!layerManager->BeginTransaction()) {
+  if (!layerManager->BeginTransaction(url)) {
     return;
   }
 
   // Send an updated focus target with this transaction. Be sure to do this
   // before we paint in the case this is an empty transaction.
   layerManager->SetFocusTarget(mAPZFocusTarget);
 
   if (frame) {
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -2111,20 +2111,18 @@ nsMathMLChar::PaintForeground(nsIFrame* 
 
 class AutoPushClipRect {
   gfxContext* mThebesContext;
 public:
   AutoPushClipRect(gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit,
                    const nsRect& aRect)
     : mThebesContext(aThebesContext) {
     mThebesContext->Save();
-    mThebesContext->NewPath();
     gfxRect clip = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit);
-    mThebesContext->SnappedRectangle(clip);
-    mThebesContext->Clip();
+    mThebesContext->SnappedClip(clip);
   }
   ~AutoPushClipRect() {
     mThebesContext->Restore();
   }
 };
 
 static nsPoint
 SnapToDevPixels(const gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit,
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -2537,19 +2537,17 @@ SetupImageLayerClip(nsCSSRendering::Imag
 
   if (aClipState.mHasAdditionalBGClipArea) {
     gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
       aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
     bgAreaGfx.Round();
     gfxUtils::ConditionRect(bgAreaGfx);
 
     aAutoSR->EnsureSaved(aCtx);
-    aCtx->NewPath();
-    aCtx->SnappedRectangle(bgAreaGfx);
-    aCtx->Clip();
+    aCtx->SnappedClip(bgAreaGfx);
   }
 
   if (aClipState.mHasRoundedCorners) {
     Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
     bgAreaGfx.Round();
 
     if (bgAreaGfx.IsEmpty()) {
       // I think it's become possible to hit this since
@@ -2600,28 +2598,24 @@ DrawBackgroundColor(nsCSSRendering::Imag
     // Make our caller not do anything.
     aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
     return;
   }
 
   aCtx->Save();
   gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx);
 
-  aCtx->NewPath();
-  aCtx->SnappedRectangle(dirty);
-  aCtx->Clip();
+  aCtx->SnappedClip(dirty);
 
   if (aClipState.mHasAdditionalBGClipArea) {
     gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
       aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
     bgAdditionalAreaGfx.Round();
     gfxUtils::ConditionRect(bgAdditionalAreaGfx);
-    aCtx->NewPath();
-    aCtx->SnappedRectangle(bgAdditionalAreaGfx);
-    aCtx->Clip();
+    aCtx->SnappedClip(bgAdditionalAreaGfx);
   }
 
   RefPtr<Path> roundedRect =
     MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
   aCtx->SetPath(roundedRect);
   aCtx->Fill();
   aCtx->Restore();
 }
@@ -2933,19 +2927,17 @@ nsCSSRendering::PaintStyleImageLayerWith
           currentLayerClipState, &aRenderingCtx, appUnitsPerPixel, &autoSR);
         if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
           // We're drawing the background for the joined continuation boxes
           // so we need to clip that to the slice that we want for this
           // frame.
           gfxRect clip =
             nsLayoutUtils::RectToGfxRect(aParams.borderArea, appUnitsPerPixel);
           autoSR.EnsureSaved(&aRenderingCtx);
-          aRenderingCtx.NewPath();
-          aRenderingCtx.SnappedRectangle(clip);
-          aRenderingCtx.Clip();
+          aRenderingCtx.SnappedClip(clip);
         }
       }
     }
 
     // Skip the following layer preparing and painting code if the current
     // layer is not selected for drawing.
     if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) {
       continue;
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -35,16 +35,18 @@
         <meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
 
         <meta-data android:name="android.max_aspect" android:value="2.1"/>
 
 #ifdef MOZ_NATIVE_DEVICES
         <service android:name="org.mozilla.gecko.RemotePresentationService" android:exported="false"/>
 #endif
 
+        <service android:name="org.mozilla.gecko.FennecKiller" />
+
         <!-- This activity handles all incoming Intents and dispatches them to other activities. -->
         <activity android:name="org.mozilla.gecko.LauncherActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar"
             android:relinquishTaskIdentity="true"
             android:taskAffinity=""
             android:exported="true"
             android:excludeFromRecents="true" />
 
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -611,16 +611,19 @@ public class BrowserApp extends GeckoApp
         }
         return super.onKeyUp(keyCode, event);
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         final Context appContext = getApplicationContext();
 
+        final Intent killerIntent = new Intent(this, FennecKiller.class);
+        startService(killerIntent);
+
         showSplashScreen = true;
 
         safeStartingIntent = new SafeIntent(getIntent());
         isInAutomation = IntentUtils.getIsInAutomationFromEnvironment(safeStartingIntent);
 
         GeckoProfile.setIntentArgs(safeStartingIntent.getStringExtra("args"));
 
         if (!isInAutomation && AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/FennecKiller.java
@@ -0,0 +1,36 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+import android.util.Log;
+
+public class FennecKiller extends Service {
+    private static final String LOGTAG = "GeckoFennecKiller";
+
+    private static final String BROWSERAPP = "org.mozilla.gecko.BrowserApp";
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onTaskRemoved(Intent intent) {
+        if (intent != null && intent.getComponent() != null &&
+                BROWSERAPP.equals(intent.getComponent().getClassName())) {
+            Process.killProcess(Process.myPid());
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return Service.START_NOT_STICKY;
+    }
+}
--- a/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml
+++ b/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml
@@ -18,11 +18,10 @@
             android:layout_height="?android:actionBarSize"
             android:layout_alignParentBottom="true"/>
 
     <ProgressBar
             android:id="@+id/page_progress"
             style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
             android:layout_width="match_parent"
             android:layout_height="3dp"
-            android:layout_alignTop="@id/gecko_view"
-            android:progress="70" />
+            android:layout_alignTop="@id/gecko_view" />
 </RelativeLayout>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1734,17 +1734,17 @@ pref("network.http.max-persistent-connec
 // host has been reached.  however, a new connection will not be created if
 // max-connections or max-connections-per-server has also been reached.
 pref("network.http.request.max-start-delay", 10);
 
 // If a connection is reset, we will retry it max-attempts times.
 pref("network.http.request.max-attempts", 10);
 
 // Headers
-pref("network.http.accept.default", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
+pref("network.http.accept.default", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
 
 // Prefs allowing granular control of referers
 // 0=don't send any, 1=send only on clicks, 2=send on image requests as well
 pref("network.http.sendRefererHeader",      2);
 // Set the default Referrer Policy; to be used unless overriden by the site
 // 0=no-referrer, 1=same-origin, 2=strict-origin-when-cross-origin,
 // 3=no-referrer-when-downgrade
 pref("network.http.referer.defaultPolicy", 3);
--- a/taskcluster/ci/test/misc.yml
+++ b/taskcluster/ci/test/misc.yml
@@ -8,18 +8,18 @@ geckoview-junit:
     target: geckoview-androidTest.apk
     max-run-time: 3600
     tier:
         by-test-platform:
             android-em-7.0-x86/opt: 3
             default: default
     chunks:
         by-test-platform:
-            android-em-4.3-arm7-api-16-ccov/debug: 4
-            android-em-4.3-arm7-api-16/debug: 4
+            android-em-4.3-arm7-api-16-ccov/debug: 6
+            android-em-4.3-arm7-api-16/debug: 6
             android-em-4.3-arm7-api-16/opt: 2
             default: 1
     mozharness:
         script: android_emulator_unittest.py
         config:
             by-test-platform:
                 android-em-4.2-x86/opt:
                     - android/android_common.py
--- a/testing/mozbase/moz.build
+++ b/testing/mozbase/moz.build
@@ -8,16 +8,17 @@ PYTHON_UNITTEST_MANIFESTS += [
     'manifestparser/tests/manifest.ini',
     'mozcrash/tests/manifest.ini',
     'mozdebug/tests/manifest.ini',
     'mozdevice/tests/manifest.ini',
     'mozfile/tests/manifest.ini',
     'mozhttpd/tests/manifest.ini',
     'mozinfo/tests/manifest.ini',
     'mozinstall/tests/manifest.ini',
+    'mozleak/tests/manifest.ini',
     'mozlog/tests/manifest.ini',
     'moznetwork/tests/manifest.ini',
     'mozprocess/tests/manifest.ini',
     'mozprofile/tests/manifest.ini',
     'mozrunner/tests/manifest.ini',
     'moztest/tests/manifest.ini',
     'mozversion/tests/manifest.ini',
 ]
--- a/testing/mozbase/mozleak/mozleak/lsan.py
+++ b/testing/mozbase/mozleak/mozleak/lsan.py
@@ -45,16 +45,17 @@ class LSANLeaks(object):
             "==\d+==LeakSanitizer has encountered a fatal error.")
         self.symbolizerOomRegExp = re.compile(
             "LLVMSymbolizer: error reading file: Cannot allocate memory")
         self.stackFrameRegExp = re.compile("    #\d+ 0x[0-9a-f]+ in ([^(</]+)")
         self.sysLibStackFrameRegExp = re.compile(
             "    #\d+ 0x[0-9a-f]+ \(([^+]+)\+0x[0-9a-f]+\)")
         self.summaryRegexp = re.compile(
             "SUMMARY: AddressSanitizer: (\d+) byte\(s\) leaked in (\d+) allocation\(s\).")
+        self.rustRegexp = re.compile("::h[a-f0-9]{16}$")
         self.setAllowed(allowed)
 
     def setAllowed(self, allowedLines):
         if not allowedLines:
             self.allowedRegexp = None
         else:
             self.allowedRegexp = re.compile(
                 "^" + "|".join([re.escape(f) for f in allowedLines]))
@@ -161,12 +162,19 @@ class LSANLeaks(object):
             self.currStack = None
             self.allowedMatch = None
         self.recordMoreFrames = False
         self.numRecordedFrames = 0
 
     def _recordFrame(self, frame):
         if self.allowedMatch is None and self.allowedRegexp is not None:
             self.allowedMatch = frame if self.allowedRegexp.match(frame) else None
+        frame = self._cleanFrame(frame)
         self.currStack.append(frame)
         self.numRecordedFrames += 1
         if self.numRecordedFrames >= self.maxNumRecordedFrames:
             self.recordMoreFrames = False
+
+    def _cleanFrame(self, frame):
+        # Rust frames aren't properly demangled and in particular can contain
+        # some trailing junk of the form ::h[a-f0-9]{16} that changes with
+        # compiler versions; see bug 1507350.
+        return self.rustRegexp.sub("", frame)
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozleak/tests/manifest.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+subsuite = mozbase, os == "linux"
+[test_lsan.py]
+skip-if = python == 3
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozleak/tests/test_lsan.py
@@ -0,0 +1,24 @@
+from __future__ import absolute_import
+import pytest
+
+import mozunit
+from mozleak import lsan
+
+
+@pytest.mark.parametrize(("input_", "expected"),
+                         [("alloc_system::platform::_$LT$impl$u20$core..alloc.."
+                           "GlobalAlloc$u20$for$u20$alloc_system..System$GT$::"
+                           "alloc::h5a1f0db41e296502",
+                           "alloc_system::platform::_$LT$impl$u20$core..alloc.."
+                           "GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc"),
+                          ("alloc_system::platform::_$LT$impl$u20$core..alloc.."
+                           "GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc",
+                           "alloc_system::platform::_$LT$impl$u20$core..alloc.."
+                           "GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc")])
+def test_clean(input_, expected):
+    leaks = lsan.LSANLeaks(None)
+    assert leaks._cleanFrame(input_) == expected
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/testing/mozharness/scripts/marionette.py
+++ b/testing/mozharness/scripts/marionette.py
@@ -302,17 +302,19 @@ class MarionetteTest(TestingMixin, Mercu
         for arg in self.config["suite_definitions"][self.test_suite]["options"]:
             cmd.append(arg % config_fmt_args)
 
         if self.mkdir_p(dirs["abs_blob_upload_dir"]) == -1:
             # Make sure that the logging directory exists
             self.fatal("Could not create blobber upload directory")
 
         if os.environ.get('MOZHARNESS_TEST_PATHS'):
-            cmd.extend(os.environ['MOZHARNESS_TEST_PATHS'].split(':'))
+            paths = [os.path.join(dirs['abs_test_install_dir'], 'marionette', 'tests', p)
+                     for p in os.environ['MOZHARNESS_TEST_PATHS'].split(':')]
+            cmd.extend(paths)
         else:
             cmd.append(manifest)
 
         try_options, try_tests = self.try_args("marionette")
         cmd.extend(self.query_tests_args(try_tests,
                                          str_format_values=config_fmt_args))
 
         env = {}
--- a/testing/web-platform/meta/css/css-fonts/font-display/__dir__.ini
+++ b/testing/web-platform/meta/css/css-fonts/font-display/__dir__.ini
@@ -1,1 +1,1 @@
-lsan-allowed: [Alloc, AllocateProtoAndIfaceCache, EntrySlotOrCreate, Realloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc::h5a1f0db41e296502, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::realloc::hfdaa883bde7dcfa7, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::PerformanceMainThread::CreateNavigationTimingEntry]
+lsan-allowed: [Alloc, AllocateProtoAndIfaceCache, EntrySlotOrCreate, Realloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::PerformanceMainThread::CreateNavigationTimingEntry]
--- a/testing/web-platform/meta/trusted-types/__dir__.ini
+++ b/testing/web-platform/meta/trusted-types/__dir__.ini
@@ -1,1 +1,1 @@
-lsan-allowed: [Alloc, NS_NewDOMEvent, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc::h82bf91dcf8d89ced, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::realloc::h3ab64a4bcafe2c39, mozilla::BasePrincipal::CreateCodebasePrincipal]
+lsan-allowed: [Alloc, NS_NewDOMEvent, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::realloc, mozilla::BasePrincipal::CreateCodebasePrincipal]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/webrtc/RTCPeerConnection-onnegotiationneeded.html.ini
@@ -0,0 +1,2 @@
+[RTCPeerConnection-onnegotiationneeded.html]
+  disabled: Bug 1506994
--- a/testing/web-platform/meta/webrtc/__dir__.ini
+++ b/testing/web-platform/meta/webrtc/__dir__.ini
@@ -1,2 +1,2 @@
 prefs: [media.navigator.permission.disabled:true, media.navigator.streams.fake:true]
-lsan-allowed: [Alloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc::h82bf91dcf8d89ced, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::realloc::h3ab64a4bcafe2c39, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::RTCRtpSender::_Create, mozilla::dom::RTCRtpTransceiver::_Create]
+lsan-allowed: [Alloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::realloc, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::RTCRtpSender::_Create, mozilla::dom::RTCRtpTransceiver::_Create]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-masking/clip/clip-fixed-pos-transform-descendant-001-ref.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<style>
+  html, body { margin: 0 }
+  div {
+    width: 100px;
+    height: 100px;
+    background: lime;
+  }
+</style>
+<div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-masking/clip/clip-fixed-pos-transform-descendant-001.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<title>CSS Masking: Transformed descendants of a fixed pos element under a clipped element get properly clipped</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="match" href="clip-fixed-pos-transform-descendant-001-ref.html">
+<link rel="help" href="https://drafts.fxtf.org/css-masking/#clip-property">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1495791">
+<style>
+html, body { margin: 0; }
+
+#clip {
+  height: 100px;
+  width: 100px;
+  background: lime;
+  clip: rect(0, auto, auto, 0);
+  position: absolute;
+}
+
+#fixed {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100px;
+  height: 100px;
+}
+
+#clipped {
+  height: 100px;
+  width: 100px;
+  background: red;
+  transform: translateY(100px);
+}
+</style>
+<div id="clip">
+  <div id="fixed">
+    <div id="clipped"></div>
+  </div>
+</div>
--- a/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
@@ -161,27 +161,27 @@
        * ::after is used to display the icons as a background that can be
        * flipped using a CSS transform in RTL mode. */
       .action-icon {
         position: relative;
         opacity: 0;
       }
       /* Ensure both pseudo elements have the same size and position. */
       .action-icon::before, .action-icon::after {
-        content: "";
         height: 200%;
         position: absolute;
         top: -50%;
         left: -3px;
         padding-left: 13px;
         padding-right: 13px;
       }
 
       /* square background */
       .action-icon::before {
+        content: "";
         background-color: currentColor;
         opacity: 0;
       }
       .action-icon:hover::before {
         opacity: 0.1;
       }
       .action-icon:hover:active::before {
         opacity: 0.2;
@@ -189,27 +189,33 @@
 
       /* icons */
       .action-icon::after {
         -moz-context-properties: fill, fill-opacity;
         fill-opacity: 0;
         background-repeat: no-repeat;
         background-position: center;
         fill: currentColor;
+        line-height: 100%;
       }
       .addon-icon::after {
-        background-image: url("chrome://global/skin/icons/shortcut.svg");
-        background-size: 16px;
+        content: url("chrome://global/skin/icons/shortcut.svg");
+        top: 2px;
+        left: -11px;
+        width: 16px;
       }
       .addon-icon:dir(rtl)::after {
         transform: scaleX(-1);
       }
       .close-icon::after {
-        background-image: url("chrome://global/skin/icons/close.svg");
-        background-size: 24px;
+        content: url("chrome://global/skin/icons/close.svg");
+        height: 100%;
+        top: 0;
+        left: -13px;
+        transform: scale(1.2);
       }
 
       #dispatch-table > thead > tr > td {
         border: none;
         background-color: var(--in-content-box-background-hover);
         padding: 5px 10px;
       }
       #dispatch-table > thead > tr > td:not(:first-child) {
@@ -245,32 +251,32 @@
         margin-inline-end: 26px;
         padding-inline-end: 18px;
         padding-inline-start: 18px;
         position: relative;
       }
       /* Putting the background image in a positioned pseudo element lets us
        * use CSS transforms on the background image, which we need for rtl. */
       .twisty::before {
-        content: "";
+        content: url("chrome://global/skin/icons/twisty-collapsed.svg");
         position: absolute;
-        height: 100%;
+        display: block;
+        line-height: 50%;
+        top: 4px; /* Half the image's height */
         width: 100%;
         left: 0;
-        background-repeat: no-repeat;
-        background-position: center;
-        background-image: url("chrome://global/skin/icons/twisty-collapsed.svg");
+        text-align: center;
         -moz-context-properties: fill;
         fill: currentColor;
       }
       .twisty:dir(rtl)::before {
         transform: scaleX(-1);
       }
       .twisty.open::before {
-        background-image: url("chrome://global/skin/icons/twisty-expanded.svg");
+        content: url("chrome://global/skin/icons/twisty-expanded.svg");
       }
       #dispatch-tbody > tr > td.indent {
         padding-inline-start: 88px;
         background-position-x: left 62px;
       }
       #dispatch-tbody > tr > td.indent:dir(rtl) {
         background-position-x: right 62px;
       }