From c2cb2f4f4c0f7655bfb1b1f18e0739e4a9abd60f Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Sun, 6 Sep 2020 20:11:50 -0400 Subject: [PATCH 1/4] fix(extensions): define platform info to prevent renderer crash --- docs/api/extensions.md | 1 + .../runtime/electron_runtime_api_delegate.cc | 56 +++++++++++++++++-- spec-main/extensions-spec.ts | 44 +++++++++------ .../extensions/chrome-runtime/background.js | 12 ++++ .../extensions/chrome-runtime/main.js | 41 ++++++++++++-- .../extensions/chrome-runtime/manifest.json | 4 ++ 6 files changed, 133 insertions(+), 25 deletions(-) create mode 100644 spec-main/fixtures/extensions/chrome-runtime/background.js diff --git a/docs/api/extensions.md b/docs/api/extensions.md index f483d6318b056..a8b32f6ac4133 100644 --- a/docs/api/extensions.md +++ b/docs/api/extensions.md @@ -74,6 +74,7 @@ The following methods of `chrome.runtime` are supported: - `chrome.runtime.getBackgroundPage` - `chrome.runtime.getManifest` +- `chrome.runtime.getPlatformInfo` - `chrome.runtime.getURL` - `chrome.runtime.connect` - `chrome.runtime.sendMessage` diff --git a/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc b/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc index b375ac69b1be4..8913cc973c19e 100644 --- a/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc +++ b/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc @@ -7,6 +7,7 @@ #include #include "build/build_config.h" +#include "components/update_client/update_query_params.h" #include "extensions/common/api/runtime.h" #include "shell/browser/extensions/electron_extension_system.h" @@ -42,10 +43,57 @@ bool ElectronRuntimeAPIDelegate::CheckForUpdates( void ElectronRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {} bool ElectronRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) { - // TODO(nornagon): put useful information here. -#if defined(OS_LINUX) - info->os = api::runtime::PLATFORM_OS_LINUX; -#endif + const char* os = update_client::UpdateQueryParams::GetOS(); + if (strcmp(os, "mac") == 0) { + info->os = extensions::api::runtime::PLATFORM_OS_MAC; + } else if (strcmp(os, "win") == 0) { + info->os = extensions::api::runtime::PLATFORM_OS_WIN; + } else if (strcmp(os, "linux") == 0) { + info->os = extensions::api::runtime::PLATFORM_OS_LINUX; + } else if (strcmp(os, "openbsd") == 0) { + info->os = extensions::api::runtime::PLATFORM_OS_OPENBSD; + } else { + NOTREACHED(); + return false; + } + + const char* arch = update_client::UpdateQueryParams::GetArch(); + if (strcmp(arch, "arm") == 0) { + info->arch = extensions::api::runtime::PLATFORM_ARCH_ARM; + } else if (strcmp(arch, "arm64") == 0) { + info->arch = extensions::api::runtime::PLATFORM_ARCH_ARM64; + } else if (strcmp(arch, "x86") == 0) { + info->arch = extensions::api::runtime::PLATFORM_ARCH_X86_32; + } else if (strcmp(arch, "x64") == 0) { + info->arch = extensions::api::runtime::PLATFORM_ARCH_X86_64; + } else if (strcmp(arch, "mipsel") == 0) { + info->arch = extensions::api::runtime::PLATFORM_ARCH_MIPS; + } else if (strcmp(arch, "mips64el") == 0) { + info->arch = extensions::api::runtime::PLATFORM_ARCH_MIPS64; + } else { + NOTREACHED(); + return false; + } + + const char* nacl_arch = update_client::UpdateQueryParams::GetNaclArch(); + if (strcmp(nacl_arch, "arm") == 0) { + info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_ARM; + } else if (strcmp(nacl_arch, "arm64") == 0) { + // Use ARM for ARM64 NaCl, as ARM64 NaCl is not available. + info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_ARM; + } else if (strcmp(nacl_arch, "x86-32") == 0) { + info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_X86_32; + } else if (strcmp(nacl_arch, "x86-64") == 0) { + info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_X86_64; + } else if (strcmp(nacl_arch, "mips32") == 0) { + info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_MIPS; + } else if (strcmp(nacl_arch, "mips64") == 0) { + info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_MIPS64; + } else { + NOTREACHED(); + return false; + } + return true; } // namespace extensions diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 113ddddde993e..2cba9a34a0d4b 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -184,28 +184,38 @@ describe('chrome extensions', () => { }); describe('chrome.runtime', () => { - let content: any; - before(async () => { + let w: BrowserWindow; + const exec = async (name: string) => { + const p = emittedOnce(ipcMain, 'success'); + await w.webContents.executeJavaScript(`exec('${name}')`); + const [, result] = await p; + return result; + }; + beforeEach(async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-runtime')); - const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); - try { - w.loadURL(url); - await emittedOnce(w.webContents, 'dom-ready'); - content = JSON.parse(await w.webContents.executeJavaScript('document.documentElement.textContent')); - expect(content).to.be.an('object'); - } finally { - w.destroy(); - } + w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } }); + w.loadURL(url); + await emittedOnce(w.webContents, 'dom-ready'); + }); + it('getManifest()', async () => { + const result = await exec('getManifest'); + expect(result).to.be.an('object').with.property('name', 'chrome-runtime'); }); - it('getManifest()', () => { - expect(content.manifest).to.be.an('object').with.property('name', 'chrome-runtime'); + it('id', async () => { + const result = await exec('id'); + expect(result).to.be.a('string').with.lengthOf(32); }); - it('id', () => { - expect(content.id).to.be.a('string').with.lengthOf(32); + it('getURL()', async () => { + const result = await exec('getURL'); + expect(result).to.be.a('string').and.match(/^chrome-extension:\/\/.*main.js$/); }); - it('getURL()', () => { - expect(content.url).to.be.a('string').and.match(/^chrome-extension:\/\/.*main.js$/); + it('getPlatformInfo()', async () => { + const result = await exec('getPlatformInfo'); + expect(result).to.be.an('object'); + expect(result.os).to.be.a('string'); + expect(result.arch).to.be.a('string'); + expect(result.nacl_arch).to.be.a('string'); }); }); diff --git a/spec-main/fixtures/extensions/chrome-runtime/background.js b/spec-main/fixtures/extensions/chrome-runtime/background.js new file mode 100644 index 0000000000000..7a81b6c8e749c --- /dev/null +++ b/spec-main/fixtures/extensions/chrome-runtime/background.js @@ -0,0 +1,12 @@ +/* global chrome */ + +chrome.runtime.onMessage.addListener((message, sender, reply) => { + switch (message) { + case 'getPlatformInfo': + chrome.runtime.getPlatformInfo(reply); + break; + } + + // Respond asynchronously + return true; +}); diff --git a/spec-main/fixtures/extensions/chrome-runtime/main.js b/spec-main/fixtures/extensions/chrome-runtime/main.js index e51e94e634c8f..c830b951dddb7 100644 --- a/spec-main/fixtures/extensions/chrome-runtime/main.js +++ b/spec-main/fixtures/extensions/chrome-runtime/main.js @@ -1,6 +1,39 @@ /* eslint-disable */ -document.documentElement.textContent = JSON.stringify({ - manifest: chrome.runtime.getManifest(), - id: chrome.runtime.id, - url: chrome.runtime.getURL('main.js') + +function evalInMainWorld(fn) { + const script = document.createElement('script') + script.textContent = `((${fn})())` + document.documentElement.appendChild(script) +} + +async function exec(name) { + let result + switch (name) { + case 'getManifest': + result = chrome.runtime.getManifest() + break + case 'id': + result = chrome.runtime.id + break + case 'getURL': + result = chrome.runtime.getURL('main.js') + break + case 'getPlatformInfo': { + result = await new Promise(resolve => { + chrome.runtime.sendMessage(name, resolve) + }) + break + } + } + + const funcStr = `() => { require('electron').ipcRenderer.send('success', ${JSON.stringify(result)}) }` + evalInMainWorld(funcStr) +} + +window.addEventListener('message', event => { + exec(event.data.name) +}) + +evalInMainWorld(() => { + window.exec = name => window.postMessage({ name }) }) diff --git a/spec-main/fixtures/extensions/chrome-runtime/manifest.json b/spec-main/fixtures/extensions/chrome-runtime/manifest.json index c73d0a5df515b..9fca66254304b 100644 --- a/spec-main/fixtures/extensions/chrome-runtime/manifest.json +++ b/spec-main/fixtures/extensions/chrome-runtime/manifest.json @@ -8,5 +8,9 @@ "run_at": "document_end" } ], + "background": { + "scripts": ["background.js"], + "persistent": false + }, "manifest_version": 2 } From 6d8ca09c6b6d1778304a09d9334839d096c822a5 Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Tue, 8 Sep 2020 19:39:33 -0400 Subject: [PATCH 2/4] fix(extensions): include update_client component in BUILD.gn The Runtime API Delegate relies on values from this component. --- BUILD.gn | 1 + 1 file changed, 1 insertion(+) diff --git a/BUILD.gn b/BUILD.gn index 6b538e1432b86..a0a9a8035c4db 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -642,6 +642,7 @@ source_set("electron_lib") { "shell/common/extensions/api", "shell/common/extensions/api:extensions_features", "//chrome/browser/resources:component_extension_resources", + "//components/update_client:update_client", "//components/zoom", "//extensions/browser", "//extensions/browser:core_api_provider", From 80175a4708955c96846ed889dc8cb70c4261b052 Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Thu, 10 Sep 2020 23:36:02 -0400 Subject: [PATCH 3/4] refactor(extensions): remove unsupported platform architecture strings --- .../api/runtime/electron_runtime_api_delegate.cc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc b/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc index 8913cc973c19e..a7ef02dc11959 100644 --- a/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc +++ b/shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc @@ -66,10 +66,6 @@ bool ElectronRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) { info->arch = extensions::api::runtime::PLATFORM_ARCH_X86_32; } else if (strcmp(arch, "x64") == 0) { info->arch = extensions::api::runtime::PLATFORM_ARCH_X86_64; - } else if (strcmp(arch, "mipsel") == 0) { - info->arch = extensions::api::runtime::PLATFORM_ARCH_MIPS; - } else if (strcmp(arch, "mips64el") == 0) { - info->arch = extensions::api::runtime::PLATFORM_ARCH_MIPS64; } else { NOTREACHED(); return false; @@ -85,10 +81,6 @@ bool ElectronRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) { info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_X86_32; } else if (strcmp(nacl_arch, "x86-64") == 0) { info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_X86_64; - } else if (strcmp(nacl_arch, "mips32") == 0) { - info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_MIPS; - } else if (strcmp(nacl_arch, "mips64") == 0) { - info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_MIPS64; } else { NOTREACHED(); return false; From 24808d1cbfc310ed697c4a5807ec50789e4ce874 Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Thu, 10 Sep 2020 23:45:12 -0400 Subject: [PATCH 4/4] test(extensions): don't use dom-ready event unless there's reason to --- spec-main/extensions-spec.ts | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 2cba9a34a0d4b..261e094739afd 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -40,9 +40,8 @@ describe('chrome extensions', () => { it('does not crash when using chrome.management', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } }); - w.loadURL('about:blank'); + await w.loadURL('about:blank'); - await emittedOnce(w.webContents, 'dom-ready'); await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); const args: any = await emittedOnce(app, 'web-contents-created'); const wc: Electron.WebContents = args[1]; @@ -60,9 +59,8 @@ describe('chrome extensions', () => { it('can open WebSQLDatabase in a background page', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } }); - w.loadURL('about:blank'); + await w.loadURL('about:blank'); - await emittedOnce(w.webContents, 'dom-ready'); await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); const args: any = await emittedOnce(app, 'web-contents-created'); const wc: Electron.WebContents = args[1]; @@ -77,8 +75,7 @@ describe('chrome extensions', () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } }); const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); - w.loadURL(`${extension.url}bare-page.html`); - await emittedOnce(w.webContents, 'dom-ready'); + await w.loadURL(`${extension.url}bare-page.html`); await expect(fetch(w.webContents, `${url}/cors`)).to.not.be.rejectedWith(TypeError); }); @@ -90,8 +87,7 @@ describe('chrome extensions', () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); - w.loadURL(url); - await emittedOnce(w.webContents, 'dom-ready'); + await w.loadURL(url); const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor'); expect(bg).to.equal('red'); }); @@ -145,8 +141,7 @@ describe('chrome extensions', () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const w = new BrowserWindow({ show: false }); // not in the session - w.loadURL(url); - await emittedOnce(w.webContents, 'dom-ready'); + await w.loadURL(url); const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor'); expect(bg).to.equal(''); }); @@ -169,8 +164,7 @@ describe('chrome extensions', () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-i18n')); w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } }); - w.loadURL(url); - await emittedOnce(w.webContents, 'dom-ready'); + await w.loadURL(url); }); it('getAcceptLanguages()', async () => { const result = await exec('getAcceptLanguages'); @@ -195,8 +189,7 @@ describe('chrome extensions', () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-runtime')); w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } }); - w.loadURL(url); - await emittedOnce(w.webContents, 'dom-ready'); + await w.loadURL(url); }); it('getManifest()', async () => { const result = await exec('getManifest'); @@ -560,8 +553,7 @@ describe('chrome extensions', () => { it('loads a ui page of an extension', async () => { const { id } = await session.defaultSession.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); const w = new BrowserWindow({ show: false }); - w.loadURL(`chrome-extension://${id}/bare-page.html`); - await emittedOnce(w.webContents, 'dom-ready'); + await w.loadURL(`chrome-extension://${id}/bare-page.html`); const textContent = await w.webContents.executeJavaScript('document.body.textContent'); expect(textContent).to.equal('ui page loaded ok\n'); }); @@ -569,8 +561,7 @@ describe('chrome extensions', () => { it('can load resources', async () => { const { id } = await session.defaultSession.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); const w = new BrowserWindow({ show: false }); - w.loadURL(`chrome-extension://${id}/page-script-load.html`); - await emittedOnce(w.webContents, 'dom-ready'); + await w.loadURL(`chrome-extension://${id}/page-script-load.html`); const textContent = await w.webContents.executeJavaScript('document.body.textContent'); expect(textContent).to.equal('script loaded ok\n'); });