diff --git a/patches/node/chore_expose_importmoduledynamically_and.patch b/patches/node/chore_expose_importmoduledynamically_and.patch index 975a2e6c2966a..24d445f35c19f 100644 --- a/patches/node/chore_expose_importmoduledynamically_and.patch +++ b/patches/node/chore_expose_importmoduledynamically_and.patch @@ -82,7 +82,7 @@ index 0645b3ddf506df2a76f5661f0ec6bb35d5d8b94e..e0f1b2d51f3055b2250f2c0dc1dfd104 MaybeLocal ModuleWrap::SyntheticModuleEvaluationStepsCallback( diff --git a/src/module_wrap.h b/src/module_wrap.h -index 58b233d036515c52d9bd5574c776c2ea65d2ecb1..5f7ef75480a76761c6fa62061c8700c812a3fc6f 100644 +index 1fc801edced9c5e44613846b4dc555804c5bae97..767d183c6b6b6d97726cf5d63c0b202bd9ae10a6 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -30,7 +30,14 @@ enum HostDefinedOptions : int { @@ -100,4 +100,21 @@ index 58b233d036515c52d9bd5574c776c2ea65d2ecb1..5f7ef75480a76761c6fa62061c8700c8 +class NODE_EXTERN ModuleWrap : public BaseObject { public: enum InternalFields { - kModuleWrapBaseField = BaseObject::kInternalFieldCount, + kModuleSlot = BaseObject::kInternalFieldCount, +@@ -65,6 +72,8 @@ class ModuleWrap : public BaseObject { + return true; + } + ++ static ModuleWrap* GetFromModule(node::Environment*, v8::Local); ++ + private: + ModuleWrap(Environment* env, + v8::Local object, +@@ -99,7 +108,6 @@ class ModuleWrap : public BaseObject { + v8::Local specifier, + v8::Local import_attributes, + v8::Local referrer); +- static ModuleWrap* GetFromModule(node::Environment*, v8::Local); + + v8::Global module_; + std::unordered_map> resolve_cache_; diff --git a/shell/common/node_bindings.cc b/shell/common/node_bindings.cc index 03e01fdbf6273..49d0697e8f0cb 100644 --- a/shell/common/node_bindings.cc +++ b/shell/common/node_bindings.cc @@ -126,6 +126,8 @@ ELECTRON_TESTING_BINDINGS(V) #endif #undef V +using node::loader::ModuleWrap; + namespace { void stop_and_close_uv_loop(uv_loop_t* loop) { @@ -216,16 +218,24 @@ v8::MaybeLocal HostImportModuleDynamically( void HostInitializeImportMetaObject(v8::Local context, v8::Local module, v8::Local meta) { - if (node::Environment::GetCurrent(context) == nullptr) { + node::Environment* env = node::Environment::GetCurrent(context); + if (env == nullptr) { if (electron::IsBrowserProcess() || electron::IsUtilityProcess()) return; return blink::V8Initializer::HostGetImportMetaProperties(context, module, meta); } - // If we're running with contextIsolation enabled in the renderer process, - // fall back to Blink's logic. if (electron::IsRendererProcess()) { + // If the module is created by Node.js, use Node.js' handling. + if (env != nullptr) { + ModuleWrap* wrap = ModuleWrap::GetFromModule(env, module); + if (wrap) + return ModuleWrap::HostInitializeImportMetaObjectCallback(context, + module, meta); + } + + // If contextIsolation is enabled, fall back to Blink's handling. blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForContext(context); if (!frame || frame->GetScriptContextWorldId(context) != @@ -235,8 +245,8 @@ void HostInitializeImportMetaObject(v8::Local context, } } - return node::loader::ModuleWrap::HostInitializeImportMetaObjectCallback( - context, module, meta); + return ModuleWrap::HostInitializeImportMetaObjectCallback(context, module, + meta); } v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings( diff --git a/spec/esm-spec.ts b/spec/esm-spec.ts index 27a0a77f64548..c967634a0471e 100644 --- a/spec/esm-spec.ts +++ b/spec/esm-spec.ts @@ -141,7 +141,7 @@ describe('esm', () => { const hostsUrl = pathToFileURL(process.platform === 'win32' ? 'C:\\Windows\\System32\\drivers\\etc\\hosts' : '/etc/hosts'); describe('without context isolation', () => { - it('should use blinks dynamic loader in the main world', async () => { + it('should use Blinks dynamic loader in the main world', async () => { const [webContents] = await loadWindowWithPreload('', { nodeIntegration: true, sandbox: false, @@ -159,11 +159,27 @@ describe('esm', () => { // This is a blink specific error message expect(error?.message).to.include('Failed to fetch dynamically imported module'); }); + + it('should use import.meta callback handling from Node.js for Node.js modules', async () => { + const result = await runFixture(path.resolve(fixturePath, 'import-meta')); + expect(result.code).to.equal(0); + }); }); describe('with context isolation', () => { - it('should use nodes esm dynamic loader in the isolated context', async () => { - const [, preloadError] = await loadWindowWithPreload(`await import(${JSON.stringify(hostsUrl)})`, { + let badFilePath = ''; + + beforeEach(async () => { + badFilePath = path.resolve(path.resolve(os.tmpdir(), 'bad-file.badjs')); + await fs.promises.writeFile(badFilePath, 'const foo = "bar";'); + }); + + afterEach(async () => { + await fs.promises.unlink(badFilePath); + }); + + it('should use Node.js ESM dynamic loader in the isolated context', async () => { + const [, preloadError] = await loadWindowWithPreload(`await import(${JSON.stringify((pathToFileURL(badFilePath)))})`, { nodeIntegration: true, sandbox: false, contextIsolation: true @@ -174,7 +190,7 @@ describe('esm', () => { expect(preloadError!.toString()).to.include('Unknown file extension'); }); - it('should use blinks dynamic loader in the main world', async () => { + it('should use Blinks dynamic loader in the main world', async () => { const [webContents] = await loadWindowWithPreload('', { nodeIntegration: true, sandbox: false, diff --git a/spec/fixtures/esm/import-meta/index.html b/spec/fixtures/esm/import-meta/index.html new file mode 100644 index 0000000000000..6899ed0c0b2ed --- /dev/null +++ b/spec/fixtures/esm/import-meta/index.html @@ -0,0 +1,15 @@ + + + + + + + Hello World! + + +

Hello World!

+ We are using Node.js , + Chromium , + and Electron . + + diff --git a/spec/fixtures/esm/import-meta/main.mjs b/spec/fixtures/esm/import-meta/main.mjs new file mode 100644 index 0000000000000..9b350298c3128 --- /dev/null +++ b/spec/fixtures/esm/import-meta/main.mjs @@ -0,0 +1,33 @@ +import { app, BrowserWindow } from 'electron' +import { fileURLToPath } from 'node:url' +import { dirname, join } from 'node:path'; + +async function createWindow() { + const mainWindow = new BrowserWindow({ + show: false, + webPreferences: { + preload: fileURLToPath(new URL('preload.mjs', import.meta.url)), + sandbox: false, + contextIsolation: false + } + }) + + await mainWindow.loadFile('index.html') + + const importMetaPreload = await mainWindow.webContents.executeJavaScript('window.importMetaPath'); + const expected = join(dirname(fileURLToPath(import.meta.url)), 'preload.mjs'); + + process.exit(importMetaPreload === expected ? 0 : 1); +} + +app.whenReady().then(() => { + createWindow() + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +app.on('window-all-closed', function () { + if (process.platform !== 'darwin') app.quit() +}) diff --git a/spec/fixtures/esm/import-meta/package.json b/spec/fixtures/esm/import-meta/package.json new file mode 100644 index 0000000000000..13ef5537eb992 --- /dev/null +++ b/spec/fixtures/esm/import-meta/package.json @@ -0,0 +1,4 @@ +{ + "main": "main.mjs", + "type": "module" +} diff --git a/spec/fixtures/esm/import-meta/preload.mjs b/spec/fixtures/esm/import-meta/preload.mjs new file mode 100644 index 0000000000000..c2b5856580a02 --- /dev/null +++ b/spec/fixtures/esm/import-meta/preload.mjs @@ -0,0 +1,3 @@ +import { fileURLToPath } from 'node:url' + +window.importMetaPath = fileURLToPath(import.meta.url)