Add an "affinity" option to webPreferences
This commit is contained in:
parent
8d55334016
commit
9c1b47361f
6 changed files with 242 additions and 12 deletions
|
@ -218,21 +218,61 @@ void AtomBrowserClient::OverrideSiteInstanceForNavigation(
|
||||||
current_instance, url))
|
current_instance, url))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
scoped_refptr<content::SiteInstance> site_instance =
|
bool is_new_instance = true;
|
||||||
content::SiteInstance::CreateForURL(browser_context, url);
|
scoped_refptr<content::SiteInstance> site_instance;
|
||||||
|
|
||||||
|
// Do we have an affinity site to manage ?
|
||||||
|
std::string affinity;
|
||||||
|
auto web_contents =
|
||||||
|
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||||
|
if (WebContentsPreferences::GetAffinity(web_contents, &affinity)
|
||||||
|
&& !affinity.empty()) {
|
||||||
|
affinity = base::ToLowerASCII(affinity);
|
||||||
|
auto iter = site_per_affinities.find(affinity);
|
||||||
|
if (iter != site_per_affinities.end()) {
|
||||||
|
site_instance = iter->second;
|
||||||
|
is_new_instance = false;
|
||||||
|
} else {
|
||||||
|
// We must not provide the url.
|
||||||
|
// This site is "isolated" and must not be taken into account
|
||||||
|
// when Chromium looking at a candidate for an url.
|
||||||
|
site_instance = content::SiteInstance::Create(
|
||||||
|
browser_context);
|
||||||
|
site_per_affinities[affinity] = site_instance.get();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
site_instance = content::SiteInstance::CreateForURL(
|
||||||
|
browser_context,
|
||||||
|
url);
|
||||||
|
}
|
||||||
*new_instance = site_instance.get();
|
*new_instance = site_instance.get();
|
||||||
|
|
||||||
// Make sure the |site_instance| is not freed when this function returns.
|
if (is_new_instance) {
|
||||||
// FIXME(zcbenz): We should adjust OverrideSiteInstanceForNavigation's
|
// Make sure the |site_instance| is not freed
|
||||||
// interface to solve this.
|
// when this function returns.
|
||||||
content::BrowserThread::PostTask(
|
// FIXME(zcbenz): We should adjust
|
||||||
content::BrowserThread::UI, FROM_HERE,
|
// OverrideSiteInstanceForNavigation's interface to solve this.
|
||||||
base::Bind(&Noop, base::RetainedRef(site_instance)));
|
content::BrowserThread::PostTask(
|
||||||
|
content::BrowserThread::UI, FROM_HERE,
|
||||||
|
base::Bind(&Noop, base::RetainedRef(site_instance)));
|
||||||
|
|
||||||
// Remember the original web contents for the pending renderer process.
|
// Remember the original web contents for the pending renderer process.
|
||||||
auto pending_process = (*new_instance)->GetProcess();
|
auto pending_process = site_instance->GetProcess();
|
||||||
pending_processes_[pending_process->GetID()] =
|
pending_processes_[pending_process->GetID()] = web_contents;
|
||||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are storing weak_ptr, is it fundamental to maintain the map up-to-date
|
||||||
|
// when an instance is destroyed.
|
||||||
|
void AtomBrowserClient::SiteInstanceDeleting(
|
||||||
|
content::SiteInstance* site_instance) {
|
||||||
|
for (auto iter = site_per_affinities.begin();
|
||||||
|
iter != site_per_affinities.end(); ++iter) {
|
||||||
|
if (iter->second == site_instance) {
|
||||||
|
site_per_affinities.erase(iter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
||||||
|
|
|
@ -113,6 +113,8 @@ class AtomBrowserClient : public brightray::BrowserClient,
|
||||||
base::TerminationStatus status,
|
base::TerminationStatus status,
|
||||||
int exit_code) override;
|
int exit_code) override;
|
||||||
|
|
||||||
|
void SiteInstanceDeleting(content::SiteInstance* site_instance) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool ShouldCreateNewSiteInstance(content::RenderFrameHost* render_frame_host,
|
bool ShouldCreateNewSiteInstance(content::RenderFrameHost* render_frame_host,
|
||||||
content::BrowserContext* browser_context,
|
content::BrowserContext* browser_context,
|
||||||
|
@ -134,6 +136,10 @@ class AtomBrowserClient : public brightray::BrowserClient,
|
||||||
|
|
||||||
std::map<int, ProcessPreferences> process_preferences_;
|
std::map<int, ProcessPreferences> process_preferences_;
|
||||||
std::map<int, base::ProcessId> render_process_host_pids_;
|
std::map<int, base::ProcessId> render_process_host_pids_;
|
||||||
|
|
||||||
|
// list of site per affinity. weak_ptr to prevent instance locking
|
||||||
|
std::map<std::string, content::SiteInstance*> site_per_affinities;
|
||||||
|
|
||||||
base::Lock process_preferences_lock_;
|
base::Lock process_preferences_lock_;
|
||||||
|
|
||||||
std::unique_ptr<AtomResourceDispatcherHostDelegate>
|
std::unique_ptr<AtomResourceDispatcherHostDelegate>
|
||||||
|
|
|
@ -237,6 +237,22 @@ bool WebContentsPreferences::IsPreferenceEnabled(
|
||||||
return bool_value;
|
return bool_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebContentsPreferences::GetPreferenceString(
|
||||||
|
const std::string& attribute_name,
|
||||||
|
content::WebContents* web_contents,
|
||||||
|
std::string* strValue) {
|
||||||
|
WebContentsPreferences* self;
|
||||||
|
if (!web_contents)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
self = FromWebContents(web_contents);
|
||||||
|
if (!self)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
base::DictionaryValue& web_preferences = self->web_preferences_;
|
||||||
|
return web_preferences.GetString(attribute_name, strValue);
|
||||||
|
}
|
||||||
|
|
||||||
bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) {
|
bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) {
|
||||||
return IsPreferenceEnabled("sandbox", web_contents);
|
return IsPreferenceEnabled("sandbox", web_contents);
|
||||||
}
|
}
|
||||||
|
@ -256,6 +272,12 @@ bool WebContentsPreferences::DisablePopups(
|
||||||
return IsPreferenceEnabled("disablePopups", web_contents);
|
return IsPreferenceEnabled("disablePopups", web_contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebContentsPreferences::GetAffinity(
|
||||||
|
content::WebContents* web_contents,
|
||||||
|
std::string* string_value) {
|
||||||
|
return GetPreferenceString("affinity", web_contents, string_value);
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void WebContentsPreferences::OverrideWebkitPrefs(
|
void WebContentsPreferences::OverrideWebkitPrefs(
|
||||||
content::WebContents* web_contents, content::WebPreferences* prefs) {
|
content::WebContents* web_contents, content::WebPreferences* prefs) {
|
||||||
|
|
|
@ -39,10 +39,15 @@ class WebContentsPreferences
|
||||||
|
|
||||||
static bool IsPreferenceEnabled(const std::string& attribute_name,
|
static bool IsPreferenceEnabled(const std::string& attribute_name,
|
||||||
content::WebContents* web_contents);
|
content::WebContents* web_contents);
|
||||||
|
static bool GetPreferenceString(const std::string& attribute_name,
|
||||||
|
content::WebContents* web_contents,
|
||||||
|
std::string* strValue);
|
||||||
static bool IsSandboxed(content::WebContents* web_contents);
|
static bool IsSandboxed(content::WebContents* web_contents);
|
||||||
static bool UsesNativeWindowOpen(content::WebContents* web_contents);
|
static bool UsesNativeWindowOpen(content::WebContents* web_contents);
|
||||||
static bool DisablePopups(content::WebContents* web_contents);
|
static bool DisablePopups(content::WebContents* web_contents);
|
||||||
static bool IsPluginsEnabled(content::WebContents* web_contents);
|
static bool IsPluginsEnabled(content::WebContents* web_contents);
|
||||||
|
static bool GetAffinity(content::WebContents* web_contents,
|
||||||
|
std::string* string_value);
|
||||||
|
|
||||||
// Modify the WebPreferences according to |web_contents|'s preferences.
|
// Modify the WebPreferences according to |web_contents|'s preferences.
|
||||||
static void OverrideWebkitPrefs(
|
static void OverrideWebkitPrefs(
|
||||||
|
|
|
@ -280,6 +280,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||||
same `partition`. If there is no `persist:` prefix, the page will use an
|
same `partition`. If there is no `persist:` prefix, the page will use an
|
||||||
in-memory session. By assigning the same `partition`, multiple pages can share
|
in-memory session. By assigning the same `partition`, multiple pages can share
|
||||||
the same session. Default is the default session.
|
the same session. Default is the default session.
|
||||||
|
* `affinity` String (optional) - Sets the expected process hosting the page. Allow to gather
|
||||||
|
several pages in the same process. There are known limitations:
|
||||||
|
you can not host in the same site, pages with different preload file,
|
||||||
|
nodeIntegration or sandbox preferences.
|
||||||
* `zoomFactor` Number (optional) - The default zoom factor of the page, `3.0` represents
|
* `zoomFactor` Number (optional) - The default zoom factor of the page, `3.0` represents
|
||||||
`300%`. Default is `1.0`.
|
`300%`. Default is `1.0`.
|
||||||
* `javascript` Boolean (optional) - Enables JavaScript support. Default is `true`.
|
* `javascript` Boolean (optional) - Enables JavaScript support. Default is `true`.
|
||||||
|
|
153
spec/api-browser-window-affinity-spec.js
Normal file
153
spec/api-browser-window-affinity-spec.js
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const assert = require('assert')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { ipcMain, BrowserWindow } = remote
|
||||||
|
const {closeWindow} = require('./window-helpers')
|
||||||
|
|
||||||
|
describe('BrowserWindow with affinity module', () => {
|
||||||
|
const fixtures = path.resolve(__dirname, 'fixtures')
|
||||||
|
const myAffinityName = 'myAffinity'
|
||||||
|
const myAffinityNameUpper = 'MYAFFINITY'
|
||||||
|
const anotherAffinityName = 'anotherAffinity'
|
||||||
|
|
||||||
|
function createWindowWithWebPrefs (webPrefs) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const w = new BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
width: 400,
|
||||||
|
height: 400,
|
||||||
|
webPreferences: webPrefs || {}
|
||||||
|
})
|
||||||
|
w.webContents.on('did-finish-load', () => {
|
||||||
|
resolve(w)
|
||||||
|
})
|
||||||
|
w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe(`BrowserWindow with an affinity '${myAffinityName}'`, () => {
|
||||||
|
let mAffinityWindow
|
||||||
|
before((done) => {
|
||||||
|
createWindowWithWebPrefs({ affinity: myAffinityName })
|
||||||
|
.then((w) => {
|
||||||
|
mAffinityWindow = w
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after((done) => {
|
||||||
|
closeWindow(mAffinityWindow, {assertSingleWindow: false}).then(() => {
|
||||||
|
mAffinityWindow = null
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a different process id than a default window', (done) => {
|
||||||
|
createWindowWithWebPrefs({})
|
||||||
|
.then((w) => {
|
||||||
|
assert.notEqual(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the different OS process Id/s')
|
||||||
|
closeWindow(w, {assertSingleWindow: false}).then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should have a different process id than a window with a different affinity '${anotherAffinityName}'`, (done) => {
|
||||||
|
createWindowWithWebPrefs({ affinity: anotherAffinityName })
|
||||||
|
.then((w) => {
|
||||||
|
assert.notEqual(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the different OS process Id/s')
|
||||||
|
closeWindow(w, {assertSingleWindow: false}).then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should have the same OS process id than a window with the same affinity '${myAffinityName}'`, (done) => {
|
||||||
|
createWindowWithWebPrefs({ affinity: myAffinityName })
|
||||||
|
.then((w) => {
|
||||||
|
assert.equal(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the same OS process Id')
|
||||||
|
closeWindow(w, {assertSingleWindow: false}).then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should have the same OS process id than a window with an equivalent affinity '${myAffinityNameUpper}' (case insensitive)`, (done) => {
|
||||||
|
createWindowWithWebPrefs({ affinity: myAffinityNameUpper })
|
||||||
|
.then((w) => {
|
||||||
|
assert.equal(mAffinityWindow.webContents.getOSProcessId(), w.webContents.getOSProcessId(), 'Should have the same OS process Id')
|
||||||
|
closeWindow(w, {assertSingleWindow: false}).then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(`BrowserWindow with an affinity : nodeIntegration=false`, () => {
|
||||||
|
const preload = path.join(fixtures, 'module', 'send-later.js')
|
||||||
|
const affinityWithNodeTrue = 'affinityWithNodeTrue'
|
||||||
|
const affinityWithNodeFalse = 'affinityWithNodeFalse'
|
||||||
|
|
||||||
|
function testNodeIntegration (present) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ipcMain.once('answer', (event, typeofProcess, typeofBuffer) => {
|
||||||
|
if (present) {
|
||||||
|
assert.notEqual(typeofProcess, 'undefined')
|
||||||
|
assert.notEqual(typeofBuffer, 'undefined')
|
||||||
|
} else {
|
||||||
|
assert.equal(typeofProcess, 'undefined')
|
||||||
|
assert.equal(typeofBuffer, 'undefined')
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
it('disables node integration when specified to false', (done) => {
|
||||||
|
Promise.all([testNodeIntegration(false), createWindowWithWebPrefs({ affinity: affinityWithNodeTrue, preload: preload, nodeIntegration: false })])
|
||||||
|
.then((args) => {
|
||||||
|
closeWindow(args[1], {assertSingleWindow: false}).then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('disables node integration when first window is false', (done) => {
|
||||||
|
Promise.all([testNodeIntegration(false), createWindowWithWebPrefs({ affinity: affinityWithNodeTrue, preload: preload, nodeIntegration: false })])
|
||||||
|
.then((args) => {
|
||||||
|
let w1 = args[1]
|
||||||
|
return Promise.all([testNodeIntegration(false), w1, createWindowWithWebPrefs({ affinity: affinityWithNodeTrue, preload: preload, nodeIntegration: true })])
|
||||||
|
})
|
||||||
|
.then((ws) => {
|
||||||
|
return Promise.all([closeWindow(ws[1], {assertSingleWindow: false}), closeWindow(ws[2], {assertSingleWindow: false})])
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('enables node integration when specified to true', (done) => {
|
||||||
|
Promise.all([testNodeIntegration(true), createWindowWithWebPrefs({ affinity: affinityWithNodeFalse, preload: preload, nodeIntegration: true })])
|
||||||
|
.then((args) => {
|
||||||
|
closeWindow(args[1], {assertSingleWindow: false}).then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('enables node integration when first window is true', (done) => {
|
||||||
|
Promise.all([testNodeIntegration(true), createWindowWithWebPrefs({ affinity: affinityWithNodeFalse, preload: preload, nodeIntegration: true })])
|
||||||
|
.then((args) => {
|
||||||
|
let w1 = args[1]
|
||||||
|
return Promise.all([testNodeIntegration(true), w1, createWindowWithWebPrefs({ affinity: affinityWithNodeFalse, preload: preload, nodeIntegration: false })])
|
||||||
|
})
|
||||||
|
.then((ws) => {
|
||||||
|
return Promise.all([closeWindow(ws[1], {assertSingleWindow: false}), closeWindow(ws[2], {assertSingleWindow: false})])
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Add table
Add a link
Reference in a new issue