Skip to content

Commit

Permalink
feat: implement web performance api
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeChampion committed Jun 12, 2023
1 parent 5ba8177 commit ddfe11e
Show file tree
Hide file tree
Showing 10 changed files with 483 additions and 8 deletions.
200 changes: 200 additions & 0 deletions integration-tests/js-compute/fixtures/performance/bin/index.js
@@ -0,0 +1,200 @@
/// <reference path="../../../../../types/index.d.ts" />
/* eslint-env serviceworker */

import { routes } from "../../../test-harness.js";
import { pass, assert, assertThrows } from "../../../assertions.js";

let error;
routes.set("/Performance/interface", () => {
let actual = Reflect.ownKeys(Performance)
let expected = ["prototype","length","name"]
error = assert(actual, expected, `Reflect.ownKeys(Performance)`)
if (error) { return error }

// Check the prototype descriptors are correct
{
actual = Reflect.getOwnPropertyDescriptor(Performance, 'prototype')
expected = {
"value": Performance.prototype,
"writable": false,
"enumerable": false,
"configurable": false
}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'prototype')`)
if (error) { return error }
}

// Check the constructor function's defined parameter length is correct
{
actual = Reflect.getOwnPropertyDescriptor(Performance, 'length')
expected = {
"value": 0,
"writable": false,
"enumerable": false,
"configurable": true
}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'length')`)
if (error) { return error }
}

// Check the constructor function's name is correct
{
actual = Reflect.getOwnPropertyDescriptor(Performance, 'name')
expected = {
"value": "Performance",
"writable": false,
"enumerable": false,
"configurable": true
}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'name')`)
if (error) { return error }
}

// Check the prototype has the correct keys
{
actual = Reflect.ownKeys(Performance.prototype)
expected = ["constructor","timeOrigin","now", Symbol.toStringTag]
error = assert(actual, expected, `Reflect.ownKeys(Performance.prototype)`)
if (error) { return error }
}

// Check the constructor on the prototype is correct
{
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype, 'constructor')
expected = { "writable": true, "enumerable": false, "configurable": true, value: Performance.prototype.constructor }
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype, 'constructor')`)
if (error) { return error }

error = assert(typeof Performance.prototype.constructor, 'function', `typeof Performance.prototype.constructor`)
if (error) { return error }

actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'length')
expected = {
"value": 0,
"writable": false,
"enumerable": false,
"configurable": true
}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'length')`)
if (error) { return error }

actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'name')
expected = {
"value": "Performance",
"writable": false,
"enumerable": false,
"configurable": true
}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.constructor, 'name')`)
if (error) { return error }
}

// Check the Symbol.toStringTag on the prototype is correct
{
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype, Symbol.toStringTag)
expected = {"value":"performance","writable":false,"enumerable":false,"configurable":true}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype, [Symbol.toStringTag])`)
if (error) { return error }

error = assert(typeof Performance.prototype[Symbol.toStringTag], 'string', `typeof Performance.prototype[Symbol.toStringTag]`)
if (error) { return error }
}


// Check the timeOrigin property is correct
{
const descriptors = Reflect.getOwnPropertyDescriptor(Performance.prototype, 'timeOrigin')
expected = { "enumerable": true, "configurable": true }
error = assert(descriptors.enumerable, true, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').enumerable`)
error = assert(descriptors.configurable, true, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').configurable`)
error = assert(descriptors.value, undefined, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').value`)
error = assert(descriptors.set, undefined, `Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').set`)
error = assert(typeof descriptors.get, 'function', `typeof Reflect.getOwnPropertyDescriptor(Performance, 'timeOrigin').get`)
if (error) { return error }

error = assert(typeof Performance.prototype.timeOrigin, 'number', `typeof Performance.prototype.timeOrigin`)
if (error) { return error }
}

// Check the now property is correct
{
actual = Reflect.getOwnPropertyDescriptor(Performance.prototype, 'now')
expected = { "writable": true, "enumerable": true, "configurable": true, value: Performance.prototype.now }
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance, 'now')`)
if (error) { return error }

error = assert(typeof Performance.prototype.now, 'function', `typeof Performance.prototype.now`)
if (error) { return error }

actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'length')
expected = {
"value": 0,
"writable": false,
"enumerable": false,
"configurable": true
}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'length')`)
if (error) { return error }

