mirror of
https://forge.fsky.io/oneflux/omegafox.git
synced 2026-02-10 06:22:03 -08:00
A workaround to restore some original Playwright functionality. Forces Playwright to run in "God mode", allowing it to bypass CORS restrictions, access shadow roots (with element.shadowRootUnl), access undocumented JS browser methods, modify DOM without `allowMainWorld`, & restore some unsupported Playwright functionality. Note: `forceScopeAccess` is not detectable/visible to the page unless the dom is directly modified (elements are added or removed).
723 lines
24 KiB
JavaScript
723 lines
24 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
const Ci = Components.interfaces;
|
|
const Cr = Components.results;
|
|
const Cu = Components.utils;
|
|
|
|
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
|
const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
|
|
const {Runtime} = ChromeUtils.import('chrome://juggler/content/content/Runtime.js');
|
|
|
|
const helper = new Helper();
|
|
|
|
class FrameTree {
|
|
constructor(rootBrowsingContext) {
|
|
helper.decorateAsEventEmitter(this);
|
|
|
|
this._rootBrowsingContext = rootBrowsingContext;
|
|
|
|
this._browsingContextGroup = rootBrowsingContext.group;
|
|
if (!this._browsingContextGroup.__jugglerFrameTrees)
|
|
this._browsingContextGroup.__jugglerFrameTrees = new Set();
|
|
this._browsingContextGroup.__jugglerFrameTrees.add(this);
|
|
this._isolatedWorlds = new Map();
|
|
|
|
this._webSocketEventService = Cc[
|
|
"@mozilla.org/websocketevent/service;1"
|
|
].getService(Ci.nsIWebSocketEventService);
|
|
|
|
this._runtime = new Runtime(false /* isWorker */);
|
|
this._workers = new Map();
|
|
this._frameIdToFrame = new Map();
|
|
this._pageReady = false;
|
|
this._javaScriptDisabled = false;
|
|
for (const browsingContext of helper.collectAllBrowsingContexts(rootBrowsingContext))
|
|
this._createFrame(browsingContext);
|
|
this._mainFrame = this.frameForBrowsingContext(rootBrowsingContext);
|
|
|
|
const webProgress = rootBrowsingContext.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIWebProgress);
|
|
this.QueryInterface = ChromeUtils.generateQI([
|
|
Ci.nsIWebProgressListener,
|
|
Ci.nsIWebProgressListener2,
|
|
Ci.nsISupportsWeakReference,
|
|
]);
|
|
|
|
this._wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].createInstance(Ci.nsIWorkerDebuggerManager);
|
|
this._wdmListener = {
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIWorkerDebuggerManagerListener]),
|
|
onRegister: this._onWorkerCreated.bind(this),
|
|
onUnregister: this._onWorkerDestroyed.bind(this),
|
|
};
|
|
this._wdm.addListener(this._wdmListener);
|
|
for (const workerDebugger of this._wdm.getWorkerDebuggerEnumerator())
|
|
this._onWorkerCreated(workerDebugger);
|
|
|
|
const flags = Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
|
|
Ci.nsIWebProgress.NOTIFY_LOCATION;
|
|
this._eventListeners = [
|
|
helper.addObserver((docShell, topic, loadIdentifier) => {
|
|
const frame = this.frameForDocShell(docShell);
|
|
if (!frame)
|
|
return;
|
|
frame._pendingNavigationId = helper.toProtocolNavigationId(loadIdentifier);
|
|
this.emit(FrameTree.Events.NavigationStarted, frame);
|
|
}, 'juggler-navigation-started-renderer'),
|
|
helper.addObserver(this._onDOMWindowCreated.bind(this), 'content-document-global-created'),
|
|
helper.addObserver(this._onDOMWindowCreated.bind(this), 'juggler-dom-window-reused'),
|
|
helper.addObserver((browsingContext, topic, why) => {
|
|
this._onBrowsingContextAttached(browsingContext);
|
|
}, 'browsing-context-attached'),
|
|
helper.addObserver((browsingContext, topic, why) => {
|
|
this._onBrowsingContextDetached(browsingContext);
|
|
}, 'browsing-context-discarded'),
|
|
helper.addObserver((subject, topic, eventInfo) => {
|
|
const [type, jugglerEventId] = eventInfo.split(' ');
|
|
this.emit(FrameTree.Events.InputEvent, { type, jugglerEventId: +(jugglerEventId ?? '0') });
|
|
}, 'juggler-mouse-event-hit-renderer'),
|
|
helper.addProgressListener(webProgress, this, flags),
|
|
];
|
|
|
|
this._dragEventListeners = [];
|
|
}
|
|
|
|
workers() {
|
|
return [...this._workers.values()];
|
|
}
|
|
|
|
runtime() {
|
|
return this._runtime;
|
|
}
|
|
|
|
setInitScripts(scripts) {
|
|
for (const world of this._isolatedWorlds.values())
|
|
world._scriptsToEvaluateOnNewDocument = [];
|
|
|
|
for (let { worldName, script } of scripts) {
|
|
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._createIsolatedContext(worldName);
|
|
}
|
|
}
|
|
}
|
|
|
|
_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) {
|
|
if (workerDebugger.type !== Ci.nsIWorkerDebugger.TYPE_DEDICATED)
|
|
return null;
|
|
if (!workerDebugger.window)
|
|
return null;
|
|
return this.frameForDocShell(workerDebugger.window.docShell);
|
|
}
|
|
|
|
_onDOMWindowCreated(window) {
|
|
const frame = this.frameForDocShell(window.docShell);
|
|
if (!frame)
|
|
return;
|
|
frame._onGlobalObjectCleared();
|
|
}
|
|
|
|
setJavaScriptDisabled(javaScriptDisabled) {
|
|
this._javaScriptDisabled = javaScriptDisabled;
|
|
for (const frame of this.frames())
|
|
frame._updateJavaScriptDisabled();
|
|
}
|
|
|
|
_onWorkerCreated(workerDebugger) {
|
|
// Note: we do not interoperate with firefox devtools.
|
|
if (workerDebugger.isInitialized)
|
|
return;
|
|
const frame = this._frameForWorker(workerDebugger);
|
|
if (!frame)
|
|
return;
|
|
const worker = new Worker(frame, workerDebugger);
|
|
this._workers.set(workerDebugger, worker);
|
|
this.emit(FrameTree.Events.WorkerCreated, worker);
|
|
}
|
|
|
|
_onWorkerDestroyed(workerDebugger) {
|
|
const worker = this._workers.get(workerDebugger);
|
|
if (!worker)
|
|
return;
|
|
worker.dispose();
|
|
this._workers.delete(workerDebugger);
|
|
this.emit(FrameTree.Events.WorkerDestroyed, worker);
|
|
}
|
|
|
|
allFramesInBrowsingContextGroup(group) {
|
|
const frames = [];
|
|
for (const frameTree of (group.__jugglerFrameTrees || [])) {
|
|
for (const frame of frameTree.frames()) {
|
|
try {
|
|
// Try accessing docShell and domWindow to filter out dead frames.
|
|
// This might happen for print-preview frames, but maybe for something else as well.
|
|
frame.docShell();
|
|
frame.domWindow();
|
|
frames.push(frame);
|
|
} catch (e) {
|
|
dump(`WARNING: unable to access docShell and domWindow of the frame[id=${frame.id()}]\n`);
|
|
}
|
|
}
|
|
}
|
|
return frames;
|
|
}
|
|
|
|
isPageReady() {
|
|
return this._pageReady;
|
|
}
|
|
|
|
forcePageReady() {
|
|
if (this._pageReady)
|
|
return false;
|
|
this._pageReady = true;
|
|
this.emit(FrameTree.Events.PageReady);
|
|
return true;
|
|
}
|
|
|
|
addBinding(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);
|
|
}
|
|
|
|
frameForBrowsingContext(browsingContext) {
|
|
if (!browsingContext)
|
|
return null;
|
|
const frameId = helper.browsingContextToFrameId(browsingContext);
|
|
return this._frameIdToFrame.get(frameId) ?? null;
|
|
}
|
|
|
|
frameForDocShell(docShell) {
|
|
if (!docShell)
|
|
return null;
|
|
const frameId = helper.browsingContextToFrameId(docShell.browsingContext);
|
|
return this._frameIdToFrame.get(frameId) ?? null;
|
|
}
|
|
|
|
frame(frameId) {
|
|
return this._frameIdToFrame.get(frameId) || null;
|
|
}
|
|
|
|
frames() {
|
|
let result = [];
|
|
collect(this._mainFrame);
|
|
return result;
|
|
|
|
function collect(frame) {
|
|
result.push(frame);
|
|
for (const subframe of frame._children)
|
|
collect(subframe);
|
|
}
|
|
}
|
|
|
|
mainFrame() {
|
|
return this._mainFrame;
|
|
}
|
|
|
|
dispose() {
|
|
this._browsingContextGroup.__jugglerFrameTrees.delete(this);
|
|
this._wdm.removeListener(this._wdmListener);
|
|
this._runtime.dispose();
|
|
helper.removeListeners(this._eventListeners);
|
|
helper.removeListeners(this._dragEventListeners);
|
|
}
|
|
|
|
onWindowEvent(event) {
|
|
if (event.type !== 'DOMDocElementInserted' || !event.target.ownerGlobal)
|
|
return;
|
|
|
|
const docShell = event.target.ownerGlobal.docShell;
|
|
const frame = this.frameForDocShell(docShell);
|
|
if (!frame) {
|
|
dump(`WARNING: ${event.type} for unknown frame ${helper.browsingContextToFrameId(docShell.browsingContext)}\n`);
|
|
return;
|
|
}
|
|
if (frame._pendingNavigationId) {
|
|
docShell.QueryInterface(Ci.nsIWebNavigation);
|
|
this._frameNavigationCommitted(frame, docShell.currentURI.spec);
|
|
}
|
|
|
|
if (frame === this._mainFrame) {
|
|
helper.removeListeners(this._dragEventListeners);
|
|
const chromeEventHandler = docShell.chromeEventHandler;
|
|
const options = {
|
|
mozSystemGroup: true,
|
|
capture: true,
|
|
};
|
|
const emitInputEvent = (event) => this.emit(FrameTree.Events.InputEvent, { type: event.type, jugglerEventId: 0 });
|
|
// Drag events are dispatched from content process, so these we don't see in the
|
|
// `juggler-mouse-event-hit-renderer` instrumentation.
|
|
this._dragEventListeners = [
|
|
helper.addEventListener(chromeEventHandler, 'dragstart', emitInputEvent, options),
|
|
helper.addEventListener(chromeEventHandler, 'dragover', emitInputEvent, options),
|
|
];
|
|
}
|
|
}
|
|
|
|
_frameNavigationCommitted(frame, url) {
|
|
for (const subframe of frame._children)
|
|
this._detachFrame(subframe);
|
|
const navigationId = frame._pendingNavigationId;
|
|
frame._pendingNavigationId = null;
|
|
frame._lastCommittedNavigationId = navigationId;
|
|
frame._url = url;
|
|
this.emit(FrameTree.Events.NavigationCommitted, frame);
|
|
if (frame === this._mainFrame)
|
|
this.forcePageReady();
|
|
}
|
|
|
|
onStateChange(progress, request, flag, status) {
|
|
if (!(request instanceof Ci.nsIChannel))
|
|
return;
|
|
const channel = request.QueryInterface(Ci.nsIChannel);
|
|
const docShell = progress.DOMWindow.docShell;
|
|
const frame = this.frameForDocShell(docShell);
|
|
if (!frame)
|
|
return;
|
|
|
|
if (!channel.isDocument) {
|
|
// Somehow, we can get worker requests here,
|
|
// while we are only interested in frame documents.
|
|
return;
|
|
}
|
|
|
|
const isStop = flag & Ci.nsIWebProgressListener.STATE_STOP;
|
|
if (isStop && frame._pendingNavigationId && status) {
|
|
// Navigation is aborted.
|
|
const navigationId = frame._pendingNavigationId;
|
|
frame._pendingNavigationId = null;
|
|
// Always report download navigation as failure to match other browsers.
|
|
const errorText = helper.getNetworkErrorStatusText(status);
|
|
this.emit(FrameTree.Events.NavigationAborted, frame, navigationId, errorText);
|
|
if (frame === this._mainFrame && status !== Cr.NS_BINDING_ABORTED)
|
|
this.forcePageReady();
|
|
}
|
|
}
|
|
|
|
onLocationChange(progress, request, location, flags) {
|
|
const docShell = progress.DOMWindow.docShell;
|
|
const frame = this.frameForDocShell(docShell);
|
|
const sameDocumentNavigation = !!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
|
|
if (frame && sameDocumentNavigation) {
|
|
frame._url = location.spec;
|
|
this.emit(FrameTree.Events.SameDocumentNavigation, frame);
|
|
}
|
|
}
|
|
|
|
_onBrowsingContextAttached(browsingContext) {
|
|
// If this browsing context doesn't belong to our frame tree - do nothing.
|
|
if (browsingContext.top !== this._rootBrowsingContext)
|
|
return;
|
|
this._createFrame(browsingContext);
|
|
}
|
|
|
|
_onBrowsingContextDetached(browsingContext) {
|
|
const frame = this.frameForBrowsingContext(browsingContext);
|
|
if (frame)
|
|
this._detachFrame(frame);
|
|
}
|
|
|
|
_createFrame(browsingContext) {
|
|
const parentFrame = this.frameForBrowsingContext(browsingContext.parent);
|
|
if (!parentFrame && this._mainFrame) {
|
|
dump(`WARNING: found docShell with the same root, but no parent!\n`);
|
|
return;
|
|
}
|
|
const frame = new Frame(this, this._runtime, browsingContext, parentFrame);
|
|
this._frameIdToFrame.set(frame.id(), frame);
|
|
if (browsingContext.docShell?.domWindow && browsingContext.docShell?.domWindow.location)
|
|
frame._url = browsingContext.docShell.domWindow.location.href;
|
|
this.emit(FrameTree.Events.FrameAttached, frame);
|
|
// Create execution context **after** reporting frame.
|
|
// This is our protocol contract.
|
|
if (frame.domWindow())
|
|
frame._onGlobalObjectCleared();
|
|
return frame;
|
|
}
|
|
|
|
_detachFrame(frame) {
|
|
// Detach all children first
|
|
for (const subframe of frame._children)
|
|
this._detachFrame(subframe);
|
|
if (frame === this._mainFrame) {
|
|
// Do not detach main frame (happens during cross-process navigation),
|
|
// as it confuses the client.
|
|
return;
|
|
}
|
|
this._frameIdToFrame.delete(frame.id());
|
|
if (frame._parentFrame)
|
|
frame._parentFrame._children.delete(frame);
|
|
frame._parentFrame = null;
|
|
frame.dispose();
|
|
this.emit(FrameTree.Events.FrameDetached, frame);
|
|
}
|
|
}
|
|
|
|
FrameTree.Events = {
|
|
FrameAttached: 'frameattached',
|
|
FrameDetached: 'framedetached',
|
|
WorkerCreated: 'workercreated',
|
|
WorkerDestroyed: 'workerdestroyed',
|
|
WebSocketCreated: 'websocketcreated',
|
|
WebSocketOpened: 'websocketopened',
|
|
WebSocketClosed: 'websocketclosed',
|
|
WebSocketFrameReceived: 'websocketframereceived',
|
|
WebSocketFrameSent: 'websocketframesent',
|
|
NavigationStarted: 'navigationstarted',
|
|
NavigationCommitted: 'navigationcommitted',
|
|
NavigationAborted: 'navigationaborted',
|
|
SameDocumentNavigation: 'samedocumentnavigation',
|
|
PageReady: 'pageready',
|
|
InputEvent: 'inputevent',
|
|
};
|
|
|
|
class IsolatedWorld {
|
|
constructor(name) {
|
|
this._name = name;
|
|
this._scriptsToEvaluateOnNewDocument = [];
|
|
this._bindings = new Map();
|
|
}
|
|
}
|
|
|
|
class Frame {
|
|
constructor(frameTree, runtime, browsingContext, parentFrame) {
|
|
this._frameTree = frameTree;
|
|
this._runtime = runtime;
|
|
this._browsingContext = browsingContext;
|
|
this._children = new Set();
|
|
this._frameId = helper.browsingContextToFrameId(browsingContext);
|
|
this._parentFrame = null;
|
|
this._url = '';
|
|
if (parentFrame) {
|
|
this._parentFrame = parentFrame;
|
|
parentFrame._children.add(this);
|
|
}
|
|
|
|
this.allowMW = ChromeUtils.camouGetBool('allowMainWorld', false);
|
|
this.forceScopeAccess = ChromeUtils.camouGetBool('forceScopeAccess', false);
|
|
|
|
this.masterSandbox = undefined;
|
|
this._lastCommittedNavigationId = null;
|
|
this._pendingNavigationId = null;
|
|
|
|
this._textInputProcessor = null;
|
|
|
|
this._worldNameToContext = new Map();
|
|
this._initialNavigationDone = false;
|
|
|
|
this._webSocketListenerInnerWindowId = 0;
|
|
// WebSocketListener calls frameReceived event before webSocketOpened.
|
|
// To avoid this, serialize event reporting.
|
|
this._webSocketInfos = new Map();
|
|
|
|
const dispatchWebSocketFrameReceived = (webSocketSerialID, frame) => this._frameTree.emit(FrameTree.Events.WebSocketFrameReceived, {
|
|
frameId: this._frameId,
|
|
wsid: webSocketSerialID + '',
|
|
opcode: frame.opCode,
|
|
data: frame.opCode !== 1 ? btoa(frame.payload) : frame.payload,
|
|
});
|
|
this._webSocketListener = {
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIWebSocketEventListener, ]),
|
|
|
|
webSocketCreated: (webSocketSerialID, uri, protocols) => {
|
|
this._frameTree.emit(FrameTree.Events.WebSocketCreated, {
|
|
frameId: this._frameId,
|
|
wsid: webSocketSerialID + '',
|
|
requestURL: uri,
|
|
});
|
|
this._webSocketInfos.set(webSocketSerialID, {
|
|
opened: false,
|
|
pendingIncomingFrames: [],
|
|
});
|
|
},
|
|
|
|
webSocketOpened: (webSocketSerialID, effectiveURI, protocols, extensions, httpChannelId) => {
|
|
this._frameTree.emit(FrameTree.Events.WebSocketOpened, {
|
|
frameId: this._frameId,
|
|
requestId: httpChannelId + '',
|
|
wsid: webSocketSerialID + '',
|
|
effectiveURL: effectiveURI,
|
|
});
|
|
const info = this._webSocketInfos.get(webSocketSerialID);
|
|
info.opened = true;
|
|
for (const frame of info.pendingIncomingFrames)
|
|
dispatchWebSocketFrameReceived(webSocketSerialID, frame);
|
|
},
|
|
|
|
webSocketMessageAvailable: (webSocketSerialID, data, messageType) => {
|
|
// We don't use this event.
|
|
},
|
|
|
|
webSocketClosed: (webSocketSerialID, wasClean, code, reason) => {
|
|
this._webSocketInfos.delete(webSocketSerialID);
|
|
let error = '';
|
|
if (!wasClean) {
|
|
const keys = Object.keys(Ci.nsIWebSocketChannel);
|
|
for (const key of keys) {
|
|
if (Ci.nsIWebSocketChannel[key] === code)
|
|
error = key;
|
|
}
|
|
}
|
|
this._frameTree.emit(FrameTree.Events.WebSocketClosed, {
|
|
frameId: this._frameId,
|
|
wsid: webSocketSerialID + '',
|
|
error,
|
|
});
|
|
},
|
|
|
|
frameReceived: (webSocketSerialID, frame) => {
|
|
// Report only text and binary frames.
|
|
if (frame.opCode !== 1 && frame.opCode !== 2)
|
|
return;
|
|
const info = this._webSocketInfos.get(webSocketSerialID);
|
|
if (info.opened)
|
|
dispatchWebSocketFrameReceived(webSocketSerialID, frame);
|
|
else
|
|
info.pendingIncomingFrames.push(frame);
|
|
},
|
|
|
|
frameSent: (webSocketSerialID, frame) => {
|
|
// Report only text and binary frames.
|
|
if (frame.opCode !== 1 && frame.opCode !== 2)
|
|
return;
|
|
this._frameTree.emit(FrameTree.Events.WebSocketFrameSent, {
|
|
frameId: this._frameId,
|
|
wsid: webSocketSerialID + '',
|
|
opcode: frame.opCode,
|
|
data: frame.opCode !== 1 ? btoa(frame.payload) : frame.payload,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
// Camoufox: Add a "God mode" master sandbox with it's own compartment
|
|
getMasterSandbox() {
|
|
if (!this.masterSandbox) {
|
|
this.masterSandbox = Cu.Sandbox(
|
|
Services.scriptSecurityManager.getSystemPrincipal(),
|
|
{
|
|
sandboxPrototype: this.domWindow(),
|
|
wantComponents: false,
|
|
wantExportHelpers: false,
|
|
wantXrays: true,
|
|
freshCompartment: true,
|
|
}
|
|
);
|
|
}
|
|
return this.masterSandbox;
|
|
}
|
|
|
|
_createIsolatedContext(name, useMaster=false) {
|
|
let sandbox;
|
|
// Camoufox: Use the master sandbox (with system principle scope access)
|
|
if (useMaster && this.forceScopeAccess) {
|
|
sandbox = this.getMasterSandbox();
|
|
} else {
|
|
// Standard access (run in domWindow principal)
|
|
sandbox = Cu.Sandbox([this.domWindow()], {
|
|
sandboxPrototype: this.domWindow(),
|
|
wantComponents: false,
|
|
wantExportHelpers: false,
|
|
wantXrays: true,
|
|
});
|
|
}
|
|
const world = this._runtime.createExecutionContext(this.domWindow(), sandbox, {
|
|
frameId: this.id(),
|
|
name,
|
|
});
|
|
// Camoufox: Create a main world for the isolated context
|
|
if (this.allowMW) {
|
|
const mainWorld = this._runtime.createMW(this.domWindow(), sandbox);
|
|
world.mainEquivalent = mainWorld;
|
|
}
|
|
this._worldNameToContext.set(name, world);
|
|
return world;
|
|
}
|
|
|
|
unsafeObject(objectId) {
|
|
for (const context of this._worldNameToContext.values()) {
|
|
const result = context.unsafeObject(objectId);
|
|
if (result)
|
|
return result.object;
|
|
}
|
|
throw new Error('Cannot find object with id = ' + objectId);
|
|
}
|
|
|
|
dispose() {
|
|
for (const context of this._worldNameToContext.values())
|
|
this._runtime.destroyExecutionContext(context);
|
|
this._worldNameToContext.clear();
|
|
}
|
|
|
|
_addBinding(worldName, name, script) {
|
|
let executionContext = this._worldNameToContext.get(worldName);
|
|
if (worldName && !executionContext)
|
|
executionContext = this._createIsolatedContext(worldName);
|
|
if (executionContext)
|
|
executionContext.addBinding(name, script);
|
|
}
|
|
|
|
_onGlobalObjectCleared() {
|
|
const webSocketService = this._frameTree._webSocketEventService;
|
|
if (this._webSocketListenerInnerWindowId && webSocketService.hasListenerFor(this._webSocketListenerInnerWindowId))
|
|
webSocketService.removeListener(this._webSocketListenerInnerWindowId, this._webSocketListener);
|
|
this._webSocketListenerInnerWindowId = this.domWindow().windowGlobalChild.innerWindowId;
|
|
webSocketService.addListener(this._webSocketListenerInnerWindowId, this._webSocketListener);
|
|
for (const context of this._worldNameToContext.values())
|
|
this._runtime.destroyExecutionContext(context);
|
|
this._worldNameToContext.clear();
|
|
// Camoufox: Scope the initial execution context to prevent leaks
|
|
this._createIsolatedContext('', true);
|
|
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);
|
|
}
|
|
|
|
const url = this.domWindow().location?.href;
|
|
if (url === 'about:blank' && !this._url) {
|
|
// Sometimes FrameTree is created too early, before the location has been set.
|
|
this._url = url;
|
|
this._frameTree.emit(FrameTree.Events.NavigationCommitted, this);
|
|
}
|
|
|
|
this._updateJavaScriptDisabled();
|
|
}
|
|
|
|
_updateJavaScriptDisabled() {
|
|
if (this._browsingContext.currentWindowContext)
|
|
this._browsingContext.currentWindowContext.allowJavascript = !this._frameTree._javaScriptDisabled;
|
|
}
|
|
|
|
mainExecutionContext() {
|
|
return this._worldNameToContext.get('');
|
|
}
|
|
|
|
textInputProcessor() {
|
|
if (!this._textInputProcessor) {
|
|
this._textInputProcessor = Cc["@mozilla.org/text-input-processor;1"].createInstance(Ci.nsITextInputProcessor);
|
|
}
|
|
this._textInputProcessor.beginInputTransactionForTests(this.docShell().DOMWindow);
|
|
return this._textInputProcessor;
|
|
}
|
|
|
|
pendingNavigationId() {
|
|
return this._pendingNavigationId;
|
|
}
|
|
|
|
lastCommittedNavigationId() {
|
|
return this._lastCommittedNavigationId;
|
|
}
|
|
|
|
docShell() {
|
|
return this._browsingContext.docShell;
|
|
}
|
|
|
|
domWindow() {
|
|
return this.docShell()?.domWindow;
|
|
}
|
|
|
|
name() {
|
|
const frameElement = this.domWindow()?.frameElement;
|
|
let name = '';
|
|
if (frameElement)
|
|
name = frameElement.getAttribute('name') || frameElement.getAttribute('id') || '';
|
|
return name;
|
|
}
|
|
|
|
parentFrame() {
|
|
return this._parentFrame;
|
|
}
|
|
|
|
id() {
|
|
return this._frameId;
|
|
}
|
|
|
|
url() {
|
|
return this._url;
|
|
}
|
|
|
|
}
|
|
|
|
class Worker {
|
|
constructor(frame, workerDebugger) {
|
|
this._frame = frame;
|
|
this._workerId = helper.generateId();
|
|
this._workerDebugger = workerDebugger;
|
|
|
|
workerDebugger.initialize('chrome://juggler/content/content/WorkerMain.js');
|
|
|
|
this._channel = new SimpleChannel(`content::worker[${this._workerId}]`, 'worker-' + this._workerId);
|
|
this._channel.setTransport({
|
|
sendMessage: obj => workerDebugger.postMessage(JSON.stringify(obj)),
|
|
dispose: () => {},
|
|
});
|
|
this._workerDebuggerListener = {
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIWorkerDebuggerListener]),
|
|
onMessage: msg => void this._channel._onMessage(JSON.parse(msg)),
|
|
onClose: () => void this._channel.dispose(),
|
|
onError: (filename, lineno, message) => {
|
|
dump(`WARNING: Error in worker: ${message} @${filename}:${lineno}\n`);
|
|
},
|
|
};
|
|
workerDebugger.addListener(this._workerDebuggerListener);
|
|
}
|
|
|
|
channel() {
|
|
return this._channel;
|
|
}
|
|
|
|
frame() {
|
|
return this._frame;
|
|
}
|
|
|
|
id() {
|
|
return this._workerId;
|
|
}
|
|
|
|
url() {
|
|
return this._workerDebugger.url;
|
|
}
|
|
|
|
dispose() {
|
|
this._channel.dispose();
|
|
this._workerDebugger.removeListener(this._workerDebuggerListener);
|
|
}
|
|
}
|
|
|
|
function channelId(channel) {
|
|
if (channel instanceof Ci.nsIIdentChannel) {
|
|
const identChannel = channel.QueryInterface(Ci.nsIIdentChannel);
|
|
return String(identChannel.channelId);
|
|
}
|
|
return helper.generateId();
|
|
}
|
|
|
|
|
|
var EXPORTED_SYMBOLS = ['FrameTree'];
|
|
this.FrameTree = FrameTree;
|
|
|