diff options
Diffstat (limited to 'extensions/timers')
-rw-r--r-- | extensions/timers/01_timers.js | 595 | ||||
-rw-r--r-- | extensions/timers/02_performance.js | 569 | ||||
-rw-r--r-- | extensions/timers/Cargo.toml | 28 | ||||
-rw-r--r-- | extensions/timers/README.md | 5 | ||||
-rw-r--r-- | extensions/timers/benches/timers_ops.rs | 40 | ||||
-rw-r--r-- | extensions/timers/lib.rs | 193 |
6 files changed, 0 insertions, 1430 deletions
diff --git a/extensions/timers/01_timers.js b/extensions/timers/01_timers.js deleted file mode 100644 index a00d5d9b9..000000000 --- a/extensions/timers/01_timers.js +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const { - ArrayPrototypeIndexOf, - ArrayPrototypePush, - ArrayPrototypeShift, - ArrayPrototypeSplice, - DateNow, - Error, - FunctionPrototypeBind, - Map, - MapPrototypeDelete, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - MathMax, - Number, - String, - TypeError, - } = window.__bootstrap.primordials; - - // Shamelessly cribbed from extensions/fetch/11_streams.js - class AssertionError extends Error { - constructor(msg) { - super(msg); - this.name = "AssertionError"; - } - } - - /** - * @param {unknown} cond - * @param {string=} msg - * @returns {asserts cond} - */ - function assert(cond, msg = "Assertion failed.") { - if (!cond) { - throw new AssertionError(msg); - } - } - - function opStopGlobalTimer() { - core.opSync("op_global_timer_stop"); - } - - function opStartGlobalTimer(timeout) { - return core.opSync("op_global_timer_start", timeout); - } - - async function opWaitGlobalTimer() { - await core.opAsync("op_global_timer"); - } - - function opNow() { - return core.opSync("op_now"); - } - - function sleepSync(millis = 0) { - return core.opSync("op_sleep_sync", millis); - } - - // Derived from https://github.com/vadimg/js_bintrees. MIT Licensed. - - class RBNode { - constructor(data) { - this.data = data; - this.left = null; - this.right = null; - this.red = true; - } - - getChild(dir) { - return dir ? this.right : this.left; - } - - setChild(dir, val) { - if (dir) { - this.right = val; - } else { - this.left = val; - } - } - } - - class RBTree { - #comparator = null; - #root = null; - - constructor(comparator) { - this.#comparator = comparator; - this.#root = null; - } - - /** Returns `null` if tree is empty. */ - min() { - let res = this.#root; - if (res === null) { - return null; - } - while (res.left !== null) { - res = res.left; - } - return res.data; - } - - /** Returns node `data` if found, `null` otherwise. */ - find(data) { - let res = this.#root; - while (res !== null) { - const c = this.#comparator(data, res.data); - if (c === 0) { - return res.data; - } else { - res = res.getChild(c > 0); - } - } - return null; - } - - /** returns `true` if inserted, `false` if duplicate. */ - insert(data) { - let ret = false; - - if (this.#root === null) { - // empty tree - this.#root = new RBNode(data); - ret = true; - } else { - const head = new RBNode(null); // fake tree root - - let dir = 0; - let last = 0; - - // setup - let gp = null; // grandparent - let ggp = head; // grand-grand-parent - let p = null; // parent - let node = this.#root; - ggp.right = this.#root; - - // search down - while (true) { - if (node === null) { - // insert new node at the bottom - node = new RBNode(data); - p.setChild(dir, node); - ret = true; - } else if (isRed(node.left) && isRed(node.right)) { - // color flip - node.red = true; - node.left.red = false; - node.right.red = false; - } - - // fix red violation - if (isRed(node) && isRed(p)) { - const dir2 = ggp.right === gp; - - assert(gp); - if (node === p.getChild(last)) { - ggp.setChild(dir2, singleRotate(gp, !last)); - } else { - ggp.setChild(dir2, doubleRotate(gp, !last)); - } - } - - const cmp = this.#comparator(node.data, data); - - // stop if found - if (cmp === 0) { - break; - } - - last = dir; - dir = Number(cmp < 0); // Fix type - - // update helpers - if (gp !== null) { - ggp = gp; - } - gp = p; - p = node; - node = node.getChild(dir); - } - - // update root - this.#root = head.right; - } - - // make root black - this.#root.red = false; - - return ret; - } - - /** Returns `true` if removed, `false` if not found. */ - remove(data) { - if (this.#root === null) { - return false; - } - - const head = new RBNode(null); // fake tree root - let node = head; - node.right = this.#root; - let p = null; // parent - let gp = null; // grand parent - let found = null; // found item - let dir = 1; - - while (node.getChild(dir) !== null) { - const last = dir; - - // update helpers - gp = p; - p = node; - node = node.getChild(dir); - - const cmp = this.#comparator(data, node.data); - - dir = cmp > 0; - - // save found node - if (cmp === 0) { - found = node; - } - - // push the red node down - if (!isRed(node) && !isRed(node.getChild(dir))) { - if (isRed(node.getChild(!dir))) { - const sr = singleRotate(node, dir); - p.setChild(last, sr); - p = sr; - } else if (!isRed(node.getChild(!dir))) { - const sibling = p.getChild(!last); - if (sibling !== null) { - if ( - !isRed(sibling.getChild(!last)) && - !isRed(sibling.getChild(last)) - ) { - // color flip - p.red = false; - sibling.red = true; - node.red = true; - } else { - assert(gp); - const dir2 = gp.right === p; - - if (isRed(sibling.getChild(last))) { - gp.setChild(dir2, doubleRotate(p, last)); - } else if (isRed(sibling.getChild(!last))) { - gp.setChild(dir2, singleRotate(p, last)); - } - - // ensure correct coloring - const gpc = gp.getChild(dir2); - assert(gpc); - gpc.red = true; - node.red = true; - assert(gpc.left); - gpc.left.red = false; - assert(gpc.right); - gpc.right.red = false; - } - } - } - } - } - - // replace and remove if found - if (found !== null) { - found.data = node.data; - assert(p); - p.setChild(p.right === node, node.getChild(node.left === null)); - } - - // update root and make it black - this.#root = head.right; - if (this.#root !== null) { - this.#root.red = false; - } - - return found !== null; - } - } - - function isRed(node) { - return node !== null && node.red; - } - - function singleRotate(root, dir) { - const save = root.getChild(!dir); - assert(save); - - root.setChild(!dir, save.getChild(dir)); - save.setChild(dir, root); - - root.red = true; - save.red = false; - - return save; - } - - function doubleRotate(root, dir) { - root.setChild(!dir, singleRotate(root.getChild(!dir), !dir)); - return singleRotate(root, dir); - } - - const { console } = globalThis; - - // Timeout values > TIMEOUT_MAX are set to 1. - const TIMEOUT_MAX = 2 ** 31 - 1; - - let globalTimeoutDue = null; - - let nextTimerId = 1; - const idMap = new Map(); - const dueTree = new RBTree((a, b) => a.due - b.due); - - function clearGlobalTimeout() { - globalTimeoutDue = null; - opStopGlobalTimer(); - } - - let pendingEvents = 0; - const pendingFireTimers = []; - - /** Process and run a single ready timer macrotask. - * This function should be registered through Deno.core.setMacrotaskCallback. - * Returns true when all ready macrotasks have been processed, false if more - * ready ones are available. The Isolate future would rely on the return value - * to repeatedly invoke this function until depletion. Multiple invocations - * of this function one at a time ensures newly ready microtasks are processed - * before next macrotask timer callback is invoked. */ - function handleTimerMacrotask() { - if (pendingFireTimers.length > 0) { - fire(ArrayPrototypeShift(pendingFireTimers)); - return pendingFireTimers.length === 0; - } - return true; - } - - async function setGlobalTimeout(due, now) { - // Since JS and Rust don't use the same clock, pass the time to rust as a - // relative time value. On the Rust side we'll turn that into an absolute - // value again. - const timeout = due - now; - assert(timeout >= 0); - // Send message to the backend. - globalTimeoutDue = due; - pendingEvents++; - // FIXME(bartlomieju): this is problematic, because `clearGlobalTimeout` - // is synchronous. That means that timer is cancelled, but this promise is still pending - // until next turn of event loop. This leads to "leaking of async ops" in tests; - // because `clearTimeout/clearInterval` might be the last statement in test function - // `opSanitizer` will immediately complain that there is pending op going on, unless - // some timeout/defer is put in place to allow promise resolution. - // Ideally `clearGlobalTimeout` doesn't return until this op is resolved, but - // I'm not if that's possible. - opStartGlobalTimer(timeout); - await opWaitGlobalTimer(); - pendingEvents--; - prepareReadyTimers(); - } - - function prepareReadyTimers() { - const now = DateNow(); - // Bail out if we're not expecting the global timer to fire. - if (globalTimeoutDue === null || pendingEvents > 0) { - return; - } - // After firing the timers that are due now, this will hold the first timer - // list that hasn't fired yet. - let nextDueNode; - while ((nextDueNode = dueTree.min()) !== null && nextDueNode.due <= now) { - dueTree.remove(nextDueNode); - // Fire all the timers in the list. - for (const timer of nextDueNode.timers) { - // With the list dropped, the timer is no longer scheduled. - timer.scheduled = false; - // Place the callback to pending timers to fire. - ArrayPrototypePush(pendingFireTimers, timer); - } - } - setOrClearGlobalTimeout(nextDueNode && nextDueNode.due, now); - } - - function setOrClearGlobalTimeout(due, now) { - if (due == null) { - clearGlobalTimeout(); - } else { - setGlobalTimeout(due, now); - } - } - - function schedule(timer, now) { - assert(!timer.scheduled); - assert(now <= timer.due); - // Find or create the list of timers that will fire at point-in-time `due`. - const maybeNewDueNode = { due: timer.due, timers: [] }; - let dueNode = dueTree.find(maybeNewDueNode); - if (dueNode === null) { - dueTree.insert(maybeNewDueNode); - dueNode = maybeNewDueNode; - } - // Append the newly scheduled timer to the list and mark it as scheduled. - ArrayPrototypePush(dueNode.timers, timer); - timer.scheduled = true; - // If the new timer is scheduled to fire before any timer that existed before, - // update the global timeout to reflect this. - if (globalTimeoutDue === null || globalTimeoutDue > timer.due) { - setOrClearGlobalTimeout(timer.due, now); - } - } - - function unschedule(timer) { - // Check if our timer is pending scheduling or pending firing. - // If either is true, they are not in tree, and their idMap entry - // will be deleted soon. Remove it from queue. - let index = -1; - if ((index = ArrayPrototypeIndexOf(pendingFireTimers, timer)) >= 0) { - ArrayPrototypeSplice(pendingFireTimers, index); - return; - } - // If timer is not in the 2 pending queues and is unscheduled, - // it is not in the tree. - if (!timer.scheduled) { - return; - } - const searchKey = { due: timer.due, timers: [] }; - // Find the list of timers that will fire at point-in-time `due`. - const list = dueTree.find(searchKey).timers; - if (list.length === 1) { - // Time timer is the only one in the list. Remove the entire list. - assert(list[0] === timer); - dueTree.remove(searchKey); - // If the unscheduled timer was 'next up', find when the next timer that - // still exists is due, and update the global alarm accordingly. - if (timer.due === globalTimeoutDue) { - const nextDueNode = dueTree.min(); - setOrClearGlobalTimeout( - nextDueNode && nextDueNode.due, - DateNow(), - ); - } - } else { - // Multiple timers that are due at the same point in time. - // Remove this timer from the list. - const index = ArrayPrototypeIndexOf(list, timer); - assert(index > -1); - ArrayPrototypeSplice(list, index, 1); - } - } - - function fire(timer) { - // If the timer isn't found in the ID map, that means it has been cancelled - // between the timer firing and the promise callback (this function). - if (!MapPrototypeHas(idMap, timer.id)) { - return; - } - // Reschedule the timer if it is a repeating one, otherwise drop it. - if (!timer.repeat) { - // One-shot timer: remove the timer from this id-to-timer map. - MapPrototypeDelete(idMap, timer.id); - } else { - // Interval timer: compute when timer was supposed to fire next. - // However make sure to never schedule the next interval in the past. - const now = DateNow(); - timer.due = MathMax(now, timer.due + timer.delay); - schedule(timer, now); - } - // Call the user callback. Intermediate assignment is to avoid leaking `this` - // to it, while also keeping the stack trace neat when it shows up in there. - const callback = timer.callback; - if ("function" === typeof callback) { - callback(); - } else { - (0, eval)(callback); - } - } - - function checkThis(thisArg) { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); - } - } - - function setTimer( - cb, - delay, - args, - repeat, - ) { - // If the callack is a function, bind `args` to the callback and bind `this` to globalThis(global). - // otherwise call `String` on it, and `eval` it on calls; do not pass variardic args to the string - let callback; - - if ("function" === typeof cb) { - callback = FunctionPrototypeBind(cb, globalThis, ...args); - } else { - callback = String(cb); - args = []; // args are ignored - } - // In the browser, the delay value must be coercible to an integer between 0 - // and INT32_MAX. Any other value will cause the timer to fire immediately. - // We emulate this behavior. - const now = DateNow(); - if (delay > TIMEOUT_MAX) { - console.warn( - `${delay} does not fit into` + - " a 32-bit signed integer." + - "\nTimeout duration was set to 1.", - ); - delay = 1; - } - delay = MathMax(0, delay | 0); - - // Create a new, unscheduled timer object. - const timer = { - id: nextTimerId++, - callback, - args, - delay, - due: now + delay, - repeat, - scheduled: false, - }; - // Register the timer's existence in the id-to-timer map. - MapPrototypeSet(idMap, timer.id, timer); - // Schedule the timer in the due table. - schedule(timer, now); - return timer.id; - } - - function setTimeout( - cb, - delay = 0, - ...args - ) { - delay >>>= 0; - checkThis(this); - return setTimer(cb, delay, args, false); - } - - function setInterval( - cb, - delay = 0, - ...args - ) { - delay >>>= 0; - checkThis(this); - return setTimer(cb, delay, args, true); - } - - function clearTimer(id) { - id >>>= 0; - const timer = MapPrototypeGet(idMap, id); - if (timer === undefined) { - // Timer doesn't exist any more or never existed. This is not an error. - return; - } - // Unschedule the timer if it is currently scheduled, and forget about it. - unschedule(timer); - MapPrototypeDelete(idMap, timer.id); - } - - function clearTimeout(id = 0) { - id >>>= 0; - if (id === 0) { - return; - } - clearTimer(id); - } - - function clearInterval(id = 0) { - id >>>= 0; - if (id === 0) { - return; - } - clearTimer(id); - } - - window.__bootstrap.timers = { - clearInterval, - setInterval, - clearTimeout, - setTimeout, - handleTimerMacrotask, - opStopGlobalTimer, - opStartGlobalTimer, - opNow, - sleepSync, - }; -})(this); diff --git a/extensions/timers/02_performance.js b/extensions/timers/02_performance.js deleted file mode 100644 index f752ba933..000000000 --- a/extensions/timers/02_performance.js +++ /dev/null @@ -1,569 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { - ArrayPrototypeFilter, - ArrayPrototypeFind, - ArrayPrototypePush, - ArrayPrototypeReverse, - ArrayPrototypeSlice, - ObjectKeys, - Symbol, - SymbolFor, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; - - const { webidl, structuredClone } = window.__bootstrap; - const consoleInternal = window.__bootstrap.console; - const { opNow } = window.__bootstrap.timers; - const { DOMException } = window.__bootstrap.domException; - - const illegalConstructorKey = Symbol("illegalConstructorKey"); - const customInspect = SymbolFor("Deno.customInspect"); - let performanceEntries = []; - - webidl.converters["PerformanceMarkOptions"] = webidl - .createDictionaryConverter( - "PerformanceMarkOptions", - [ - { - key: "detail", - converter: webidl.converters.any, - }, - { - key: "startTime", - converter: webidl.converters.DOMHighResTimeStamp, - }, - ], - ); - - webidl.converters["DOMString or DOMHighResTimeStamp"] = (V, opts) => { - if (webidl.type(V) === "Number" && V !== null) { - return webidl.converters.DOMHighResTimeStamp(V, opts); - } - return webidl.converters.DOMString(V, opts); - }; - - webidl.converters["PerformanceMeasureOptions"] = webidl - .createDictionaryConverter( - "PerformanceMeasureOptions", - [ - { - key: "detail", - converter: webidl.converters.any, - }, - { - key: "start", - converter: webidl.converters["DOMString or DOMHighResTimeStamp"], - }, - { - key: "duration", - converter: webidl.converters.DOMHighResTimeStamp, - }, - { - key: "end", - converter: webidl.converters["DOMString or DOMHighResTimeStamp"], - }, - ], - ); - - webidl.converters["DOMString or PerformanceMeasureOptions"] = (V, opts) => { - if (webidl.type(V) === "Object" && V !== null) { - return webidl.converters["PerformanceMeasureOptions"](V, opts); - } - return webidl.converters.DOMString(V, opts); - }; - - function findMostRecent( - name, - type, - ) { - return ArrayPrototypeFind( - ArrayPrototypeReverse(ArrayPrototypeSlice(performanceEntries)), - (entry) => entry.name === name && entry.entryType === type, - ); - } - - function convertMarkToTimestamp(mark) { - if (typeof mark === "string") { - const entry = findMostRecent(mark, "mark"); - if (!entry) { - throw new DOMException( - `Cannot find mark: "${mark}".`, - "SyntaxError", - ); - } - return entry.startTime; - } - if (mark < 0) { - throw new TypeError("Mark cannot be negative."); - } - return mark; - } - - function filterByNameType( - name, - type, - ) { - return ArrayPrototypeFilter( - performanceEntries, - (entry) => - (name ? entry.name === name : true) && - (type ? entry.entryType === type : true), - ); - } - - const now = opNow; - - const _name = Symbol("[[name]]"); - const _entryType = Symbol("[[entryType]]"); - const _startTime = Symbol("[[startTime]]"); - const _duration = Symbol("[[duration]]"); - class PerformanceEntry { - [_name] = ""; - [_entryType] = ""; - [_startTime] = 0; - [_duration] = 0; - - get name() { - webidl.assertBranded(this, PerformanceEntry); - return this[_name]; - } - - get entryType() { - webidl.assertBranded(this, PerformanceEntry); - return this[_entryType]; - } - - get startTime() { - webidl.assertBranded(this, PerformanceEntry); - return this[_startTime]; - } - - get duration() { - webidl.assertBranded(this, PerformanceEntry); - return this[_duration]; - } - - constructor( - name = null, - entryType = null, - startTime = null, - duration = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - this[webidl.brand] = webidl.brand; - - this[_name] = name; - this[_entryType] = entryType; - this[_startTime] = startTime; - this[_duration] = duration; - } - - toJSON() { - webidl.assertBranded(this, PerformanceEntry); - return { - name: this[_name], - entryType: this[_entryType], - startTime: this[_startTime], - duration: this[_duration], - }; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: this instanceof PerformanceEntry, - keys: [ - "name", - "entryType", - "startTime", - "duration", - ], - })); - } - } - webidl.configurePrototype(PerformanceEntry); - - const _detail = Symbol("[[detail]]"); - class PerformanceMark extends PerformanceEntry { - [SymbolToStringTag] = "PerformanceMark"; - - [_detail] = null; - - get detail() { - webidl.assertBranded(this, PerformanceMark); - return this[_detail]; - } - - get entryType() { - webidl.assertBranded(this, PerformanceMark); - return "mark"; - } - - constructor( - name, - options = {}, - ) { - const prefix = "Failed to construct 'PerformanceMark'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters.DOMString(name, { - prefix, - context: "Argument 1", - }); - - options = webidl.converters.PerformanceMarkOptions(options, { - prefix, - context: "Argument 2", - }); - - const { detail = null, startTime = now() } = options; - - super(name, "mark", startTime, 0, illegalConstructorKey); - this[webidl.brand] = webidl.brand; - if (startTime < 0) { - throw new TypeError("startTime cannot be negative"); - } - this[_detail] = structuredClone(detail); - } - - toJSON() { - webidl.assertBranded(this, PerformanceMark); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: this instanceof PerformanceMark, - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); - } - } - webidl.configurePrototype(PerformanceMark); - - class PerformanceMeasure extends PerformanceEntry { - [SymbolToStringTag] = "PerformanceMeasure"; - - [_detail] = null; - - get detail() { - webidl.assertBranded(this, PerformanceMeasure); - return this[_detail]; - } - - get entryType() { - webidl.assertBranded(this, PerformanceMeasure); - return "measure"; - } - - constructor( - name = null, - startTime = null, - duration = null, - detail = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - - super(name, "measure", startTime, duration, key); - this[webidl.brand] = webidl.brand; - this[_detail] = structuredClone(detail); - } - - toJSON() { - webidl.assertBranded(this, PerformanceMeasure); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: this instanceof PerformanceMeasure, - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); - } - } - webidl.configurePrototype(PerformanceMeasure); - - class Performance { - constructor() { - webidl.illegalConstructor(); - } - - clearMarks(markName = undefined) { - webidl.assertBranded(this, Performance); - if (markName !== undefined) { - markName = webidl.converters.DOMString(markName, { - prefix: "Failed to execute 'clearMarks' on 'Performance'", - context: "Argument 1", - }); - - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => !(entry.name === markName && entry.entryType === "mark"), - ); - } else { - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => entry.entryType !== "mark", - ); - } - } - - clearMeasures(measureName = undefined) { - webidl.assertBranded(this, Performance); - if (measureName !== undefined) { - measureName = webidl.converters.DOMString(measureName, { - prefix: "Failed to execute 'clearMeasures' on 'Performance'", - context: "Argument 1", - }); - - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => - !(entry.name === measureName && entry.entryType === "measure"), - ); - } else { - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => entry.entryType !== "measure", - ); - } - } - - getEntries() { - webidl.assertBranded(this, Performance); - return filterByNameType(); - } - - getEntriesByName( - name, - type = undefined, - ) { - webidl.assertBranded(this, Performance); - const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters.DOMString(name, { - prefix, - context: "Argument 1", - }); - - if (type !== undefined) { - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 2", - }); - } - - return filterByNameType(name, type); - } - - getEntriesByType(type) { - webidl.assertBranded(this, Performance); - const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 1", - }); - - return filterByNameType(undefined, type); - } - - mark( - markName, - markOptions = {}, - ) { - webidl.assertBranded(this, Performance); - const prefix = "Failed to execute 'mark' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - markName = webidl.converters.DOMString(markName, { - prefix, - context: "Argument 1", - }); - - markOptions = webidl.converters.PerformanceMarkOptions(markOptions, { - prefix, - context: "Argument 2", - }); - - // 3.1.1.1 If the global object is a Window object and markName uses the - // same name as a read only attribute in the PerformanceTiming interface, - // throw a SyntaxError. - not implemented - const entry = new PerformanceMark(markName, markOptions); - // 3.1.1.7 Queue entry - not implemented - ArrayPrototypePush(performanceEntries, entry); - return entry; - } - - measure( - measureName, - startOrMeasureOptions = {}, - endMark = undefined, - ) { - webidl.assertBranded(this, Performance); - const prefix = "Failed to execute 'measure' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - measureName = webidl.converters.DOMString(measureName, { - prefix, - context: "Argument 1", - }); - - startOrMeasureOptions = webidl.converters - ["DOMString or PerformanceMeasureOptions"](startOrMeasureOptions, { - prefix, - context: "Argument 2", - }); - - if (endMark !== undefined) { - endMark = webidl.converters.DOMString(endMark, { - prefix, - context: "Argument 3", - }); - } - - if ( - startOrMeasureOptions && typeof startOrMeasureOptions === "object" && - ObjectKeys(startOrMeasureOptions).length > 0 - ) { - if (endMark) { - throw new TypeError("Options cannot be passed with endMark."); - } - if ( - !("start" in startOrMeasureOptions) && - !("end" in startOrMeasureOptions) - ) { - throw new TypeError( - "A start or end mark must be supplied in options.", - ); - } - if ( - "start" in startOrMeasureOptions && - "duration" in startOrMeasureOptions && - "end" in startOrMeasureOptions - ) { - throw new TypeError( - "Cannot specify start, end, and duration together in options.", - ); - } - } - let endTime; - if (endMark) { - endTime = convertMarkToTimestamp(endMark); - } else if ( - typeof startOrMeasureOptions === "object" && - "end" in startOrMeasureOptions - ) { - endTime = convertMarkToTimestamp(startOrMeasureOptions.end); - } else if ( - typeof startOrMeasureOptions === "object" && - "start" in startOrMeasureOptions && - "duration" in startOrMeasureOptions - ) { - const start = convertMarkToTimestamp(startOrMeasureOptions.start); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - endTime = start + duration; - } else { - endTime = now(); - } - let startTime; - if ( - typeof startOrMeasureOptions === "object" && - "start" in startOrMeasureOptions - ) { - startTime = convertMarkToTimestamp(startOrMeasureOptions.start); - } else if ( - typeof startOrMeasureOptions === "object" && - "end" in startOrMeasureOptions && - "duration" in startOrMeasureOptions - ) { - const end = convertMarkToTimestamp(startOrMeasureOptions.end); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - startTime = end - duration; - } else if (typeof startOrMeasureOptions === "string") { - startTime = convertMarkToTimestamp(startOrMeasureOptions); - } else { - startTime = 0; - } - const entry = new PerformanceMeasure( - measureName, - startTime, - endTime - startTime, - typeof startOrMeasureOptions === "object" - ? startOrMeasureOptions.detail ?? null - : null, - illegalConstructorKey, - ); - ArrayPrototypePush(performanceEntries, entry); - return entry; - } - - now() { - webidl.assertBranded(this, Performance); - return now(); - } - - toJSON() { - webidl.assertBranded(this, Performance); - return {}; - } - - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: this instanceof Performance, - keys: [], - })); - } - - get [SymbolToStringTag]() { - return "Performance"; - } - } - webidl.configurePrototype(Performance); - - window.__bootstrap.performance = { - PerformanceEntry, - PerformanceMark, - PerformanceMeasure, - Performance, - performance: webidl.createBranded(Performance), - }; -})(this); diff --git a/extensions/timers/Cargo.toml b/extensions/timers/Cargo.toml deleted file mode 100644 index eeaef5749..000000000 --- a/extensions/timers/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -[package] -name = "deno_timers" -version = "0.12.0" -authors = ["the Deno authors"] -edition = "2018" -license = "MIT" -readme = "README.md" -repository = "https://github.com/denoland/deno" -description = "Timers API implementation for Deno" - -[lib] -path = "lib.rs" - -[dependencies] -deno_core = { version = "0.96.0", path = "../../core" } -tokio = { version = "1.8.1", features = ["full"] } - -[dev-dependencies] -deno_bench_util = { version = "0.8.0", path = "../../bench_util" } -deno_url = { version = "0.14.0", path = "../url" } -deno_web = { version = "0.45.0", path = "../web" } -deno_webidl = { version = "0.14.0", path = "../webidl" } - -[[bench]] -name = "timers_ops" -harness = false diff --git a/extensions/timers/README.md b/extensions/timers/README.md deleted file mode 100644 index 5a2a8e516..000000000 --- a/extensions/timers/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# deno_timers - -This crate implements the timers API. - -Spec: https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers diff --git a/extensions/timers/benches/timers_ops.rs b/extensions/timers/benches/timers_ops.rs deleted file mode 100644 index 269d9627d..000000000 --- a/extensions/timers/benches/timers_ops.rs +++ /dev/null @@ -1,40 +0,0 @@ -use deno_core::Extension; - -use deno_bench_util::bench_or_profile; -use deno_bench_util::bencher::{benchmark_group, Bencher}; -use deno_bench_util::{bench_js_async, bench_js_sync}; -use deno_web::BlobStore; - -fn setup() -> Vec<Extension> { - vec![ - deno_webidl::init(), - deno_url::init(), - deno_web::init(BlobStore::default(), None), - deno_timers::init::<deno_timers::NoTimersPermission>(), - Extension::builder() - .js(vec![ - ("setup", - Box::new(|| Ok(r#" - const { opNow, setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers; - Deno.core.setMacrotaskCallback(handleTimerMacrotask); - "#.to_owned())), - ), - ]) - .state(|state| { - state.put(deno_timers::NoTimersPermission{}); - Ok(()) - }) - .build() - ] -} - -fn bench_op_now(b: &mut Bencher) { - bench_js_sync(b, r#"opNow();"#, setup); -} - -fn bench_set_timeout(b: &mut Bencher) { - bench_js_async(b, r#"setTimeout(() => {}, 0);"#, setup); -} - -benchmark_group!(benches, bench_op_now, bench_set_timeout,); -bench_or_profile!(benches); diff --git a/extensions/timers/lib.rs b/extensions/timers/lib.rs deleted file mode 100644 index 5f65ae3ef..000000000 --- a/extensions/timers/lib.rs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -//! This module helps deno implement timers. -//! -//! As an optimization, we want to avoid an expensive calls into rust for every -//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is -//! implemented that calls into Rust for only the smallest timeout. Thus we -//! only need to be able to start, cancel and await a single timer (or Delay, as Tokio -//! calls it) for an entire Isolate. This is what is implemented here. - -use deno_core::error::AnyError; -use deno_core::futures; -use deno_core::futures::channel::oneshot; -use deno_core::futures::FutureExt; -use deno_core::futures::TryFutureExt; -use deno_core::include_js_files; -use deno_core::op_async; -use deno_core::op_sync; -use deno_core::Extension; -use deno_core::OpState; -use std::cell::RefCell; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::thread::sleep; -use std::time::Duration; -use std::time::Instant; - -pub trait TimersPermission { - fn allow_hrtime(&mut self) -> bool; - fn check_unstable(&self, state: &OpState, api_name: &'static str); -} - -pub struct NoTimersPermission; - -impl TimersPermission for NoTimersPermission { - fn allow_hrtime(&mut self) -> bool { - false - } - fn check_unstable(&self, _: &OpState, _: &'static str) {} -} - -pub fn init<P: TimersPermission + 'static>() -> Extension { - Extension::builder() - .js(include_js_files!( - prefix "deno:extensions/timers", - "01_timers.js", - "02_performance.js", - )) - .ops(vec![ - ("op_global_timer_stop", op_sync(op_global_timer_stop)), - ("op_global_timer_start", op_sync(op_global_timer_start)), - ("op_global_timer", op_async(op_global_timer)), - ("op_now", op_sync(op_now::<P>)), - ("op_sleep_sync", op_sync(op_sleep_sync::<P>)), - ]) - .state(|state| { - state.put(GlobalTimer::default()); - state.put(StartTime::now()); - Ok(()) - }) - .build() -} - -pub type StartTime = Instant; - -type TimerFuture = Pin<Box<dyn Future<Output = Result<(), ()>>>>; - -#[derive(Default)] -pub struct GlobalTimer { - tx: Option<oneshot::Sender<()>>, - pub future: Option<TimerFuture>, -} - -impl GlobalTimer { - pub fn cancel(&mut self) { - if let Some(tx) = self.tx.take() { - tx.send(()).ok(); - } - } - - pub fn new_timeout(&mut self, deadline: Instant) { - if self.tx.is_some() { - self.cancel(); - } - assert!(self.tx.is_none()); - self.future.take(); - - let (tx, rx) = oneshot::channel(); - self.tx = Some(tx); - - let delay = tokio::time::sleep_until(deadline.into()).boxed_local(); - let rx = rx - .map_err(|err| panic!("Unexpected error in receiving channel {:?}", err)); - - let fut = futures::future::select(delay, rx) - .then(|_| futures::future::ok(())) - .boxed_local(); - self.future = Some(fut); - } -} - -pub fn op_global_timer_stop( - state: &mut OpState, - _args: (), - _: (), -) -> Result<(), AnyError> { - let global_timer = state.borrow_mut::<GlobalTimer>(); - global_timer.cancel(); - Ok(()) -} - -// Set up a timer that will be later awaited by JS promise. -// It's a separate op, because canceling a timeout immediately -// after setting it caused a race condition (because Tokio timeout) -// might have been registered after next event loop tick. -// -// See https://github.com/denoland/deno/issues/7599 for more -// details. -pub fn op_global_timer_start( - state: &mut OpState, - timeout: u64, - _: (), -) -> Result<(), AnyError> { - // According to spec, minimum allowed timeout is 4 ms. - // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers - // TODO(#10974) Per spec this is actually a little more complicated than this. - // The minimum timeout depends on the nesting level of the timeout. - let timeout = std::cmp::max(timeout, 4); - - let deadline = Instant::now() + Duration::from_millis(timeout); - let global_timer = state.borrow_mut::<GlobalTimer>(); - global_timer.new_timeout(deadline); - Ok(()) -} - -pub async fn op_global_timer( - state: Rc<RefCell<OpState>>, - _args: (), - _: (), -) -> Result<(), AnyError> { - let maybe_timer_fut = { - let mut s = state.borrow_mut(); - let global_timer = s.borrow_mut::<GlobalTimer>(); - global_timer.future.take() - }; - if let Some(timer_fut) = maybe_timer_fut { - let _ = timer_fut.await; - } - Ok(()) -} - -// Returns a milliseconds and nanoseconds subsec -// since the start time of the deno runtime. -// If the High precision flag is not set, the -// nanoseconds are rounded on 2ms. -pub fn op_now<TP>( - state: &mut OpState, - _argument: (), - _: (), -) -> Result<f64, AnyError> -where - TP: TimersPermission + 'static, -{ - let start_time = state.borrow::<StartTime>(); - let seconds = start_time.elapsed().as_secs(); - let mut subsec_nanos = start_time.elapsed().subsec_nanos() as f64; - let reduced_time_precision = 2_000_000.0; // 2ms in nanoseconds - - // If the permission is not enabled - // Round the nano result on 2 milliseconds - // see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision - if !state.borrow_mut::<TP>().allow_hrtime() { - subsec_nanos -= subsec_nanos % reduced_time_precision; - } - - let result = (seconds * 1_000) as f64 + (subsec_nanos / 1_000_000.0); - - Ok(result) -} - -pub fn op_sleep_sync<TP>( - state: &mut OpState, - millis: u64, - _: (), -) -> Result<(), AnyError> -where - TP: TimersPermission + 'static, -{ - state.borrow::<TP>().check_unstable(state, "Deno.sleepSync"); - sleep(Duration::from_millis(millis)); - Ok(()) -} |