fix: fetch-dependent interfaces in Web Workers (#42597)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
parent
82664bfdcc
commit
3d139fc424
6 changed files with 101 additions and 13 deletions
|
@ -17,6 +17,14 @@ const { makeRequireFunction } = __non_webpack_require__('internal/modules/helper
|
||||||
global.module = new Module('electron/js2c/worker_init');
|
global.module = new Module('electron/js2c/worker_init');
|
||||||
global.require = makeRequireFunction(global.module);
|
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.
|
// Set the __filename to the path of html file if it is file: protocol.
|
||||||
// NB. 'self' isn't defined in an AudioWorklet.
|
// NB. 'self' isn't defined in an AudioWorklet.
|
||||||
if (typeof self !== 'undefined' && self.location.protocol === 'file:') {
|
if (typeof self !== 'undefined' && self.location.protocol === 'file:') {
|
||||||
|
|
|
@ -66,9 +66,25 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
|
||||||
std::shared_ptr<node::Environment> env =
|
std::shared_ptr<node::Environment> env =
|
||||||
node_bindings_->CreateEnvironment(worker_context, nullptr);
|
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::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.
|
// Add Electron extended APIs.
|
||||||
electron_bindings_->BindTo(env->isolate(), env->process_object());
|
electron_bindings_->BindTo(env->isolate(), env->process_object());
|
||||||
|
@ -76,16 +92,6 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
|
||||||
// Load everything.
|
// Load everything.
|
||||||
node_bindings_->LoadEnvironment(env.get());
|
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.
|
// Make uv loop being wrapped by window context.
|
||||||
node_bindings_->set_uv_env(env.get());
|
node_bindings_->set_uv_env(env.get());
|
||||||
|
|
||||||
|
|
|
@ -1014,12 +1014,35 @@ describe('chromium features', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Worker has node integration with nodeIntegrationInWorker', async () => {
|
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`);
|
w.loadURL(`file://${fixturesPath}/pages/worker.html`);
|
||||||
const [, data] = await once(ipcMain, 'worker-result');
|
const [, data] = await once(ipcMain, 'worker-result');
|
||||||
expect(data).to.equal('object function object function');
|
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', () => {
|
describe('SharedWorker', () => {
|
||||||
it('can work', async () => {
|
it('can work', async () => {
|
||||||
const w = new BrowserWindow({ show: false });
|
const w = new BrowserWindow({ show: false });
|
||||||
|
|
12
spec/fixtures/pages/worker-fetch.html
vendored
Normal file
12
spec/fixtures/pages/worker-fetch.html
vendored
Normal file
|
@ -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
spec/fixtures/workers/worker_node_fetch.js
vendored
Normal file
7
spec/fixtures/workers/worker_node_fetch.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
self.postMessage([
|
||||||
|
typeof fetch,
|
||||||
|
typeof Response,
|
||||||
|
typeof Request,
|
||||||
|
typeof Headers,
|
||||||
|
typeof FormData
|
||||||
|
].join(' '));
|
|
@ -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 () => {
|
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 appPath = path.join(mainFixturesPath, 'apps', 'libuv-hang', 'main.js');
|
||||||
const appProcess = childProcess.spawn(process.execPath, [appPath], {
|
const appProcess = childProcess.spawn(process.execPath, [appPath], {
|
||||||
|
|
Loading…
Reference in a new issue