Browse Source

feat: add enableWebSQL webpreference (#23311)

* feat: add enableWebSQL webpreference

* chore: update indexedDB test
Robo 5 years ago
parent
commit
a707a3eda3

+ 2 - 0
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

+ 2 - 1
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

+ 2 - 1
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|.

+ 5 - 0
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

+ 6 - 0
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";

+ 2 - 0
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[];

+ 6 - 0
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())

+ 160 - 0
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,<html></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', () => {

+ 2 - 1
spec/fixtures/pages/storage/web_sql.html

@@ -1,8 +1,9 @@
 <script>
+  const {ipcRenderer} = require('electron')
   try {
     window.openDatabase('test', '1.0', 'test database', 65536)
+    ipcRenderer.send('web-sql-response', null)
   } catch (e) {
-    const {ipcRenderer} = require('electron')
     ipcRenderer.send('web-sql-response', e.message)
   }
 </script>