// 13. Let job be NewPromiseResolveThenableJob(promise, resolution, // thenAction). Handle<NativeContext> then_context; if (!JSReceiver::GetContextForMicrotask(Handle<JSReceiver>::cast(then_action)) .ToHandle(&then_context)) { then_context = isolate->native_context(); }
Handle<PromiseResolveThenableJobTask> task = isolate->factory()->NewPromiseResolveThenableJobTask( promise, Handle<JSReceiver>::cast(resolution), Handle<JSReceiver>::cast(then_action), then_context); if (isolate->debug()->is_active() && IsJSPromise(*resolution)) { // Mark the dependency of the new {promise} on the {resolution}. Object::SetProperty(isolate, resolution, isolate->factory()->promise_handled_by_symbol(), promise) .Check(); } MicrotaskQueue* microtask_queue = then_context->microtask_queue(); if (microtask_queue) microtask_queue->EnqueueMicrotask(*task); // 15. Return undefined. return isolate->factory()->undefined_value(); }
如上所述,有许多实现算法的变体。基于浏览器的环境遵循 HTML 规范,并且通常依赖于执行环境的操作系统 API 来实现机制。在这种情况下,值得在每个具体浏览器引擎的源代码中,以及相应的库和操作系统 API 中寻找实现示例。在使用第三方库(例如 libuv)的平台上,可以在该库本身中找到实现示例(libuv 是开源的)。然而,应该理解每个实现都是独立的,可能与其他实现有很大的差异。
以下清单显示了事件循环操作所必需的接口。这些接口仅用于演示目的,符合 HTML 规范,并且以任何真实 HOST 执行器的内部结构。事件循环算法本身将在下面给出。
如前所述,有几种实现此算法的变体。基于浏览器的环境遵循 HTML 规范,并且通常依赖于操作系统 API 进行实际实现。在这方面,建议在每个具体浏览器引擎的源代码中以及相应的库和操作系统 API 中寻找实现示例。在使用第三方库的平台上,如 libuv,有利于在库本身内检查实现示例(libuv 是开源的)。然而,重要的是要注意,每个实现都是独立的,并且可能与其他实现有很大的不同。
/** * A browsing context is a programmatic representation of a series of documents, multiple of which can live within a * single navigable. Each browsing context has a corresponding WindowProxy object, as well as the following: * * - An `opener browsing context`, a browsing context or null, initially null. * - An `opener origin` at creation, an origin or null, initially null. * - An `is popup` boolean, initially false. * - An `is auxiliary` boolean, initially false. * - An `initial UR`L, a URL or null, initially null. * - A `virtual browsing context group ID` integer, initially 0. This is used by cross-origin opener policy reporting, * to keep track of the browsing context group switches that would have happened if the report-only policy had been * enforced. * * A browsing context's active window is its WindowProxy object's [[Window]] internal slot value. A browsing context's * active document is its active window's associated Document. * * A browsing context's top-level traversable is its active document's node navigable's top-level traversable. * * A browsing context whose is auxiliary is true is known as an auxiliary browsing context. Auxiliary browsing contexts * are always top-level browsing contexts. * * Note: For a demonstration purposes and for simplicity the BrowserContext is reflecting the Window interface which is * not fully correct, as the might be different implementations of the BrowserContext. */ interface BrowsingContext extends Window {} /** * A navigation request is a request whose destination is "document", "embed", "frame", "iframe", or "object" * * Note: For a demonstration purposes and for simplicity the NavigationRequest is reflecting the Window interface * which is not correct as the NavigationRequest is a different structure mostly use for * `Handle Fetch` (https://w3c.github.io/ServiceWorker/#handle-fetch) */ interface NavigationRequest extends Window {} interface Environment { id: string; creationURL: URL; topLevelCreationURL: URL; topLevelOrigin: string | null; targetBrowsingContext: BrowsingContext | NavigationRequest | null; activeServiceWorker: ServiceWorker | null; executionReady: boolean; }
interface Task { // A series of steps specifying the work to be done by the task. // will be defined in a certain Task implementation steps: Steps; // One of the task sources, used to group and serialize related tasks. // // Per its source field, each task is defined as coming from a specific task source. For each event loop, every // task source must be associated with a specific task queue. // // Essentially, task sources are used within standards to separate logically-different types of tasks, // which a user agent might wish to distinguish between. Task queues are used by user agents to coalesce task sources // within a given event loop. source: unknown; // A Document associated with the task, or null for tasks that are not in a window event loop. // A task is runnable if its document is either null or fully active. document: Document | null; // A set of environment settings objects used for tracking script evaluation during the task. environmentSettingsObject: Set<EnvironmentSettingsObjects>; }
interface GlobalTask extends Task { steps: Steps; // redefine/implement steps for this particular task type }
interface EventLoop { taskQueue: Set<Task>; // Each event loop has a microtask queue, which is a queue of microtasks, initially empty. A microtask is a colloquial // way of referring to a task that was created via the queue a microtask algorithm. // // For microtaskQueue is used just to illustrate that the specification supposes it to be a logical queue, rather // than a set of tasks. From technical perspective, a real implementation might use a `Set` like for taskQueue, or // any other structure at the discretion of the agent's developer. microtaskQueue: Array<Task>; // Each event loop has a currently running task, which is either a task or null. Initially, this is null. It is used // to handle reentrancy. currentRunningTask: Task | null; // Each event loop has a performing a microtask checkpoint boolean, which is initially false. It is used to prevent // reentrant invocation of the perform a microtask checkpoint algorithm. performingAMicrotaskCheckpoint: boolean; }
interface WindowEventLoop extends EventLoop { // Each window event loop has a DOMHighResTimeStamp last render opportunity time, initially set to zero. lastRenderOpportunityTime: number; // Each window event loop has a DOMHighResTimeStamp last idle period start time, initially set to zero. lastIdlePeriodStartTime: number; }
/** Just for demonstration purposes. Such a helper not necessarily should be presented in the real implementation */ function isWindowEventLoop(eventLoop: EventLoop): eventLoop is WindowEventLoop { return 'lastRenderOpportunityTime' in eventLoop && 'lastIdlePeriodStartTime' in eventLoop; }
/** * Processing the event loop according to the `8.1.7.3 Processing model` * https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model */ function runEventLoop(eventLoop: EventLoop) { // 1. Let oldestTask and taskStartTime be null. let oldestTask: Task | null = null; let taskStartTime: number | null = null; while (true) { // 2. check if the taskQueue has a runnable task and if there is one // 2.1. Let taskQueue be one such task queue, chosen in an implementation-defined manner. // 2.2. ... will be done below // 2.3. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue. oldestTask = getFirstRunnableTaskFromQueueAndRemove(eventLoop.taskQueue); if (oldestTask !== null) { // 2.2. Set taskStartTime to the unsafe shared current time. taskStartTime = Date.now(); // 2.4. Set the event loop's currently running task to oldestTask. eventLoop.currentRunningTask = oldestTask; // 2.5. Perform oldestTask's steps. performTaskSteps(oldestTask.steps); // 2.6. Set the event loop's currently running task back to null. eventLoop.currentRunningTask = null; // 2.7. Perform a microtask checkpoint. performMicrotaskCheckpoint(eventLoop); } // 3. Let hasARenderingOpportunity be false. let hasARenderingOpportunity = false; // 4. Let `now` be the unsafe shared current time. let now = Date.now(); // 5. If oldestTask is not null, then: if (oldestTask !== null) { // 5.1. Let top-level browsing contexts be an empty set. const topLevelBrowsingContexts = new Set(); // 5.2. For each environment settings object settings of oldestTask's script evaluation // environment settings object set: oldestTask.environmentSettingsObject.forEach((settingsObject) => { // 5.2.1. Let `global` be settings's global object. const global = settingsObject.targetBrowsingContext; // 5.2.2. If `global` is not a Window object, then continue. if (!(global instanceof Window)) { return; } // 5.2.3. If global's browsing context is null, then continue. if (!global.document) { return; } // 5.2.4. Let tlbc be global's browsing context's top-level browsing context. const tlbc = global.document; // 5.2.5. If tlbc is not null, then append it to top-level browsing contexts. if (tlbc !== null) { topLevelBrowsingContexts.add(tlbc) } }); // 5.3. Report long tasks, passing in taskStartTime, now (the end time of the task), top-level browsing contexts, // and oldestTask. // https://w3c.github.io/longtasks/#report-long-tasks // ... } // 6. if this is a window event loop, then: Update the rendering if (isWindowEventLoop(eventLoop)) { updateRendering(eventLoop); } // 7. If all of the following are true: // - this is a window event loop; // - there is no task in this event loop's task queues whose document is fully active; // - this event loop's microtask queue is empty; and // - hasARenderingOpportunity is false, // then: // ...run computeDeadline and hasPendingRenders steps for WindowEventLoop // 8. If this is a WorkerEventLoop, then: // ...run animation frame callbacks and update the rendering of that dedicated worker }
/** Finds and returns the first runnable task in the queue. The found Task will be removed from the queue */ function getFirstRunnableTaskFromQueueAndRemove(taskQueue: Set<Task>): Task | null { //... return null; }
/** * Performs a microtask checkpoint * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint */ function performMicrotaskCheckpoint(eventLoop: EventLoop) { // 1. If the event loop's performing a microtask checkpoint is true, then return. if (eventLoop.performingAMicrotaskCheckpoint) { return; } // 2. Set the event loop's performing a microtask checkpoint to true. eventLoop.performingAMicrotaskCheckpoint = true; // 3. While the event loop's microtask queue is not empty: while (eventLoop.microtaskQueue.length > 0) { // 3.1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue. const oldestMicrotask = eventLoop.microtaskQueue.shift(); // 3.2. Set the event loop's currently running task to oldestMicrotask. eventLoop.currentRunningTask = oldestMicrotask; // 3.3. Run oldestMicrotask. performTaskSteps(oldestMicrotask.steps); // 3.4. Set the event loop's currently running task back to null. eventLoop.currentRunningTask = null; } // 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected // promises on that environment settings object. // ... // 5. Cleanup Indexed Database transactions. // ... // 6. Perform ClearKeptObjects(). // ... // 7. Set the event loop's performing a microtask checkpoint to false. eventLoop.performingAMicrotaskCheckpoint = false; }
/** * Runs `Update the rendering` steps for WindowEventLoop * https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering */ function updateRendering(eventLoop: WindowEventLoop) { // ... reveal that Document // ... flush autofocus candidates for that Document // ... run the resize steps for that Document // ... run the scroll steps for that Document // ... evaluate media queries and report changes for that Document // ... update animations and send events for that Document // ... run the fullscreen steps for that Document // ... run the animation frame callbacks for that Document // ... if the focused area of that Document is not a focusable area, then run the focusing steps for that // Document's viewport // ... perform pending transition operations for that Document // ... run the update intersection observations steps for that Document // ... invoke the mark paint timing algorithm // ... update the rendering or user interface of that Document and its node navigable // ... run process top layer removals given Document. }