Browse Source

fix: fetch-dependent interfaces in Web Workers (#42595)

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <[email protected]>
trop[bot] 10 months ago
parent
commit
79751340c6

+ 8 - 0
lib/worker/init.ts

@@ -17,6 +17,14 @@ const { makeRequireFunction } = __non_webpack_require__('internal/modules/helper
 global.module = new Module('electron/js2c/worker_init');
 global.require = makeRequireFunction(global.module);
 
+// See WebWorkerObserver::WorkerScriptReadyForEvaluation.
+if ((globalThis as any).blinkfetch) {
+  const keys = ['fetch', 'Response', 'FormData', 'Request', 'Headers'];
+  for (const key of keys) {
+    (globalThis as any)[key] = (globalThis as any)[`blink${key}`];
+  }
+}
+
 // Set the __filename to the path of html file if it is file: protocol.
 // NB. 'self' isn't defined in an AudioWorklet.
 if (typeof self !== 'undefined' && self.location.protocol === 'file:') {

+ 18 - 12
shell/renderer/web_worker_observer.cc

@@ -66,9 +66,25 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
   std::shared_ptr<node::Environment> env =
       node_bindings_->CreateEnvironment(worker_context, nullptr);
 
+  // We need to use the Blink implementation of fetch in web workers
+  // Node.js deletes the global fetch function when their fetch implementation
+  // is disabled, so we need to save and re-add it after the Node.js environment
+  // is loaded. See corresponding change in node/init.ts.
   v8::Local<v8::Object> global = worker_context->Global();
-  v8::Local<v8::String> fetch_string = gin::StringToV8(env->isolate(), "fetch");
-  v8::MaybeLocal<v8::Value> fetch = global->Get(worker_context, fetch_string);
+
+  std::vector<std::string> keys = {"fetch", "Response", "FormData", "Request",
+                                   "Headers"};
+  for (const auto& key : keys) {
+    v8::MaybeLocal<v8::Value> value =
+        global->Get(worker_context, gin::StringToV8(isolate, key.c_str()));
+    if (!value.IsEmpty()) {
+      std::string blink_key = "blink" + key;
+      global
+          ->Set(worker_context, gin::StringToV8(isolate, blink_key.c_str()),
+                value.ToLocalChecked())
+          .Check();
+    }
+  }
 
   // Add Electron extended APIs.
   electron_bindings_->BindTo(env->isolate(), env->process_object());
@@ -76,16 +92,6 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
   // Load everything.
   node_bindings_->LoadEnvironment(env.get());
 
-  // We need to use the Blink implementation of fetch in WebWorker process
-  // Node.js deletes the global fetch function when their fetch implementation
-  // is disabled, so we need to save and re-add it after the Node.js environment
-  // is loaded.
-  if (!fetch.IsEmpty()) {
-    worker_context->Global()
-        ->Set(worker_context, fetch_string, fetch.ToLocalChecked())
-        .Check();
-  }
-
   // Make uv loop being wrapped by window context.
   node_bindings_->set_uv_env(env.get());
 

+ 24 - 1
spec/chromium-spec.ts

@@ -1019,12 +1019,35 @@ describe('chromium features', () => {
     });
 
     it('Worker has node integration with nodeIntegrationInWorker', async () => {
-      const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true, contextIsolation: false } });
+      const w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true,
+          nodeIntegrationInWorker: true,
+          contextIsolation: false
+        }
+      });
+
       w.loadURL(`file://${fixturesPath}/pages/worker.html`);
       const [, data] = await once(ipcMain, 'worker-result');
       expect(data).to.equal('object function object function');
     });
 
+    it('Worker has access to fetch-dependent interfaces with nodeIntegrationInWorker', async () => {
+      const w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true,
+          nodeIntegrationInWorker: true,
+          contextIsolation: false
+        }
+      });
+
+      w.loadURL(`file://${fixturesPath}/pages/worker-fetch.html`);
+      const [, data] = await once(ipcMain, 'worker-fetch-result');
+      expect(data).to.equal('function function function function function');
+    });
+
     describe('SharedWorker', () => {
       it('can work', async () => {
         const w = new BrowserWindow({ show: false });

+ 12 - 0
spec/fixtures/pages/worker-fetch.html

@@ -0,0 +1,12 @@
+<html>
+<body>
+<script type="text/javascript" charset="utf-8">
+  const { ipcRenderer } = require('electron')
+  let worker = new Worker(`../workers/worker_node_fetch.js`)
+  worker.onmessage = function (event) {
+    ipcRenderer.send('worker-fetch-result', event.data)
+    worker.terminate()
+  }
+</script>
+</body>
+</html>

+ 7 - 0
spec/fixtures/workers/worker_node_fetch.js

@@ -0,0 +1,7 @@
+self.postMessage([
+  typeof fetch,
+  typeof Response,
+  typeof Request,
+  typeof Headers,
+  typeof FormData
+].join(' '));

+ 32 - 0
spec/node-spec.ts

@@ -159,6 +159,38 @@ describe('node feature', () => {
     });
   });
 
+  describe('fetch', () => {
+    itremote('works correctly when nodeIntegration is enabled in the renderer', async (fixtures: string) => {
+      const file = require('node:path').join(fixtures, 'hello.txt');
+      expect(() => {
+        fetch('file://' + file);
+      }).to.not.throw();
+
+      expect(() => {
+        const formData = new FormData();
+        formData.append('username', 'Groucho');
+      }).not.to.throw();
+
+      expect(() => {
+        const request = new Request('https://example.com', {
+          method: 'POST',
+          body: JSON.stringify({ foo: 'bar' })
+        });
+        expect(request.method).to.equal('POST');
+      }).not.to.throw();
+
+      expect(() => {
+        const response = new Response('Hello, world!');
+        expect(response.status).to.equal(200);
+      }).not.to.throw();
+
+      expect(() => {
+        const headers = new Headers();
+        headers.append('Content-Type', 'text/xml');
+      }).not.to.throw();
+    }, [fixtures]);
+  });
+
   it('does not hang when using the fs module in the renderer process', async () => {
     const appPath = path.join(mainFixturesPath, 'apps', 'libuv-hang', 'main.js');
     const appProcess = childProcess.spawn(process.execPath, [appPath], {