| /* | |
| * 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 }; |