Merge pull request #6470 from electron/session-options

Ad options for creating Session
This commit is contained in:
Cheng Zhao 2016-07-14 12:07:39 +09:00 committed by GitHub
commit 183b599b9c
14 changed files with 189 additions and 133 deletions

View file

@ -497,7 +497,7 @@ bool App::IsAccessibilitySupportEnabled() {
void App::ImportCertificate(
const base::DictionaryValue& options,
const net::CompletionCallback& callback) {
auto browser_context = brightray::BrowserContext::From("", false);
auto browser_context = AtomBrowserContext::From("", false);
if (!certificate_manager_model_) {
std::unique_ptr<base::DictionaryValue> copy = options.CreateDeepCopy();
CertificateManagerModel::Create(

View file

@ -6,6 +6,7 @@
#include "atom/browser/atom_browser_client.h"
#include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/browser.h"
#include "atom/browser/net/url_request_async_asar_job.h"
#include "atom/browser/net/url_request_buffer_job.h"
#include "atom/browser/net/url_request_fetch_job.h"
@ -192,7 +193,13 @@ void Protocol::BuildPrototype(
namespace {
void RegisterStandardSchemes(
const std::vector<std::string>& schemes) {
const std::vector<std::string>& schemes, mate::Arguments* args) {
if (atom::Browser::Get()->is_ready()) {
args->ThrowError("protocol.registerStandardSchemes should be called before "
"app is ready");
return;
}
auto policy = content::ChildProcessSecurityPolicy::GetInstance();
for (const auto& scheme : schemes) {
url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT);

View file

@ -11,6 +11,7 @@
#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/browser.h"
#include "atom/browser/atom_browser_context.h"
#include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/atom_permission_manager.h"
@ -20,6 +21,7 @@
#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/file_path_converter.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 "base/files/file_path.h"
#include "base/guid.h"
@ -163,6 +165,8 @@ namespace api {
namespace {
const char kPersistPrefix[] = "persist:";
// The wrapSession funtion which is implemented in JavaScript
using WrapSessionCallback = base::Callback<void(v8::Local<v8::Value>)>;
WrapSessionCallback g_wrap_session;
@ -533,10 +537,19 @@ mate::Handle<Session> Session::CreateFrom(
// static
mate::Handle<Session> Session::FromPartition(
v8::Isolate* isolate, const std::string& partition, bool in_memory) {
auto browser_context = brightray::BrowserContext::From(partition, in_memory);
return CreateFrom(isolate,
static_cast<AtomBrowserContext*>(browser_context.get()));
v8::Isolate* isolate, const std::string& partition,
const base::DictionaryValue& options) {
scoped_refptr<AtomBrowserContext> browser_context;
if (partition.empty()) {
browser_context = AtomBrowserContext::From("", false, options);
} else if (base::StartsWith(partition, kPersistPrefix,
base::CompareCase::SENSITIVE)) {
std::string name = partition.substr(8);
browser_context = AtomBrowserContext::From(name, false, options);
} else {
browser_context = AtomBrowserContext::From(partition, true, options);
}
return CreateFrom(isolate, browser_context.get());
}
// static
@ -576,11 +589,23 @@ void SetWrapSession(const WrapSessionCallback& callback) {
namespace {
v8::Local<v8::Value> FromPartition(
const std::string& partition, mate::Arguments* args) {
if (!atom::Browser::Get()->is_ready()) {
args->ThrowError("Session can only be received when app is ready");
return v8::Null(args->isolate());
}
base::DictionaryValue options;
args->GetNext(&options);
return atom::api::Session::FromPartition(
args->isolate(), partition, options).ToV8();
}
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) {
v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports);
dict.SetMethod("fromPartition", &atom::api::Session::FromPartition);
dict.SetMethod("fromPartition", &FromPartition);
dict.SetMethod("_setWrapSession", &atom::api::SetWrapSession);
}

View file

@ -8,6 +8,7 @@
#include <string>
#include "atom/browser/api/trackable_object.h"
#include "base/values.h"
#include "content/public/browser/download_manager.h"
#include "native_mate/handle.h"
#include "net/base/completion_callback.h"
@ -47,9 +48,10 @@ class Session: public mate::TrackableObject<Session>,
static mate::Handle<Session> CreateFrom(
v8::Isolate* isolate, AtomBrowserContext* browser_context);
// Gets the Session of |partition| and |in_memory|.
// Gets the Session of |partition|.
static mate::Handle<Session> FromPartition(
v8::Isolate* isolate, const std::string& partition, bool in_memory);
v8::Isolate* isolate, const std::string& partition,
const base::DictionaryValue& options = base::DictionaryValue());
AtomBrowserContext* browser_context() const { return browser_context_.get(); }

View file

@ -285,16 +285,11 @@ WebContents::WebContents(v8::Isolate* isolate,
std::string partition;
mate::Handle<api::Session> session;
if (options.Get("session", &session)) {
} else if (options.Get("partition", &partition) && !partition.empty()) {
bool in_memory = true;
if (base::StartsWith(partition, "persist:", base::CompareCase::SENSITIVE)) {
in_memory = false;
partition = partition.substr(8);
}
session = Session::FromPartition(isolate, partition, in_memory);
} else if (options.Get("partition", &partition)) {
session = Session::FromPartition(isolate, partition);
} else {
// Use the default session if not specified.
session = Session::FromPartition(isolate, "", false);
session = Session::FromPartition(isolate, "");
}
session_.Reset(isolate, session.ToV8());

View file

@ -46,7 +46,7 @@ void AtomAccessTokenStore::SaveAccessToken(const GURL& server_url,
}
void AtomAccessTokenStore::GetRequestContextOnUIThread() {
auto browser_context = brightray::BrowserContext::From("", false);
auto browser_context = AtomBrowserContext::From("", false);
request_context_getter_ = browser_context->GetRequestContext();
}

View file

@ -63,8 +63,9 @@ std::string RemoveWhitespace(const std::string& str) {
} // namespace
AtomBrowserContext::AtomBrowserContext(const std::string& partition,
bool in_memory)
AtomBrowserContext::AtomBrowserContext(
const std::string& partition, bool in_memory,
const base::DictionaryValue& options)
: brightray::BrowserContext(partition, in_memory),
network_delegate_(new AtomNetworkDelegate) {
// Construct user agent string.
@ -82,6 +83,10 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition,
CHROME_VERSION_STRING);
}
user_agent_ = content::BuildUserAgentFromProduct(user_agent);
// Read options.
use_cache_ = true;
options.GetBoolean("cache", &use_cache_);
}
AtomBrowserContext::~AtomBrowserContext() {
@ -144,7 +149,7 @@ net::HttpCache::BackendFactory*
AtomBrowserContext::CreateHttpCacheBackendFactory(
const base::FilePath& base_path) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kDisableHttpCache))
if (!use_cache_ || command_line->HasSwitch(switches::kDisableHttpCache))
return new NoCacheBackend;
else
return brightray::BrowserContext::CreateHttpCacheBackendFactory(base_path);
@ -190,14 +195,15 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) {
pref_registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths);
}
} // namespace atom
namespace brightray {
// static
scoped_refptr<BrowserContext> BrowserContext::Create(
const std::string& partition, bool in_memory) {
return make_scoped_refptr(new atom::AtomBrowserContext(partition, in_memory));
scoped_refptr<AtomBrowserContext> AtomBrowserContext::From(
const std::string& partition, bool in_memory,
const base::DictionaryValue& options) {
auto browser_context = brightray::BrowserContext::Get(partition, in_memory);
if (browser_context)
return static_cast<AtomBrowserContext*>(browser_context.get());
return new AtomBrowserContext(partition, in_memory, options);
}
} // namespace brightray
} // namespace atom

View file

@ -18,8 +18,12 @@ class WebViewManager;
class AtomBrowserContext : public brightray::BrowserContext {
public:
AtomBrowserContext(const std::string& partition, bool in_memory);
~AtomBrowserContext() override;
// Get or create the BrowserContext according to its |partition| and
// |in_memory|. The |options| will be passed to constructor when there is no
// existing BrowserContext.
static scoped_refptr<AtomBrowserContext> From(
const std::string& partition, bool in_memory,
const base::DictionaryValue& options = base::DictionaryValue());
void SetUserAgent(const std::string& user_agent);
@ -43,11 +47,17 @@ class AtomBrowserContext : public brightray::BrowserContext {
AtomNetworkDelegate* network_delegate() const { return network_delegate_; }
protected:
AtomBrowserContext(const std::string& partition, bool in_memory,
const base::DictionaryValue& options);
~AtomBrowserContext() override;
private:
std::unique_ptr<AtomDownloadManagerDelegate> download_manager_delegate_;
std::unique_ptr<WebViewManager> guest_manager_;
std::unique_ptr<AtomPermissionManager> permission_manager_;
std::string user_agent_;
bool use_cache_;
// Managed by brightray::BrowserContext.
AtomNetworkDelegate* network_delegate_;

View file

@ -20,17 +20,25 @@ const ses = win.webContents.session
The `session` module has the following methods:
### session.fromPartition(partition)
### session.fromPartition(partition[, options])
* `partition` String
* `options` Object
* `cache` Boolean - Whether to enable cache.
Returns a new `Session` instance from `partition` string.
Returns a `Session` instance from `partition` string. When there is an existing
`Session` with the same `partition`, it will be returned; othewise a new
`Session` instance will be created with `options`.
If `partition` starts with `persist:`, the page will use a persistent session
available to all pages in the app with the same `partition`. if there is no
`persist:` prefix, the page will use an in-memory session. If the `partition` is
empty then default session of the app will be returned.
To create a `Session` with `options`, you have to ensure the `Session` with the
`partition` has never been used before. There is no way to change the `options`
of an existing `Session` object.
## Properties
The `session` module has the following properties:

View file

@ -1,23 +1,27 @@
const {app, session} = require('electron')
const {registerStandardSchemes} = process.atomBinding('protocol')
exports.registerStandardSchemes = function (schemes) {
if (app.isReady()) {
console.warn('protocol.registerStandardSchemes should be called before app is ready')
return
// Global protocol APIs.
module.exports = process.atomBinding('protocol')
// Fallback protocol APIs of default session.
Object.setPrototypeOf(module.exports, new Proxy({}, {
get (target, property) {
if (!app.isReady()) return
const protocol = session.defaultSession.protocol
if (!protocol.hasOwnProperty(property)) return
// Returning a native function directly would throw error.
return (...args) => protocol[property](...args)
},
ownKeys () {
if (!app.isReady()) return []
return Object.getOwnPropertyNames(session.defaultSession.protocol)
},
getOwnPropertyDescriptor (target) {
return { configurable: true, enumerable: true }
}
registerStandardSchemes(schemes)
}
const setupProtocol = function () {
let protocol = session.defaultSession.protocol
for (let method in protocol) {
exports[method] = protocol[method].bind(protocol)
}
}
if (app.isReady()) {
setupProtocol()
} else {
app.once('ready', setupProtocol)
}
}))

View file

@ -1,44 +1,22 @@
const {EventEmitter} = require('events')
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) {
if (!electron.app.isReady()) {
throw new Error('session module can only be used when app is ready')
}
return bindings.fromPartition(partition, persist)
}
// Returns the Session from |partition| string.
Session.fromPartition = function (partition = '') {
if (partition === '') return exports.defaultSession
if (partition.startsWith(PERSIST_PREFIX)) {
return fromPartition(partition.substr(PERSIST_PREFIX.length), false)
} else {
return fromPartition(partition, true)
}
}
const {app} = require('electron')
const {fromPartition, _setWrapSession} = process.atomBinding('session')
// Returns the default session.
Object.defineProperty(Session, 'defaultSession', {
Object.defineProperties(exports, {
defaultSession: {
enumerable: true,
get: function () {
return fromPartition('', false)
get () { return fromPartition('') }
},
fromPartition: {
enumerable: true,
value: fromPartition
}
})
const wrapSession = function (session) {
// Wraps native Session class.
_setWrapSession(function (session) {
// Session is an EventEmitter.
Object.setPrototypeOf(session, EventEmitter.prototype)
Session.emit('session-created', session)
}
bindings._setWrapSession(wrapSession)
module.exports = Session
app.emit('session-created', session)
})

View file

@ -1,4 +1,4 @@
const {app, ipcMain, session, webContents, BrowserWindow} = require('electron')
const {app, ipcMain, webContents, BrowserWindow} = require('electron')
const {getAllWebContents} = process.atomBinding('web_contents')
const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllWebContents()
@ -86,6 +86,7 @@ const startBackgroundPages = function (manifest) {
}
const contents = webContents.create({
partition: 'persist:__chrome_extension',
isBackgroundPage: true,
commandLineSwitches: ['--background-page']
})
@ -284,6 +285,39 @@ app.on('web-contents-created', function (event, webContents) {
})
})
// The chrome-extension: can map a extension URL request to real file path.
const chromeExtensionHandler = function (request, callback) {
const parsed = url.parse(request.url)
if (!parsed.hostname || !parsed.path) return callback()
const manifest = manifestMap[parsed.hostname]
if (!manifest) return callback()
const page = backgroundPages[parsed.hostname]
if (page && parsed.path === `/${page.name}`) {
return callback({
mimeType: 'text/html',
data: page.html
})
}
fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
if (err) {
return callback(-6) // FILE_NOT_FOUND
} else {
return callback(content)
}
})
}
app.on('session-created', function (ses) {
ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
if (error) {
console.error(`Unable to register chrome-extension protocol: ${error}`)
}
})
})
// The persistent path of "DevTools Extensions" preference file.
let loadedExtensionsPath = null
@ -309,38 +343,6 @@ app.on('will-quit', function () {
// We can not use protocol or BrowserWindow until app is ready.
app.once('ready', function () {
// The chrome-extension: can map a extension URL request to real file path.
const chromeExtensionHandler = function (request, callback) {
const parsed = url.parse(request.url)
if (!parsed.hostname || !parsed.path) return callback()
const manifest = manifestMap[parsed.hostname]
if (!manifest) return callback()
const page = backgroundPages[parsed.hostname]
if (page && parsed.path === `/${page.name}`) {
return callback({
mimeType: 'text/html',
data: page.html
})
}
fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
if (err) {
return callback(-6) // FILE_NOT_FOUND
} else {
return callback(content)
}
})
}
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.
loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
try {

View file

@ -3,12 +3,8 @@ const http = require('http')
const path = require('path')
const fs = require('fs')
const ipcRenderer = require('electron').ipcRenderer
const remote = require('electron').remote
const ipcMain = remote.ipcMain
const session = remote.session
const BrowserWindow = remote.BrowserWindow
const {ipcRenderer, remote} = require('electron')
const {ipcMain, session, BrowserWindow} = remote
describe('session module', function () {
this.timeout(10000)
@ -35,7 +31,30 @@ describe('session module', function () {
w = null
})
describe('session.cookies', function () {
describe('session.defaultSession', function () {
it('returns the default session', function () {
assert.equal(session.defaultSession, session.fromPartition(''))
})
})
describe('session.fromPartition(partition, options)', function () {
it('returns existing session with same partition', function () {
assert.equal(session.fromPartition('test'), session.fromPartition('test'))
})
it('created session is ref-counted', function () {
const partition = 'test2'
const userAgent = 'test-agent'
const ses1 = session.fromPartition(partition)
ses1.setUserAgent(userAgent)
assert.equal(ses1.getUserAgent(), userAgent)
ses1.destroy()
const ses2 = session.fromPartition(partition)
assert.notEqual(ses2.getUserAgent(), userAgent)
})
})
describe('ses.cookies', function () {
it('should get cookies', function (done) {
var server = http.createServer(function (req, res) {
res.setHeader('Set-Cookie', ['0=0'])
@ -141,7 +160,7 @@ describe('session module', function () {
})
})
describe('session.clearStorageData(options)', function () {
describe('ses.clearStorageData(options)', function () {
fixtures = path.resolve(__dirname, 'fixtures')
it('clears localstorage data', function (done) {
ipcMain.on('count', function (event, count) {
@ -163,7 +182,7 @@ describe('session module', function () {
})
})
describe('session will-download event', function () {
describe('will-download event', function () {
var w = null
beforeEach(function () {
@ -280,7 +299,7 @@ describe('session module', function () {
})
})
describe('session.protocol', function () {
describe('ses.protocol', function () {
const partitionName = 'temp'
const protocolName = 'sp'
const partitionProtocol = session.fromPartition(partitionName).protocol

2
vendor/brightray vendored

@ -1 +1 @@
Subproject commit 3b993f9fd7ffdd0e92bb77521a8b7f32af5eba5b
Subproject commit 91abdb01a1825c12522fd5fc2349a7ba9a091a48