Skip to content

Commit ddfe11e

Browse files
Jake ChampionJakeChampion
authored andcommitted
feat: implement web performance api
1 parent 5ba8177 commit ddfe11e

File tree

10 files changed

+483
-8
lines changed

10 files changed

+483
-8
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/// <reference path="../../../../../types/index.d.ts" />
2+
/* eslint-env serviceworker */
3+
4+
import { routes } from "../../../test-harness.js";
5+
import { pass, assert, assertThrows } from "../../../assertions.js";
6+
7+
let error;
8+
routes.set("/Performance/interface", () => {
9+
let actual = Reflect.ownKeys(Performance)
10+
let expected = ["prototype","length","name"]
11+
error = assert(actual, expected, `Reflect.ownKeys(Performance)`)
12+
if (error) { return error }
13+
14+
// Check the prototype descriptors are correct
15+
{
16+
actual = Reflect.getOwnPropertyDescriptor(Performance, 'prototype')
17+
expected = {
18+
"value": Performance.prototype,
19+
"writable": false,
20+
"enumerable": false,
21+
"configurable": false
22+
}
23+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'prototype')`)
24+
if (error) { return error }
25+
}
26+
27+
// Check the constructor function's defined parameter length is correct
28+
{
29+
actual = Reflect.getOwnPropertyDescriptor(Performance, 'length')
30+
expected = {
31+
"value": 0,
32+
"writable": false,
33+
"enumerable": false,
34+
"configurable": true
35+
}
36+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'length')`)
37+
if (error) { return error }
38+
}
39+
40+
// Check the constructor function's name is correct
41+
{
42+
actual = Reflect.getOwnPropertyDescriptor(Performance, 'name')
43+
expected = {
44+
"value": "Performance",
45+
"writable": false,
46+
"enumerable": false,
47+
"configurable": true
48+
}
49+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'name')`)
50+
if (error) { return error }
51+
}
52+
53+
// Check the prototype has the correct keys
54+
{
55+
actual = Reflect.ownKeys(Performance.prototype)
56+
expected = ["constructor","timeOrigin","now", Symbol.toStringTag]
57+
error = assert(actual, expected, `Reflect.ownKeys(Performance.prototype)`)
58+
if (error) { return error }
59+
}
60+
61+
// Check the constructor on the prototype is correct
62+
{
63+
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype, 'constructor')
64+
expected = { "writable": true, "enumerable": false, "configurable": true, value: Performance.prototype.constructor }
65+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype, 'constructor')`)
66+
if (error) { return error }
67+
68+
error = assert(typeof Performance.prototype.constructor, 'function', `typeof Performance.prototype.constructor`)
69+
if (error) { return error }
70+
71+
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'length')
72+
expected = {
73+
"value": 0,
74+
"writable": false,
75+
"enumerable": false,
76+
"configurable": true
77+
}
78+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'length')`)
79+
if (error) { return error }
80+
81+
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'name')
82+
expected = {
83+
"value": "Performance",
84+
"writable": false,
85+
"enumerable": false,
86+
"configurable": true
87+
}
88+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'name')`)
89+
if (error) { return error }
90+
}
91+
92+
// Check the Symbol.toStringTag on the prototype is correct
93+
{
94+
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype, Symbol.toStringTag)
95+
expected = {"value":"performance","writable":false,"enumerable":false,"configurable":true}
96+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype, [Symbol.toStringTag])`)
97+
if (error) { return error }
98+
99+
error = assert(typeof Performance.prototype[Symbol.toStringTag], 'string', `typeof Performance.prototype[Symbol.toStringTag]`)
100+
if (error) { return error }
101+
}
102+
103+
104+
// Check the timeOrigin property is correct
105+
{
106+
const descriptors = Reflect.getOwnPropertyDescriptor(Performance.prototype, 'timeOrigin')
107+
expected = { "enumerable": true, "configurable": true }
108+
error = assert(descriptors.enumerable, true, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').enumerable`)
109+
error = assert(descriptors.configurable, true, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').configurable`)
110+
error = assert(descriptors.value, undefined, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').value`)
111+
error = assert(descriptors.set, undefined, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').set`)
112+
error = assert(typeof descriptors.get, 'function', `typeof Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').get`)
113+
if (error) { return error }
114+
115+
error = assert(typeof Performance.prototype.timeOrigin, 'number', `typeof Performance.prototype.timeOrigin`)
116+
if (error) { return error }
117+
}
118+
119+
// Check the now property is correct
120+
{
121+
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype, 'now')
122+
expected = { "writable": true, "enumerable": true, "configurable": true, value: Performance.prototype.now }
123+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'now')`)
124+
if (error) { return error }
125+
126+
error = assert(typeof Performance.prototype.now, 'function', `typeof Performance.prototype.now`)
127+
if (error) { return error }
128+
129+
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'length')
130+
expected = {
131+
"value": 0,
132+
"writable": false,
133+
"enumerable": false,
134+
"configurable": true
135+
}
136+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'length')`)
137+
if (error) { return error }
138+
139+
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'name')
140+
expected = {
141+
"value": "now",
142+
"writable": false,
143+
"enumerable": false,
144+
"configurable": true
145+
}
146+
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'name')`)
147+
if (error) { return error }
148+
}
149+
150+
return pass('ok')
151+
});
152+
153+
routes.set("/globalThis.performance", () => {
154+
error = assert(globalThis.performance instanceof Performance, true, `globalThis.performance instanceof Performance`)
155+
if (error) { return error }
156+
157+
return pass('ok')
158+
});
159+
160+
routes.set("/globalThis.performance/now", () => {
161+
error = assertThrows(() => new performance.now())
162+
if (error) { return error }
163+
164+
error = assert(typeof performance.now(), 'number')
165+
if (error) { return error }
166+
167+
error = assert(performance.now() > 0, true)
168+
if (error) { return error }
169+
170+
error = assert(Number.isNaN(performance.now()), false)
171+
if (error) { return error }
172+
173+
error = assert(Number.isFinite(performance.now()), true)
174+
if (error) { return error }
175+
176+
error = assert(performance.now() < Date.now(), true)
177+
if (error) { return error }
178+
179+
return pass('ok')
180+
});
181+
182+
routes.set("/globalThis.performance/timeOrigin", () => {
183+
error = assert(typeof performance.timeOrigin, 'number')
184+
if (error) { return error }
185+
186+
error = assert(performance.timeOrigin > 0, true)
187+
if (error) { return error }
188+
189+
error = assert(Number.isNaN(performance.timeOrigin), false)
190+
if (error) { return error }
191+
192+
error = assert(Number.isFinite(performance.timeOrigin), true)
193+
if (error) { return error }
194+
195+
196+
error = assert(performance.timeOrigin < Date.now(), true)
197+
if (error) { return error }
198+
199+
return pass('ok')
200+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"GET /Performance/interface": {
3+
"environments": [ "viceroy" ],
4+
"downstream_request": {
5+
"method": "GET",
6+
"pathname": "/Performance/interface"
7+
},
8+
"downstream_response": {
9+
"status": 200,
10+
"body": "ok"
11+
}
12+
},
13+
"GET /globalThis.performance": {
14+
"environments": [ "viceroy" ],
15+
"downstream_request": {
16+
"method": "GET",
17+
"pathname": "/globalThis.performance"
18+
},
19+
"downstream_response": {
20+
"status": 200,
21+
"body": "ok"
22+
}
23+
},
24+
"GET /globalThis.performance/now": {
25+
"environments": [ "viceroy" ],
26+
"downstream_request": {
27+
"method": "GET",
28+
"pathname": "/globalThis.performance/now"
29+
},
30+
"downstream_response": {
31+
"status": 200,
32+
"body": "ok"
33+
}
34+
},
35+
"GET /globalThis.performance/timeOrigin": {
36+
"environments": [ "viceroy" ],
37+
"downstream_request": {
38+
"method": "GET",
39+
"pathname": "/globalThis.performance/timeOrigin"
40+
},
41+
"downstream_response": {
42+
"status": 200,
43+
"body": "ok"
44+
}
45+
}
46+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include "performance.h"
2+
#include "builtin.h"
3+
#include <chrono>
4+
5+
namespace {
6+
using FpMilliseconds = std::chrono::duration<float, std::chrono::milliseconds::period>;
7+
} // namespace
8+
9+
namespace builtins {
10+
11+
std::optional<std::chrono::steady_clock::time_point> Performance::timeOrigin;
12+
13+
// https://w3c.github.io/hr-time/#dom-performance-now
14+
bool Performance::now(JSContext *cx, unsigned argc, JS::Value *vp) {
15+
METHOD_HEADER(0);
16+
MOZ_ASSERT(builtins::Performance::timeOrigin.has_value());
17+
18+
auto finish = std::chrono::high_resolution_clock::now();
19+
auto duration = FpMilliseconds(finish - builtins::Performance::timeOrigin.value()).count();
20+
21+
JS::RootedValue elapsed(cx, JS::Float32Value(duration));
22+
args.rval().set(elapsed);
23+
return true;
24+
}
25+
26+
bool Performance::timeOrigin_get(JSContext *cx, unsigned argc, JS::Value *vp) {
27+
MOZ_ASSERT(builtins::Performance::timeOrigin.has_value());
28+
METHOD_HEADER(0);
29+
auto time = FpMilliseconds(builtins::Performance::timeOrigin.value().time_since_epoch()).count();
30+
JS::RootedValue elapsed(cx, JS::Float32Value(time));
31+
args.rval().set(elapsed);
32+
return true;
33+
}
34+
35+
const JSFunctionSpec Performance::methods[] = {JS_FN("now", now, 0, JSPROP_ENUMERATE), JS_FS_END};
36+
37+
const JSPropertySpec Performance::properties[] = {
38+
JS_PSG("timeOrigin", timeOrigin_get, JSPROP_ENUMERATE),
39+
JS_STRING_SYM_PS(toStringTag, "performance", JSPROP_READONLY), JS_PS_END};
40+
41+
const JSFunctionSpec Performance::static_methods[] = {JS_FS_END};
42+
const JSPropertySpec Performance::static_properties[] = {JS_PS_END};
43+
44+
bool Performance::create(JSContext *cx, JS::HandleObject global) {
45+
JS::RootedObject performance(cx, JS_NewObjectWithGivenProto(cx, &builtins::Performance::class_,
46+
builtins::Performance::proto_obj));
47+
if (!performance) {
48+
return false;
49+
}
50+
if (!JS_DefineProperty(cx, global, "performance", performance, 0)) {
51+
return false;
52+
}
53+
if (!JS_DefineProperties(cx, performance, properties)) {
54+
return false;
55+
}
56+
return JS_DefineFunctions(cx, performance, methods);
57+
}
58+
59+
bool Performance::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
60+
JS_ReportErrorUTF8(cx, "%s can't be instantiated directly", class_name);
61+
return false;
62+
}
63+
64+
bool Performance::init_class(JSContext *cx, JS::HandleObject global) {
65+
return init_class_impl(cx, global);
66+
}
67+
} // namespace builtins
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef JS_COMPUTE_RUNTIME_BUILTIN_PERFORMANCE_H
2+
#define JS_COMPUTE_RUNTIME_BUILTIN_PERFORMANCE_H
3+
4+
#include "builtin.h"
5+
6+
namespace builtins {
7+
8+
class Performance : public BuiltinImpl<Performance> {
9+
private:
10+
public:
11+
static constexpr const char *class_name = "Performance";
12+
static const int ctor_length = 0;
13+
enum Slots { Count };
14+
static const JSFunctionSpec methods[];
15+
static const JSFunctionSpec static_methods[];
16+
static const JSPropertySpec properties[];
17+
static const JSPropertySpec static_properties[];
18+
static std::optional<std::chrono::steady_clock::time_point> timeOrigin;
19+
20+
static bool now(JSContext *cx, unsigned argc, JS::Value *vp);
21+
static bool timeOrigin_get(JSContext *cx, unsigned argc, JS::Value *vp);
22+
23+
static bool create(JSContext *cx, JS::HandleObject global);
24+
static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp);
25+
static bool init_class(JSContext *cx, JS::HandleObject global);
26+
};
27+
28+
} // namespace builtins
29+
30+
#endif

runtime/js-compute-runtime/js-compute-builtins.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "builtins/request-response.h"
5858
#include "builtins/secret-store.h"
5959
#include "builtins/shared/console.h"
60+
#include "builtins/shared/performance.h"
6061
#include "builtins/shared/text-decoder.h"
6162
#include "builtins/shared/text-encoder.h"
6263
#include "builtins/shared/url.h"
@@ -1350,6 +1351,12 @@ bool define_fastly_sys(JSContext *cx, HandleObject global, FastlyOptions options
13501351
if (!builtins::SimpleCacheEntry::init_class(cx, global)) {
13511352
return false;
13521353
}
1354+
if (!builtins::Performance::init_class(cx, global)) {
1355+
return false;
1356+
}
1357+
if (!builtins::Performance::create(cx, global)) {
1358+
return false;
1359+
}
13531360

13541361
pending_async_tasks = new JS::PersistentRootedObjectVector(cx);
13551362

0 commit comments

Comments
 (0)