Implement PerformanceObserver
https://bugs.webkit.org/show_bug.cgi?id=167546
<rdar://problem/30247959>
Reviewed by Ryosuke Niwa.
Source/JavaScriptCore:
* runtime/CommonIdentifiers.h:
Source/WebCore:
This implements PerformanceObserver from Performance Timeline Level 2:
https://w3c.github.io/performance-timeline/
Tests: performance-api/performance-observer-api.html
performance-api/performance-observer-basic.html
performance-api/performance-observer-callback-mutate.html
performance-api/performance-observer-callback-task.html
performance-api/performance-observer-entry-sort.html
performance-api/performance-observer-exception.html
performance-api/performance-observer-nested.html
performance-api/performance-observer-order.html
performance-api/performance-observer-periodic.html
performance-api/performance-timeline-api.html
* CMakeLists.txt:
* DerivedSources.make:
* WebCore.xcodeproj/project.pbxproj:
New files.
* page/Performance.h:
* page/Performance.cpp:
(WebCore::Performance::mark):
(WebCore::Performance::measure):
(WebCore::Performance::registerPerformanceObserver):
(WebCore::Performance::unregisterPerformanceObserver):
(WebCore::Performance::queueEntry):
Register PerformanceObservers with the Performance object.
When new PerformanceEntries are created (Mark and Measure
right now) check them against observers.
* page/PerformanceEntry.cpp:
(WebCore::PerformanceEntry::PerformanceEntry):
(WebCore::PerformanceEntry::typeForEntryTypeString):
* page/PerformanceEntry.h:
(WebCore::PerformanceEntry::type):
Give PerformanceEntry a convenience enum for easy comparison
and to know if it is one of the built-in known types (which the
PerformanceObserver API takes into account).
* page/PerformanceObserver.cpp: Added.
(WebCore::PerformanceObserver::PerformanceObserver):
(WebCore::PerformanceObserver::observe):
(WebCore::PerformanceObserver::disconnect):
(WebCore::PerformanceObserver::queueEntry):
(WebCore::PerformanceObserver::deliver):
* page/PerformanceObserver.h:
(WebCore::PerformanceObserver::create):
(WebCore::PerformanceObserver::typeFilter):
- TypeErrors on observe bad behavior
- Completely replace types filter on observe
- Handle register and unregister
- Handle calling the callback
* page/PerformanceObserverCallback.idl: Added.
* page/PerformanceObserverEntryList.cpp: Added.
(WebCore::PerformanceObserverEntryList::PerformanceObserverEntryList):
(WebCore::PerformanceObserverEntryList::getEntries):
(WebCore::PerformanceObserverEntryList::getEntriesByType):
(WebCore::PerformanceObserverEntryList::getEntriesByName):
* page/PerformanceObserverEntryList.h:
(WebCore::PerformanceObserverEntryList::create):
* page/PerformanceObserverEntryList.idl: Added.
Implement sorting and filtering of entries.
* page/PerformanceObserver.idl: Added.
* page/PerformanceObserverCallback.h:
(WebCore::PerformanceObserverCallback::~PerformanceObserverCallback):
Mostly autogenerated.
* page/PerformanceUserTiming.cpp:
(WebCore::UserTiming::mark):
(WebCore::UserTiming::measure):
* page/PerformanceUserTiming.h:
Update these to return the entry so it can be passed on to
any interested PerformanceObservers.
Source/WebInspectorUI:
* UserInterface/Models/NativeFunctionParameters.js:
Improve API view display of built-in performance methods.
LayoutTests:
* performance-api/performance-observer-api-expected.txt: Added.
* performance-api/performance-observer-api.html: Added.
* performance-api/performance-observer-basic-expected.txt: Added.
* performance-api/performance-observer-basic.html: Added.
* performance-api/performance-observer-callback-mutate-expected.txt: Added.
* performance-api/performance-observer-callback-mutate.html: Added.
* performance-api/performance-observer-callback-task-expected.txt: Added.
* performance-api/performance-observer-callback-task.html: Added.
* performance-api/performance-observer-entry-sort-expected.txt: Added.
* performance-api/performance-observer-entry-sort.html: Added.
* performance-api/performance-observer-exception-expected.txt: Added.
* performance-api/performance-observer-exception.html: Added.
* performance-api/performance-observer-nested-expected.txt: Added.
* performance-api/performance-observer-nested.html: Added.
* performance-api/performance-observer-order-expected.txt: Added.
* performance-api/performance-observer-order.html: Added.
* performance-api/performance-observer-periodic-expected.txt: Added.
* performance-api/performance-observer-periodic.html: Added.
PerformanceObserver tests.
* performance-api/performance-timeline-api-expected.txt: Added.
* performance-api/performance-timeline-api.html: Added.
Performance timeline tests.
* platform/efl/js/dom/global-constructors-attributes-expected.txt:
* platform/gtk/js/dom/global-constructors-attributes-expected.txt:
* platform/mac-elcapitan/js/dom/global-constructors-attributes-expected.txt:
* platform/mac-wk1/js/dom/global-constructors-attributes-expected.txt:
* platform/mac-yosemite/js/dom/global-constructors-attributes-expected.txt:
* platform/mac/js/dom/global-constructors-attributes-expected.txt:
* platform/win/js/dom/global-constructors-attributes-expected.txt:
New global constructors.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@211406 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/performance-api/performance-observer-api-expected.txt b/LayoutTests/performance-api/performance-observer-api-expected.txt
new file mode 100644
index 0000000..28efd87
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-api-expected.txt
@@ -0,0 +1,35 @@
+Basic Interface test for PerformanceObserver APIs.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PerformanceObserver
+PASS PerformanceObserver is defined.
+PASS PerformanceObserver.prototype.observe is defined.
+PASS PerformanceObserver.prototype.disconnect is defined.
+PASS PerformanceObserver() threw exception TypeError: Constructor requires 'new' operator.
+PASS new PerformanceObserver() threw exception TypeError: Not enough arguments.
+PASS new PerformanceObserver(1) threw exception TypeError: Argument 1 ('callback') to the PerformanceObserver constructor must be a function.
+PASS observer = new PerformanceObserver(function() {}) did not throw exception.
+PASS observer.observe() threw exception TypeError: Not enough arguments.
+PASS observer.observe("mark") threw exception TypeError: Type error.
+PASS observer.observe({}) threw exception TypeError: Member PerformanceObserverInit.entryTypes is required and must be an instance of sequence.
+PASS observer.observe({entryTypes:"mark"}) threw exception TypeError: Value is not a sequence.
+PASS observer.observe({entryTypes:[]}) threw exception TypeError: entryTypes cannot be an empty list.
+PASS observer.observe({entryTypes:["not-real"]}) threw exception TypeError: entryTypes contained only unsupported types.
+PASS observer.observe({entryTypes:["mark"]}) did not throw exception.
+PASS observer.observe({entryTypes:["mark", "not-real"]}) did not throw exception.
+PASS observer.observe({entryTypes:["mark", "measure"]}) did not throw exception.
+PASS observer.disconnect() did not throw exception.
+PASS observer.disconnect() did not throw exception.
+
+PerformanceObserverEntryList
+PASS PerformanceObserverEntryList is defined.
+PASS PerformanceObserverEntryList.prototype.getEntries is defined.
+PASS PerformanceObserverEntryList.prototype.getEntriesByType is defined.
+PASS PerformanceObserverEntryList.prototype.getEntriesByName is defined.
+PASS new PerformanceObserverEntryList() threw exception TypeError: function is not a constructor (evaluating 'new PerformanceObserverEntryList()').
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-api.html b/LayoutTests/performance-api/performance-observer-api.html
new file mode 100644
index 0000000..648bc45
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-api.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Basic Interface test for PerformanceObserver APIs.");
+
+debug("PerformanceObserver");
+shouldBeDefined(`PerformanceObserver`);
+shouldBeDefined(`PerformanceObserver.prototype.observe`);
+shouldBeDefined(`PerformanceObserver.prototype.disconnect`);
+shouldThrow(`PerformanceObserver()`);
+shouldThrow(`new PerformanceObserver()`);
+shouldThrow(`new PerformanceObserver(1)`);
+shouldNotThrow(`observer = new PerformanceObserver(function() {})`);
+shouldThrow(`observer.observe()`);
+shouldThrow(`observer.observe("mark")`);
+shouldThrow(`observer.observe({})`);
+shouldThrow(`observer.observe({entryTypes:"mark"})`);
+shouldThrow(`observer.observe({entryTypes:[]})`);
+shouldThrow(`observer.observe({entryTypes:["not-real"]})`);
+shouldNotThrow(`observer.observe({entryTypes:["mark"]})`);
+shouldNotThrow(`observer.observe({entryTypes:["mark", "not-real"]})`);
+shouldNotThrow(`observer.observe({entryTypes:["mark", "measure"]})`);
+shouldNotThrow(`observer.disconnect()`);
+shouldNotThrow(`observer.disconnect()`);
+
+debug("");
+debug("PerformanceObserverEntryList");
+shouldBeDefined(`PerformanceObserverEntryList`);
+shouldBeDefined(`PerformanceObserverEntryList.prototype.getEntries`);
+shouldBeDefined(`PerformanceObserverEntryList.prototype.getEntriesByType`);
+shouldBeDefined(`PerformanceObserverEntryList.prototype.getEntriesByName`);
+shouldThrow(`new PerformanceObserverEntryList()`);
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-basic-expected.txt b/LayoutTests/performance-api/performance-observer-basic-expected.txt
new file mode 100644
index 0000000..2b45ed7
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-basic-expected.txt
@@ -0,0 +1,52 @@
+Basic Behavior test for PerformanceObserver APIs.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Inside PerformanceObserver callback
+PASS argumentsLength === 2 is true
+PASS list instanceof PerformanceObserverEntryList is true
+PASS obs instanceof PerformanceObserver is true
+PASS obs === observer is true
+FAIL thisObject instanceof PerformanceObserver should be true. Was false.
+FAIL thisObject === observer should be true. Was false.
+
+PerformanceObserverEntryList APIs
+PASS list.getEntries() instanceof Array is true
+PASS list.getEntries().length === 2 is true
+PASS list.getEntries()[0] instanceof PerformanceEntry is true
+PASS list.getEntries()[0].name is "mark3"
+PASS list.getEntries()[1].name is "mark4"
+PASS list.getEntries()[0].startTime <= list.getEntries()[1].startTime is true
+PASS list.getEntriesByType() threw exception TypeError: Not enough arguments.
+PASS list.getEntriesByType("not-real").length === 0 is true
+PASS list.getEntriesByType("mark").length === 2 is true
+PASS list.getEntriesByType("mark")[0] instanceof PerformanceEntry is true
+PASS list.getEntriesByType("mark")[0].name is "mark3"
+PASS list.getEntriesByType("mark")[1].name is "mark4"
+PASS list.getEntriesByName() threw exception TypeError: Not enough arguments.
+PASS list.getEntriesByName("not-real").length === 0 is true
+PASS list.getEntriesByName("mark1").length === 0 is true
+PASS list.getEntriesByName("mark3").length === 1 is true
+PASS list.getEntriesByName("mark3")[0] instanceof PerformanceEntry is true
+PASS list.getEntriesByName("mark3")[0].name is "mark3"
+PASS list.getEntriesByName("mark4").length === 1 is true
+PASS list.getEntriesByName("mark4")[0] instanceof PerformanceEntry is true
+PASS list.getEntriesByName("mark4")[0].name is "mark4"
+PASS list.getEntriesByName() threw exception TypeError: Not enough arguments.
+PASS list.getEntriesByName("not-real").length === 0 is true
+PASS list.getEntriesByName("mark1").length === 0 is true
+PASS list.getEntriesByName("mark3").length === 1 is true
+PASS list.getEntriesByName("mark3")[0] instanceof PerformanceEntry is true
+PASS list.getEntriesByName("mark3")[0].name is "mark3"
+PASS list.getEntriesByName("mark4").length === 1 is true
+PASS list.getEntriesByName("mark4")[0] instanceof PerformanceEntry is true
+PASS list.getEntriesByName("mark4")[0].name is "mark4"
+PASS list.getEntriesByName("mark3", "not-real").length === 0 is true
+PASS list.getEntriesByName("mark3", "mark").length === 1 is true
+PASS list.getEntriesByName(null, "mark").length === 0 is true
+PASS list.getEntriesByName(undefined, "mark").length === 0 is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-basic.html b/LayoutTests/performance-api/performance-observer-basic.html
new file mode 100644
index 0000000..6b0f6d6
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-basic.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Basic Behavior test for PerformanceObserver APIs.");
+window.jsTestIsAsync = true;
+
+// PerformanceObservers that are not actively observing should not fire.
+let observerNotObserving = new PerformanceObserver(function() {
+ testFailed("PerformanceObserver never registered should not be called");
+});
+
+let observerNotObserving2 = new PerformanceObserver(function() {
+ testFailed("PerformanceObserver not actively observing should not be called");
+});
+observerNotObserving2.observe({entryTypes: ["mark"]});
+observerNotObserving2.disconnect();
+
+// PerformanceObservers for different entry types should not fire.
+let observerNotMarks = new PerformanceObserver(function() {
+ testFailed("PerformanceObserver observing measures should not be called");
+});
+
+// Observer sees marks while it is registered.
+performance.mark("mark1");
+window.observer = new PerformanceObserver(function(list, obs) {
+ window.argumentsLength = arguments.length;
+ window.list = list;
+ window.obs = obs;
+ window.thisObject = this;
+
+ debug("Inside PerformanceObserver callback");
+ shouldBeTrue(`argumentsLength === 2`);
+ shouldBeTrue(`list instanceof PerformanceObserverEntryList`);
+ shouldBeTrue(`obs instanceof PerformanceObserver`);
+ shouldBeTrue(`obs === observer`);
+ // FIXME: <https://webkit.org/b/167549> Invoking generated callback should allow setting the `this` object
+ shouldBeTrue(`thisObject instanceof PerformanceObserver`);
+ shouldBeTrue(`thisObject === observer`);
+
+ debug("");
+ debug("PerformanceObserverEntryList APIs");
+
+ shouldBeTrue(`list.getEntries() instanceof Array`);
+ shouldBeTrue(`list.getEntries().length === 2`);
+ shouldBeTrue(`list.getEntries()[0] instanceof PerformanceEntry`);
+ shouldBeEqualToString(`list.getEntries()[0].name`, "mark3");
+ shouldBeEqualToString(`list.getEntries()[1].name`, "mark4");
+ shouldBeTrue(`list.getEntries()[0].startTime <= list.getEntries()[1].startTime`);
+
+ shouldThrow(`list.getEntriesByType()`);
+ shouldBeTrue(`list.getEntriesByType("not-real").length === 0`);
+ shouldBeTrue(`list.getEntriesByType("mark").length === 2`);
+ shouldBeTrue(`list.getEntriesByType("mark")[0] instanceof PerformanceEntry`);
+ shouldBeEqualToString(`list.getEntriesByType("mark")[0].name`, "mark3");
+ shouldBeEqualToString(`list.getEntriesByType("mark")[1].name`, "mark4");
+
+ shouldThrow(`list.getEntriesByName()`);
+ shouldBeTrue(`list.getEntriesByName("not-real").length === 0`);
+ shouldBeTrue(`list.getEntriesByName("mark1").length === 0`);
+ shouldBeTrue(`list.getEntriesByName("mark3").length === 1`);
+ shouldBeTrue(`list.getEntriesByName("mark3")[0] instanceof PerformanceEntry`);
+ shouldBeEqualToString(`list.getEntriesByName("mark3")[0].name`, "mark3");
+ shouldBeTrue(`list.getEntriesByName("mark4").length === 1`);
+ shouldBeTrue(`list.getEntriesByName("mark4")[0] instanceof PerformanceEntry`);
+ shouldBeEqualToString(`list.getEntriesByName("mark4")[0].name`, "mark4");
+
+ shouldThrow(`list.getEntriesByName()`);
+ shouldBeTrue(`list.getEntriesByName("not-real").length === 0`);
+ shouldBeTrue(`list.getEntriesByName("mark1").length === 0`);
+ shouldBeTrue(`list.getEntriesByName("mark3").length === 1`);
+ shouldBeTrue(`list.getEntriesByName("mark3")[0] instanceof PerformanceEntry`);
+ shouldBeEqualToString(`list.getEntriesByName("mark3")[0].name`, "mark3");
+ shouldBeTrue(`list.getEntriesByName("mark4").length === 1`);
+ shouldBeTrue(`list.getEntriesByName("mark4")[0] instanceof PerformanceEntry`);
+ shouldBeEqualToString(`list.getEntriesByName("mark4")[0].name`, "mark4");
+
+ shouldBeTrue(`list.getEntriesByName("mark3", "not-real").length === 0`);
+ shouldBeTrue(`list.getEntriesByName("mark3", "mark").length === 1`);
+ shouldBeTrue(`list.getEntriesByName(null, "mark").length === 0`);
+ shouldBeTrue(`list.getEntriesByName(undefined, "mark").length === 0`);
+
+ finishJSTest();
+});
+performance.mark("mark2");
+observer.observe({entryTypes: ["mark"]});
+performance.mark("mark3");
+performance.mark("mark4");
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-callback-mutate-expected.txt b/LayoutTests/performance-api/performance-observer-callback-mutate-expected.txt
new file mode 100644
index 0000000..9ec540f
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-callback-mutate-expected.txt
@@ -0,0 +1,42 @@
+Test PerformanceObserver mutating itself while in its callback.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Observering: ["measure"]
+PASS PerformanceObserver callback fired
+PASS list.getEntries().length is 1
+PASS list.getEntries()[0].entryType is "measure"
+PASS list.getEntries()[0].name is "measure1"
+Observering: ["mark"]
+ - measure1
+PASS PerformanceObserver callback fired
+PASS list.getEntries().length is 1
+PASS list.getEntries()[0].entryType is "mark"
+PASS list.getEntries()[0].name is "mark2"
+Observering: ["measure"]
+ - mark2
+PASS PerformanceObserver callback fired
+PASS list.getEntries().length is 2
+PASS list.getEntries()[0].entryType is "measure"
+PASS list.getEntries()[0].name is "measure3"
+PASS list.getEntries()[1].entryType is "mark"
+PASS list.getEntries()[1].name is "mark-before-change-observe-state-to-measure"
+Observering: ["mark","measure"]
+ - measure3
+ - mark-before-change-observe-state-to-measure
+PASS PerformanceObserver callback fired
+PASS list.getEntries().length is 3
+PASS list.getEntries()[0].entryType is "measure"
+PASS list.getEntries()[0].name is "measure-before-change-observe-state-to-both"
+PASS list.getEntries()[1].entryType is "measure"
+PASS list.getEntries()[1].name is "measure4"
+PASS list.getEntries()[2].entryType is "mark"
+PASS list.getEntries()[2].name is "mark4"
+ - measure-before-change-observe-state-to-both
+ - measure4
+ - mark4
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-callback-mutate.html b/LayoutTests/performance-api/performance-observer-callback-mutate.html
new file mode 100644
index 0000000..f88eca4
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-callback-mutate.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Test PerformanceObserver mutating itself while in its callback.");
+window.jsTestIsAsync = true;
+
+// Observer is first watching measures => then marks => measures => both.
+// NOTE: Measures are sorted before marks due to startTime.
+
+window.callbackCount = 0;
+window.list = null;
+
+let observer = new PerformanceObserver((list) => {
+ window.list = list;
+ callbackCount++;
+ testPassed("PerformanceObserver callback fired");
+
+ if (callbackCount === 1) {
+ // Expected: [measure1]
+ shouldBe(`list.getEntries().length`, `1`);
+ shouldBeEqualToString(`list.getEntries()[0].entryType`, "measure");
+ shouldBeEqualToString(`list.getEntries()[0].name`, "measure1");
+
+ updateObserver(["mark"]);
+
+ performance.mark("mark2");
+ performance.measure("measure2");
+ } else if (callbackCount === 2) {
+ // Expected: [mark2]
+ shouldBe(`list.getEntries().length`, `1`);
+ shouldBeEqualToString(`list.getEntries()[0].entryType`, "mark");
+ shouldBeEqualToString(`list.getEntries()[0].name`, "mark2");
+
+ performance.mark("mark-before-change-observe-state-to-measure");
+ performance.measure("measure-before-change-observe-state-to-measure");
+
+ updateObserver(["measure"]);
+
+ performance.mark("mark3");
+ performance.measure("measure3");
+ } else if (callbackCount === 3) {
+ // Expected: [measure3, mark-before-change-observe-state-to-measure]
+ shouldBe(`list.getEntries().length`, `2`);
+ shouldBeEqualToString(`list.getEntries()[0].entryType`, "measure");
+ shouldBeEqualToString(`list.getEntries()[0].name`, "measure3");
+ shouldBeEqualToString(`list.getEntries()[1].entryType`, "mark");
+ shouldBeEqualToString(`list.getEntries()[1].name`, "mark-before-change-observe-state-to-measure");
+
+ performance.mark("mark-before-change-observe-state-to-both");
+ performance.measure("measure-before-change-observe-state-to-both");
+
+ updateObserver(["mark", "measure"]);
+
+ performance.mark("mark4");
+ performance.measure("measure4");
+ } else if (callbackCount === 4) {
+ // Expected: [measure-before-change-observe-state-to-both, measure4, mark4]
+ shouldBe(`list.getEntries().length`, `3`);
+ shouldBeEqualToString(`list.getEntries()[0].entryType`, "measure");
+ shouldBeEqualToString(`list.getEntries()[0].name`, "measure-before-change-observe-state-to-both");
+ shouldBeEqualToString(`list.getEntries()[1].entryType`, "measure");
+ shouldBeEqualToString(`list.getEntries()[1].name`, "measure4");
+ shouldBeEqualToString(`list.getEntries()[2].entryType`, "mark");
+ shouldBeEqualToString(`list.getEntries()[2].name`, "mark4");
+
+ performance.mark("mark-before-disconnect");
+ performance.measure("measure-before-disconnect");
+
+ observer.disconnect();
+
+ performance.mark("mark-after-disconnect");
+ performance.measure("measure-after-disconnect");
+
+ setTimeout(finishJSTest, 50);
+ } else if (callbackCount === 5)
+ testFailed("Should not have received another callback, the observer was disconnected");
+
+ for (let mark of list.getEntries())
+ debug(" - " + mark.name);
+});
+
+function updateObserver(entryTypes) {
+ debug("Observering: " + JSON.stringify(entryTypes));
+ observer.observe({entryTypes});
+}
+
+updateObserver(["measure"]);
+performance.mark("mark1");
+performance.measure("measure1");
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-callback-task-expected.txt b/LayoutTests/performance-api/performance-observer-callback-task-expected.txt
new file mode 100644
index 0000000..203e3a0
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-callback-task-expected.txt
@@ -0,0 +1,16 @@
+Test PerformanceObserver callback is a task, not a microtask.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS PerformanceObserver callback fired
+PASS microtaskExecuted is false
+PASS list.getEntries().length is 1
+PASS Promise microtask fired
+PASS PerformanceObserver callback fired
+PASS list.getEntries().length is 2
+PASS microtaskExecuted is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-callback-task.html b/LayoutTests/performance-api/performance-observer-callback-task.html
new file mode 100644
index 0000000..616ea2f
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-callback-task.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Test PerformanceObserver callback is a task, not a microtask.");
+window.jsTestIsAsync = true;
+
+window.microtaskExecuted = false;
+window.callbackCount = 0;
+window.list = null;
+
+let observer = new PerformanceObserver((list) => {
+ window.list = list;
+ callbackCount++;
+
+ testPassed("PerformanceObserver callback fired");
+
+ if (callbackCount === 1) {
+ shouldBeFalse(`microtaskExecuted`);
+ shouldBe(`list.getEntries().length`, `1`);
+ performance.mark("mark2");
+ Promise.resolve().then(() => {
+ testPassed("Promise microtask fired");
+ microtaskExecuted = true;
+ performance.mark("mark3");
+ });
+ return;
+ }
+
+ if (callbackCount === 2) {
+ shouldBe(`list.getEntries().length`, `2`);
+ shouldBeTrue(`microtaskExecuted`);
+ finishJSTest();
+ return;
+ }
+
+ testFailed("Callback should not have fired again");
+});
+
+observer.observe({entryTypes: ["mark"]});
+performance.mark("mark1");
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-entry-sort-expected.txt b/LayoutTests/performance-api/performance-observer-entry-sort-expected.txt
new file mode 100644
index 0000000..cbcbb41
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-entry-sort-expected.txt
@@ -0,0 +1,27 @@
+Test PerformanceObserver mutating itself while in its callback.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS PerformanceObserver callback fired
+PASS list.getEntries().length is 8
+PASS sorted is true
+PASS list.getEntries()[0].entryType is "measure"
+PASS list.getEntries()[1].entryType is "measure"
+PASS list.getEntries()[2].entryType is "measure"
+PASS list.getEntries()[3].entryType is "mark"
+PASS list.getEntries()[3].name is "mark1"
+PASS list.getEntries()[7].entryType is "mark"
+PASS list.getEntries()[7].name is "mark3"
+PASS list.getEntries()[0].name is "measure1"
+PASS list.getEntries()[1].name is "measure2"
+PASS list.getEntries()[2].name is "measure3"
+PASS list.getEntries()[3].name is "mark1"
+PASS list.getEntries()[4].name is "mark2"
+PASS list.getEntries()[5].name is "measure-matching-mark2-1"
+PASS list.getEntries()[6].name is "measure-matching-mark2-2"
+PASS list.getEntries()[7].name is "mark3"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-entry-sort.html b/LayoutTests/performance-api/performance-observer-entry-sort.html
new file mode 100644
index 0000000..6eb1930
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-entry-sort.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Test PerformanceObserver mutating itself while in its callback.");
+window.jsTestIsAsync = true;
+
+function wait() {
+ let now = performance.now();
+ while (now === performance.now())
+ continue;
+}
+
+let observer = new PerformanceObserver((list) => {
+ testPassed("PerformanceObserver callback fired");
+
+ window.list = list;
+ window.sorted = true;
+
+ let entries = list.getEntries();
+ let lastStartTime = entries[0].startTime;
+ for (let i = 0; i < entries.length; ++i) {
+ let currentStartTime = entries[i].startTime;
+ if (lastStartTime > currentStartTime) {
+ sorted = false;
+ break;
+ }
+ lastStartTime = currentStartTime;
+ }
+
+ shouldBe(`list.getEntries().length`, `8`);
+ shouldBeTrue(`sorted`);
+
+ // Regardless of how identical start time elements are sorted,
+ // the first three values should be measures1,2,3 with 0 start time,
+ // followed by mark1. The last entry will be mark3.
+ shouldBeEqualToString(`list.getEntries()[0].entryType`, "measure");
+ shouldBeEqualToString(`list.getEntries()[1].entryType`, "measure");
+ shouldBeEqualToString(`list.getEntries()[2].entryType`, "measure");
+ shouldBeEqualToString(`list.getEntries()[3].entryType`, "mark");
+ shouldBeEqualToString(`list.getEntries()[3].name`, "mark1");
+ shouldBeEqualToString(`list.getEntries()[7].entryType`, "mark");
+ shouldBeEqualToString(`list.getEntries()[7].name`, "mark3");
+
+ // WebKit wants a stable sort based matching user expectations.
+ // The spec is currently imprecise here.
+ // <https://github.com/w3c/performance-timeline/issues/67>
+ shouldBeEqualToString(`list.getEntries()[0].name`, "measure1");
+ shouldBeEqualToString(`list.getEntries()[1].name`, "measure2");
+ shouldBeEqualToString(`list.getEntries()[2].name`, "measure3");
+ shouldBeEqualToString(`list.getEntries()[3].name`, "mark1");
+ shouldBeEqualToString(`list.getEntries()[4].name`, "mark2");
+ shouldBeEqualToString(`list.getEntries()[5].name`, "measure-matching-mark2-1");
+ shouldBeEqualToString(`list.getEntries()[6].name`, "measure-matching-mark2-2");
+ shouldBeEqualToString(`list.getEntries()[7].name`, "mark3");
+
+ finishJSTest();
+});
+
+observer.observe({entryTypes: ["mark", "measure"]});
+
+performance.mark("mark1");
+performance.measure("measure1");
+wait(); // Ensure mark1 !== mark2 startTime by making sure performance.now advances.
+performance.mark("mark2");
+performance.measure("measure2");
+performance.measure("measure-matching-mark2-1", "mark2");
+wait(); // Ensure mark2 !== mark3 startTime by making sure performance.now advances.
+performance.mark("mark3");
+performance.measure("measure3");
+performance.measure("measure-matching-mark2-2", "mark2");
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-exception-expected.txt b/LayoutTests/performance-api/performance-observer-exception-expected.txt
new file mode 100644
index 0000000..0e56362
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-exception-expected.txt
@@ -0,0 +1,13 @@
+CONSOLE MESSAGE: line 23: EXCEPTION MESSAGE IN CALLBACK
+Ensure PerformanceObserver Exceptions are propogated.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PerformanceObserver callback fired
+PASS onerror: EXCEPTION MESSAGE IN CALLBACK
+PASS onerror: TypeError: Cannot call a class constructor without |new|
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-exception.html b/LayoutTests/performance-api/performance-observer-exception.html
new file mode 100644
index 0000000..6a11cca
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-exception.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Ensure PerformanceObserver Exceptions are propogated.");
+window.jsTestIsAsync = true;
+
+let seenExceptions = 0;
+
+window.onerror = function(message) {
+ testPassed("onerror: " + message);
+
+ seenExceptions++;
+ if (seenExceptions === 2)
+ finishJSTest();
+}
+
+let observer1 = new PerformanceObserver((list) => {
+ debug("PerformanceObserver callback fired");
+ throw "EXCEPTION MESSAGE IN CALLBACK";
+});
+
+class MyObserver {};
+let observer2 = new PerformanceObserver(MyObserver);
+
+observer1.observe({entryTypes: ["mark"]});
+observer2.observe({entryTypes: ["mark"]});
+performance.mark("mark1");
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-nested-expected.txt b/LayoutTests/performance-api/performance-observer-nested-expected.txt
new file mode 100644
index 0000000..ebfe056
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-nested-expected.txt
@@ -0,0 +1,23 @@
+Test PerformanceObserver handling of entries added while in callback.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS PerformanceObserver 1 callback fired
+PASS list.getEntries().length is 1
+ - mark1
+PASS PerformanceObserver 2 callback fired
+PASS list.getEntries().length is 2
+ - mark1
+ - mark2
+PASS PerformanceObserver 1 callback fired
+PASS list.getEntries().length is 2
+ - mark2
+ - mark3
+PASS PerformanceObserver 2 callback fired
+PASS list.getEntries().length is 1
+ - mark3
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-nested.html b/LayoutTests/performance-api/performance-observer-nested.html
new file mode 100644
index 0000000..6b8804f
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-nested.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Test PerformanceObserver handling of entries added while in callback.");
+window.jsTestIsAsync = true;
+
+window.callbackCount = 0;
+window.list = null;
+
+let observer1 = new PerformanceObserver((list) => {
+ window.list = list;
+ callbackCount++;
+ testPassed("PerformanceObserver 1 callback fired");
+
+ if (callbackCount === 1) {
+ performance.mark("mark2");
+ shouldBe(`list.getEntries().length`, `1`);
+ } else if (callbackCount === 3) {
+ shouldBe(`list.getEntries().length`, `2`);
+ } else {
+ testFailed("Unexpected callback count");
+ finishJSTest();
+ }
+
+ for (let mark of list.getEntries())
+ debug(" - " + mark.name);
+});
+
+let observer2 = new PerformanceObserver((list) => {
+ window.list = list;
+ callbackCount++;
+ testPassed("PerformanceObserver 2 callback fired");
+
+ if (callbackCount === 2) {
+ performance.mark("mark3");
+ shouldBe(`list.getEntries().length`, `2`);
+ } else if (callbackCount === 4) {
+ shouldBe(`list.getEntries().length`, `1`);
+ } else {
+ testFailed("Unexpected callback count");
+ finishJSTest();
+ }
+
+ for (let mark of list.getEntries())
+ debug(" - " + mark.name);
+
+ if (callbackCount === 4)
+ finishJSTest();
+});
+
+observer1.observe({entryTypes: ["mark"]});
+observer2.observe({entryTypes: ["mark"]});
+performance.mark("mark1");
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-order-expected.txt b/LayoutTests/performance-api/performance-observer-order-expected.txt
new file mode 100644
index 0000000..9a114d2
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-order-expected.txt
@@ -0,0 +1,21 @@
+Ensure PerformanceObservers are notified in order of registration (observe).
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS PerformanceObserver 1 callback fired
+PASS count is 1
+PASS PerformanceObserver 2 callback fired
+PASS count is 2
+PASS PerformanceObserver 3 callback fired
+PASS count is 3
+PASS PerformanceObserver 4 callback fired
+PASS count is 4
+PASS PerformanceObserver 5 callback fired
+PASS count is 5
+PASS PerformanceObserver 6 callback fired
+PASS count is 6
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-order.html b/LayoutTests/performance-api/performance-observer-order.html
new file mode 100644
index 0000000..45d26cd
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-order.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Ensure PerformanceObservers are notified in order of registration (observe).");
+window.jsTestIsAsync = true;
+
+window.count = 0;
+
+let observer1 = new PerformanceObserver((list) => {
+ count++;
+ testPassed("PerformanceObserver 1 callback fired");
+ shouldBe(`count`, `1`);
+});
+
+let observer2 = new PerformanceObserver((list) => {
+ count++;
+ testPassed("PerformanceObserver 2 callback fired");
+ shouldBe(`count`, `2`);
+});
+
+// Ensure creation doesn't impact order.
+let observerSpecial = new PerformanceObserver((list) => {
+ count++;
+ testPassed("PerformanceObserver 6 callback fired");
+ shouldBe(`count`, `6`);
+ finishJSTest();
+});
+
+let observer3 = new PerformanceObserver((list) => {
+ count++;
+ testPassed("PerformanceObserver 3 callback fired");
+ shouldBe(`count`, `3`);
+});
+
+let observer4 = new PerformanceObserver((list) => {
+ count++;
+ testPassed("PerformanceObserver 4 callback fired");
+ shouldBe(`count`, `4`);
+});
+
+let observer5 = new PerformanceObserver((list) => {
+ count++;
+ testPassed("PerformanceObserver 5 callback fired");
+ shouldBe(`count`, `5`);
+});
+
+// Register all in order.
+for (let o of [observer1, observer2, observerSpecial, observer3, observer4, observer5])
+ o.observe({entryTypes: ["mark"]});
+
+// Unregister and re-register moves this to the back of the line.
+observerSpecial.disconnect();
+observerSpecial.observe({entryTypes: ["mark"]});
+
+performance.mark("mark1");
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-observer-periodic-expected.txt b/LayoutTests/performance-api/performance-observer-periodic-expected.txt
new file mode 100644
index 0000000..550fca6
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-periodic-expected.txt
@@ -0,0 +1,16 @@
+Ensure PerformanceObserver callback fires for all observed entries between observe/disconnect calls.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PerformanceObserver callback fired: 1 entries
+PASS mark1
+PerformanceObserver callback fired: 2 entries
+PASS mark2
+PASS mark3
+PerformanceObserver callback fired: 1 entries
+PASS mark7
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-observer-periodic.html b/LayoutTests/performance-api/performance-observer-periodic.html
new file mode 100644
index 0000000..38746b3
--- /dev/null
+++ b/LayoutTests/performance-api/performance-observer-periodic.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Ensure PerformanceObserver callback fires for all observed entries between observe/disconnect calls.");
+window.jsTestIsAsync = true;
+
+let shouldEnd = false;
+
+let observer = new PerformanceObserver((list) => {
+ debug("PerformanceObserver callback fired: " + list.getEntries().length + " entries");
+ for (let mark of list.getEntries()) {
+ if (mark.name === "mark5" || mark.name === "mark6")
+ testFailed("Should not have observed " + mark.name);
+ else
+ testPassed(mark.name);
+ }
+ if (shouldEnd)
+ finishJSTest();
+});
+observer.observe({entryTypes: ["mark"]});
+
+// ---
+
+performance.mark("mark1");
+
+setTimeout(() => {
+ performance.mark("mark2");
+ performance.mark("mark3");
+}, 50);
+
+setTimeout(() => {
+ performance.mark("mark4");
+ observer.disconnect();
+ performance.mark("mark5"); // NOT seen.
+}, 100);
+
+setTimeout(() => {
+ performance.mark("mark6"); // NOT seen.
+ observer.observe({entryTypes: ["mark"]});
+ performance.mark("mark7");
+ shouldEnd = true;
+}, 150);
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/performance-timeline-api-expected.txt b/LayoutTests/performance-api/performance-timeline-api-expected.txt
new file mode 100644
index 0000000..e3f2076
--- /dev/null
+++ b/LayoutTests/performance-api/performance-timeline-api-expected.txt
@@ -0,0 +1,48 @@
+Basic Interface test for performance-timeline APIs.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PerformanceEntry
+PASS PerformanceEntry is defined.
+PASS "name" in PerformanceEntry.prototype is true
+PASS "entryType" in PerformanceEntry.prototype is true
+PASS "startTime" in PerformanceEntry.prototype is true
+PASS "duration" in PerformanceEntry.prototype is true
+PASS new PerformanceEntry() threw exception TypeError: function is not a constructor (evaluating 'new PerformanceEntry()').
+
+Performance extensions
+PASS Performance.prototype.getEntries is defined.
+PASS Performance.prototype.getEntriesByType is defined.
+PASS Performance.prototype.getEntriesByName is defined.
+PASS performance.getEntries() instanceof Array is true
+PASS performance.getEntries().length === 0 is true
+PASS performance.mark("test"); did not throw exception.
+PASS performance.getEntries().length === 1 is true
+PASS performance.getEntries()[0] instanceof PerformanceEntry is true
+PASS performance.getEntries()[0].name is "test"
+PASS performance.getEntries()[0].entryType is "mark"
+PASS typeof performance.getEntries()[0].startTime === "number" is true
+PASS typeof performance.getEntries()[0].duration === "number" is true
+PASS performance.getEntriesByType() threw exception TypeError: Not enough arguments.
+PASS performance.getEntriesByType("not-real").length === 0 is true
+PASS performance.getEntriesByType("mark").length === 1 is true
+PASS performance.getEntriesByType("mark")[0] instanceof PerformanceEntry is true
+PASS performance.getEntriesByType("mark")[0].name is "test"
+PASS performance.getEntriesByType("mark")[0].entryType is "mark"
+PASS typeof performance.getEntriesByType("mark")[0].startTime === "number" is true
+PASS typeof performance.getEntriesByType("mark")[0].duration === "number" is true
+PASS performance.getEntriesByName() threw exception TypeError: Not enough arguments.
+PASS performance.getEntriesByName("not-real").length === 0 is true
+PASS performance.getEntriesByName("test").length === 1 is true
+PASS performance.getEntriesByName("test")[0] instanceof PerformanceEntry is true
+PASS performance.getEntriesByName("test")[0].name is "test"
+PASS performance.getEntriesByName("test")[0].entryType is "mark"
+PASS typeof performance.getEntriesByName("test")[0].startTime === "number" is true
+PASS typeof performance.getEntriesByName("test")[0].duration === "number" is true
+PASS performance.getEntriesByName("test", "not-real").length === 0 is true
+PASS performance.getEntriesByName("test", "mark").length === 1 is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/performance-timeline-api.html b/LayoutTests/performance-api/performance-timeline-api.html
new file mode 100644
index 0000000..e173919
--- /dev/null
+++ b/LayoutTests/performance-api/performance-timeline-api.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Basic Interface test for performance-timeline APIs.");
+
+debug("PerformanceEntry");
+shouldBeDefined("PerformanceEntry");
+shouldBeTrue(`"name" in PerformanceEntry.prototype`);
+shouldBeTrue(`"entryType" in PerformanceEntry.prototype`);
+shouldBeTrue(`"startTime" in PerformanceEntry.prototype`);
+shouldBeTrue(`"duration" in PerformanceEntry.prototype`);
+shouldThrow(`new PerformanceEntry()`);
+
+// NOTE: The APIs below may be going away. Replaced by PerformanceObserver.
+
+debug("");
+debug("Performance extensions");
+shouldBeDefined(`Performance.prototype.getEntries`);
+shouldBeDefined(`Performance.prototype.getEntriesByType`);
+shouldBeDefined(`Performance.prototype.getEntriesByName`);
+
+shouldBeTrue(`performance.getEntries() instanceof Array`);
+shouldBeTrue(`performance.getEntries().length === 0`);
+shouldNotThrow(`performance.mark("test");`);
+shouldBeTrue(`performance.getEntries().length === 1`);
+shouldBeTrue(`performance.getEntries()[0] instanceof PerformanceEntry`);
+shouldBeEqualToString(`performance.getEntries()[0].name`, "test");
+shouldBeEqualToString(`performance.getEntries()[0].entryType`, "mark");
+shouldBeTrue(`typeof performance.getEntries()[0].startTime === "number"`);
+shouldBeTrue(`typeof performance.getEntries()[0].duration === "number"`);
+
+shouldThrow(`performance.getEntriesByType()`);
+shouldBeTrue(`performance.getEntriesByType("not-real").length === 0`);
+shouldBeTrue(`performance.getEntriesByType("mark").length === 1`);
+shouldBeTrue(`performance.getEntriesByType("mark")[0] instanceof PerformanceEntry`);
+shouldBeEqualToString(`performance.getEntriesByType("mark")[0].name`, "test");
+shouldBeEqualToString(`performance.getEntriesByType("mark")[0].entryType`, "mark");
+shouldBeTrue(`typeof performance.getEntriesByType("mark")[0].startTime === "number"`);
+shouldBeTrue(`typeof performance.getEntriesByType("mark")[0].duration === "number"`);
+
+shouldThrow(`performance.getEntriesByName()`);
+shouldBeTrue(`performance.getEntriesByName("not-real").length === 0`);
+shouldBeTrue(`performance.getEntriesByName("test").length === 1`);
+shouldBeTrue(`performance.getEntriesByName("test")[0] instanceof PerformanceEntry`);
+shouldBeEqualToString(`performance.getEntriesByName("test")[0].name`, "test");
+shouldBeEqualToString(`performance.getEntriesByName("test")[0].entryType`, "mark");
+shouldBeTrue(`typeof performance.getEntriesByName("test")[0].startTime === "number"`);
+shouldBeTrue(`typeof performance.getEntriesByName("test")[0].duration === "number"`);
+shouldBeTrue(`performance.getEntriesByName("test", "not-real").length === 0`);
+shouldBeTrue(`performance.getEntriesByName("test", "mark").length === 1`);
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>