From c54affe9f7c51cf0e7189c665fa61224a4905074 Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Mon, 27 Jul 2020 10:34:02 +1000 Subject: [PATCH] chore: add providers --- spec/operators/timeoutWith-spec.ts | 5 ++-- src/internal/ReplaySubject.ts | 5 ++-- src/internal/Scheduler.ts | 8 ++---- .../observable/dom/animationFrames.ts | 7 ++--- src/internal/operators/timestamp.ts | 3 ++- .../scheduler/dateTimestampProvider.ts | 14 ++++++++++ .../scheduler/performanceTimestampProvider.ts | 14 ++++++++++ .../requestAnimationFrameProvider.ts | 26 +++++++++++++++++++ src/internal/testing/TestScheduler.ts | 13 +++++++++- 9 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/internal/scheduler/dateTimestampProvider.ts create mode 100644 src/internal/scheduler/performanceTimestampProvider.ts create mode 100644 src/internal/scheduler/requestAnimationFrameProvider.ts diff --git a/spec/operators/timeoutWith-spec.ts b/spec/operators/timeoutWith-spec.ts index d0995260af..b523637a48 100644 --- a/spec/operators/timeoutWith-spec.ts +++ b/spec/operators/timeoutWith-spec.ts @@ -1,4 +1,5 @@ import { hot, cold, expectObservable, expectSubscriptions } from '../helpers/marble-testing'; +import { dateTimestampProvider } from '../../src/internal/scheduler/dateTimestampProvider'; import { timeoutWith, mergeMap } from 'rxjs/operators'; import { TestScheduler } from 'rxjs/testing'; import { of } from 'rxjs'; @@ -226,7 +227,7 @@ describe('timeoutWith operator', () => { const e2subs: string[] = []; const expected = '--a--b--c--d--e--|'; - const timeoutValue = new Date(Date.now() + (expected.length + 2) * 10); + const timeoutValue = new Date(dateTimestampProvider.now() + (expected.length + 2) * 10); const result = e1.pipe(timeoutWith(timeoutValue, e2, rxTestScheduler)); @@ -242,7 +243,7 @@ describe('timeoutWith operator', () => { const e2subs: string[] = []; const expected = '---a---#'; - const result = e1.pipe(timeoutWith(new Date(Date.now() + 100), e2, rxTestScheduler)); + const result = e1.pipe(timeoutWith(new Date(dateTimestampProvider.now() + 100), e2, rxTestScheduler)); expectObservable(result).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); diff --git a/src/internal/ReplaySubject.ts b/src/internal/ReplaySubject.ts index 220f50c746..63f35c3fc7 100644 --- a/src/internal/ReplaySubject.ts +++ b/src/internal/ReplaySubject.ts @@ -4,6 +4,7 @@ import { Subscriber } from './Subscriber'; import { Subscription } from './Subscription'; import { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError'; import { SubjectSubscription } from './SubjectSubscription'; +import { dateTimestampProvider } from "./scheduler/dateTimestampProvider"; /** * A variant of {@link Subject} that "replays" old values to new subscribers by emitting them when they first subscribe. @@ -49,7 +50,7 @@ export class ReplaySubject extends Subject { */ constructor(bufferSize: number = Infinity, windowTime: number = Infinity, - private timestampProvider: TimestampProvider = Date) { + private timestampProvider: TimestampProvider = dateTimestampProvider) { super(); this._bufferSize = bufferSize < 1 ? 1 : bufferSize; this._windowTime = windowTime < 1 ? 1 : windowTime; @@ -120,7 +121,7 @@ export class ReplaySubject extends Subject { private _getNow(): number { const { timestampProvider: scheduler } = this; - return scheduler ? scheduler.now() : Date.now(); + return scheduler ? scheduler.now() : dateTimestampProvider.now(); } private _trimBufferThenGetEvents(): ReplayEvent[] { diff --git a/src/internal/Scheduler.ts b/src/internal/Scheduler.ts index e41e41ba87..c4fb41971d 100644 --- a/src/internal/Scheduler.ts +++ b/src/internal/Scheduler.ts @@ -1,6 +1,7 @@ import { Action } from './scheduler/Action'; import { Subscription } from './Subscription'; import { SchedulerLike, SchedulerAction } from './types'; +import { dateTimestampProvider } from "./scheduler/dateTimestampProvider"; /** * An execution context and a data structure to order tasks and schedule their @@ -23,12 +24,7 @@ import { SchedulerLike, SchedulerAction } from './types'; */ export class Scheduler implements SchedulerLike { - /** - * Note: the extra arrow function wrapper is to make testing by overriding - * Date.now easier. - * @nocollapse - */ - public static now: () => number = () => Date.now(); + public static now: () => number = dateTimestampProvider.now; constructor(private SchedulerAction: typeof Action, now: () => number = Scheduler.now) { diff --git a/src/internal/observable/dom/animationFrames.ts b/src/internal/observable/dom/animationFrames.ts index adb8849149..3249aa7f13 100644 --- a/src/internal/observable/dom/animationFrames.ts +++ b/src/internal/observable/dom/animationFrames.ts @@ -1,5 +1,6 @@ import { Observable } from '../../Observable'; import { TimestampProvider } from "../../types"; +import { dateTimestampProvider } from 'rxjs/internal/scheduler/dateTimestampProvider'; /** * An observable of animation frames @@ -73,8 +74,8 @@ import { TimestampProvider } from "../../types"; * * @param timestampProvider An object with a `now` method that provides a numeric timestamp */ -export function animationFrames(timestampProvider: TimestampProvider = Date) { - return timestampProvider === Date ? DEFAULT_ANIMATION_FRAMES : animationFramesFactory(timestampProvider); +export function animationFrames(timestampProvider: TimestampProvider = dateTimestampProvider) { + return timestampProvider === dateTimestampProvider ? DEFAULT_ANIMATION_FRAMES : animationFramesFactory(timestampProvider); } /** @@ -100,4 +101,4 @@ function animationFramesFactory(timestampProvider: TimestampProvider) { * In the common case, where `Date` is passed to `animationFrames` as the default, * we use this shared observable to reduce overhead. */ -const DEFAULT_ANIMATION_FRAMES = animationFramesFactory(Date); +const DEFAULT_ANIMATION_FRAMES = animationFramesFactory(dateTimestampProvider); diff --git a/src/internal/operators/timestamp.ts b/src/internal/operators/timestamp.ts index 170ff8532b..ba1ea39d5e 100644 --- a/src/internal/operators/timestamp.ts +++ b/src/internal/operators/timestamp.ts @@ -1,4 +1,5 @@ import { OperatorFunction, TimestampProvider, Timestamp } from '../types'; +import { dateTimestampProvider } from '../scheduler/dateTimestampProvider'; import { map } from './map'; /** @@ -32,6 +33,6 @@ import { map } from './map'; * * @param timestampProvider An object with a `now()` method used to get the current timestamp. */ -export function timestamp(timestampProvider: TimestampProvider = Date): OperatorFunction> { +export function timestamp(timestampProvider: TimestampProvider = dateTimestampProvider): OperatorFunction> { return map((value: T) => ({ value, timestamp: timestampProvider.now()})); } \ No newline at end of file diff --git a/src/internal/scheduler/dateTimestampProvider.ts b/src/internal/scheduler/dateTimestampProvider.ts new file mode 100644 index 0000000000..3153fd9dab --- /dev/null +++ b/src/internal/scheduler/dateTimestampProvider.ts @@ -0,0 +1,14 @@ +import { TimestampProvider } from "../types"; + +interface DateTimestampProvider extends TimestampProvider { + delegate: TimestampProvider | undefined; +} + +export const dateTimestampProvider: DateTimestampProvider = { + now() { + // Use the variable rather than `this` so that the function can be called + // without being bound to the provider. + return (dateTimestampProvider.delegate || Date).now(); + }, + delegate: undefined +}; \ No newline at end of file diff --git a/src/internal/scheduler/performanceTimestampProvider.ts b/src/internal/scheduler/performanceTimestampProvider.ts new file mode 100644 index 0000000000..e40c521bad --- /dev/null +++ b/src/internal/scheduler/performanceTimestampProvider.ts @@ -0,0 +1,14 @@ +import { TimestampProvider } from "../types"; + +interface PerformanceTimestampProvider extends TimestampProvider { + delegate: TimestampProvider | undefined; +} + +export const performanceTimestampProvider: PerformanceTimestampProvider = { + now() { + // Use the variable rather than `this` so that the function can be called + // without being bound to the provider. + return (performanceTimestampProvider.delegate || performance).now(); + }, + delegate: undefined +}; \ No newline at end of file diff --git a/src/internal/scheduler/requestAnimationFrameProvider.ts b/src/internal/scheduler/requestAnimationFrameProvider.ts new file mode 100644 index 0000000000..0a02bdb9cb --- /dev/null +++ b/src/internal/scheduler/requestAnimationFrameProvider.ts @@ -0,0 +1,26 @@ +import { Subscription } from "../Subscription"; + +type RequestAnimationFrameProvider = { + schedule(callback: FrameRequestCallback): Subscription; + delegate: { + requestAnimationFrame: typeof requestAnimationFrame; + cancelAnimationFrame: typeof cancelAnimationFrame; + } | undefined; +}; + +export const requestAnimationFrameProvider: RequestAnimationFrameProvider = { + schedule(callback) { + let request = requestAnimationFrame; + let cancel = cancelAnimationFrame; + // Use the variable rather than `this` so that the function can be called + // without being bound to the provider. + const { delegate } = requestAnimationFrameProvider; + if (delegate) { + request = delegate.requestAnimationFrame; + cancel = delegate.cancelAnimationFrame; + } + const handle = request(callback); + return new Subscription(() => cancel(handle)); + }, + delegate: undefined +}; \ No newline at end of file diff --git a/src/internal/testing/TestScheduler.ts b/src/internal/testing/TestScheduler.ts index 03c578f3c4..3d1807f9cf 100644 --- a/src/internal/testing/TestScheduler.ts +++ b/src/internal/testing/TestScheduler.ts @@ -8,6 +8,9 @@ import { VirtualTimeScheduler, VirtualAction } from '../scheduler/VirtualTimeSch import { AsyncScheduler } from '../scheduler/AsyncScheduler'; import { ObservableNotification } from '../types'; import { COMPLETE_NOTIFICATION, errorNotification, nextNotification } from '../Notification'; +import { dateTimestampProvider } from '../scheduler/dateTimestampProvider'; +import { performanceTimestampProvider } from '../scheduler/performanceTimestampProvider'; +import { requestAnimationFrameProvider } from '../scheduler/requestAnimationFrameProvider'; const defaultMaxFrame: number = 750; @@ -18,6 +21,7 @@ export interface RunHelpers { time: typeof TestScheduler.prototype.createTime; expectObservable: typeof TestScheduler.prototype.expectObservable; expectSubscriptions: typeof TestScheduler.prototype.expectSubscriptions; + repaints: (marbles: string) => void; } interface FlushableTest { @@ -409,14 +413,18 @@ export class TestScheduler extends VirtualTimeScheduler { this.maxFrames = Infinity; this.runMode = true; AsyncScheduler.delegate = this; + dateTimestampProvider.delegate = this; + performanceTimestampProvider.delegate = this; + requestAnimationFrameProvider.delegate = undefined; // TODO - const helpers = { + const helpers: RunHelpers = { cold: this.createColdObservable.bind(this), hot: this.createHotObservable.bind(this), flush: this.flush.bind(this), time: this.createTime.bind(this), expectObservable: this.expectObservable.bind(this), expectSubscriptions: this.expectSubscriptions.bind(this), + repaints: (marbles) => { /* TODO */ }, }; try { const ret = callback(helpers); @@ -427,6 +435,9 @@ export class TestScheduler extends VirtualTimeScheduler { this.maxFrames = prevMaxFrames; this.runMode = false; AsyncScheduler.delegate = undefined; + dateTimestampProvider.delegate = undefined; + performanceTimestampProvider.delegate = undefined; + requestAnimationFrameProvider.delegate = undefined; } } }