browser(firefox): simplify isolated world structures (#6521)
This commit is contained in:
parent
45ee257a26
commit
84031d4a07
|
|
@ -1,2 +1,2 @@
|
|||
1259
|
||||
Changed: yurys@chromium.org Tue 11 May 2021 09:09:59 AM PDT
|
||||
1260
|
||||
Changed: yurys@chromium.org Wed 12 May 2021 08:49:17 AM PDT
|
||||
|
|
|
|||
|
|
@ -22,14 +22,12 @@ class FrameTree {
|
|||
if (!this._browsingContextGroup.__jugglerFrameTrees)
|
||||
this._browsingContextGroup.__jugglerFrameTrees = new Set();
|
||||
this._browsingContextGroup.__jugglerFrameTrees.add(this);
|
||||
this._scriptsToEvaluateOnNewDocument = new Map();
|
||||
this._isolatedWorlds = new Map();
|
||||
|
||||
this._webSocketEventService = Cc[
|
||||
"@mozilla.org/websocketevent/service;1"
|
||||
].getService(Ci.nsIWebSocketEventService);
|
||||
|
||||
this._bindings = new Map();
|
||||
this._runtime = new Runtime(false /* isWorker */);
|
||||
this._workers = new Map();
|
||||
this._docShellToFrame = new Map();
|
||||
|
|
@ -74,22 +72,25 @@ class FrameTree {
|
|||
}
|
||||
|
||||
addScriptToEvaluateOnNewDocument(script, worldName) {
|
||||
const scriptId = helper.generateId();
|
||||
if (worldName) {
|
||||
this._isolatedWorlds.set(scriptId, {script, worldName});
|
||||
worldName = worldName || '';
|
||||
const existing = this._isolatedWorlds.has(worldName);
|
||||
const world = this._ensureWorld(worldName);
|
||||
world._scriptsToEvaluateOnNewDocument.push(script);
|
||||
// FIXME: 'should inherit http credentials from browser context' fails without this
|
||||
if (worldName && !existing) {
|
||||
for (const frame of this.frames())
|
||||
frame.createIsolatedWorld(worldName);
|
||||
} else {
|
||||
this._scriptsToEvaluateOnNewDocument.set(scriptId, script);
|
||||
frame._createIsolatedContext(worldName);
|
||||
}
|
||||
return {scriptId};
|
||||
}
|
||||
|
||||
removeScriptToEvaluateOnNewDocument(scriptId) {
|
||||
if (this._isolatedWorlds.has(scriptId))
|
||||
this._isolatedWorlds.delete(scriptId);
|
||||
else
|
||||
this._scriptsToEvaluateOnNewDocument.delete(scriptId);
|
||||
_ensureWorld(worldName) {
|
||||
worldName = worldName || '';
|
||||
let world = this._isolatedWorlds.get(worldName);
|
||||
if (!world) {
|
||||
world = new IsolatedWorld(worldName);
|
||||
this._isolatedWorlds.set(worldName, world);
|
||||
}
|
||||
return world;
|
||||
}
|
||||
|
||||
_frameForWorker(workerDebugger) {
|
||||
|
|
@ -149,7 +150,9 @@ class FrameTree {
|
|||
}
|
||||
|
||||
addBinding(worldName, name, script) {
|
||||
this._bindings.set(worldName + ':' + name, {worldName, name, script});
|
||||
worldName = worldName || '';
|
||||
const world = this._ensureWorld(worldName);
|
||||
world._bindings.set(name, script);
|
||||
for (const frame of this.frames())
|
||||
frame._addBinding(worldName, name, script);
|
||||
}
|
||||
|
|
@ -314,6 +317,14 @@ FrameTree.Events = {
|
|||
Load: 'load',
|
||||
};
|
||||
|
||||
class IsolatedWorld {
|
||||
constructor(name) {
|
||||
this._name = name;
|
||||
this._scriptsToEvaluateOnNewDocument = [];
|
||||
this._bindings = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
class Frame {
|
||||
constructor(frameTree, runtime, docShell, parentFrame) {
|
||||
this._frameTree = frameTree;
|
||||
|
|
@ -335,9 +346,8 @@ class Frame {
|
|||
this._pendingNavigationURL = null;
|
||||
|
||||
this._textInputProcessor = null;
|
||||
this._executionContext = null;
|
||||
|
||||
this._isolatedWorlds = new Map();
|
||||
this._worldNameToContext = new Map();
|
||||
this._initialNavigationDone = false;
|
||||
|
||||
this._webSocketListenerInnerWindowId = 0;
|
||||
|
|
@ -425,7 +435,7 @@ class Frame {
|
|||
};
|
||||
}
|
||||
|
||||
createIsolatedWorld(name) {
|
||||
_createIsolatedContext(name) {
|
||||
const principal = [this.domWindow()]; // extended principal
|
||||
const sandbox = Cu.Sandbox(principal, {
|
||||
sandboxPrototype: this.domWindow(),
|
||||
|
|
@ -437,13 +447,12 @@ class Frame {
|
|||
frameId: this.id(),
|
||||
name,
|
||||
});
|
||||
this._isolatedWorlds.set(world.id(), world);
|
||||
this._worldNameToContext.set(name, world);
|
||||
return world;
|
||||
}
|
||||
|
||||
unsafeObject(objectId) {
|
||||
const contexts = [this.executionContext(), ...this._isolatedWorlds.values()];
|
||||
for (const context of contexts) {
|
||||
for (const context of this._worldNameToContext.values()) {
|
||||
const result = context.unsafeObject(objectId);
|
||||
if (result)
|
||||
return result.object;
|
||||
|
|
@ -452,38 +461,19 @@ class Frame {
|
|||
}
|
||||
|
||||
dispose() {
|
||||
for (const world of this._isolatedWorlds.values())
|
||||
this._runtime.destroyExecutionContext(world);
|
||||
this._isolatedWorlds.clear();
|
||||
if (this._executionContext)
|
||||
this._runtime.destroyExecutionContext(this._executionContext);
|
||||
this._executionContext = null;
|
||||
}
|
||||
|
||||
_getOrCreateIsolatedContext(worldName) {
|
||||
for (let context of this._isolatedWorlds.values()) {
|
||||
if (context.auxData().name === worldName)
|
||||
return context;
|
||||
}
|
||||
return this.createIsolatedWorld(worldName);
|
||||
for (const context of this._worldNameToContext.values())
|
||||
this._runtime.destroyExecutionContext(context);
|
||||
this._worldNameToContext.clear();
|
||||
}
|
||||
|
||||
_addBinding(worldName, name, script) {
|
||||
const executionContext = worldName ? this._getOrCreateIsolatedContext(worldName) : this._executionContext;
|
||||
let executionContext = this._worldNameToContext.get(worldName);
|
||||
if (worldName && !executionContext)
|
||||
executionContext = this._createIsolatedContext(worldName);
|
||||
if (executionContext)
|
||||
executionContext.addBinding(name, script);
|
||||
}
|
||||
|
||||
_evaluateScriptSafely(executionContext, script) {
|
||||
try {
|
||||
let result = executionContext.evaluateScript(script);
|
||||
if (result && result.objectId)
|
||||
executionContext.disposeObject(result.objectId);
|
||||
} catch (e) {
|
||||
dump(`ERROR: ${e.message}\n${e.stack}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
_onGlobalObjectCleared() {
|
||||
const webSocketService = this._frameTree._webSocketEventService;
|
||||
if (this._webSocketListenerInnerWindowId)
|
||||
|
|
@ -491,33 +481,28 @@ class Frame {
|
|||
this._webSocketListenerInnerWindowId = this.domWindow().windowGlobalChild.innerWindowId;
|
||||
webSocketService.addListener(this._webSocketListenerInnerWindowId, this._webSocketListener);
|
||||
|
||||
if (this._executionContext)
|
||||
this._runtime.destroyExecutionContext(this._executionContext);
|
||||
for (const world of this._isolatedWorlds.values())
|
||||
this._runtime.destroyExecutionContext(world);
|
||||
this._isolatedWorlds.clear();
|
||||
for (const context of this._worldNameToContext.values())
|
||||
this._runtime.destroyExecutionContext(context);
|
||||
this._worldNameToContext.clear();
|
||||
|
||||
this._executionContext = this._runtime.createExecutionContext(this.domWindow(), this.domWindow(), {
|
||||
this._worldNameToContext.set('', this._runtime.createExecutionContext(this.domWindow(), this.domWindow(), {
|
||||
frameId: this._frameId,
|
||||
name: '',
|
||||
});
|
||||
for (const {script, worldName} of this._frameTree._isolatedWorlds.values())
|
||||
this.createIsolatedWorld(worldName);
|
||||
|
||||
// Add bindings before evaluating scripts.
|
||||
for (const [id, {worldName, name, script}] of this._frameTree._bindings)
|
||||
this._addBinding(worldName, name, script);
|
||||
|
||||
for (const script of this._frameTree._scriptsToEvaluateOnNewDocument.values())
|
||||
this._evaluateScriptSafely(this._executionContext, script);
|
||||
for (const {script, worldName} of this._frameTree._isolatedWorlds.values()) {
|
||||
const context = worldName ? this._getOrCreateIsolatedContext(worldName) : this.executionContext();
|
||||
this._evaluateScriptSafely(context, script);
|
||||
}));
|
||||
for (const [name, world] of this._frameTree._isolatedWorlds) {
|
||||
if (name)
|
||||
this._createIsolatedContext(name);
|
||||
const executionContext = this._worldNameToContext.get(name);
|
||||
// Add bindings before evaluating scripts.
|
||||
for (const [name, script] of world._bindings)
|
||||
executionContext.addBinding(name, script);
|
||||
for (const script of world._scriptsToEvaluateOnNewDocument)
|
||||
executionContext.evaluateScriptSafely(script);
|
||||
}
|
||||
}
|
||||
|
||||
executionContext() {
|
||||
return this._executionContext;
|
||||
mainExecutionContext() {
|
||||
return this._worldNameToContext.get('');
|
||||
}
|
||||
|
||||
textInputProcessor() {
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ class PageAgent {
|
|||
insertText: this._insertText.bind(this),
|
||||
navigate: this._navigate.bind(this),
|
||||
reload: this._reload.bind(this),
|
||||
removeScriptToEvaluateOnNewDocument: ({scriptId}) => this._frameTree.removeScriptToEvaluateOnNewDocument(scriptId),
|
||||
screenshot: this._screenshot.bind(this),
|
||||
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
|
||||
setCacheDisabled: this._setCacheDisabled.bind(this),
|
||||
|
|
@ -251,8 +250,8 @@ class PageAgent {
|
|||
return;
|
||||
const frame = this._findFrameForNode(inputElement);
|
||||
this._browserPage.emit('pageFileChooserOpened', {
|
||||
executionContextId: frame.executionContext().id(),
|
||||
element: frame.executionContext().rawValueToRemoteObject(inputElement)
|
||||
executionContextId: frame.mainExecutionContext().id(),
|
||||
element: frame.mainExecutionContext().rawValueToRemoteObject(inputElement)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,9 +217,9 @@ class Runtime {
|
|||
if (obj.promiseState === 'fulfilled')
|
||||
return {success: true, obj: obj.promiseValue};
|
||||
if (obj.promiseState === 'rejected') {
|
||||
const global = executionContext._global;
|
||||
exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return;
|
||||
exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return;
|
||||
const debuggee = executionContext._debuggee;
|
||||
exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return;
|
||||
exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return;
|
||||
return {success: false, obj: null};
|
||||
}
|
||||
let resolve, reject;
|
||||
|
|
@ -245,15 +245,15 @@ class Runtime {
|
|||
pendingPromise.resolve({success: true, obj: obj.promiseValue});
|
||||
return;
|
||||
};
|
||||
const global = pendingPromise.executionContext._global;
|
||||
pendingPromise.exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return;
|
||||
pendingPromise.exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return;
|
||||
const debuggee = pendingPromise.executionContext._debuggee;
|
||||
pendingPromise.exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return;
|
||||
pendingPromise.exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return;
|
||||
pendingPromise.resolve({success: false, obj: null});
|
||||
}
|
||||
|
||||
createExecutionContext(domWindow, contextGlobal, auxData) {
|
||||
// Note: domWindow is null for workers.
|
||||
const context = new ExecutionContext(this, domWindow, contextGlobal, this._debugger.addDebuggee(contextGlobal), auxData);
|
||||
const context = new ExecutionContext(this, domWindow, contextGlobal, auxData);
|
||||
this._executionContexts.set(context._id, context);
|
||||
if (domWindow)
|
||||
this._windowToExecutionContext.set(domWindow, context);
|
||||
|
|
@ -286,15 +286,15 @@ class Runtime {
|
|||
}
|
||||
|
||||
class ExecutionContext {
|
||||
constructor(runtime, domWindow, contextGlobal, global, auxData) {
|
||||
constructor(runtime, domWindow, contextGlobal, auxData) {
|
||||
this._runtime = runtime;
|
||||
this._domWindow = domWindow;
|
||||
this._contextGlobal = contextGlobal;
|
||||
this._global = global;
|
||||
this._debuggee = runtime._debugger.addDebuggee(contextGlobal);
|
||||
this._remoteObjects = new Map();
|
||||
this._id = generateId();
|
||||
this._auxData = auxData;
|
||||
this._jsonStringifyObject = this._global.executeInGlobal(`((stringify, dateProto, object) => {
|
||||
this._jsonStringifyObject = this._debuggee.executeInGlobal(`((stringify, dateProto, object) => {
|
||||
const oldToJson = dateProto.toJSON;
|
||||
dateProto.toJSON = undefined;
|
||||
let hasSymbol = false;
|
||||
|
|
@ -325,7 +325,7 @@ class ExecutionContext {
|
|||
if (this._domWindow && this._domWindow.document)
|
||||
this._domWindow.document.notifyUserGestureActivation();
|
||||
|
||||
let {success, obj} = this._getResult(this._global.executeInGlobal(script), exceptionDetails);
|
||||
let {success, obj} = this._getResult(this._debuggee.executeInGlobal(script), exceptionDetails);
|
||||
userInputHelper && userInputHelper.destruct();
|
||||
if (!success)
|
||||
return null;
|
||||
|
|
@ -338,8 +338,16 @@ class ExecutionContext {
|
|||
return this._createRemoteObject(obj);
|
||||
}
|
||||
|
||||
evaluateScriptSafely(script) {
|
||||
try {
|
||||
this._debuggee.executeInGlobal(script);
|
||||
} catch (e) {
|
||||
dump(`ERROR: ${e.message}\n${e.stack}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async evaluateFunction(functionText, args, exceptionDetails = {}) {
|
||||
const funEvaluation = this._getResult(this._global.executeInGlobal('(' + functionText + ')'), exceptionDetails);
|
||||
const funEvaluation = this._getResult(this._debuggee.executeInGlobal('(' + functionText + ')'), exceptionDetails);
|
||||
if (!funEvaluation.success)
|
||||
return null;
|
||||
if (!funEvaluation.obj.callable)
|
||||
|
|
@ -384,11 +392,7 @@ class ExecutionContext {
|
|||
}, this._contextGlobal, {
|
||||
defineAs: name,
|
||||
});
|
||||
try {
|
||||
this._global.executeInGlobal(script);
|
||||
} catch (e) {
|
||||
dump(`ERROR: ${e.message}\n${e.stack}\n`);
|
||||
}
|
||||
this.evaluateScriptSafely(script);
|
||||
}
|
||||
|
||||
unsafeObject(objectId) {
|
||||
|
|
@ -398,14 +402,14 @@ class ExecutionContext {
|
|||
}
|
||||
|
||||
rawValueToRemoteObject(rawValue) {
|
||||
const debuggerObj = this._global.makeDebuggeeValue(rawValue);
|
||||
const debuggerObj = this._debuggee.makeDebuggeeValue(rawValue);
|
||||
return this._createRemoteObject(debuggerObj);
|
||||
}
|
||||
|
||||
_instanceOf(debuggerObj, rawObj, className) {
|
||||
if (this._domWindow)
|
||||
return rawObj instanceof this._domWindow[className];
|
||||
return this._global.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._global.makeDebuggeeValue(className)}).return;
|
||||
return this._debuggee.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._debuggee.makeDebuggeeValue(className)}).return;
|
||||
}
|
||||
|
||||
_createRemoteObject(debuggerObj) {
|
||||
|
|
@ -489,13 +493,13 @@ class ExecutionContext {
|
|||
};
|
||||
}
|
||||
const baseObject = Array.isArray(obj) ? '([])' : '({})';
|
||||
const debuggerObj = this._global.executeInGlobal(baseObject).return;
|
||||
const debuggerObj = this._debuggee.executeInGlobal(baseObject).return;
|
||||
debuggerObj.defineProperties(properties);
|
||||
return debuggerObj;
|
||||
}
|
||||
|
||||
_serialize(obj) {
|
||||
const result = this._global.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject});
|
||||
const result = this._debuggee.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject});
|
||||
if (result.throw)
|
||||
throw new Error('Object is not serializable');
|
||||
return result.return === undefined ? undefined : JSON.parse(result.return);
|
||||
|
|
@ -530,9 +534,9 @@ class ExecutionContext {
|
|||
return {success: false, obj: null};
|
||||
}
|
||||
if (completionValue.throw) {
|
||||
if (this._global.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}).return) {
|
||||
exceptionDetails.text = this._global.executeInGlobalWithBindings('e.message', {e: completionValue.throw}).return;
|
||||
exceptionDetails.stack = this._global.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}).return;
|
||||
if (this._debuggee.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}).return) {
|
||||
exceptionDetails.text = this._debuggee.executeInGlobalWithBindings('e.message', {e: completionValue.throw}).return;
|
||||
exceptionDetails.stack = this._debuggee.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}).return;
|
||||
} else {
|
||||
exceptionDetails.value = this._serialize(completionValue.throw);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,10 +89,10 @@ function initialize() {
|
|||
if (value !== undefined)
|
||||
applySetting[name](value);
|
||||
}
|
||||
for (const script of scriptsToEvaluateOnNewDocument)
|
||||
frameTree.addScriptToEvaluateOnNewDocument(script);
|
||||
for (const { worldName, name, script } of bindings)
|
||||
frameTree.addBinding(worldName, name, script);
|
||||
for (const script of scriptsToEvaluateOnNewDocument)
|
||||
frameTree.addScriptToEvaluateOnNewDocument(script);
|
||||
|
||||
pageAgent = new PageAgent(messageManager, channel, frameTree);
|
||||
|
||||
|
|
|
|||
|
|
@ -336,10 +336,6 @@ class PageHandler {
|
|||
return await this._contentPage.send('addScriptToEvaluateOnNewDocument', options);
|
||||
}
|
||||
|
||||
async ['Page.removeScriptToEvaluateOnNewDocument'](options) {
|
||||
return await this._contentPage.send('removeScriptToEvaluateOnNewDocument', options);
|
||||
}
|
||||
|
||||
async ['Page.dispatchKeyEvent'](options) {
|
||||
return await this._contentPage.send('dispatchKeyEvent', options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -768,16 +768,8 @@ const Page = {
|
|||
params: {
|
||||
script: t.String,
|
||||
worldName: t.Optional(t.String),
|
||||
},
|
||||
returns: {
|
||||
scriptId: t.String,
|
||||
}
|
||||
},
|
||||
'removeScriptToEvaluateOnNewDocument': {
|
||||
params: {
|
||||
scriptId: t.String,
|
||||
},
|
||||
},
|
||||
'navigate': {
|
||||
params: {
|
||||
frameId: t.String,
|
||||
|
|
|
|||
Loading…
Reference in a new issue