feat: move webFrame scheme privilege methods to main process (#16416)

* chore: deprecate webFrame.registerURLSchemeAsPrivileged

* Add register schemes protocol api

* update branch to enable browser process API

* Revert deprecation changes

* Fetch API support

* Updated api to take an array, still working on tests

* Update tests

* Remove web frame API

* Minor changes

* update scheme registrations on browser and renderer process

* fix: enable ses.getBlobData spec

* Update breaking changes doc
This commit is contained in:
Nitish Sakhawalkar 2019-01-28 23:11:01 -08:00 committed by Cheng Zhao
parent 257de6a963
commit 940c4c0787
19 changed files with 319 additions and 319 deletions

View file

@ -213,18 +213,30 @@ base::RefCountedMemory* AtomContentClient::GetDataResourceBytes(
} }
void AtomContentClient::AddAdditionalSchemes(Schemes* schemes) { void AtomContentClient::AddAdditionalSchemes(Schemes* schemes) {
schemes->standard_schemes.push_back("chrome-extension");
std::vector<std::string> splited; std::vector<std::string> splited;
ConvertStringWithSeparatorToVector(&splited, ",", ConvertStringWithSeparatorToVector(&splited, ",",
switches::kRegisterServiceWorkerSchemes); switches::kServiceWorkerSchemes);
for (const std::string& scheme : splited) for (const std::string& scheme : splited)
schemes->service_worker_schemes.push_back(scheme); schemes->service_worker_schemes.push_back(scheme);
schemes->service_worker_schemes.push_back(url::kFileScheme); schemes->service_worker_schemes.push_back(url::kFileScheme);
ConvertStringWithSeparatorToVector(&splited, ",", switches::kStandardSchemes);
for (const std::string& scheme : splited)
schemes->standard_schemes.push_back(scheme);
schemes->standard_schemes.push_back("chrome-extension");
ConvertStringWithSeparatorToVector(&splited, ",", switches::kSecureSchemes); ConvertStringWithSeparatorToVector(&splited, ",", switches::kSecureSchemes);
for (const std::string& scheme : splited) for (const std::string& scheme : splited)
schemes->secure_schemes.push_back(scheme); schemes->secure_schemes.push_back(scheme);
ConvertStringWithSeparatorToVector(&splited, ",",
switches::kBypassCSPSchemes);
for (const std::string& scheme : splited)
schemes->csp_bypassing_schemes.push_back(scheme);
ConvertStringWithSeparatorToVector(&splited, ",", switches::kCORSSchemes);
for (const std::string& scheme : splited)
schemes->cors_enabled_schemes.push_back(scheme);
} }
void AtomContentClient::AddPepperPlugins( void AtomContentClient::AddPepperPlugins(

View file

@ -24,47 +24,119 @@
using content::BrowserThread; using content::BrowserThread;
namespace atom {
namespace api {
namespace { namespace {
// List of registered custom standard schemes. // List of registered custom standard schemes.
std::vector<std::string> g_standard_schemes; std::vector<std::string> g_standard_schemes;
struct SchemeOptions {
bool standard = false;
bool secure = false;
bool bypassCSP = false;
bool allowServiceWorkers = false;
bool supportFetchAPI = false;
bool corsEnabled = false;
};
struct CustomScheme {
std::string scheme;
SchemeOptions options;
};
} // namespace } // namespace
namespace mate {
template <>
struct Converter<CustomScheme> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
CustomScheme* out) {
mate::Dictionary dict;
if (!ConvertFromV8(isolate, val, &dict))
return false;
if (!dict.Get("scheme", &(out->scheme)))
return false;
mate::Dictionary opt;
// options are optional. Default values specified in SchemeOptions are used
if (dict.Get("options", &opt)) {
opt.Get("standard", &(out->options.standard));
opt.Get("supportFetchAPI", &(out->options.supportFetchAPI));
opt.Get("secure", &(out->options.secure));
opt.Get("bypassCSP", &(out->options.bypassCSP));
opt.Get("allowServiceWorkers", &(out->options.allowServiceWorkers));
opt.Get("supportFetchAPI", &(out->options.supportFetchAPI));
opt.Get("corsEnabled", &(out->options.corsEnabled));
}
return true;
}
};
} // namespace mate
namespace atom {
namespace api {
std::vector<std::string> GetStandardSchemes() { std::vector<std::string> GetStandardSchemes() {
return g_standard_schemes; return g_standard_schemes;
} }
void RegisterStandardSchemes(const std::vector<std::string>& schemes, void RegisterSchemesAsPrivileged(v8::Local<v8::Value> val,
mate::Arguments* args) { mate::Arguments* args) {
g_standard_schemes = schemes; std::vector<CustomScheme> custom_schemes;
if (!mate::ConvertFromV8(args->isolate(), val, &custom_schemes)) {
args->ThrowError("Argument must be an array of custom schemes.");
return;
}
mate::Dictionary opts; std::vector<std::string> secure_schemes, cspbypassing_schemes, fetch_schemes,
bool secure = false; service_worker_schemes, cors_schemes;
args->GetNext(&opts) && opts.Get("secure", &secure); for (const auto& custom_scheme : custom_schemes) {
// Register scheme to privileged list (https, wss, data, chrome-extension)
// Dynamically register the schemes. if (custom_scheme.options.standard) {
auto* policy = content::ChildProcessSecurityPolicy::GetInstance(); auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
for (const std::string& scheme : schemes) { url::AddStandardScheme(custom_scheme.scheme.c_str(),
url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITH_HOST); url::SCHEME_WITH_HOST);
if (secure) { g_standard_schemes.push_back(custom_scheme.scheme);
url::AddSecureScheme(scheme.c_str()); policy->RegisterWebSafeScheme(custom_scheme.scheme);
}
if (custom_scheme.options.secure) {
secure_schemes.push_back(custom_scheme.scheme);
url::AddSecureScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.bypassCSP) {
cspbypassing_schemes.push_back(custom_scheme.scheme);
url::AddCSPBypassingScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.corsEnabled) {
cors_schemes.push_back(custom_scheme.scheme);
url::AddCorsEnabledScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.supportFetchAPI) {
fetch_schemes.push_back(custom_scheme.scheme);
}
if (custom_scheme.options.allowServiceWorkers) {
service_worker_schemes.push_back(custom_scheme.scheme);
} }
policy->RegisterWebSafeScheme(scheme);
} }
const auto AppendSchemesToCmdLine = [](const char* switch_name,
std::vector<std::string> schemes) {
// Add the schemes to command line switches, so child processes can also // Add the schemes to command line switches, so child processes can also
// register them. // register them.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
atom::switches::kStandardSchemes, base::JoinString(schemes, ",")); switch_name, base::JoinString(schemes, ","));
if (secure) { };
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
atom::switches::kSecureSchemes, base::JoinString(schemes, ",")); AppendSchemesToCmdLine(atom::switches::kSecureSchemes, secure_schemes);
} AppendSchemesToCmdLine(atom::switches::kBypassCSPSchemes,
cspbypassing_schemes);
AppendSchemesToCmdLine(atom::switches::kCORSSchemes, cors_schemes);
AppendSchemesToCmdLine(atom::switches::kFetchSchemes, fetch_schemes);
AppendSchemesToCmdLine(atom::switches::kServiceWorkerSchemes,
service_worker_schemes);
AppendSchemesToCmdLine(atom::switches::kStandardSchemes, g_standard_schemes);
} }
Protocol::Protocol(v8::Isolate* isolate, AtomBrowserContext* browser_context) Protocol::Protocol(v8::Isolate* isolate, AtomBrowserContext* browser_context)
@ -73,12 +145,6 @@ Protocol::Protocol(v8::Isolate* isolate, AtomBrowserContext* browser_context)
} }
Protocol::~Protocol() {} Protocol::~Protocol() {}
void Protocol::RegisterServiceWorkerSchemes(
const std::vector<std::string>& schemes) {
atom::AtomBrowserClient::SetCustomServiceWorkerSchemes(schemes);
}
void Protocol::UnregisterProtocol(const std::string& scheme, void Protocol::UnregisterProtocol(const std::string& scheme,
mate::Arguments* args) { mate::Arguments* args) {
CompletionCallback callback; CompletionCallback callback;
@ -195,8 +261,6 @@ void Protocol::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) { v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(mate::StringToV8(isolate, "Protocol")); prototype->SetClassName(mate::StringToV8(isolate, "Protocol"));
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("registerServiceWorkerSchemes",
&Protocol::RegisterServiceWorkerSchemes)
.SetMethod("registerStringProtocol", .SetMethod("registerStringProtocol",
&Protocol::RegisterProtocol<URLRequestStringJob>) &Protocol::RegisterProtocol<URLRequestStringJob>)
.SetMethod("registerBufferProtocol", .SetMethod("registerBufferProtocol",
@ -228,16 +292,16 @@ void Protocol::BuildPrototype(v8::Isolate* isolate,
namespace { namespace {
void RegisterStandardSchemes(const std::vector<std::string>& schemes, void RegisterSchemesAsPrivileged(v8::Local<v8::Value> val,
mate::Arguments* args) { mate::Arguments* args) {
if (atom::Browser::Get()->is_ready()) { if (atom::Browser::Get()->is_ready()) {
args->ThrowError( args->ThrowError(
"protocol.registerStandardSchemes should be called before " "protocol.registerSchemesAsPrivileged should be called before "
"app is ready"); "app is ready");
return; return;
} }
atom::api::RegisterStandardSchemes(schemes, args); atom::api::RegisterSchemesAsPrivileged(val, args);
} }
void Initialize(v8::Local<v8::Object> exports, void Initialize(v8::Local<v8::Object> exports,
@ -246,7 +310,7 @@ void Initialize(v8::Local<v8::Object> exports,
void* priv) { void* priv) {
v8::Isolate* isolate = context->GetIsolate(); v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports); mate::Dictionary dict(isolate, exports);
dict.SetMethod("registerStandardSchemes", &RegisterStandardSchemes); dict.SetMethod("registerSchemesAsPrivileged", &RegisterSchemesAsPrivileged);
dict.SetMethod("getStandardSchemes", &atom::api::GetStandardSchemes); dict.SetMethod("getStandardSchemes", &atom::api::GetStandardSchemes);
} }

View file

@ -34,7 +34,8 @@ namespace atom {
namespace api { namespace api {
std::vector<std::string> GetStandardSchemes(); std::vector<std::string> GetStandardSchemes();
void RegisterStandardSchemes(const std::vector<std::string>& schemes,
void RegisterSchemesAsPrivileged(v8::Local<v8::Value> val,
mate::Arguments* args); mate::Arguments* args);
class Protocol : public mate::TrackableObject<Protocol> { class Protocol : public mate::TrackableObject<Protocol> {
@ -94,9 +95,6 @@ class Protocol : public mate::TrackableObject<Protocol> {
DISALLOW_COPY_AND_ASSIGN(CustomProtocolHandler); DISALLOW_COPY_AND_ASSIGN(CustomProtocolHandler);
}; };
// Register schemes that can handle service worker.
void RegisterServiceWorkerSchemes(const std::vector<std::string>& schemes);
// Register the protocol with certain request job. // Register the protocol with certain request job.
template <typename RequestJob> template <typename RequestJob>
void RegisterProtocol(const std::string& scheme, void RegisterProtocol(const std::string& scheme,

View file

@ -114,9 +114,6 @@ namespace {
// Next navigation should not restart renderer process. // Next navigation should not restart renderer process.
bool g_suppress_renderer_process_restart = false; bool g_suppress_renderer_process_restart = false;
// Custom schemes to be registered to handle service worker.
base::NoDestructor<std::string> g_custom_service_worker_schemes;
bool IsSameWebSite(content::BrowserContext* browser_context, bool IsSameWebSite(content::BrowserContext* browser_context,
const GURL& src_url, const GURL& src_url,
const GURL& dest_url) { const GURL& dest_url) {
@ -148,11 +145,6 @@ void AtomBrowserClient::SuppressRendererProcessRestartForOnce() {
g_suppress_renderer_process_restart = true; g_suppress_renderer_process_restart = true;
} }
void AtomBrowserClient::SetCustomServiceWorkerSchemes(
const std::vector<std::string>& schemes) {
*g_custom_service_worker_schemes = base::JoinString(schemes, ",");
}
AtomBrowserClient* AtomBrowserClient::Get() { AtomBrowserClient* AtomBrowserClient::Get() {
return g_browser_client; return g_browser_client;
} }
@ -477,18 +469,15 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
return; return;
// Copy following switches to child process. // Copy following switches to child process.
static const char* const kCommonSwitchNames[] = {switches::kStandardSchemes, static const char* const kCommonSwitchNames[] = {
switches::kEnableSandbox, switches::kStandardSchemes, switches::kEnableSandbox,
switches::kSecureSchemes}; switches::kSecureSchemes, switches::kBypassCSPSchemes,
switches::kCORSSchemes, switches::kFetchSchemes,
switches::kServiceWorkerSchemes};
command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(), command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
kCommonSwitchNames, kCommonSwitchNames,
arraysize(kCommonSwitchNames)); arraysize(kCommonSwitchNames));
// The registered service worker schemes.
if (!g_custom_service_worker_schemes->empty())
command_line->AppendSwitchASCII(switches::kRegisterServiceWorkerSchemes,
*g_custom_service_worker_schemes);
#if defined(OS_WIN) #if defined(OS_WIN)
// Append --app-user-model-id. // Append --app-user-model-id.
PWSTR current_app_id; PWSTR current_app_id;

View file

@ -49,10 +49,6 @@ class AtomBrowserClient : public content::ContentBrowserClient,
// Don't force renderer process to restart for once. // Don't force renderer process to restart for once.
static void SuppressRendererProcessRestartForOnce(); static void SuppressRendererProcessRestartForOnce();
// Custom schemes to be registered to handle service worker.
static void SetCustomServiceWorkerSchemes(
const std::vector<std::string>& schemes);
NotificationPresenter* GetNotificationPresenter(); NotificationPresenter* GetNotificationPresenter();
void WebNotificationAllowed(int render_process_id, void WebNotificationAllowed(int render_process_id,

View file

@ -179,11 +179,20 @@ const char kDisableHttpCache[] = "disable-http-cache";
const char kStandardSchemes[] = "standard-schemes"; const char kStandardSchemes[] = "standard-schemes";
// Register schemes to handle service worker. // Register schemes to handle service worker.
const char kRegisterServiceWorkerSchemes[] = "register-service-worker-schemes"; const char kServiceWorkerSchemes[] = "service-worker-schemes";
// Register schemes as secure. // Register schemes as secure.
const char kSecureSchemes[] = "secure-schemes"; const char kSecureSchemes[] = "secure-schemes";
// Register schemes as bypassing CSP.
const char kBypassCSPSchemes[] = "bypasscsp-schemes";
// Register schemes as support fetch API.
const char kFetchSchemes[] = "fetch-schemes";
// Register schemes as CORS enabled.
const char kCORSSchemes[] = "cors-schemes";
// The browser process app model ID // The browser process app model ID
const char kAppUserModelId[] = "app-user-model-id"; const char kAppUserModelId[] = "app-user-model-id";

View file

@ -89,8 +89,11 @@ extern const char kPpapiFlashPath[];
extern const char kPpapiFlashVersion[]; extern const char kPpapiFlashVersion[];
extern const char kDisableHttpCache[]; extern const char kDisableHttpCache[];
extern const char kStandardSchemes[]; extern const char kStandardSchemes[];
extern const char kRegisterServiceWorkerSchemes[]; extern const char kServiceWorkerSchemes[];
extern const char kSecureSchemes[]; extern const char kSecureSchemes[];
extern const char kBypassCSPSchemes[];
extern const char kFetchSchemes[];
extern const char kCORSSchemes[];
extern const char kAppUserModelId[]; extern const char kAppUserModelId[];
extern const char kAppPath[]; extern const char kAppPath[];

View file

@ -31,7 +31,6 @@
#include "third_party/blink/public/web/web_script_execution_callback.h" #include "third_party/blink/public/web/web_script_execution_callback.h"
#include "third_party/blink/public/web/web_script_source.h" #include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_view.h" #include "third_party/blink/public/web/web_view.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "url/url_util.h" #include "url/url_util.h"
#include "atom/common/node_includes.h" #include "atom/common/node_includes.h"
@ -262,54 +261,6 @@ void WebFrame::SetSpellCheckProvider(mate::Arguments* args,
new AtomWebFrameObserver(render_frame, std::move(spell_check_client)); new AtomWebFrameObserver(render_frame, std::move(spell_check_client));
} }
void WebFrame::RegisterURLSchemeAsBypassingCSP(const std::string& scheme) {
// Register scheme to bypass pages's Content Security Policy.
blink::SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
WTF::String::FromUTF8(scheme.data(), scheme.length()));
}
void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme,
mate::Arguments* args) {
// TODO(deepak1556): blink::SchemeRegistry methods should be called
// before any renderer threads are created. Fixing this would break
// current api. Change it with 2.0.
// Read optional flags
bool secure = true;
bool bypassCSP = true;
bool allowServiceWorkers = true;
bool supportFetchAPI = true;
bool corsEnabled = true;
if (args->Length() == 2) {
mate::Dictionary options;
if (args->GetNext(&options)) {
options.Get("secure", &secure);
options.Get("bypassCSP", &bypassCSP);
options.Get("allowServiceWorkers", &allowServiceWorkers);
options.Get("supportFetchAPI", &supportFetchAPI);
options.Get("corsEnabled", &corsEnabled);
}
}
// Register scheme to privileged list (https, wss, data, chrome-extension)
WTF::String privileged_scheme(
WTF::String::FromUTF8(scheme.data(), scheme.length()));
if (bypassCSP) {
blink::SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
privileged_scheme);
}
if (allowServiceWorkers) {
blink::SchemeRegistry::RegisterURLSchemeAsAllowingServiceWorkers(
privileged_scheme);
}
if (supportFetchAPI) {
blink::SchemeRegistry::RegisterURLSchemeAsSupportingFetchAPI(
privileged_scheme);
}
if (corsEnabled) {
url::AddCorsEnabledScheme(scheme.c_str());
}
}
void WebFrame::InsertText(const std::string& text) { void WebFrame::InsertText(const std::string& text) {
web_frame_->FrameWidget()->GetActiveWebInputMethodController()->CommitText( web_frame_->FrameWidget()->GetActiveWebInputMethodController()->CommitText(
blink::WebString::FromUTF8(text), blink::WebString::FromUTF8(text),
@ -522,10 +473,6 @@ void WebFrame::BuildPrototype(v8::Isolate* isolate,
&WebFrame::AllowGuestViewElementDefinition) &WebFrame::AllowGuestViewElementDefinition)
.SetMethod("getWebFrameId", &WebFrame::GetWebFrameId) .SetMethod("getWebFrameId", &WebFrame::GetWebFrameId)
.SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider) .SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider)
.SetMethod("registerURLSchemeAsBypassingCSP",
&WebFrame::RegisterURLSchemeAsBypassingCSP)
.SetMethod("registerURLSchemeAsPrivileged",
&WebFrame::RegisterURLSchemeAsPrivileged)
.SetMethod("insertText", &WebFrame::InsertText) .SetMethod("insertText", &WebFrame::InsertText)
.SetMethod("insertCSS", &WebFrame::InsertCSS) .SetMethod("insertCSS", &WebFrame::InsertCSS)
.SetMethod("executeJavaScript", &WebFrame::ExecuteJavaScript) .SetMethod("executeJavaScript", &WebFrame::ExecuteJavaScript)

View file

@ -57,10 +57,6 @@ class WebFrame : public mate::Wrappable<WebFrame> {
const std::string& language, const std::string& language,
v8::Local<v8::Object> provider); v8::Local<v8::Object> provider);
void RegisterURLSchemeAsBypassingCSP(const std::string& scheme);
void RegisterURLSchemeAsPrivileged(const std::string& scheme,
mate::Arguments* args);
// Editing. // Editing.
void InsertText(const std::string& text); void InsertText(const std::string& text);
void InsertCSS(const std::string& css); void InsertCSS(const std::string& css);

View file

@ -163,6 +163,25 @@ void RendererClientBase::RenderThreadStarted() {
blink::SchemeRegistry::RegisterURLSchemeAsSecure( blink::SchemeRegistry::RegisterURLSchemeAsSecure(
WTF::String::FromUTF8(scheme.data(), scheme.length())); WTF::String::FromUTF8(scheme.data(), scheme.length()));
std::vector<std::string> fetch_enabled_schemes =
ParseSchemesCLISwitch(command_line, switches::kFetchSchemes);
for (const std::string& scheme : fetch_enabled_schemes) {
blink::WebSecurityPolicy::RegisterURLSchemeAsSupportingFetchAPI(
blink::WebString::FromASCII(scheme));
}
std::vector<std::string> service_worker_schemes =
ParseSchemesCLISwitch(command_line, switches::kServiceWorkerSchemes);
for (const std::string& scheme : service_worker_schemes)
blink::WebSecurityPolicy::RegisterURLSchemeAsAllowingServiceWorkers(
blink::WebString::FromASCII(scheme));
std::vector<std::string> csp_bypassing_schemes =
ParseSchemesCLISwitch(command_line, switches::kBypassCSPSchemes);
for (const std::string& scheme : csp_bypassing_schemes)
blink::SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
WTF::String::FromUTF8(scheme.data(), scheme.length()));
// Allow file scheme to handle service worker by default. // Allow file scheme to handle service worker by default.
// FIXME(zcbenz): Can this be moved elsewhere? // FIXME(zcbenz): Can this be moved elsewhere?
blink::WebSecurityPolicy::RegisterURLSchemeAsAllowingServiceWorkers("file"); blink::WebSecurityPolicy::RegisterURLSchemeAsAllowingServiceWorkers("file");

View file

@ -57,6 +57,11 @@ The following `webPreferences` option default values are deprecated in favor of
Child windows opened with the `nativeWindowOpen` option will always have Node.js integration disabled. Child windows opened with the `nativeWindowOpen` option will always have Node.js integration disabled.
## Privileged Schemes Registration
Renderer process APIs `webFrame.setRegisterURLSchemeAsPrivileged` and `webFrame.registerURLSchemeAsBypassingCSP` as well as browser process API `protocol.registerStandardSchemes` have been removed.
A new API, `protocol.registerSchemesAsPrivileged` has been added and should be used for registering custom schemes with the required privileges. Custom schemes are required to be registered before app ready.
# Planned Breaking API Changes (4.0) # Planned Breaking API Changes (4.0)
The following list includes the breaking API changes planned for Electron 4.0. The following list includes the breaking API changes planned for Electron 4.0.

View file

@ -28,12 +28,26 @@ of the `app` module gets emitted.
The `protocol` module has the following methods: The `protocol` module has the following methods:
### `protocol.registerStandardSchemes(schemes[, options])` ### `protocol.registerSchemesAsPrivileged(schemes[, options])`
* `schemes` String[] - Custom schemes to be registered as standard schemes. * `custom_schemes` [CustomScheme[]](structures/custom-scheme.md)
* `options` Object (optional)
* `secure` Boolean (optional) - `true` to register the scheme as secure.
Default `false`. **Note:** This method can only be used before the `ready` event of the `app`
module gets emitted and can be called only once.
Registers the `scheme` as standard, secure, bypasses content security policy for resources,
allows registering ServiceWorker and supports fetch API.
Specify an option with the value of `true` to enable the capability.
An example of registering a privileged scheme, with bypassing Content Security Policy:
```javascript
const { protocol } = require('electron')
protocol.registerSchemesAsPrivileged([
{ scheme: 'foo', options: { bypassCSP: true } }
])
```
A standard scheme adheres to what RFC 3986 calls [generic URI A standard scheme adheres to what RFC 3986 calls [generic URI
syntax](https://tools.ietf.org/html/rfc3986#section-3). For example `http` and syntax](https://tools.ietf.org/html/rfc3986#section-3). For example `http` and
@ -59,23 +73,7 @@ error for the scheme.
By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB, cookies) By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB, cookies)
are disabled for non standard schemes. So in general if you want to register a are disabled for non standard schemes. So in general if you want to register a
custom protocol to replace the `http` protocol, you have to register it as a standard scheme: custom protocol to replace the `http` protocol, you have to register it as a standard scheme.
```javascript
const { app, protocol } = require('electron')
protocol.registerStandardSchemes(['atom'])
app.on('ready', () => {
protocol.registerHttpProtocol('atom', '...')
})
```
**Note:** This method can only be used before the `ready` event of the `app`
module gets emitted.
### `protocol.registerServiceWorkerSchemes(schemes)`
* `schemes` String[] - Custom schemes to be registered to handle service workers.
### `protocol.registerFileProtocol(scheme, handler[, completion])` ### `protocol.registerFileProtocol(scheme, handler[, completion])`

View file

@ -0,0 +1,10 @@
# CustomScheme Object
* `scheme` String - Custom schemes to be registered with options.
* `options` Object (optional)
* `standard` Boolean (optional) - Default false.
* `secure` Boolean (optional) - Default false.
* `bypassCSP` Boolean (optional) - Default false.
* `allowServiceWorkers` Boolean (optional) - Default false.
* `supportFetchAPI` Boolean (optional) - Default false.
* `corsEnabled` Boolean (optional) - Default false.

View file

@ -95,34 +95,6 @@ webFrame.setSpellCheckProvider('en-US', {
}) })
``` ```
### `webFrame.registerURLSchemeAsBypassingCSP(scheme)`
* `scheme` String
Resources will be loaded from this `scheme` regardless of the current page's
Content Security Policy.
### `webFrame.registerURLSchemeAsPrivileged(scheme[, options])`
* `scheme` String
* `options` Object (optional)
* `secure` Boolean (optional) - Default true.
* `bypassCSP` Boolean (optional) - Default true.
* `allowServiceWorkers` Boolean (optional) - Default true.
* `supportFetchAPI` Boolean (optional) - Default true.
* `corsEnabled` Boolean (optional) - Default true.
Registers the `scheme` as secure, bypasses content security policy for resources,
allows registering ServiceWorker and supports fetch API.
Specify an option with the value of `false` to omit it from the registration.
An example of registering a privileged scheme, without bypassing Content Security Policy:
```javascript
const { webFrame } = require('electron')
webFrame.registerURLSchemeAsPrivileged('foo', { bypassCSP: false })
```
### `webFrame.insertText(text)` ### `webFrame.insertText(text)`
* `text` String * `text` String

View file

@ -1087,7 +1087,7 @@ describe('protocol module', () => {
}) })
}) })
describe('protocol.registerStandardSchemes', () => { describe('protocol.registerSchemesAsPrivileged standard', () => {
const standardScheme = remote.getGlobal('standardScheme') const standardScheme = remote.getGlobal('standardScheme')
const origin = `${standardScheme}://fake-host` const origin = `${standardScheme}://fake-host`
const imageURL = `${origin}/test.png` const imageURL = `${origin}/test.png`
@ -1189,4 +1189,102 @@ describe('protocol module', () => {
}) })
}) })
}) })
describe('protocol.registerSchemesAsPrivileged cors-fetch', function () {
const standardScheme = remote.getGlobal('standardScheme')
const fixtures = path.resolve(__dirname, 'fixtures')
let w = null
beforeEach((done) => {
protocol.unregisterProtocol(standardScheme, () => done())
})
afterEach((done) => {
closeWindow(w).then(() => {
w = null
done()
})
})
it('supports fetch api by default', (done) => {
const url = 'file://' + fixtures + '/assets/logo.png'
window.fetch(url)
.then(function (response) {
assert(response.ok)
done()
})
.catch(function (err) {
done('unexpected error : ' + err)
})
})
it('allows CORS requests by default', (done) => {
allowsCORSRequests('cors', 200, `<html>
<script>
const {ipcRenderer} = require('electron')
fetch('cors://myhost').then(function (response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
it('disallows CORS, but allows fetch requests, when specified', (done) => {
allowsCORSRequests('no-cors', 'failed', `<html>
<script>
const {ipcRenderer} = require('electron')
fetch('no-cors://myhost').then(function (response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
it('allows CORS, but disallows fetch requests, when specified', (done) => {
allowsCORSRequests('no-fetch', 'failed', `<html>
<script>
const {ipcRenderer} = require('electron')
fetch('no-fetch://myhost').then(function
(response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
function allowsCORSRequests (corsScheme, expected, content, done) {
const url = standardScheme + '://fake-host'
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
const handler = (request, callback) => {
callback({ data: content, mimeType: 'text/html' })
}
protocol.registerStringProtocol(standardScheme, handler, (error) => {
if (error) { return done(error) }
})
protocol.registerStringProtocol(corsScheme,
(request, callback) => {
callback('')
}, (error) => {
if (error) { return done(error) }
ipcMain.once('response', function (event, status) {
assert.strictEqual(status, expected)
protocol.unregisterProtocol(corsScheme, () => done())
})
w.loadURL(url)
})
}
})
}) })

View file

@ -695,12 +695,9 @@ describe('session module', () => {
}) })
}) })
// FIXME: Disabled with C71 upgrade describe('ses.getBlobData(identifier, callback)', () => {
// Re-enable with new api from
// https://github.com/electron/electron/tree/webframe-scheme-api
xdescribe('ses.getBlobData(identifier, callback)', () => {
it('returns blob data for uuid', (done) => { it('returns blob data for uuid', (done) => {
const scheme = 'temp' const scheme = 'cors-blob'
const protocol = session.defaultSession.protocol const protocol = session.defaultSession.protocol
const url = `${scheme}://host` const url = `${scheme}://host`
before(() => { before(() => {
@ -723,8 +720,6 @@ describe('session module', () => {
}) })
const content = `<html> const content = `<html>
<script> <script>
const {webFrame} = require('electron')
webFrame.registerURLSchemeAsPrivileged('${scheme}')
let fd = new FormData(); let fd = new FormData();
fd.append('file', new Blob(['${postData}'], {type:'application/json'})); fd.append('file', new Blob(['${postData}'], {type:'application/json'}));
fetch('${url}', {method:'POST', body: fd }); fetch('${url}', {method:'POST', body: fd });

View file

@ -4,7 +4,7 @@ const dirtyChai = require('dirty-chai')
const path = require('path') const path = require('path')
const { closeWindow } = require('./window-helpers') const { closeWindow } = require('./window-helpers')
const { remote, webFrame } = require('electron') const { remote, webFrame } = require('electron')
const { BrowserWindow, protocol, ipcMain } = remote const { BrowserWindow, ipcMain } = remote
const { expect } = chai const { expect } = chai
chai.use(dirtyChai) chai.use(dirtyChai)
@ -20,124 +20,6 @@ describe('webFrame module', function () {
return closeWindow(w).then(function () { w = null }) return closeWindow(w).then(function () { w = null })
}) })
// FIXME: Disabled with C70.
xdescribe('webFrame.registerURLSchemeAsPrivileged', function () {
it('supports fetch api by default', function (done) {
const url = 'file://' + fixtures + '/assets/logo.png'
window.fetch(url).then(function (response) {
assert(response.ok)
done()
}).catch(function (err) {
done('unexpected error : ' + err)
})
})
it('allows CORS requests by default', function (done) {
allowsCORSRequests(200, `<html>
<script>
const {ipcRenderer, webFrame} = require('electron')
webFrame.registerURLSchemeAsPrivileged('cors1')
fetch('cors1://myhost').then(function (response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
it('allows CORS and fetch requests when specified', function (done) {
allowsCORSRequests(200, `<html>
<script>
const {ipcRenderer, webFrame} = require('electron')
webFrame.registerURLSchemeAsPrivileged('cors2', { supportFetchAPI: true, corsEnabled: true })
fetch('cors2://myhost').then(function (response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
it('allows CORS and fetch requests when half-specified', function (done) {
allowsCORSRequests(200, `<html>
<script>
const {ipcRenderer, webFrame} = require('electron')
webFrame.registerURLSchemeAsPrivileged('cors3', { supportFetchAPI: true })
fetch('cors3://myhost').then(function (response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
it('disallows CORS, but allows fetch requests, when specified', function (done) {
allowsCORSRequests('failed', `<html>
<script>
const {ipcRenderer, webFrame} = require('electron')
webFrame.registerURLSchemeAsPrivileged('cors4', { supportFetchAPI: true, corsEnabled: false })
fetch('cors4://myhost').then(function (response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
it('allows CORS, but disallows fetch requests, when specified', function (done) {
allowsCORSRequests('failed', `<html>
<script>
const {ipcRenderer, webFrame} = require('electron')
webFrame.registerURLSchemeAsPrivileged('cors5', { supportFetchAPI: false, corsEnabled: true })
fetch('cors5://myhost').then(function (response) {
ipcRenderer.send('response', response.status)
}).catch(function (response) {
ipcRenderer.send('response', 'failed')
})
</script>
</html>`, done)
})
let runNumber = 1
function allowsCORSRequests (expected, content, done) {
const standardScheme = remote.getGlobal('standardScheme') + runNumber
const corsScheme = 'cors' + runNumber
runNumber++
const url = standardScheme + '://fake-host'
w = new BrowserWindow({ show: false })
after(function (done) {
protocol.unregisterProtocol(corsScheme, function () {
protocol.unregisterProtocol(standardScheme, function () {
done()
})
})
})
const handler = function (request, callback) {
callback({ data: content, mimeType: 'text/html' })
}
protocol.registerStringProtocol(standardScheme, handler, function (error) {
if (error) return done(error)
})
protocol.registerStringProtocol(corsScheme, function (request, callback) {
callback('')
}, function (error) {
if (error) return done(error)
ipcMain.once('response', function (event, status) {
assert.strictEqual(status, expected)
done()
})
w.loadURL(url)
})
}
})
it('supports setting the visual and layout zoom level limits', function () { it('supports setting the visual and layout zoom level limits', function () {
assert.doesNotThrow(function () { assert.doesNotThrow(function () {
webFrame.setVisualZoomLevelLimits(1, 50) webFrame.setVisualZoomLevelLimits(1, 50)

32
spec/package-lock.json generated
View file

@ -72,7 +72,7 @@
}, },
"bl": { "bl": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -228,7 +228,7 @@
}, },
"commander": { "commander": {
"version": "2.15.1", "version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"dev": true "dev": true
}, },
@ -267,7 +267,7 @@
}, },
"dbus-native": { "dbus-native": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/dbus-native/-/dbus-native-0.2.5.tgz", "resolved": "http://registry.npmjs.org/dbus-native/-/dbus-native-0.2.5.tgz",
"integrity": "sha512-ocxMKCV7QdiNhzhFSeEMhj258OGtvpANSb3oWGiotmI5h1ZIse0TMPcSLiXSpqvbYvQz2Y5RsYPMNYLWhg9eBw==", "integrity": "sha512-ocxMKCV7QdiNhzhFSeEMhj258OGtvpANSb3oWGiotmI5h1ZIse0TMPcSLiXSpqvbYvQz2Y5RsYPMNYLWhg9eBw==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -358,7 +358,7 @@
}, },
"duplexer": { "duplexer": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
"dev": true "dev": true
}, },
@ -512,7 +512,7 @@
}, },
"get-stream": { "get-stream": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true "dev": true
}, },
@ -753,7 +753,7 @@
}, },
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
@ -761,7 +761,7 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
} }
} }
@ -941,7 +941,7 @@
}, },
"os-homedir": { "os-homedir": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"optional": true "optional": true
}, },
@ -958,7 +958,7 @@
}, },
"os-tmpdir": { "os-tmpdir": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true "dev": true
}, },
@ -1000,7 +1000,7 @@
}, },
"path-is-absolute": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true "dev": true
}, },
@ -1018,7 +1018,7 @@
}, },
"pause-stream": { "pause-stream": {
"version": "0.0.11", "version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
"integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -1139,7 +1139,7 @@
}, },
"rimraf": { "rimraf": {
"version": "2.2.8", "version": "2.2.8",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
"integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=",
"dev": true "dev": true
}, },
@ -1305,7 +1305,7 @@
}, },
"string_decoder": { "string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": { "requires": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
@ -1321,7 +1321,7 @@
}, },
"strip-eof": { "strip-eof": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true "dev": true
}, },
@ -1391,7 +1391,7 @@
}, },
"through": { "through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true "dev": true
}, },
@ -1486,7 +1486,7 @@
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true, "dev": true,
"requires": { "requires": {

View file

@ -97,7 +97,14 @@ global.nativeModulesEnabled = !process.env.ELECTRON_SKIP_NATIVE_MODULE_TESTS
// Register app as standard scheme. // Register app as standard scheme.
global.standardScheme = 'app' global.standardScheme = 'app'
global.zoomScheme = 'zoom' global.zoomScheme = 'zoom'
protocol.registerStandardSchemes([global.standardScheme, global.zoomScheme], { secure: true }) protocol.registerSchemesAsPrivileged([
{ scheme: global.standardScheme, options: { standard: true, secure: true } },
{ scheme: global.zoomScheme, options: { standard: true, secure: true } },
{ scheme: 'cors', options: { corsEnabled: true, supportFetchAPI: true } },
{ scheme: 'cors-blob', options: { corsEnabled: true, supportFetchAPI: true } },
{ scheme: 'no-cors', options: { supportFetchAPI: true } },
{ scheme: 'no-fetch', options: { corsEnabled: true } }
])
app.on('window-all-closed', function () { app.on('window-all-closed', function () {
app.quit() app.quit()