Merge mozilla-central to autoland. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Tue, 20 Nov 2018 23:48:34 +0200
changeset 447373 ee4915caaa4ffe70c99d47f5d3761010a2a6acf5
parent 447372 7a3593c06bf77e7054d153180360f19a24c3f00b (current diff)
parent 447336 8991d660f20e3eea652e060c30e17670b45a9257 (diff)
child 447374 48d5a633e54ac0c601528a6532e3c7ff4043bd8c
push id35075
push usershindli@mozilla.com
push dateWed, 21 Nov 2018 04:04:02 +0000
treeherdermozilla-central@8540104bb0bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
docshell/base/nsDocShell.cpp
gfx/thebes/gfxPlatform.cpp
modules/libpref/init/all.js
--- 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
@@ -7564,22 +7564,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
   }
@@ -9101,27 +9095,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;
@@ -3291,16 +3294,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;
       }