actual = Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'name')
expected = {
"value": "now",
"writable": false,
"enumerable": false,
"configurable": true
}
error = assert(actual, expected, `Reflect.getOwnPropertyDescriptor(Performance.prototype.now, 'name')`)
if (error) { return error }
}

return pass('ok')
});

routes.set("/globalThis.performance", () => {
error = assert(globalThis.performance instanceof Performance, true, `globalThis.performance instanceof Performance`)
if (error) { return error }

return pass('ok')
});

routes.set("/globalThis.performance/now", () => {
error = assertThrows(() => new performance.now())
if (error) { return error }

error = assert(typeof performance.now(), 'number')
if (error) { return error }

error = assert(performance.now() > 0, true)
if (error) { return error }

error = assert(Number.isNaN(performance.now()), false)
if (error) { return error }

error = assert(Number.isFinite(performance.now()), true)
if (error) { return error }

error = assert(performance.now() < Date.now(), true)
if (error) { return error }

return pass('ok')
});

routes.set("/globalThis.performance/timeOrigin", () => {
error = assert(typeof performance.timeOrigin, 'number')
if (error) { return error }

error = assert(performance.timeOrigin > 0, true)
if (error) { return error }

error = assert(Number.isNaN(performance.timeOrigin), false)
if (error) { return error }

error = assert(Number.isFinite(performance.timeOrigin), true)
if (error) { return error }


error = assert(performance.timeOrigin < Date.now(), true)
if (error) { return error }

return pass('ok')
});
46 changes: 46 additions & 0 deletions integration-tests/js-compute/fixtures/performance/tests.json
@@ -0,0 +1,46 @@
{
"GET /Performance/interface": {
"environments": [ "viceroy" ],
"downstream_request": {
"method": "GET",
"pathname": "/Performance/interface"
},
"downstream_response": {
"status": 200,
"body": "ok"
}
},
"GET /globalThis.performance": {
"environments": [ "viceroy" ],
"downstream_request": {
"method": "GET",
"pathname": "/globalThis.performance"
},
"downstream_response": {
"status": 200,
"body": "ok"
}
},
"GET /globalThis.performance/now": {
"environments": [ "viceroy" ],
"downstream_request": {
"method": "GET",
"pathname": "/globalThis.performance/now"
},
"downstream_response": {
"status": 200,
"body": "ok"
}
},
"GET /globalThis.performance/timeOrigin": {
"environments": [ "viceroy" ],
"downstream_request": {
"method": "GET",
"pathname": "/globalThis.performance/timeOrigin"
},
"downstream_response": {
"status": 200,
"body": "ok"
}
}
}
67 changes: 67 additions & 0 deletions runtime/js-compute-runtime/builtins/shared/performance.cpp
@@ -0,0 +1,67 @@
#include "performance.h"
#include "builtin.h"
#include <chrono>

namespace {
using FpMilliseconds = std::chrono::duration<float, std::chrono::milliseconds::period>;
} // namespace

namespace builtins {

std::optional<std::chrono::steady_clock::time_point> Performance::timeOrigin;

// https://w3c.github.io/hr-time/#dom-performance-now
bool Performance::now(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0);
MOZ_ASSERT(builtins::Performance::timeOrigin.has_value());

auto finish = std::chrono::high_resolution_clock::now();
auto duration = FpMilliseconds(finish - builtins::Performance::timeOrigin.value()).count();

JS::RootedValue elapsed(cx, JS::Float32Value(duration));
args.rval().set(elapsed);
return true;
}

