/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ if (typeof __VRT_PLAYBACK_SPEED__ === 'undefined') { window.__VRT_PLAYBACK_SPEED__ = 1; } const nativeRaf = window.requestAnimationFrame; const nativeSetTimeout = window.setTimeout; const nativeSetInterval = window.setInterval; const FIXED_FRAME_TIME = 16; const MAX_FRAME_TIME = 80; const TIMELINE_START = 1566458693300; window.__VRT_TIMELINE_PAUSED__ = true; let realFrameStartTime = 0; /** Control timeline loop */ let rafCbs = []; let frameIdx = 0; let timelineTime = 0; function runFrame() { realFrameStartTime = NativeDate.now(); frameIdx++; timelineTime += FIXED_FRAME_TIME; const currentRafCbs = rafCbs; // Clear before calling the callbacks. raf may be registered in the callback rafCbs = []; currentRafCbs.forEach((cb) => { try { cb(); } catch (e) { // Catch error to avoid following tasks. __VRT_LOG_ERRORS__(e.toString()); } }); flushTimeoutHandlers(); flushIntervalHandlers(); } function timelineLoop() { if (!__VRT_TIMELINE_PAUSED__) { runFrame(); } nativeRaf(timelineLoop); } nativeRaf(timelineLoop); window.requestAnimationFrame = function (cb) { rafCbs.push(cb); }; /** Mock setTimeout, setInterval */ let timeoutHandlers = []; let intervalHandlers = []; let timeoutId = 1; let intervalId = 1; window.setTimeout = function (cb, timeout) { const elapsedFrame = Math.ceil(Math.max(timeout || 0, 1) / FIXED_FRAME_TIME); timeoutHandlers.push({ callback: cb, id: timeoutId, frame: frameIdx + elapsedFrame }); return timeoutId++; } window.clearTimeout = function (id) { const idx = timeoutHandlers.findIndex(handler => { return handler.id === id }); if (idx >= 0) { timeoutHandlers.splice(idx, 1); } } function flushTimeoutHandlers() { // Copy the array. In case setTimeout/clearTimeout is invoked in the callback. const savedTimeoutHandlers = timeoutHandlers.slice(); for (let i = 0; i < savedTimeoutHandlers.length; i++) { const handler = savedTimeoutHandlers[i]; if (handler.frame === frameIdx) { // Need find index again. In case setTimeout/clearTimeout is invoked in the callback. const idx = timeoutHandlers.indexOf(handler); timeoutHandlers.splice(idx, 1); try { handler.callback(); } catch (e) { // Catch error to avoid following tasks. __VRT_LOG_ERRORS__(e.toString()); } } } } window.setInterval = function (cb, interval) { const intervalFrame = Math.ceil(Math.max(interval || 0, 1) / FIXED_FRAME_TIME); intervalHandlers.push({ callback: cb, id: intervalId, intervalFrame, frame: frameIdx + intervalFrame }); return intervalId++; } window.clearInterval = function (id) { const idx = intervalHandlers.findIndex(handler => { return handler.id === id; }); if (idx >= 0) { intervalHandlers.splice(idx, 1); } } function flushIntervalHandlers() { // Copy the array. In case setInterval/clearInterval is invoked in the callback. const savedIntervalHandlers = intervalHandlers.slice(); for (let i = 0; i < savedIntervalHandlers.length; i++) { const handler = savedIntervalHandlers[i]; if (handler.frame === frameIdx) { try { handler.callback(); } catch (e) { // Catch error to avoid following tasks. __VRT_LOG_ERRORS__(e.toString()); } handler.frame += handler.intervalFrame; } } } /** Mock Date */ const NativeDate = window.Date; const mockNow = function () { // // Use same time in one frame. // var realFrameTime = NativeDate.now(); // // Split frame. Add 8ms offset on the second half // // Avoid infinite loop when some logic determine whether to break the loop based on the execution time. // // For example https://github.com/apache/echarts/blob/737e23c0054e6b501ecc6f562920cffae953b5c6/src/core/echarts.ts#L537 // var frameDeltaTime = realFrameTime - realFrameStartTime; var frameDeltaTime = 0; // Use same time in one frame. return TIMELINE_START + (timelineTime + frameDeltaTime) * window.__VRT_PLAYBACK_SPEED__; }; function MockDate(...args) { if (!args.length) { return new NativeDate(mockNow()); } else { return new NativeDate(...args); } } MockDate.prototype = Object.create(NativeDate.prototype); Object.setPrototypeOf(MockDate, NativeDate); MockDate.now = mockNow; window.Date = MockDate; // TODO Do we need to mock performance? Or leave some API that can keep real. export function start() { window.__VRT_TIMELINE_PAUSED__ = false; } export function pause() { window.__VRT_TIMELINE_PAUSED__ = true; } export function resume() { window.__VRT_TIMELINE_PAUSED__ = false; } export { nativeRaf, nativeSetInterval, nativeSetTimeout };