Bug 1518999 - Implement PerformancePaintTiming for FirstContentfulPaint r=smaug,mstange
authorSean Feng <sefeng@mozilla.com>
Tue, 27 Oct 2020 16:25:50 +0000
changeset 554727 3fbb53f6d6ef6ec9fc2099fa25abeadfd616b6ba
parent 554726 822af3fb6462c53f36e10d76da7caddf6d644814
child 554728 47629f89cb6a8a8a663789855591a3d17466493b
push id37898
push userabutkovits@mozilla.com
push dateWed, 28 Oct 2020 09:24:21 +0000
treeherdermozilla-central@83bf4fd3b1fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, mstange
bugs1518999
milestone84.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
Bug 1518999 - Implement PerformancePaintTiming for FirstContentfulPaint r=smaug,mstange Spec: https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming We only support FirstContentfulPaint at the moment. Differential Revision: https://phabricator.services.mozilla.com/D66463
dom/performance/Performance.h
dom/performance/PerformanceMainThread.cpp
dom/performance/PerformanceMainThread.h
dom/performance/PerformanceObserver.cpp
dom/performance/PerformancePaintTiming.cpp
dom/performance/PerformancePaintTiming.h
dom/performance/PerformanceWorker.h
dom/performance/moz.build
dom/performance/tests/logo.png
dom/performance/tests/mochitest.ini
dom/performance/tests/test_performance_paint_observer.html
dom/performance/tests/test_performance_paint_observer_helper.html
dom/performance/tests/test_performance_paint_timing.html
dom/performance/tests/test_performance_paint_timing_helper.html
dom/tests/mochitest/general/test_interfaces.js
dom/webidl/PerformancePaintTiming.webidl
dom/webidl/moz.build
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/generic/nsTextFrame.cpp
layout/generic/nsVideoFrame.cpp
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
--- a/dom/performance/Performance.h
+++ b/dom/performance/Performance.h
@@ -17,16 +17,17 @@ class nsITimedChannel;
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 class PerformanceEntry;
 class PerformanceNavigation;
+class PerformancePaintTiming;
 class PerformanceObserver;
 class PerformanceService;
 class PerformanceStorage;
 class PerformanceTiming;
 class WorkerPrivate;
 
 // Base class for main-thread and worker Performance API
 class Performance : public DOMEventTargetHelper {
@@ -80,16 +81,18 @@ class Performance : public DOMEventTarge
   void RemoveObserver(PerformanceObserver* aObserver);
   MOZ_CAN_RUN_SCRIPT void NotifyObservers();
   void CancelNotificationObservers();
 
   virtual PerformanceTiming* Timing() = 0;
 
   virtual PerformanceNavigation* Navigation() = 0;
 
+  virtual void SetFCPTimingEntry(PerformancePaintTiming* aEntry) = 0;
+
   IMPL_EVENT_HANDLER(resourcetimingbufferfull)
 
   virtual void GetMozMemory(JSContext* aCx,
                             JS::MutableHandle<JSObject*> aObj) = 0;
 
   virtual nsDOMNavigationTiming* GetDOMTiming() const = 0;
 
   virtual nsITimedChannel* GetChannel() const = 0;
--- a/dom/performance/PerformanceMainThread.cpp
+++ b/dom/performance/PerformanceMainThread.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PerformanceMainThread.h"
 #include "PerformanceNavigation.h"
+#include "PerformancePaintTiming.h"
 #include "mozilla/StaticPrefs_dom.h"
 #include "mozilla/StaticPrefs_privacy.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
@@ -38,24 +39,24 @@ void GetURLSpecFromChannel(nsITimedChann
 }
 
 }  // namespace
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PerformanceMainThread)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PerformanceMainThread,
                                                 Performance)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming, mNavigation, mDocEntry)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming, mNavigation, mDocEntry, mFCPTiming)
   tmp->mMozMemory = nullptr;
   mozilla::DropJSObjects(this);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PerformanceMainThread,
                                                   Performance)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTiming, mNavigation, mDocEntry)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTiming, mNavigation, mDocEntry, mFCPTiming)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceMainThread,
                                                Performance)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_ADDREF_INHERITED(PerformanceMainThread, Performance)
