browser-extension-utils 0.3.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* requestIdleCallback Polyfill
|
|
3
|
+
*
|
|
4
|
+
* This utility provides a backward-compatible implementation of `window.requestIdleCallback` for environments
|
|
5
|
+
* that do not support it natively. It allows scheduling low-priority background tasks to execute during
|
|
6
|
+
* browser idle periods, ensuring that latency-critical events like animations and input responses remain smooth.
|
|
7
|
+
*
|
|
8
|
+
* Implementation Strategy:
|
|
9
|
+
* 1. **MessageChannel as Scheduler**: Utilizes `MessageChannel` to schedule tasks. `MessageChannel` posts messages
|
|
10
|
+
* as macro-tasks, which allows the event loop to handle high-priority events (like rendering) between task executions.
|
|
11
|
+
* 2. **Task Queue Management**: Maintains a FIFO queue of registered callbacks.
|
|
12
|
+
* 3. **Time Budgeting**: Each execution cycle is allocated a small time budget (approx. 10ms) to process tasks.
|
|
13
|
+
* If the budget is exceeded, remaining tasks are deferred to the next cycle, yielding control back to the browser.
|
|
14
|
+
*
|
|
15
|
+
* Reference:
|
|
16
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Define types for requestIdleCallback and cancelIdleCallback
|
|
20
|
+
export type IdleCallbackHandle = number
|
|
21
|
+
|
|
22
|
+
export interface IdleDeadline {
|
|
23
|
+
readonly didTimeout: boolean
|
|
24
|
+
timeRemaining: () => number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IdleRequestOptions {
|
|
28
|
+
timeout?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type IdleRequestCallback = (deadline: IdleDeadline) => void
|
|
32
|
+
|
|
33
|
+
declare global {
|
|
34
|
+
interface Window {
|
|
35
|
+
requestIdleCallback: (
|
|
36
|
+
callback: IdleRequestCallback,
|
|
37
|
+
options?: IdleRequestOptions
|
|
38
|
+
) => IdleCallbackHandle
|
|
39
|
+
cancelIdleCallback: (handle: IdleCallbackHandle) => void
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Polyfill function
|
|
44
|
+
export const polyfillRequestIdleCallback = (): void => {
|
|
45
|
+
if (
|
|
46
|
+
globalThis.window === undefined ||
|
|
47
|
+
(typeof globalThis.requestIdleCallback === 'function' &&
|
|
48
|
+
typeof globalThis.cancelIdleCallback === 'function')
|
|
49
|
+
) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const taskQueue: Array<{
|
|
54
|
+
id: IdleCallbackHandle
|
|
55
|
+
callback: IdleRequestCallback
|
|
56
|
+
}> = []
|
|
57
|
+
let runTaskHandle: IdleCallbackHandle = 0
|
|
58
|
+
let handleCounter: IdleCallbackHandle = 0
|
|
59
|
+
const channel = new MessageChannel()
|
|
60
|
+
|
|
61
|
+
channel.port1.start()
|
|
62
|
+
channel.port1.addEventListener('message', () => {
|
|
63
|
+
runTaskHandle = 0
|
|
64
|
+
const startTime = performance.now()
|
|
65
|
+
const deadline: IdleDeadline = {
|
|
66
|
+
get didTimeout() {
|
|
67
|
+
return false
|
|
68
|
+
},
|
|
69
|
+
timeRemaining: () => Math.max(0, 10 - (performance.now() - startTime)),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
while (taskQueue.length > 0 && deadline.timeRemaining() > 1) {
|
|
73
|
+
const task = taskQueue.shift()
|
|
74
|
+
if (task) {
|
|
75
|
+
try {
|
|
76
|
+
task.callback(deadline)
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(error)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (taskQueue.length > 0) {
|
|
84
|
+
runTaskHandle = 1
|
|
85
|
+
channel.port2.postMessage(undefined)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
globalThis.requestIdleCallback = (
|
|
90
|
+
callback: IdleRequestCallback,
|
|
91
|
+
_options?: IdleRequestOptions
|
|
92
|
+
): IdleCallbackHandle => {
|
|
93
|
+
handleCounter++
|
|
94
|
+
taskQueue.push({
|
|
95
|
+
id: handleCounter,
|
|
96
|
+
callback,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
if (!runTaskHandle) {
|
|
100
|
+
// Schedule the task execution.
|
|
101
|
+
// We use `channel.port2.postMessage` to trigger the callback in the next event loop tick (macro-task).
|
|
102
|
+
// This ensures the current call stack clears before the idle tasks begin.
|
|
103
|
+
runTaskHandle = 1 // Just a flag
|
|
104
|
+
channel.port2.postMessage(undefined)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return handleCounter
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
globalThis.cancelIdleCallback = (handle: IdleCallbackHandle): void => {
|
|
111
|
+
const index = taskQueue.findIndex((task) => task.id === handle)
|
|
112
|
+
if (index !== -1) {
|
|
113
|
+
taskQueue.splice(index, 1)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default polyfillRequestIdleCallback
|
|
119
|
+
|
|
120
|
+
// Auto-execute if in browser environment
|
|
121
|
+
if (globalThis.window !== undefined) {
|
|
122
|
+
polyfillRequestIdleCallback()
|
|
123
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-extension-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Utilities for developing browser extensions and userscripts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./lib/index.ts",
|
|
9
|
-
"./userscript": "./lib/userscript.ts"
|
|
9
|
+
"./userscript": "./lib/userscript.ts",
|
|
10
|
+
"./request-idle-callback-polyfill": "./lib/request-idle-callback-polyfill.ts"
|
|
10
11
|
},
|
|
11
12
|
"scripts": {
|
|
12
13
|
"p": "prettier --write .",
|