From a707a3eda37a959496a6fdbc0989ee25272bf668 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 6 May 2020 12:52:59 -0700 Subject: [PATCH] feat: add enableWebSQL webpreference (#23311) * feat: add enableWebSQL webpreference * chore: update indexedDB test --- docs/api/browser-window.md | 2 + lib/browser/guest-view-manager.js | 3 +- lib/browser/guest-window-manager.js | 3 +- shell/browser/web_contents_preferences.cc | 5 + shell/common/options_switches.cc | 6 + shell/common/options_switches.h | 2 + shell/renderer/content_settings_observer.cc | 6 + spec-main/chromium-spec.ts | 160 ++++++++++++++++++++ spec/fixtures/pages/storage/web_sql.html | 3 +- 9 files changed, 187 insertions(+), 3 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 25a7ccff8a7..da634e7eea6 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -385,6 +385,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. visible to users. * `spellcheck` Boolean (optional) - Whether to enable the builtin spellchecker. Default is `true`. + * `enableWebSQL` Boolean (optional) - Whether to enable the [WebSQL api](https://www.w3.org/TR/webdatabase/). + Default is `true`. When setting minimum or maximum window size with `minWidth`/`maxWidth`/ `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index ded8dc6a43a..386181bcc71 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -217,7 +217,8 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn ['nodeIntegration', false], ['enableRemoteModule', false], ['sandbox', true], - ['nodeIntegrationInSubFrames', false] + ['nodeIntegrationInSubFrames', false], + ['enableWebSQL', false] ]); // Inherit certain option values from embedder diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index 98759436004..08dc5d742e0 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -19,7 +19,8 @@ const inheritedWebPreferences = new Map([ ['enableRemoteModule', false], ['sandbox', true], ['webviewTag', false], - ['nodeIntegrationInSubFrames', false] + ['nodeIntegrationInSubFrames', false], + ['enableWebSQL', false] ]); // Copy attribute of |parent| to |child| if it is not defined in |child|. diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc index c96c69db581..fc482b58d3b 100644 --- a/shell/browser/web_contents_preferences.cc +++ b/shell/browser/web_contents_preferences.cc @@ -130,6 +130,7 @@ WebContentsPreferences::WebContentsPreferences( SetDefaultBoolIfUndefined(options::kImages, true); SetDefaultBoolIfUndefined(options::kTextAreasAreResizable, true); SetDefaultBoolIfUndefined(options::kWebGL, true); + SetDefaultBoolIfUndefined(options::kEnableWebSQL, true); bool webSecurity = true; SetDefaultBoolIfUndefined(options::kWebSecurity, webSecurity); // If webSecurity was explicity set to false, let's inherit that into @@ -419,6 +420,10 @@ void WebContentsPreferences::AppendCommandLineSwitches( } #endif + // Whether to allow the WebSQL api + if (IsEnabled(options::kEnableWebSQL)) + command_line->AppendSwitch(switches::kEnableWebSQL); + // We are appending args to a webContents so let's save the current state // of our preferences object so that during the lifetime of the WebContents // we can fetch the options used to initally configure the WebContents diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc index 3b09a5d2769..3056a779156 100644 --- a/shell/common/options_switches.cc +++ b/shell/common/options_switches.cc @@ -182,6 +182,8 @@ const char kSpellcheck[] = "spellcheck"; const char kEnableRemoteModule[] = "enableRemoteModule"; #endif +const char kEnableWebSQL[] = "enableWebSQL"; + } // namespace options namespace switches { @@ -250,6 +252,10 @@ const char kNodeIntegrationInWorker[] = "node-integration-in-worker"; // environments will be created in sub-frames. const char kNodeIntegrationInSubFrames[] = "node-integration-in-subframes"; +// Command switch passed to render process to control whether WebSQL api +// is allowed. +const char kEnableWebSQL[] = "enable-websql"; + // Widevine options // Path to Widevine CDM binaries. const char kWidevineCdmPath[] = "widevine-cdm-path"; diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index d33a548ba88..7f4d140d573 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -84,6 +84,7 @@ extern const char kImages[]; extern const char kTextAreasAreResizable[]; extern const char kWebGL[]; extern const char kNavigateOnDragDrop[]; +extern const char kEnableWebSQL[]; #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) extern const char kSpellcheck[]; @@ -129,6 +130,7 @@ extern const char kWebviewTag[]; extern const char kNodeIntegrationInSubFrames[]; extern const char kDisableElectronSiteInstanceOverrides[]; extern const char kEnableNodeLeakageInRenderers[]; +extern const char kEnableWebSQL[]; extern const char kWidevineCdmPath[]; extern const char kWidevineCdmVersion[]; diff --git a/shell/renderer/content_settings_observer.cc b/shell/renderer/content_settings_observer.cc index 0a843601e21..6966fdc3b5e 100644 --- a/shell/renderer/content_settings_observer.cc +++ b/shell/renderer/content_settings_observer.cc @@ -5,6 +5,7 @@ #include "shell/renderer/content_settings_observer.h" #include "content/public/renderer/render_frame.h" +#include "shell/common/options_switches.h" #include "third_party/blink/public/platform/url_conversion.h" #include "third_party/blink/public/platform/web_security_origin.h" #include "third_party/blink/public/web/web_local_frame.h" @@ -20,6 +21,11 @@ ContentSettingsObserver::ContentSettingsObserver( ContentSettingsObserver::~ContentSettingsObserver() = default; bool ContentSettingsObserver::AllowDatabase() { + if (!base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableWebSQL)) { + return false; + } + blink::WebFrame* frame = render_frame()->GetWebFrame(); if (frame->GetSecurityOrigin().IsOpaque() || frame->Top()->GetSecurityOrigin().IsOpaque()) diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index 894f856baa6..c9d4dfd6829 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -1010,6 +1010,166 @@ describe('chromium features', () => { testLocalStorageAfterXSiteRedirect('after a cross-site redirect'); testLocalStorageAfterXSiteRedirect('after a cross-site redirect in sandbox mode', { sandbox: true }); }); + + describe('enableWebSQL webpreference', () => { + const standardScheme = (global as any).standardScheme; + const origin = `${standardScheme}://fake-host`; + const filePath = path.join(fixturesPath, 'pages', 'storage', 'web_sql.html'); + const sqlPartition = 'web-sql-preference-test'; + const sqlSession = session.fromPartition(sqlPartition); + const securityError = 'An attempt was made to break through the security policy of the user agent.'; + let contents: WebContents, w: BrowserWindow; + + before(() => { + sqlSession.protocol.registerFileProtocol(standardScheme, (request, callback) => { + callback({ path: filePath }); + }); + }); + + after(() => { + sqlSession.protocol.unregisterProtocol(standardScheme); + }); + + afterEach(async () => { + if (contents) { + (contents as any).destroy(); + contents = null as any; + } + await closeAllWindows(); + (w as any) = null; + }); + + it('default value allows websql', async () => { + contents = (webContents as any).create({ + session: sqlSession, + nodeIntegration: true + }); + contents.loadURL(origin); + const [, error] = await emittedOnce(ipcMain, 'web-sql-response'); + expect(error).to.be.null(); + }); + + it('when set to false can disallow websql', async () => { + contents = (webContents as any).create({ + session: sqlSession, + nodeIntegration: true, + enableWebSQL: false + }); + contents.loadURL(origin); + const [, error] = await emittedOnce(ipcMain, 'web-sql-response'); + expect(error).to.equal(securityError); + }); + + it('when set to false does not disable indexedDB', async () => { + contents = (webContents as any).create({ + session: sqlSession, + nodeIntegration: true, + enableWebSQL: false + }); + contents.loadURL(origin); + const [, error] = await emittedOnce(ipcMain, 'web-sql-response'); + expect(error).to.equal(securityError); + const dbName = 'random'; + const result = await contents.executeJavaScript(` + new Promise((resolve, reject) => { + try { + let req = window.indexedDB.open('${dbName}'); + req.onsuccess = (event) => { + let db = req.result; + resolve(db.name); + } + req.onerror = (event) => { resolve(event.target.code); } + } catch (e) { + resolve(e.message); + } + }); + `); + expect(result).to.equal(dbName); + }); + + it('child webContents can override when the embedder has allowed websql', async () => { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true, + webviewTag: true, + session: sqlSession + } + }); + w.webContents.loadURL(origin); + const [, error] = await emittedOnce(ipcMain, 'web-sql-response'); + expect(error).to.be.null(); + const webviewResult = emittedOnce(ipcMain, 'web-sql-response'); + await w.webContents.executeJavaScript(` + new Promise((resolve, reject) => { + const webview = new WebView(); + webview.setAttribute('src', '${origin}'); + webview.setAttribute('webpreferences', 'enableWebSQL=0'); + webview.setAttribute('partition', '${sqlPartition}'); + webview.setAttribute('nodeIntegration', 'on'); + document.body.appendChild(webview); + webview.addEventListener('dom-ready', () => resolve()); + }); + `); + const [, childError] = await webviewResult; + expect(childError).to.equal(securityError); + }); + + it('child webContents cannot override when the embedder has disallowed websql', async () => { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true, + enableWebSQL: false, + webviewTag: true, + session: sqlSession + } + }); + w.webContents.loadURL('data:text/html,'); + const webviewResult = emittedOnce(ipcMain, 'web-sql-response'); + await w.webContents.executeJavaScript(` + new Promise((resolve, reject) => { + const webview = new WebView(); + webview.setAttribute('src', '${origin}'); + webview.setAttribute('webpreferences', 'enableWebSQL=1'); + webview.setAttribute('partition', '${sqlPartition}'); + webview.setAttribute('nodeIntegration', 'on'); + document.body.appendChild(webview); + webview.addEventListener('dom-ready', () => resolve()); + }); + `); + const [, childError] = await webviewResult; + expect(childError).to.equal(securityError); + }); + + it('child webContents can use websql when the embedder has allowed websql', async () => { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true, + webviewTag: true, + session: sqlSession + } + }); + w.webContents.loadURL(origin); + const [, error] = await emittedOnce(ipcMain, 'web-sql-response'); + expect(error).to.be.null(); + const webviewResult = emittedOnce(ipcMain, 'web-sql-response'); + await w.webContents.executeJavaScript(` + new Promise((resolve, reject) => { + const webview = new WebView(); + webview.setAttribute('src', '${origin}'); + webview.setAttribute('webpreferences', 'enableWebSQL=1'); + webview.setAttribute('partition', '${sqlPartition}'); + webview.setAttribute('nodeIntegration', 'on'); + document.body.appendChild(webview); + webview.addEventListener('dom-ready', () => resolve()); + }); + `); + const [, childError] = await webviewResult; + expect(childError).to.be.null(); + }); + }); }); ifdescribe(features.isPDFViewerEnabled())('PDF Viewer', () => { diff --git a/spec/fixtures/pages/storage/web_sql.html b/spec/fixtures/pages/storage/web_sql.html index 8ba4eec9252..7d737a382ee 100644 --- a/spec/fixtures/pages/storage/web_sql.html +++ b/spec/fixtures/pages/storage/web_sql.html @@ -1,8 +1,9 @@