Skip to content

Commit

Permalink
✨ use MessageChannel when requestIdleCallback isn't available
Browse files Browse the repository at this point in the history
Safari on desktop and mobile doesn't support requestIdleCallback. This adds supports for Safari.
  • Loading branch information
astoilkov committed Jan 7, 2022
1 parent 2e2013d commit 586228e
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/nextTask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default function nextTask(callback: () => void): void {
const channel = new MessageChannel()
channel.port2.postMessage(undefined)
// eslint-disable-next-line unicorn/prefer-add-event-listener
channel.port1.onmessage = (): void => callback()
}
4 changes: 4 additions & 0 deletions src/phaseTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export type IdlePhase = {
let shouldRequestAnimationFrame = false

const idlePhaseTracker = createPhaseTracker((callback: (idlePhase: IdlePhase) => void) => {
if (typeof requestIdleCallback === 'undefined') {
return
}

const handleIdleCallback = (): void => {
requestIdleCallback(
(deadline) => {
Expand Down
7 changes: 6 additions & 1 deletion src/yieldControl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import nextTask from './nextTask'
import waitCallback from './waitCallback'
import isTimeToYield from './isTimeToYield'
import requestLaterMicrotask from './requestLaterMicrotask'
Expand All @@ -20,7 +21,11 @@ export default async function yieldControl(priority: 'user-visible' | 'backgroun
}

async function schedule(priority: 'user-visible' | 'background'): Promise<void> {
if (priority === 'user-visible') {
if (typeof requestIdleCallback === 'undefined') {
await waitCallback(requestAnimationFrame)

await waitCallback(nextTask)
} else if (priority === 'user-visible') {
await waitCallback(requestLaterMicrotask)

await waitCallback(requestIdleCallback, {
Expand Down
62 changes: 62 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,38 @@ describe('main-thread-scheduling', () => {

expect(jestFn.mock.calls.length).toBe(1)
})

it(`use MessageChannel when requstIdleCallback isn't available`, async () => {
const original = window.requestIdleCallback
// @ts-ignore
window.requestIdleCallback = undefined

const mock = createMessageChannelMock()

const jestFn = jest.fn()

;(async () => {
await yieldControl('background')

jestFn()
})()

await wait()

requestAnimationFrameMock.callRequestAnimationFrame()

await wait()

mock.callMessage()

await wait()

expect(jestFn.mock.calls.length).toBe(1)

mock.mockRestore()

window.requestIdleCallback = original
})
})

// we use wait because:
Expand Down Expand Up @@ -447,6 +479,36 @@ function createRequestAnimationFrameMock() {
}
}

function createMessageChannelMock() {
const messageChannels: MessageChannelMock[] = []

class MessageChannelMock {
port1: {
onmessage?: () => void
} = {}
port2 = {
postMessage() {},
}
constructor() {
messageChannels.push(this)
}
}

;(global as any).MessageChannel = MessageChannelMock

return {
callMessage() {
for (const channel of messageChannels) {
channel.port1.onmessage?.()
}
},

mockRestore() {
;(global as any).MessageChannel = undefined
},
}
}

function isTimeToYieldMocked(priority: 'background' | 'user-visible'): boolean {
const originalDateNow = Date.now

Expand Down

0 comments on commit 586228e

Please sign in to comment.