diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 0b7612400ff0..bc29b988eeea 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -263,13 +263,14 @@ void App::OnContinueUserActivity( } #endif -void App::OnLogin(LoginHandler* login_handler) { +void App::OnLogin(LoginHandler* login_handler, + const base::DictionaryValue& request_details) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); bool prevent_default = Emit( "login", WebContents::CreateFrom(isolate(), login_handler->GetWebContents()), - login_handler->request(), + request_details, login_handler->auth_info(), base::Bind(&PassLoginInformation, make_scoped_refptr(login_handler))); diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index ba274deb9ccd..b550f0ba7b90 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -70,7 +70,8 @@ class App : public AtomBrowserClient::Delegate, void OnActivate(bool has_visible_windows) override; void OnWillFinishLaunching() override; void OnFinishLaunching() override; - void OnLogin(LoginHandler* login_handler) override; + void OnLogin(LoginHandler* login_handler, + const base::DictionaryValue& request_details) override; #if defined(OS_MACOSX) void OnContinueUserActivity( bool* prevent_default, diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index de4de9de59d9..db8facfe0953 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -12,7 +12,7 @@ #include "atom/browser/net/url_request_fetch_job.h" #include "atom/browser/net/url_request_string_job.h" #include "atom/common/native_mate_converters/callback.h" -#include "atom/common/native_mate_converters/net_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "base/command_line.h" @@ -173,17 +173,10 @@ void RegisterStandardSchemes( base::JoinString(schemes, ",")); } -mate::Handle CreateProtocol(v8::Isolate* isolate) { - auto browser_context = static_cast( - atom::AtomBrowserMainParts::Get()->browser_context()); - return atom::api::Protocol::Create(isolate, browser_context); -} - void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); - dict.SetMethod("createProtocolObject", base::Bind(&CreateProtocol, isolate)); dict.SetMethod("registerStandardSchemes", &RegisterStandardSchemes); } diff --git a/atom/browser/api/atom_api_protocol.h b/atom/browser/api/atom_api_protocol.h index d33e63fa9cff..734d179dfacd 100644 --- a/atom/browser/api/atom_api_protocol.h +++ b/atom/browser/api/atom_api_protocol.h @@ -9,6 +9,7 @@ #include #include +#include "atom/browser/api/trackable_object.h" #include "atom/browser/net/atom_url_request_job_factory.h" #include "base/callback.h" #include "base/containers/scoped_ptr_hash_map.h" @@ -16,7 +17,10 @@ #include "native_mate/arguments.h" #include "native_mate/dictionary.h" #include "native_mate/handle.h" -#include "native_mate/wrappable.h" + +namespace base { +class DictionaryValue; +} namespace net { class URLRequest; @@ -30,10 +34,10 @@ class AtomURLRequestJobFactory; namespace api { -class Protocol : public mate::Wrappable { +class Protocol : public mate::TrackableObject { public: using Handler = - base::Callback)>; + base::Callback)>; using CompletionCallback = base::Callback)>; using BooleanCallback = base::Callback; diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 15aa2afe9cf3..602729b9e382 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -9,6 +9,7 @@ #include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/api/atom_api_download_item.h" +#include "atom/browser/api/atom_api_protocol.h" #include "atom/browser/api/atom_api_web_request.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -462,6 +463,14 @@ v8::Local Session::Cookies(v8::Isolate* isolate) { return v8::Local::New(isolate, cookies_); } +v8::Local Session::Protocol(v8::Isolate* isolate) { + if (protocol_.IsEmpty()) { + auto handle = atom::api::Protocol::Create(isolate, browser_context()); + protocol_.Reset(isolate, handle.ToV8()); + } + return v8::Local::New(isolate, protocol_); +} + v8::Local Session::WebRequest(v8::Isolate* isolate) { if (web_request_.IsEmpty()) { auto handle = atom::api::WebRequest::Create(isolate, browser_context()); @@ -512,6 +521,7 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("allowNTLMCredentialsForDomains", &Session::AllowNTLMCredentialsForDomains) .SetProperty("cookies", &Session::Cookies) + .SetProperty("protocol", &Session::Protocol) .SetProperty("webRequest", &Session::WebRequest); } diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 0cebf09ea1f3..bb67fa106de1 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -81,10 +81,12 @@ class Session: public mate::TrackableObject, void ClearHostResolverCache(mate::Arguments* args); void AllowNTLMCredentialsForDomains(const std::string& domains); v8::Local Cookies(v8::Isolate* isolate); + v8::Local Protocol(v8::Isolate* isolate); v8::Local WebRequest(v8::Isolate* isolate); // Cached object. v8::Global cookies_; + v8::Global protocol_; v8::Global web_request_; // The X-DevTools-Emulate-Network-Conditions-Client-Id. diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc index 59ac258ea13a..1115aa049d69 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.cc +++ b/atom/browser/atom_resource_dispatcher_host_delegate.cc @@ -31,10 +31,13 @@ void HandleExternalProtocolInUI( if (!web_contents) return; - GURL escaped_url(net::EscapeExternalHandlerValue(url.spec())); - auto callback = base::Bind(&OnOpenExternal, escaped_url); auto permission_helper = WebContentsPermissionHelper::FromWebContents(web_contents); + if (!permission_helper) + return; + + GURL escaped_url(net::EscapeExternalHandlerValue(url.spec())); + auto callback = base::Bind(&OnOpenExternal, escaped_url); permission_helper->RequestOpenExternalPermission(callback, has_user_gesture); } diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 093209ef7c47..8c00c1287376 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -151,8 +151,12 @@ void Browser::DidFinishLaunching() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching()); } -void Browser::RequestLogin(LoginHandler* login_handler) { - FOR_EACH_OBSERVER(BrowserObserver, observers_, OnLogin(login_handler)); +void Browser::RequestLogin( + LoginHandler* login_handler, + std::unique_ptr request_details) { + FOR_EACH_OBSERVER(BrowserObserver, + observers_, + OnLogin(login_handler, *(request_details.get()))); } void Browser::NotifyAndShutdown() { diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 18d0c97c93ac..2c1678083034 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -21,6 +21,7 @@ #endif namespace base { +class DictionaryValue; class FilePath; } @@ -165,7 +166,8 @@ class Browser : public WindowListObserver { void DidFinishLaunching(); // Request basic auth login. - void RequestLogin(LoginHandler* login_handler); + void RequestLogin(LoginHandler* login_handler, + std::unique_ptr request_details); void AddObserver(BrowserObserver* obs) { observers_.AddObserver(obs); diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index c8b0082beb7e..41aeceb6375b 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -49,7 +49,8 @@ class BrowserObserver { virtual void OnFinishLaunching() {} // The browser requests HTTP login. - virtual void OnLogin(LoginHandler* login_handler) {} + virtual void OnLogin(LoginHandler* login_handler, + const base::DictionaryValue& request_details) {} #if defined(OS_MACOSX) // The browser wants to resume a user activity via handoff. (OS X only) diff --git a/atom/browser/login_handler.cc b/atom/browser/login_handler.cc index 1a6c6947b617..827154b0d0b4 100644 --- a/atom/browser/login_handler.cc +++ b/atom/browser/login_handler.cc @@ -5,6 +5,8 @@ #include "atom/browser/login_handler.h" #include "atom/browser/browser.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "base/values.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/resource_dispatcher_host.h" @@ -37,11 +39,18 @@ LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, render_frame_id_(0) { content::ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame( &render_process_host_id_, &render_frame_id_); + + // Fill request details on IO thread. + std::unique_ptr request_details( + new base::DictionaryValue); + FillRequestDetails(request_details.get(), request_); + BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&Browser::RequestLogin, base::Unretained(Browser::Get()), - base::RetainedRef(make_scoped_refptr(this)))); + base::RetainedRef(make_scoped_refptr(this)), + base::Passed(&request_details))); } LoginHandler::~LoginHandler() { diff --git a/atom/browser/login_handler.h b/atom/browser/login_handler.h index 52ec1abf5b1a..ba1371336cc1 100644 --- a/atom/browser/login_handler.h +++ b/atom/browser/login_handler.h @@ -36,7 +36,6 @@ class LoginHandler : public content::ResourceDispatcherHostLoginDelegate { void Login(const base::string16& username, const base::string16& password); const net::AuthChallengeInfo* auth_info() const { return auth_info_.get(); } - const net::URLRequest* request() const { return request_; } protected: ~LoginHandler() override; diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index fd0c52b3554f..323342ca823d 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -71,18 +71,13 @@ bool MatchesFilterCondition(net::URLRequest* request, // Overloaded by multiple types to fill the |details| object. void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) { + FillRequestDetails(details, request); details->SetInteger("id", request->identifier()); - details->SetString("url", request->url().spec()); - details->SetString("method", request->method()); details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); auto info = content::ResourceRequestInfo::ForRequest(request); details->SetString("resourceType", info ? ResourceTypeToString(info->GetResourceType()) : "other"); - std::unique_ptr list(new base::ListValue); - GetUploadData(list.get(), request); - if (!list->empty()) - details->Set("uploadData", std::move(list)); } void ToDictionary(base::DictionaryValue* details, diff --git a/atom/browser/net/js_asker.cc b/atom/browser/net/js_asker.cc index 8362c1add1c6..67fb578650fb 100644 --- a/atom/browser/net/js_asker.cc +++ b/atom/browser/net/js_asker.cc @@ -44,7 +44,7 @@ void HandlerCallback(const BeforeStartCallback& before_start, void AskForOptions(v8::Isolate* isolate, const JavaScriptHandler& handler, - net::URLRequest* request, + std::unique_ptr request_details, const BeforeStartCallback& before_start, const ResponseCallback& callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -53,7 +53,7 @@ void AskForOptions(v8::Isolate* isolate, v8::Local context = isolate->GetCurrentContext(); v8::Context::Scope context_scope(context); handler.Run( - request, + *(request_details.get()), mate::ConvertToV8(isolate, base::Bind(&HandlerCallback, before_start, callback))); } diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h index 6301f4de2c2a..df64a2175049 100644 --- a/atom/browser/net/js_asker.h +++ b/atom/browser/net/js_asker.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_NET_JS_ASKER_H_ #define ATOM_BROWSER_NET_JS_ASKER_H_ +#include "atom/common/native_mate_converters/net_converter.h" #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" @@ -19,7 +20,7 @@ namespace atom { using JavaScriptHandler = - base::Callback)>; + base::Callback)>; namespace internal { @@ -31,7 +32,7 @@ using ResponseCallback = // Ask handler for options in UI thread. void AskForOptions(v8::Isolate* isolate, const JavaScriptHandler& handler, - net::URLRequest* request, + std::unique_ptr request_details, const BeforeStartCallback& before_start, const ResponseCallback& callback); @@ -67,12 +68,15 @@ class JsAsker : public RequestJob { private: // RequestJob: void Start() override { + std::unique_ptr request_details( + new base::DictionaryValue); + FillRequestDetails(request_details.get(), RequestJob::request()); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&internal::AskForOptions, isolate_, handler_, - RequestJob::request(), + base::Passed(&request_details), base::Bind(&JsAsker::BeforeStartInUI, weak_factory_.GetWeakPtr()), base::Bind(&JsAsker::OnResponse, diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 772ae6e22087..71bc50fde7d3 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -22,22 +22,6 @@ namespace mate { -// static -v8::Local Converter::ToV8( - v8::Isolate* isolate, const net::URLRequest* val) { - std::unique_ptr dict(new base::DictionaryValue); - dict->SetString("method", val->method()); - std::string url; - if (!val->url_chain().empty()) url = val->url().spec(); - dict->SetStringWithoutPathExpansion("url", url); - dict->SetString("referrer", val->referrer()); - std::unique_ptr list(new base::ListValue); - atom::GetUploadData(list.get(), val); - if (!list->empty()) - dict->Set("uploadData", std::move(list)); - return mate::ConvertToV8(isolate, *(dict.get())); -} - // static v8::Local Converter::ToV8( v8::Isolate* isolate, const net::AuthChallengeInfo* val) { @@ -69,6 +53,19 @@ v8::Local Converter>::ToV8( namespace atom { +void FillRequestDetails(base::DictionaryValue* details, + const net::URLRequest* request) { + details->SetString("method", request->method()); + std::string url; + if (!request->url_chain().empty()) url = request->url().spec(); + details->SetStringWithoutPathExpansion("url", url); + details->SetString("referrer", request->referrer()); + std::unique_ptr list(new base::ListValue); + GetUploadData(list.get(), request); + if (!list->empty()) + details->Set("uploadData", std::move(list)); +} + void GetUploadData(base::ListValue* upload_data_list, const net::URLRequest* request) { const net::UploadDataStream* upload_data = request->get_upload(); diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index b7fd9481a207..37e42806955d 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -9,6 +9,7 @@ #include "native_mate/converter.h" namespace base { +class DictionaryValue; class ListValue; } @@ -20,12 +21,6 @@ class X509Certificate; namespace mate { -template<> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const net::URLRequest* val); -}; - template<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, @@ -42,6 +37,9 @@ struct Converter> { namespace atom { +void FillRequestDetails(base::DictionaryValue* details, + const net::URLRequest* request); + void GetUploadData(base::ListValue* upload_data_list, const net::URLRequest* request); diff --git a/docs/api/session.md b/docs/api/session.md index e7affddde932..030b2c7542ea 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -547,3 +547,22 @@ The `listener` will be called with `listener(details)` when an error occurs. * `timestamp` Double * `fromCache` Boolean * `error` String - The error description. + +#### `ses.protocol` + +Returns an instance of [protocol](protocol.md) module for this session. + +```javascript +const {app, session} = require('electron') +const path = require('path') + +app.on('ready', function () { + const protocol = session.fromPartition(partitionName).protocol + protocol.registerFileProtocol('atom', function (request, callback) { + var url = request.url.substr(7) + callback({path: path.normalize(__dirname + '/' + url)}) + }, function (error) { + if (error) + console.error('Failed to register protocol') + }) +}) diff --git a/lib/browser/api/protocol.js b/lib/browser/api/protocol.js index b146931a2b48..4daf4edaccd6 100644 --- a/lib/browser/api/protocol.js +++ b/lib/browser/api/protocol.js @@ -1,5 +1,5 @@ -const {app} = require('electron') -const {createProtocolObject, registerStandardSchemes} = process.atomBinding('protocol') +const {app, session} = require('electron') +const {registerStandardSchemes} = process.atomBinding('protocol') exports.registerStandardSchemes = function (schemes) { if (app.isReady()) { @@ -10,7 +10,7 @@ exports.registerStandardSchemes = function (schemes) { } app.once('ready', function () { - let protocol = createProtocolObject() + let protocol = session.defaultSession.protocol for (let method in protocol) { exports[method] = protocol[method].bind(protocol) } diff --git a/lib/browser/api/session.js b/lib/browser/api/session.js index dc1c529878fb..4cfb27f76d63 100644 --- a/lib/browser/api/session.js +++ b/lib/browser/api/session.js @@ -3,6 +3,7 @@ const electron = require('electron') const bindings = process.atomBinding('session') const PERSIST_PREFIX = 'persist:' +const Session = new EventEmitter() // Wrapper of binding.fromPartition that checks for ready event. const fromPartition = function (partition, persist) { @@ -14,7 +15,7 @@ const fromPartition = function (partition, persist) { } // Returns the Session from |partition| string. -exports.fromPartition = function (partition = '') { +Session.fromPartition = function (partition = '') { if (partition === '') return exports.defaultSession if (partition.startsWith(PERSIST_PREFIX)) { @@ -25,7 +26,7 @@ exports.fromPartition = function (partition = '') { } // Returns the default session. -Object.defineProperty(exports, 'defaultSession', { +Object.defineProperty(Session, 'defaultSession', { enumerable: true, get: function () { return fromPartition('', false) @@ -35,6 +36,9 @@ Object.defineProperty(exports, 'defaultSession', { const wrapSession = function (session) { // Session is an EventEmitter. Object.setPrototypeOf(session, EventEmitter.prototype) + Session.emit('session-created', session) } bindings._setWrapSession(wrapSession) + +module.exports = Session diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js index 5b6bd5fbe1c8..fc49f5e06be5 100644 --- a/lib/browser/chrome-extension.js +++ b/lib/browser/chrome-extension.js @@ -1,4 +1,4 @@ -const {app, ipcMain, protocol, webContents, BrowserWindow} = require('electron') +const {app, ipcMain, session, webContents, BrowserWindow} = require('electron') const {getAllWebContents} = process.atomBinding('web_contents') const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllBrowserWindow() @@ -280,10 +280,12 @@ app.once('ready', function () { } }) } - protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) { - if (error) { - console.error(`Unable to register chrome-extension protocol: ${error}`) - } + session.on('session-created', function (ses) { + ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) { + if (error) { + console.error(`Unable to register chrome-extension protocol: ${error}`) + } + }) }) // Load persisted extensions. diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 6a76ba5ddb6c..bad3b80a9b5c 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -883,6 +883,45 @@ describe('browser-window module', function () { }) }) + it('works when used with partitions', function (done) { + this.timeout(10000) + + if (w != null) { + w.destroy() + } + w = new BrowserWindow({ + show: false, + webPreferences: { + partition: 'temp' + } + }) + + var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo') + BrowserWindow.removeDevToolsExtension('foo') + BrowserWindow.addDevToolsExtension(extensionPath) + + w.webContents.on('devtools-opened', function () { + var showPanelIntevalId = setInterval(function () { + if (w && w.devToolsWebContents) { + w.devToolsWebContents.executeJavaScript('(' + (function () { + var lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id + WebInspector.inspectorView.showPanel(lastPanelId) + }).toString() + ')()') + } else { + clearInterval(showPanelIntevalId) + } + }, 100) + }) + + w.loadURL('about:blank') + w.webContents.openDevTools({mode: 'bottom'}) + + ipcMain.once('answer', function (event, message) { + assert.equal(message.runtimeId, 'foo') + done() + }) + }) + it('serializes the registered extensions on quit', function () { var extensionName = 'foo' var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index d655788c79b3..9529ec5e0072 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -16,8 +16,15 @@ describe('session module', function () { var fixtures = path.resolve(__dirname, 'fixtures') var w = null var url = 'http://127.0.0.1' + var partitionName = 'temp' + var protocolName = 'sp' + const tempProtocol = session.fromPartition(partitionName).protocol + const protocol = session.defaultSession.protocol beforeEach(function () { + if (w != null) { + w.destroy() + } w = new BrowserWindow({ show: false, width: 400, @@ -26,7 +33,10 @@ describe('session module', function () { }) afterEach(function () { - w.destroy() + if (w != null) { + w.destroy() + } + w = null }) describe('session.cookies', function () { @@ -262,4 +272,43 @@ describe('session module', function () { }) }) }) + + describe('session.protocol', function () { + beforeEach(function () { + if (w != null) { + w.destroy() + } + w = new BrowserWindow({ + show: false, + width: 400, + height: 400, + webPreferences: { + partition: partitionName + } + }) + }) + + it('handles requests from a partition', function (done) { + var handler = function (error, callback) { + callback({ + data: 'test' + }) + } + tempProtocol.registerStringProtocol(protocolName, handler, function (error) { + if (error) { + return done(error) + } + protocol.isProtocolHandled(protocolName, function (result) { + assert.equal(result, false) + tempProtocol.isProtocolHandled(protocolName, function (result) { + assert.equal(result, true) + w.webContents.on('did-finish-load', function () { + done() + }) + w.loadURL(protocolName + "://fake-host") + }) + }) + }) + }) + }) })