diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index c21c93383..06319b91a 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -8,6 +8,7 @@ #include #include +#include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/native_window.h" #include "atom/browser/web_view_manager.h" #include "atom/common/native_mate_converters/value_converter.h" @@ -99,12 +100,14 @@ WebContentsPreferences::WebContentsPreferences( // Set WebPreferences defaults onto the JS object SetDefaultBoolIfUndefined(options::kPlugins, false); SetDefaultBoolIfUndefined(options::kExperimentalFeatures, false); - bool node = SetDefaultBoolIfUndefined(options::kNodeIntegration, true); + bool node = SetDefaultBoolIfUndefined(options::kNodeIntegration, true, + Status::Deprecated); SetDefaultBoolIfUndefined(options::kNodeIntegrationInWorker, false); - SetDefaultBoolIfUndefined(options::kWebviewTag, node); + SetDefaultBoolIfUndefined(options::kWebviewTag, node, Status::Deprecated); SetDefaultBoolIfUndefined(options::kSandbox, false); SetDefaultBoolIfUndefined(options::kNativeWindowOpen, false); - SetDefaultBoolIfUndefined(options::kContextIsolation, false); + SetDefaultBoolIfUndefined(options::kContextIsolation, false, + Status::Deprecated); SetDefaultBoolIfUndefined("javascript", true); SetDefaultBoolIfUndefined("images", true); SetDefaultBoolIfUndefined("textAreasAreResizable", true); @@ -134,15 +137,24 @@ WebContentsPreferences::~WebContentsPreferences() { bool WebContentsPreferences::SetDefaultBoolIfUndefined( const base::StringPiece& key, - bool val) { + bool val, + Status status) { auto* current_value = preference_.FindKeyOfType(key, base::Value::Type::BOOLEAN); if (current_value) { return current_value->GetBool(); } else { preference_.SetKey(key, base::Value(val)); - return val; + + if (status == Status::Deprecated && web_contents_) { + auto internal_contents = atom::api::WebContents::CreateFrom( + v8::Isolate::GetCurrent(), web_contents_); + internal_contents->Emit("-deprecated-default", + std::string("webPreferences.") + key.data(), + /* oldDefault */ val, /* newDefault */ !val); + } } + return val; } bool WebContentsPreferences::IsEnabled(const base::StringPiece& name, diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index d835347af..e238b38cc 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -66,11 +66,15 @@ class WebContentsPreferences friend class content::WebContentsUserData; friend class AtomBrowserClient; + enum class Status { Deprecated, Stable }; + // Get WebContents according to process ID. static content::WebContents* GetWebContentsFromProcessID(int process_id); // Set preference value to given bool if user did not provide value - bool SetDefaultBoolIfUndefined(const base::StringPiece& key, bool val); + bool SetDefaultBoolIfUndefined(const base::StringPiece& key, + bool val, + Status status = Status::Stable); static std::vector instances_; diff --git a/default_app/default_app.js b/default_app/default_app.js index 1b9da74e5..7da927a79 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -16,7 +16,10 @@ exports.load = (appUrl) => { autoHideMenuBar: true, backgroundColor: '#FFFFFF', webPreferences: { - nodeIntegrationInWorker: true + nodeIntegration: false, + webviewTag: false, + contextIsolation: true, + preload: path.resolve(__dirname, 'renderer.js') }, useContentSize: true, show: false diff --git a/default_app/index.html b/default_app/index.html index fd4525f12..c2a796291 100644 --- a/default_app/index.html +++ b/default_app/index.html @@ -83,8 +83,6 @@ - - \ No newline at end of file diff --git a/default_app/renderer.js b/default_app/renderer.js index 977aad7ab..fcca5fa41 100644 --- a/default_app/renderer.js +++ b/default_app/renderer.js @@ -4,49 +4,60 @@ const path = require('path') const URL = require('url') const electronPath = path.relative(process.cwd(), remote.process.execPath) -Array.from(document.querySelectorAll('a[href]')).forEach(link => { - // safely add `?utm_source=default_app - let url = URL.parse(link.getAttribute('href'), true) - url.query = Object.assign(url.query, {utm_source: 'default_app'}) - url = URL.format(url) +function initialize () { + Array.from(document.querySelectorAll('a[href]')).forEach(link => { + // safely add `?utm_source=default_app + let url = URL.parse(link.getAttribute('href'), true) + url.query = Object.assign(url.query, {utm_source: 'default_app'}) + url = URL.format(url) - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) }) -}) -document.querySelector('.electron-version').innerText = `Electron v${process.versions.electron}` -document.querySelector('.chrome-version').innerText = `Chromium v${process.versions.chrome}` -document.querySelector('.node-version').innerText = `Node v${process.versions.node}` -document.querySelector('.v8-version').innerText = `v8 v${process.versions.v8}` -document.querySelector('.command-example').innerText = `${electronPath} path-to-app` + document.querySelector('.electron-version').innerText = `Electron v${process.versions.electron}` + document.querySelector('.chrome-version').innerText = `Chromium v${process.versions.chrome}` + document.querySelector('.node-version').innerText = `Node v${process.versions.node}` + document.querySelector('.v8-version').innerText = `v8 v${process.versions.v8}` + document.querySelector('.command-example').innerText = `${electronPath} path-to-app` -function getOcticonSvg (name) { - const octiconPath = path.resolve(__dirname, 'node_modules', 'octicons', 'build', 'svg', `${name}.svg`) - if (fs.existsSync(octiconPath)) { - const content = fs.readFileSync(octiconPath, 'utf8') - const div = document.createElement('div') - div.innerHTML = content - return div + function getOcticonSvg (name) { + const octiconPath = path.resolve(__dirname, 'node_modules', 'octicons', 'build', 'svg', `${name}.svg`) + if (fs.existsSync(octiconPath)) { + const content = fs.readFileSync(octiconPath, 'utf8') + const div = document.createElement('div') + div.innerHTML = content + return div + } + return null } - return null -} -function loadSVG (element) { - for (const cssClass of element.classList) { - if (cssClass.startsWith('octicon-')) { - const icon = getOcticonSvg(cssClass.substr(8)) - if (icon) { - icon.classList = element.classList - element.parentNode.insertBefore(icon, element) - element.remove() - break + function loadSVG (element) { + for (const cssClass of element.classList) { + if (cssClass.startsWith('octicon-')) { + const icon = getOcticonSvg(cssClass.substr(8)) + if (icon) { + icon.classList = element.classList + element.parentNode.insertBefore(icon, element) + element.remove() + break + } } } } + + for (const element of document.querySelectorAll('.octicon')) { + loadSVG(element) + } } -for (const element of document.querySelectorAll('.octicon')) { - loadSVG(element) +function onReadyStateChange () { + if (document.readyState === 'complete') { + document.removeEventListener('readystatechange', onReadyStateChange) + initialize() + } } + +document.addEventListener('readystatechange', onReadyStateChange) diff --git a/docs/api/breaking-changes.md b/docs/api/breaking-changes.md index f0e54c3b7..0e06ee967 100644 --- a/docs/api/breaking-changes.md +++ b/docs/api/breaking-changes.md @@ -34,6 +34,18 @@ app.releaseSingleInstance() app.releaseSingleInstanceLock() ``` +## `new BrowserWindow({ webPreferences: { ... }})` + +```js +// Deprecated defaults +const webPreferences = {} +new BrowserWindow({ webPreferences }) + +// webPreferences.contextIsolation - Default was false, will be true +// webPreferences.nodeIntegration - Default was true, will be false +// webPreferences.webviewTag - Default was true, will be false +``` + # Breaking API Changes (3.0) diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 8f5bd037b..6880f8481 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -293,6 +293,10 @@ WebContents.prototype._init = function () { ipcMain.emit(channel, event, ...args) }) + this.on('-deprecated-default', function (event, key, oldDefault, newDefault) { + deprecate.warnDefault(key, oldDefault, newDefault) + }) + // Handle context menu action request from pepper plugin. this.on('pepper-context-menu', function (event, params, callback) { // Access Menu via electron.Menu to prevent circular require. diff --git a/lib/common/api/deprecate.js b/lib/common/api/deprecate.js index 4a42b2a69..e5e851365 100644 --- a/lib/common/api/deprecate.js +++ b/lib/common/api/deprecate.js @@ -31,6 +31,12 @@ deprecate.warn = (oldName, newName) => { return deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`) } +deprecate.warnDefault = (propName, oldDefault, newDefault) => { + return deprecate.log(`The default value of '${propName}' is changing from \ +'${oldDefault}' to '${newDefault}' in a future release. If you want to keep \ +the current value, please explicitly declare the property.`) +} + let deprecationHandler = null // Print deprecation message. diff --git a/lib/renderer/security-warnings.js b/lib/renderer/security-warnings.js index b24f9e28b..f4af6e04b 100644 --- a/lib/renderer/security-warnings.js +++ b/lib/renderer/security-warnings.js @@ -61,6 +61,13 @@ const getIsRemoteProtocol = function () { * @returns {boolean} Is a CSP with `unsafe-eval` set? */ const isUnsafeEvalEnabled = function () { + // FIXME(MarshallOfSound): Although not exactly true, this warning is incorrect + // when contextIsolation is enabled + // FIXME(MarshallOfSound): Once remote issues have gone away we can remove + // the falsey check + const prefs = getWebPreferences() + if (prefs && prefs.contextIsolation) return false + try { //eslint-disable-next-line new Function('');