bool Performance::timeOrigin_get(JSContext *cx, unsigned argc, JS::Value *vp) {
MOZ_ASSERT(builtins::Performance::timeOrigin.has_value());
METHOD_HEADER(0);
auto time = FpMilliseconds(builtins::Performance::timeOrigin.value().time_since_epoch()).count();
JS::RootedValue elapsed(cx, JS::Float32Value(time));
args.rval().set(elapsed);
return true;
}

const JSFunctionSpec Performance::methods[] = {JS_FN("now", now, 0, JSPROP_ENUMERATE), JS_FS_END};

const JSPropertySpec Performance::properties[] = {
JS_PSG("timeOrigin", timeOrigin_get, JSPROP_ENUMERATE),
JS_STRING_SYM_PS(toStringTag, "performance", JSPROP_READONLY), JS_PS_END};

const JSFunctionSpec Performance::static_methods[] = {JS_FS_END};
const JSPropertySpec Performance::static_properties[] = {JS_PS_END};

bool Performance::create(JSContext *cx, JS::HandleObject global) {
JS::RootedObject performance(cx, JS_NewObjectWithGivenProto(cx, &builtins::Performance::class_,
builtins::Performance::proto_obj));
if (!performance) {
return false;
}
if (!JS_DefineProperty(cx, global, "performance", performance, 0)) {
return false;
}
if (!JS_DefineProperties(cx, performance, properties)) {
return false;
}
return JS_DefineFunctions(cx, performance, methods);
}

bool Performance::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
JS_ReportErrorUTF8(cx, "%s can't be instantiated directly", class_name);
return false;
}

bool Performance::init_class(JSContext *cx, JS::HandleObject global) {
return init_class_impl(cx, global);
}
} // namespace builtins
30 changes: 30 additions & 0 deletions runtime/js-compute-runtime/builtins/shared/performance.h
@@ -0,0 +1,30 @@
#ifndef JS_COMPUTE_RUNTIME_BUILTIN_PERFORMANCE_H
#define JS_COMPUTE_RUNTIME_BUILTIN_PERFORMANCE_H

#include "builtin.h"

namespace builtins {

class Performance : public BuiltinImpl<Performance> {
private:
public:
static constexpr const char *class_name = "Performance";
static const int ctor_length = 0;
enum Slots { Count };
static const JSFunctionSpec methods[];
static const JSFunctionSpec static_methods[];
static const JSPropertySpec properties[];
static const JSPropertySpec static_properties[];
static std::optional<std::chrono::steady_clock::time_point> timeOrigin;

static bool now(JSContext *cx, unsigned argc, JS::Value *vp);
static bool timeOrigin_get(JSContext *cx, unsigned argc, JS::Value *vp);

static bool create(JSContext *cx, JS::HandleObject global);
static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp);
static bool init_class(JSContext *cx, JS::HandleObject global);
};

} // namespace builtins

#endif
7 changes: 7 additions & 0 deletions runtime/js-compute-runtime/js-compute-builtins.cpp
Expand Up @@ -57,6 +57,7 @@
#include "builtins/request-response.h"
#include "builtins/secret-store.h"
#include "builtins/shared/console.h"
#include "builtins/shared/performance.h"
#include "builtins/shared/text-decoder.h"
#include "builtins/shared/text-encoder.h"
#include "builtins/shared/url.h"
Expand Down Expand Up @@ -1350,6 +1351,12 @@ bool define_fastly_sys(JSContext *cx, HandleObject global, FastlyOptions options
if (!builtins::SimpleCacheEntry::init_class(cx, global)) {
return false;
}
if (!builtins::Performance::init_class(cx, global)) {
return false;
}
if (!builtins::Performance::create(cx, global)) {
return false;
}

pending_async_tasks = new JS::PersistentRootedObjectVector(cx);

Expand Down

0 comments on commit ddfe11e

Please sign in to comment.