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.6",
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 .",