@@ -156,16 +157,24 @@ void PerformanceMainThread::AddRawEntry(
   // The PerformanceResourceTiming object will use the PerformanceTimingData
   // object to get all the required timings.
   auto entry =
       MakeRefPtr<PerformanceResourceTiming>(std::move(aData), this, aEntryName);
   entry->SetInitiatorType(aInitiatorType);
   InsertResourceEntry(entry);
 }
 
+void PerformanceMainThread::SetFCPTimingEntry(PerformancePaintTiming* aEntry) {
+  MOZ_ASSERT(aEntry);
+  if (!mFCPTiming) {
+    mFCPTiming = aEntry;
+    QueueEntry(aEntry);
+  }
+}
+
 // To be removed once bug 1124165 lands
 bool PerformanceMainThread::IsPerformanceTimingAttribute(
     const nsAString& aName) {
   // Note that toJSON is added to this list due to bug 1047848
   static const char* attributes[] = {"navigationStart",
                                      "unloadEventStart",
                                      "unloadEventEnd",
                                      "redirectStart",
@@ -371,16 +380,19 @@ void PerformanceMainThread::GetEntries(
 
   aRetval = mResourceEntries.Clone();
   aRetval.AppendElements(mUserEntries);
 
   if (mDocEntry) {
     aRetval.AppendElement(mDocEntry);
   }
 
+  if (mFCPTiming) {
+    aRetval.AppendElement(mFCPTiming);
+  }
   aRetval.Sort(PerformanceEntryComparator());
 }
 
 void PerformanceMainThread::GetEntriesByType(
     const nsAString& aEntryType, nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
   // We return an empty list when 'privacy.resistFingerprinting' is on.
   if (nsContentUtils::ShouldResistFingerprinting()) {
     aRetval.Clear();
@@ -391,30 +403,44 @@ void PerformanceMainThread::GetEntriesBy
     aRetval.Clear();
 
     if (mDocEntry) {
       aRetval.AppendElement(mDocEntry);
     }
     return;
   }
 
+  if (aEntryType.EqualsLiteral("paint")) {
+    if (mFCPTiming) {
+      aRetval.AppendElement(mFCPTiming);
+      return;
+    }
+  }
+
   Performance::GetEntriesByType(aEntryType, aRetval);
 }
 
 void PerformanceMainThread::GetEntriesByName(
     const nsAString& aName, const Optional<nsAString>& aEntryType,
     nsTArray<RefPtr<PerformanceEntry>>& aRetval) {
   // We return an empty list when 'privacy.resistFingerprinting' is on.
   if (nsContentUtils::ShouldResistFingerprinting()) {
     aRetval.Clear();
     return;
   }
 
   Performance::GetEntriesByName(aName, aEntryType, aRetval);
 
+  if (mFCPTiming && mFCPTiming->GetName().Equals(aName) &&
+      (!aEntryType.WasPassed() ||
+       mFCPTiming->GetEntryType().Equals(aEntryType.Value()))) {
+    aRetval.AppendElement(mFCPTiming);
+    return;
+  }
+
   // The navigation entry is the first one. If it exists and the name matches,
   // let put it in front.
   if (mDocEntry && mDocEntry->GetName().Equals(aName)) {
     aRetval.InsertElementAt(0, mDocEntry);
     return;
   }
 }
 
--- a/dom/performance/PerformanceMainThread.h
+++ b/dom/performance/PerformanceMainThread.h
@@ -33,16 +33,17 @@ class PerformanceMainThread final : publ
   virtual PerformanceNavigation* Navigation() override;
 
   virtual void AddEntry(nsIHttpChannel* channel,
                         nsITimedChannel* timedChannel) override;
 
   void AddRawEntry(UniquePtr<PerformanceTimingData>,
                    const nsAString& aInitiatorType,
                    const nsAString& aEntryName);
+  virtual void SetFCPTimingEntry(PerformancePaintTiming* aEntry) override;
 
   TimeStamp CreationTimeStamp() const override;
 
   DOMHighResTimeStamp CreationTime() const override;
 
   virtual void GetMozMemory(JSContext* aCx,
                             JS::MutableHandle<JSObject*> aObj) override;
 
@@ -85,16 +86,17 @@ class PerformanceMainThread final : publ
 
   void DispatchBufferFullEvent() override;
 
   RefPtr<PerformanceNavigationTiming> mDocEntry;
   RefPtr<nsDOMNavigationTiming> mDOMTiming;
   nsCOMPtr<nsITimedChannel> mChannel;
   RefPtr<PerformanceTiming> mTiming;
   RefPtr<PerformanceNavigation> mNavigation;
+  RefPtr<PerformancePaintTiming> mFCPTiming;
   JS::Heap<JSObject*> mMozMemory;
 
   const bool mCrossOriginIsolated;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
--- a/dom/performance/PerformanceObserver.cpp
+++ b/dom/performance/PerformanceObserver.cpp
@@ -127,21 +127,18 @@ void PerformanceObserver::QueueEntry(Per
   }
   mQueuedEntries.AppendElement(aEntry);
 }
 
 /*
  * Keep this list in alphabetical order.
  * https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
  */
-static const char16_t* const sValidTypeNames[4] = {
-    u"mark",
-    u"measure",
-    u"navigation",
-    u"resource",
+static const char16_t* const sValidTypeNames[5] = {
+    u"mark", u"measure", u"navigation", u"paint", u"resource",
 };
 
 void PerformanceObserver::ReportUnsupportedTypesErrorToConsole(
     bool aIsMainThread, const char* msgId, const nsString& aInvalidTypes) {
   if (!aIsMainThread) {
     nsTArray<nsString> params;
     params.AppendElement(aInvalidTypes);
     WorkerPrivate::ReportErrorToConsole(msgId, params);
new file mode 100644
--- /dev/null
+++ b/dom/performance/PerformancePaintTiming.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PerformancePaintTiming.h"
+#include "MainThreadUtils.h"
+#include "mozilla/dom/PerformanceMeasureBinding.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PerformancePaintTiming, PerformanceEntry,
+                                   mPerformance)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformancePaintTiming)
+NS_INTERFACE_MAP_END_INHERITING(PerformanceEntry)
+
+NS_IMPL_ADDREF_INHERITED(PerformancePaintTiming, PerformanceEntry)
+NS_IMPL_RELEASE_INHERITED(PerformancePaintTiming, PerformanceEntry)
+
+PerformancePaintTiming::PerformancePaintTiming(Performance* aPerformance,
+                                               const nsAString& aName,
+                                               const TimeStamp& aStartTime)
+    : PerformanceEntry(aPerformance->GetParentObject(), aName, u"paint"_ns),
+      mPerformance(aPerformance),
+      mStartTime(aStartTime) {}
+
+PerformancePaintTiming::~PerformancePaintTiming() = default;
+
+JSObject* PerformancePaintTiming::WrapObject(
+    JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+  return PerformancePaintTiming_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+DOMHighResTimeStamp PerformancePaintTiming::StartTime() const {
+  DOMHighResTimeStamp rawValue =
+      mPerformance->GetDOMTiming()->TimeStampToDOMHighRes(mStartTime);
+  return nsRFPService::ReduceTimePrecisionAsMSecs(
+      rawValue, mPerformance->GetRandomTimelineSeed(),
+      mPerformance->IsSystemPrincipal(), mPerformance->CrossOriginIsolated());
+}
+
+size_t PerformancePaintTiming::SizeOfIncludingThis(
+    mozilla::MallocSizeOf aMallocSizeOf) const {
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
new file mode 100644
--- /dev/null
+++ b/dom/performance/PerformancePaintTiming.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PerformancePaintTiming_h___
+#define mozilla_dom_PerformancePaintTiming_h___
+
+#include "mozilla/dom/PerformanceEntry.h"
+#include "mozilla/dom/PerformancePaintTimingBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+// https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming
+// Unlike timeToContentfulPaint, this timing is generated during
+// displaylist building, when a frame is contentful, we collect
+// the timestamp. Whereas, timeToContentfulPaint uses a compositor-side
+// timestamp.
+class PerformancePaintTiming final : public PerformanceEntry {
+ public:
+  PerformancePaintTiming(Performance* aPerformance, const nsAString& aName,
+                         const TimeStamp& aStartTime);
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PerformancePaintTiming,
+                                           PerformanceEntry)
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
+
+  DOMHighResTimeStamp StartTime() const override;
+
+  size_t SizeOfIncludingThis(
+      mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+  ~PerformancePaintTiming();
+  RefPtr<Performance> mPerformance;
+  const TimeStamp mStartTime;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_PerformanacePaintTiming_h___ */
--- a/dom/performance/PerformanceWorker.h
+++ b/dom/performance/PerformanceWorker.h
@@ -28,16 +28,20 @@ class PerformanceWorker final : public P
     return nullptr;
   }
 
   virtual PerformanceNavigation* Navigation() override {
     MOZ_CRASH("This should not be called on workers.");
     return nullptr;
   }
 
+  virtual void SetFCPTimingEntry(PerformancePaintTiming* aEntry) override {
+    MOZ_CRASH("This should not be called on workers.");
+  }
+
   TimeStamp CreationTimeStamp() const override;
 
   DOMHighResTimeStamp CreationTime() const override;
 
   virtual void GetMozMemory(JSContext* aCx,
                             JS::MutableHandle<JSObject*> aObj) override {
     MOZ_CRASH("This should not be called on workers.");
   }
--- a/dom/performance/moz.build
+++ b/dom/performance/moz.build
@@ -12,16 +12,17 @@ EXPORTS.mozilla.dom += [
     "PerformanceEntry.h",
     "PerformanceMainThread.h",
     "PerformanceMark.h",
     "PerformanceMeasure.h",
     "PerformanceNavigation.h",
     "PerformanceNavigationTiming.h",
     "PerformanceObserver.h",
     "PerformanceObserverEntryList.h",
+    'PerformancePaintTiming.h',
     "PerformanceResourceTiming.h",
     "PerformanceServerTiming.h",
     "PerformanceService.h",
     "PerformanceStorage.h",
     "PerformanceStorageWorker.h",
     "PerformanceTiming.h",
 ]
 
@@ -30,16 +31,17 @@ UNIFIED_SOURCES += [
     "PerformanceEntry.cpp",
     "PerformanceMainThread.cpp",
     "PerformanceMark.cpp",
     "PerformanceMeasure.cpp",
     "PerformanceNavigation.cpp",
     "PerformanceNavigationTiming.cpp",
     "PerformanceObserver.cpp",
     "PerformanceObserverEntryList.cpp",
+    'PerformancePaintTiming.cpp',
     "PerformanceResourceTiming.cpp",
     "PerformanceServerTiming.cpp",
     "PerformanceService.cpp",
     "PerformanceStorageWorker.cpp",
     "PerformanceTiming.cpp",
     "PerformanceWorker.cpp",
 ]
 
new file mode 100644
index 0000000000000000000000000000000000000000..a05926bcd7148b648cf3ba3e84c70c6e6a51d5da
GIT binary patch
literal 21901
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
ON}g`Mt}V`?+5Z5uCyDI<
--- a/dom/performance/tests/mochitest.ini
+++ b/dom/performance/tests/mochitest.ini
@@ -9,16 +9,24 @@ support-files =
   test_worker_performance_entries.js
   test_worker_performance_entries.sjs
   empty.js
   serverTiming.sjs
 
 [test_performance_observer.html]
 [test_performance_user_timing.html]
 [test_performance_navigation_timing.html]
+[test_performance_paint_timing.html]
+support-files =
+  test_performance_paint_timing_helper.html
+  logo.png
+[test_performance_paint_observer.html]
+support-files =
+  test_performance_paint_observer_helper.html
+  logo.png
 [test_worker_user_timing.html]
 [test_worker_observer.html]
 [test_sharedWorker_performance_user_timing.html]
 skip-if = true # Bug 1571904
 [test_worker_performance_now.html]
 [test_timeOrigin.html]
 skip-if = toolkit == 'android' && !is_fennec # Bug 1525959
 [test_worker_performance_entries.html]
new file mode 100644
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_observer.html
@@ -0,0 +1,40 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+    <!--
+      https://bugzilla.mozilla.org/show_bug.cgi?id=1518999
+    -->
+    <head>
+        <title>Test for Bug 1518999 (Observer API) </title>
+        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+        <script src="/tests/SimpleTest/SimpleTest.js"></script>
+        <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    </head>
+    <body>
+        <a target="_blank"
+          href="https://bugzilla.mozilla.org/show_bug.cgi?id=1518999">Mozilla
+          Bug 1518999 - Paint Timing API For Observers</a>
+        <p id="display"></p>
+        <div id="content" style="display: none">
+        <pre id="test">
+            <script class="testbody" type="text/javascript">
+             let tab;
+             function runTest() {
+                tab = window.open("test_performance_paint_observer_helper.html");
+             }
+
+             function done() {
+                tab.close();
+                SimpleTest.finish();
+             }
+
+             SimpleTest.waitForExplicitFinish();
+             addLoadEvent(runTest);
+            </script>
+        </pre>
+        </div>
+    </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_observer_helper.html
@@ -0,0 +1,35 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+  <body>
+  </body>
+  <script>
+    var promise = new Promise(resolve => {
+      var observer = new PerformanceObserver(list => resolve(list));
+      observer.observe({entryTypes: ["paint"]});
+    });
+
+    promise.then(list => {
+      var perfEntries = list.getEntries();
+      opener.is(list.getEntries().length, 1);
+      opener.isDeeply(list.getEntries(),
+                performance.getEntriesByType("paint"),
+                "Observed 'paint' entries should equal to entries obtained by getEntriesByType.");
+      opener.isDeeply(list.getEntries({name: "paint"}),
+                performance.getEntriesByName("paint"),
+                "getEntries with name filter should return correct results.");
+      opener.isDeeply(list.getEntries({entryType: "paint"}),
+                performance.getEntriesByType("paint"),
+                "getEntries with entryType filter should return correct results.");
+      opener.done();
+    });
+
+    const img = document.createElement("IMG");
+    img.src = "http://example.org/tests/dom/performance/tests/logo.png";
+    document.body.appendChild(img);
+
+  </script>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_timing.html
@@ -0,0 +1,38 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+    <!--
+      https://bugzilla.mozilla.org/show_bug.cgi?id=1518999
+    -->
+    <head>
+        <title>Test for Bug 1518999</title>
+        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+        <script src="/tests/SimpleTest/SimpleTest.js"></script>
+        <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    </head>
+    <body>
+        <a target="_blank"
+          href="https://bugzilla.mozilla.org/show_bug.cgi?id=1518999">Mozilla
+          Bug 1518999 - Paint Timing API</a>
+        <p id="display"></p>
+        <div id="content" style="display: none">
+        <pre id="test">
+            <script class="testbody" type="text/javascript">
+             let tab;
+             function runTest() {
+                tab = window.open("test_performance_paint_timing_helper.html");
+             }
+             function done() {
+                tab.close();
+                SimpleTest.finish();
+             }
+             SimpleTest.waitForExplicitFinish();
+             addLoadEvent(runTest);
+            </script>
+        </pre>
+        </div>
+    </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/performance/tests/test_performance_paint_timing_helper.html
@@ -0,0 +1,65 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+    <!--
+      https://bugzilla.mozilla.org/show_bug.cgi?id=1518999
+    -->
+    <head>
+        <title>Test for Bug 1518999</title>
+        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    </head>
+    <body>
+        <div id="main"></div>
+        <div id="image"></div>
+        <div id="test">
+            <script class="testbody" type="text/javascript">
+             async function runTest() {
+                const paintEntries = performance.getEntriesByType('paint');
+                opener.is(paintEntries.length, 0, "No paint entries yet");
+
+                const img = document.createElement("img");
+                img.src = "http://example.org/tests/dom/performance/tests/logo.png";
+
+                img.onload = function() {
+                  function getAndTestEntries(runCount) {
+                    function testEntries(entries) {
+                      opener.is(entries.length, 1, "FCP Only returns");
+                      opener.is(entries[0].entryType, "paint", "entryType is paint");
+                      opener.is(entries[0].name, "first-contentful-paint",
+                                "Returned entry should be first-contentful-paint" );
+                      const fcpEntriesGotByName =
+                          performance.getEntriesByName('first-contentful-paint');
+                      opener.is(fcpEntriesGotByName.length, 1, "entries length should match");
+                      opener.is(entries[0], fcpEntriesGotByName[0], "should be the same entry");
+                      opener.done();
+                    }
+                    const entries = performance.getEntriesByType('paint');
+                    if (entries.length < 1) {
+                      if (runCount < 4) {
+                        opener.SimpleTest.requestFlakyTimeout("FCP is being registered asynchronously, so wait a bit of time");
+                        setTimeout(function() {
+                          getAndTestEntries(runCount + 1);
+                        }, 20);
+                      } else {
+                        opener.ok(false, "Unable to find paint entries within a reasonable amount of time");
+                        opener.done();
+                      }
+                    } else {
+                        testEntries(entries);
+                    }
+                  }
+                  getAndTestEntries(1);
+                }
+                document.body.appendChild(img);
+             }
+             window.onload = function() {
+                runTest();
+             }
+            </script>
+        </div>
+        </div>
+    </body>
+</html>
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -852,16 +852,18 @@ var interfaceNamesInGlobalScope = [
   { name: "PerformanceNavigation", insecureContext: true },
   // IMPORTANT: Do not change this list without review from a DOM peer!
   { name: "PerformanceNavigationTiming", insecureContext: true },
   // IMPORTANT: Do not change this list without review from a DOM peer!
   { name: "PerformanceObserver", insecureContext: true },
   // IMPORTANT: Do not change this list without review from a DOM peer!
   { name: "PerformanceObserverEntryList", insecureContext: true },
   // IMPORTANT: Do not change this list without review from a DOM peer!
+  { name: "PerformancePaintTiming", insecureContext: true },
+  // IMPORTANT: Do not change this list without review from a DOM peer!
   { name: "PerformanceResourceTiming", insecureContext: true },
   // IMPORTANT: Do not change this list without review from a DOM peer!
   { name: "PerformanceServerTiming", insecureContext: false },
   // IMPORTANT: Do not change this list without review from a DOM peer!
   { name: "PerformanceTiming", insecureContext: true },
   // IMPORTANT: Do not change this list without review from a DOM peer!
   { name: "PeriodicWave", insecureContext: true },
   // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PerformancePaintTiming.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+[Exposed=(Window)]
+interface PerformancePaintTiming : PerformanceEntry
+{
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -748,16 +748,17 @@ WEBIDL_FILES = [
     "Performance.webidl",
     "PerformanceEntry.webidl",
     "PerformanceMark.webidl",
     "PerformanceMeasure.webidl",
     "PerformanceNavigation.webidl",
     "PerformanceNavigationTiming.webidl",
     "PerformanceObserver.webidl",
     "PerformanceObserverEntryList.webidl",
+    'PerformancePaintTiming.webidl',
     "PerformanceResourceTiming.webidl",
     "PerformanceServerTiming.webidl",
     "PerformanceTiming.webidl",
     "PeriodicWave.webidl",
     "Permissions.webidl",
     "PermissionStatus.webidl",
     "Plugin.webidl",
     "PluginArray.webidl",
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -85,16 +85,17 @@
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPrefs_layout.h"
 #include "mozilla/StaticPrefs_zoom.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/dom/PerformancePaintTiming.h"
 #include "mozilla/layers/APZThreadUtils.h"
 #include "MobileViewportManager.h"
 #include "mozilla/dom/ImageTracker.h"
 
 // Needed for Start/Stop of Image Animation
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 
@@ -185,16 +186,17 @@ nsPresContext::nsPresContext(dom::Docume
       mNextFrameRateMultiplier(0),
       mViewportScrollStyles(StyleOverflow::Auto, StyleOverflow::Auto),
       // mImageAnimationMode is initialised below, in constructor body
       mImageAnimationModePref(imgIContainer::kNormalAnimMode),
       mType(aType),
       mInflationDisabledForShrinkWrap(false),
       mInteractionTimeEnabled(true),
       mHasPendingInterrupt(false),
+      mHasEverBuiltInvisibleText(false),
       mPendingInterruptFromTest(false),
       mInterruptsEnabled(false),
       mSendAfterPaintToContent(false),
       mDrawImageBackground(true),  // always draw the background
       mDrawColorBackground(true),
       // mNeverAnimate is initialised below, in constructor body
       mPaginated(aType != eContext_Galley),
       mCanPaginatedScroll(false),
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -1020,16 +1020,19 @@ class nsPresContext : public nsISupports
 
   bool HadNonBlankPaint() const { return mHadNonBlankPaint; }
   bool HadContentfulPaint() const { return mHadContentfulPaint; }
   void NotifyNonBlankPaint();
   void NotifyContentfulPaint();
   void NotifyPaintStatusReset();
   void NotifyDOMContentFlushed();
 
+  bool HasEverBuiltInvisibleText() const { return mHasEverBuiltInvisibleText; }
+  void SetBuiltInvisibleText() { mHasEverBuiltInvisibleText = true; }
+
   bool UsesExChUnits() const { return mUsesExChUnits; }
 
   void SetUsesExChUnits(bool aValue) { mUsesExChUnits = aValue; }
 
   bool IsDeviceSizePageSize();
 
   bool HasWarnedAboutPositionedTableParts() const {
     return mHasWarnedAboutPositionedTableParts;
@@ -1267,16 +1270,17 @@ class nsPresContext : public nsISupports
  protected:
   static constexpr size_t kThemeChangeKindBits = 2;
   static_assert(unsigned(mozilla::widget::ThemeChangeKind::AllBits) <=
                     (1u << kThemeChangeKindBits) - 1,
                 "theme change kind doesn't fit");
 
   unsigned mInteractionTimeEnabled : 1;
   unsigned mHasPendingInterrupt : 1;
+  unsigned mHasEverBuiltInvisibleText : 1;
   unsigned mPendingInterruptFromTest : 1;
   unsigned mInterruptsEnabled : 1;
   unsigned mSendAfterPaintToContent : 1;
   unsigned mDrawImageBackground : 1;
   unsigned mDrawColorBackground : 1;
   unsigned mNeverAnimate : 1;
   unsigned mPaginated : 1;
   unsigned mCanPaginatedScroll : 1;
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -4901,16 +4901,19 @@ void nsTextFrame::BuildDisplayList(nsDis
   if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) ||
        (isTextTransparent && !StyleText()->HasTextShadow())) &&
       aBuilder->IsForPainting() && !SVGUtils::IsInSVGTextSubtree(this)) {
     isSelected.emplace(IsSelected());
     if (!isSelected.value()) {
       TextDecorations textDecs;
       GetTextDecorations(PresContext(), eResolvedColors, textDecs);
       if (!textDecs.HasDecorationLines()) {
+        if (auto* currentPresContext = aBuilder->CurrentPresContext()) {
+          currentPresContext->SetBuiltInvisibleText();
+        }
         return;
       }
     }
   }
 
   aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this, isSelected);
 }
 
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -503,16 +503,23 @@ class nsDisplayVideo : public nsPaintedD
       // cheap (i.e. hardware accelerated).
       return LayerState::LAYER_ACTIVE;
     }
     HTMLMediaElement* elem =
         static_cast<HTMLMediaElement*>(mFrame->GetContent());
     return elem->IsPotentiallyPlaying() ? LayerState::LAYER_ACTIVE_FORCE
                                         : LayerState::LAYER_INACTIVE;
   }
+
+  // Only report FirstContentfulPaint when the video is set
+  bool IsContentful() const override {
+    nsVideoFrame* f = static_cast<nsVideoFrame*>(Frame());
+    HTMLVideoElement* video = HTMLVideoElement::FromNode(f->GetContent());
+    return video->VideoWidth() > 0;
+  }
 };
 
 void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                     const nsDisplayListSet& aLists) {
   if (!IsVisibleForPainting()) return;
 
   DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -1102,32 +1102,37 @@ static bool DisplayListIsNonBlank(nsDisp
 
 // A contentful paint is a paint that does contains DOM content (text,
 // images, non-blank canvases, SVG): "First Contentful Paint entry
 // contains a DOMHighResTimeStamp reporting the time when the browser
 // first rendered any text, image (including background images),
 // non-white canvas or SVG. This excludes any content of iframes, but
 // includes text with pending webfonts. This is the first time users
 // could start consuming page content."
-static bool DisplayListIsContentful(nsDisplayList* aList) {
+static bool DisplayListIsContentful(nsDisplayListBuilder* aBuilder,
+                                    nsDisplayList* aList) {
   for (nsDisplayItem* i : *aList) {
     DisplayItemType type = i->GetType();
     nsDisplayList* children = i->GetChildren();
 
     switch (type) {
       case DisplayItemType::TYPE_SUBDOCUMENT:  // iframes are ignored
         break;
       // CANVASes check if they may have been modified (as a stand-in
       // actually tracking all modifications)
       default:
         if (i->IsContentful()) {
-          return true;
+          bool dummy;
+          nsRect bound = i->GetBounds(aBuilder, &dummy);
+          if (!bound.IsEmpty()) {
+            return true;
+          }
         }
         if (children) {
-          if (DisplayListIsContentful(children)) {
+          if (DisplayListIsContentful(aBuilder, children)) {
             return true;
           }
         }
         break;
     }
   }
   return false;
 }
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -4646,16 +4646,24 @@ class nsDisplayBackgroundImage : public 
 
   void RemoveFrame(nsIFrame* aFrame) override {
     if (aFrame == mDependentFrame) {
       mDependentFrame = nullptr;
     }
     nsDisplayImageContainer::RemoveFrame(aFrame);
   }
 
+  // Match https://w3c.github.io/paint-timing/#contentful-image
+  bool IsContentful() const override {
+    const auto& styleImage =
+        mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mImage;
+
+    return styleImage.IsSizeAvailable() && styleImage.IsUrl();
+  }
+
  protected:
   typedef class mozilla::layers::ImageContainer ImageContainer;
   typedef class mozilla::layers::ImageLayer ImageLayer;
 
   bool CanBuildWebRenderDisplayItems(LayerManager* aManager,
                                      nsDisplayListBuilder* aBuilder);
   nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder,
                            nsIFrame* aFrameForBounds = nullptr);