Repository: electron/electron
Pull request: build(deps-dev): replace timers-browserify

#### Description of Change

timers-browserify has not been maintained for several years. This PR replaces its functionality to a solution that will exist in this repo instead.

Once this is merged, this dependency bump PR for timers-browserify can be closed: https://github.com/electron/electron/pull/48173

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included
- [x] I have built and tested this PR
- [ ] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: none


Changed files:
filename: build/webpack/webpack.config.base.js
patch: @@ -122,8 +122,8 @@ if ((globalThis.process || binding.process).argv.includes("--profile-electron-in
           'electron/renderer$': electronAPIFile,
           'electron/common$': electronAPIFile,
           'electron/utility$': electronAPIFile,
-          // Force timers to resolve to our dependency that doesn't use window.postMessage
-          timers: path.resolve(electronRoot, 'node_modules', 'timers-browserify', 'main.js')
+          // Force timers to resolve to our own shim that doesn't use window.postMessage
+          timers: path.resolve(electronRoot, 'lib', 'common', 'timers-shim.ts')
         },
         extensions: ['.ts', '.js'],
         fallback: {

filename: lib/common/timers-shim.ts
patch: @@ -0,0 +1,102 @@
+// Drop-in replacement for timers-browserify@1.4.2.
+// Provides the Node.js 'timers' API surface for renderer/web webpack bundles
+// without relying on window.postMessage (which the newer timers-browserify 2.x
+// polyfill uses and can interfere with Electron IPC).
+
+const immediateIds: Record<number, boolean> = {};
+let nextImmediateId = 0;
+
+// --- DOM timer wrappers ------------------------------------------------
+// Wrap the global setTimeout/setInterval so we return Timeout objects that
+// expose ref/unref/close matching the Node.js API shape.
+
+class Timeout {
+  _id: ReturnType<typeof globalThis.setTimeout>;
+  _clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void;
+
+  constructor (id: ReturnType<typeof globalThis.setTimeout>, clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void) {
+    this._id = id;
+    this._clearFn = clearFn;
+  }
+
+  unref () {}
+  ref () {}
+
+  close () {
+    this._clearFn.call(globalThis, this._id);
+  }
+}
+
+export const setTimeout = function (...args: Parameters<typeof globalThis.setTimeout>): Timeout {
+  return new Timeout(globalThis.setTimeout(...args), globalThis.clearTimeout);
+};
+
+export const setInterval = function (...args: Parameters<typeof globalThis.setInterval>): Timeout {
+  return new Timeout(globalThis.setInterval(...args), globalThis.clearInterval);
+};
+
+export const clearTimeout = function (timeout: Timeout | undefined) {
+  if (timeout) timeout.close();
+};
+
+export const clearInterval = clearTimeout;
+
+// --- Legacy enroll/unenroll (Node < 11 API, preserved for compatibility) ------
+
+interface EnrollableItem {
+  _idleTimeoutId?: ReturnType<typeof setTimeout>;
+  _idleTimeout?: number;
+  _onTimeout?: () => void;
+}
+
+export const enroll = function (item: EnrollableItem, msecs: number) {
+  clearTimeout(item._idleTimeoutId);
+  item._idleTimeout = msecs;
+};
+
+export const unenroll = function (item: EnrollableItem) {
+  clearTimeout(item._idleTimeoutId);
+  item._idleTimeout = -1;
+};
+
+export const active = function (item: EnrollableItem) {
+  clearTimeout(item._idleTimeoutId);
+
+  const msecs = item._idleTimeout;
+  if (msecs !== undefined && msecs >= 0) {
+    item._idleTimeoutId = setTimeout(function onTimeout () {
+      if (item._onTimeout) item._onTimeout();
+    }, msecs);
+  }
+};
+
+export const _unrefActive = active;
+
+// --- setImmediate / clearImmediate -------------------------------------
+// Prefer the native implementations when available. Fall back to a
+// nextTick-based shim that avoids window.postMessage.
+
+const clearImmediateFallback = function (id: number) {
+  delete immediateIds[id];
+};
+
+export const setImmediate = typeof globalThis.setImmediate === 'function'
+  ? globalThis.setImmediate
+  : function (fn: (...args: unknown[]) => void, ...rest: unknown[]) {
+    const id = nextImmediateId++;
+
+    immediateIds[id] = true;
+
+    Promise.resolve().then(function onMicrotask () {
+      if (immediateIds[id]) {
+        fn(...rest);
+        clearImmediateFallback(id);
+      }
+    });
+
+    return id;
+  };
+
+export const clearImmediate = typeof globalThis.clearImmediate === 'function'
+  ? globalThis.clearImmediate
+  : clearImmediateFallback;

filename: package.json
patch: @@ -49,7 +49,6 @@
     "stream-json": "^1.9.1",
     "tap-xunit": "^2.4.1",
     "temp": "^0.9.4",
-    "timers-browserify": "1.4.2",
     "ts-loader": "^8.0.2",
     "ts-node": "6.2.0",
     "typescript": "^5.8.3",

filename: yarn.lock
patch: @@ -602,7 +602,6 @@ __metadata:
     stream-json: "npm:^1.9.1"
     tap-xunit: "npm:^2.4.1"
     temp: "npm:^0.9.4"
-    timers-browserify: "npm:1.4.2"
     ts-loader: "npm:^8.0.2"
     ts-node: "npm:6.2.0"
     typescript: "npm:^5.8.3"
@@ -10614,7 +10613,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"process@npm:^0.11.10, process@npm:~0.11.0":
+"process@npm:^0.11.10":
   version: 0.11.10
   resolution: "process@npm:0.11.10"
   checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3
@@ -12462,15 +12461,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"timers-browserify@npm:1.4.2":
-  version: 1.4.2
-  resolution: "timers-browserify@npm:1.4.2"
-  dependencies:
-    process: "npm:~0.11.0"
-  checksum: 10c0/96e9b6d629bbb8bed7c55745112d065a2abdc33f3c29354c62de2fb02916893994b28678d675cdfceb12ca8e26f5e77e41fda9b2aa449f74ba0bbf191735a656
-  languageName: node
-  linkType: hard
-
 "tiny-async-pool@npm:^2.1.0":
   version: 2.1.0
   resolution: "tiny-async-pool@npm:2.1.0"