mirror of
https://github.com/daijro/camoufox.git
synced 2026-08-20 00:01:00 +00:00
fix: migrate Juggler modules from JSM to ESM for Firefox 146 compatibility
- Converted ChannelEventSink from JSM to ESM (.sys.mjs) - Changed ChromeUtils.import() to ChromeUtils.importESModule() across all modules - Updated .jsm file extensions to .sys.mjs for system module imports - Replaced EXPORTED_SYMBOLS with export keyword in Helper.js, NetworkObserver.js, SimpleChannel.js, TargetRegistry.js, and JugglerFrameParent.jsm - Updated EventEmitter import from resource://gre/modules/EventEmitter.jsm to .
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/* 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/. */
|
||||
|
||||
import { ComponentUtils } from "resource://gre/modules/ComponentUtils.sys.mjs";
|
||||
|
||||
const Cm = Components.manager;
|
||||
|
||||
/**
|
||||
* This is a nsIChannelEventSink implementation that monitors channel redirects.
|
||||
* This has been forked from:
|
||||
* https://searchfox.org/mozilla-central/source/devtools/server/actors/network-monitor/channel-event-sink.js
|
||||
* The rest of this module is also more or less forking:
|
||||
* https://searchfox.org/mozilla-central/source/devtools/server/actors/network-monitor/network-observer.js
|
||||
* TODO(try to re-unify /remote/ with /devtools code)
|
||||
*/
|
||||
const SINK_CLASS_DESCRIPTION = "NetworkMonitor Channel Event Sink";
|
||||
const SINK_CLASS_ID = Components.ID("{c2b4c83e-607a-405a-beab-0ef5dbfb7617}");
|
||||
const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
|
||||
const SINK_CATEGORY_NAME = "net-channel-event-sinks";
|
||||
|
||||
function ChannelEventSink() {
|
||||
this.wrappedJSObject = this;
|
||||
this.collectors = new Set();
|
||||
}
|
||||
|
||||
ChannelEventSink.prototype = {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink"]),
|
||||
|
||||
registerCollector(collector) {
|
||||
this.collectors.add(collector);
|
||||
},
|
||||
|
||||
unregisterCollector(collector) {
|
||||
this.collectors.delete(collector);
|
||||
|
||||
if (this.collectors.size == 0) {
|
||||
ChannelEventSinkFactory.unregister();
|
||||
}
|
||||
},
|
||||
|
||||
asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
|
||||
for (const collector of this.collectors) {
|
||||
try {
|
||||
collector._onChannelRedirect(oldChannel, newChannel, flags);
|
||||
} catch (ex) {
|
||||
console.error(
|
||||
"StackTraceCollector.onChannelRedirect threw an exception",
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
callback.onRedirectVerifyCallback(Cr.NS_OK);
|
||||
},
|
||||
};
|
||||
|
||||
export const ChannelEventSinkFactory =
|
||||
ComponentUtils.generateSingletonFactory(ChannelEventSink);
|
||||
|
||||
ChannelEventSinkFactory.register = function () {
|
||||
const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
if (registrar.isCIDRegistered(SINK_CLASS_ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
registrar.registerFactory(
|
||||
SINK_CLASS_ID,
|
||||
SINK_CLASS_DESCRIPTION,
|
||||
SINK_CONTRACT_ID,
|
||||
ChannelEventSinkFactory
|
||||
);
|
||||
|
||||
Services.catMan.addCategoryEntry(
|
||||
SINK_CATEGORY_NAME,
|
||||
SINK_CONTRACT_ID,
|
||||
SINK_CONTRACT_ID,
|
||||
false,
|
||||
true
|
||||
);
|
||||
};
|
||||
|
||||
ChannelEventSinkFactory.unregister = function () {
|
||||
const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.unregisterFactory(SINK_CLASS_ID, ChannelEventSinkFactory);
|
||||
|
||||
Services.catMan.deleteCategoryEntry(
|
||||
SINK_CATEGORY_NAME,
|
||||
SINK_CONTRACT_ID,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
ChannelEventSinkFactory.getService = function () {
|
||||
// Make sure the ChannelEventSink service is registered before accessing it
|
||||
ChannelEventSinkFactory.register();
|
||||
|
||||
return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink)
|
||||
.wrappedJSObject;
|
||||
};
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
class Helper {
|
||||
export class Helper {
|
||||
decorateAsEventEmitter(objectToDecorate) {
|
||||
const { EventEmitter } = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
|
||||
const { EventEmitter } = ChromeUtils.importESModule('resource://gre/modules/EventEmitter.sys.mjs');
|
||||
const emitter = new EventEmitter();
|
||||
objectToDecorate.on = emitter.on.bind(emitter);
|
||||
objectToDecorate.addEventListener = emitter.on.bind(emitter);
|
||||
@@ -172,7 +172,7 @@ class Helper {
|
||||
|
||||
const helper = new Helper();
|
||||
|
||||
class EventWatcher {
|
||||
export class EventWatcher {
|
||||
constructor(receiver, eventNames, pendingEventWatchers = new Set()) {
|
||||
this._pendingEventWatchers = pendingEventWatchers;
|
||||
this._pendingEventWatchers.add(this);
|
||||
@@ -233,7 +233,3 @@ class EventWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = [ "Helper", "EventWatcher" ];
|
||||
this.Helper = Helper;
|
||||
this.EventWatcher = EventWatcher;
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const { TargetRegistry } = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js');
|
||||
const { Helper } = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const { TargetRegistry } = ChromeUtils.importESModule('chrome://juggler/content/TargetRegistry.js');
|
||||
const { Helper } = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
|
||||
const helper = new Helper();
|
||||
|
||||
var EXPORTED_SYMBOLS = ['JugglerFrameParent'];
|
||||
|
||||
class JugglerFrameParent extends JSWindowActorParent {
|
||||
export class JugglerFrameParent extends JSWindowActorParent {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -18,7 +16,7 @@ class JugglerFrameParent extends JSWindowActorParent {
|
||||
// Actors are registered per the WindowGlobalParent / WindowGlobalChild pair. We are only
|
||||
// interested in those WindowGlobalParent actors that are matching current browsingContext
|
||||
// window global.
|
||||
// See https://github.com/mozilla/gecko-dev/blob/cd2121e7d83af1b421c95e8c923db70e692dab5f/testing/mochitest/BrowserTestUtils/BrowserTestUtilsParent.sys.mjs#L15
|
||||
// See https://github.com/mozilla-firefox/firefox/blob/35e22180b0b61413dd8eccf6c00b1c6fac073eee/testing/mochitest/BrowserTestUtils/BrowserTestUtilsParent.sys.mjs#L15
|
||||
if (!this.manager?.isCurrentGlobal)
|
||||
return;
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
|
||||
const { ChannelEventSinkFactory } = ChromeUtils.import("chrome://remote/content/cdp/observers/ChannelEventSink.jsm");
|
||||
const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs');
|
||||
const { ChannelEventSinkFactory } = ChromeUtils.importESModule("chrome://juggler/content/ChannelEventSink.sys.mjs");
|
||||
|
||||
|
||||
const Cc = Components.classes;
|
||||
@@ -28,7 +28,7 @@ const MAX_RESPONSE_STORAGE_SIZE = 100 * 1024 * 1024;
|
||||
|
||||
const pageNetworkSymbol = Symbol('PageNetwork');
|
||||
|
||||
class PageNetwork {
|
||||
export class PageNetwork {
|
||||
static forPageTarget(target) {
|
||||
if (!target)
|
||||
return undefined;
|
||||
@@ -143,9 +143,10 @@ class NetworkRequest {
|
||||
const target = this._networkObserver._targetRegistry.targetForBrowserId(browsingContext.browserId);
|
||||
this._pageNetwork = PageNetwork.forPageTarget(target);
|
||||
}
|
||||
this._expectingInterception = false;
|
||||
this._shouldYieldInterceptionToServiceWorker = false;
|
||||
this._expectingResumedRequest = undefined; // { method, headers, postData }
|
||||
this._overriddenHeadersForRedirect = redirectedFrom?._overriddenHeadersForRedirect;
|
||||
this._sentOnRequest = false;
|
||||
this._sentOnResponse = false;
|
||||
this._fulfilled = false;
|
||||
|
||||
@@ -204,11 +205,13 @@ class NetworkRequest {
|
||||
this._interceptedChannel.synthesizeHeader(header.name, header.value);
|
||||
if (header.name.toLowerCase() === 'set-cookie') {
|
||||
Services.cookies.QueryInterface(Ci.nsICookieService);
|
||||
Services.cookies.setCookieStringFromHttp(this.httpChannel.URI, header.value, this.httpChannel);
|
||||
for (const cookieString of header.value.split('\n'))
|
||||
Services.cookies.setCookieStringFromHttp(this.httpChannel.URI, cookieString, this.httpChannel);
|
||||
}
|
||||
}
|
||||
const synthesized = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
synthesized.data = base64body ? atob(base64body) : '';
|
||||
if (base64body)
|
||||
synthesized.setByteStringData(atob(base64body));
|
||||
this._interceptedChannel.startSynthesizedResponse(synthesized, null, null, '', false);
|
||||
this._interceptedChannel.finishSynthesizedResponse();
|
||||
this._interceptedChannel = undefined;
|
||||
@@ -316,9 +319,8 @@ class NetworkRequest {
|
||||
const interceptController = this._fallThroughInterceptController();
|
||||
if (interceptController && interceptController.shouldPrepareForIntercept(aURI, channel)) {
|
||||
// We assume that interceptController is a service worker if there is one,
|
||||
// and yield interception to it. We are not going to intercept ourselves,
|
||||
// so we send onRequest now.
|
||||
this._sendOnRequest(false);
|
||||
// and yield interception to it.
|
||||
this._shouldYieldInterceptionToServiceWorker = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -327,12 +329,6 @@ class NetworkRequest {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We do not want to intercept any redirects, because we are not able
|
||||
// to intercept subresource redirects, and it's unreliable for main requests.
|
||||
// We do not sendOnRequest here, because redirects do that in constructor.
|
||||
if (this.redirectedFromId)
|
||||
return false;
|
||||
|
||||
const shouldIntercept = this._shouldIntercept();
|
||||
if (!shouldIntercept) {
|
||||
// We are not intercepting - ready to issue onRequest.
|
||||
@@ -340,21 +336,24 @@ class NetworkRequest {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._expectingInterception = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// nsINetworkInterceptController
|
||||
channelIntercepted(intercepted) {
|
||||
if (!this._expectingInterception) {
|
||||
// We are not intercepting, fall-through.
|
||||
const interceptController = this._fallThroughInterceptController();
|
||||
if (interceptController)
|
||||
interceptController.channelIntercepted(intercepted);
|
||||
// Yield to a service worker if determined so in shouldPrepareForIntercept().
|
||||
const serviceWorker = this._shouldYieldInterceptionToServiceWorker ? this._fallThroughInterceptController() : undefined;
|
||||
// Clear the flag to avoid an infinite loop. After service worker, we should intercept ourselves.
|
||||
this._shouldYieldInterceptionToServiceWorker = false;
|
||||
|
||||
if (serviceWorker) {
|
||||
const interceptedChannel = intercepted.QueryInterface(Ci.nsIInterceptedChannel);
|
||||
// If service worker will not actually intercept the request, we want to be called again.
|
||||
interceptedChannel.interceptAfterServiceWorkerResets();
|
||||
serviceWorker.channelIntercepted(intercepted);
|
||||
return;
|
||||
}
|
||||
|
||||
this._expectingInterception = false;
|
||||
this._interceptedChannel = intercepted.QueryInterface(Ci.nsIInterceptedChannel);
|
||||
|
||||
const pageNetwork = this._pageNetwork;
|
||||
@@ -408,6 +407,7 @@ class NetworkRequest {
|
||||
// See https://github.com/microsoft/playwright/issues/9418#issuecomment-944836244
|
||||
if (aRequest !== this.httpChannel)
|
||||
return;
|
||||
this._sendOnRequest(false);
|
||||
try {
|
||||
this._originalListener.onStartRequest(aRequest);
|
||||
} catch (e) {
|
||||
@@ -445,6 +445,10 @@ class NetworkRequest {
|
||||
}
|
||||
|
||||
_shouldIntercept() {
|
||||
// We do not want to intercept any redirects, because we are not able
|
||||
// to intercept subresource redirects, and it's unreliable for main requests.
|
||||
if (this.redirectedFromId)
|
||||
return false;
|
||||
const pageNetwork = this._pageNetwork;
|
||||
if (!pageNetwork)
|
||||
return false;
|
||||
@@ -465,8 +469,15 @@ class NetworkRequest {
|
||||
}
|
||||
|
||||
_sendOnRequest(isIntercepted) {
|
||||
// Note: we call _sendOnRequest either after we intercepted the request,
|
||||
// or at the first moment we know that we are not going to intercept.
|
||||
if (this._sentOnRequest) {
|
||||
// We can come here twice because:
|
||||
// - Redirects call _sendOnRequest in the constructor and from inside interception.
|
||||
// - All other requests might call _sendOnRequest from onStartRequest and from inside interception.
|
||||
// - All requests call _sendOnRequest from _sendOnResponse to avoid responses without requests.
|
||||
return;
|
||||
}
|
||||
this._sentOnRequest = true;
|
||||
|
||||
const pageNetwork = this._pageNetwork;
|
||||
if (!pageNetwork)
|
||||
return;
|
||||
@@ -489,11 +500,21 @@ class NetworkRequest {
|
||||
}
|
||||
|
||||
_sendOnResponse(fromCache, opt_statusCode, opt_statusText) {
|
||||
// For internal redirects, and perhaps something else that we lack test coverage for,
|
||||
// we can arrive here before onStartRequest has fired. Make sure we
|
||||
// notify about the request first.
|
||||
this._sendOnRequest(false);
|
||||
|
||||
if (this._sentOnResponse) {
|
||||
// We can come here twice because of internal redirects, e.g. service workers.
|
||||
// We can come here twice because of an internal redirect, for example:
|
||||
// - request was intercepted by a service worker;
|
||||
// - HSTS redirect;
|
||||
// - CORS preflight;
|
||||
// - who knows what else?
|
||||
return;
|
||||
}
|
||||
this._sentOnResponse = true;
|
||||
|
||||
const pageNetwork = this._pageNetwork;
|
||||
if (!pageNetwork)
|
||||
return;
|
||||
@@ -512,6 +533,8 @@ class NetworkRequest {
|
||||
};
|
||||
|
||||
const { status, statusText, headers } = responseHead(this.httpChannel, opt_statusCode, opt_statusText);
|
||||
if (redirectStatus.includes(status) && this._overriddenHeadersForRedirect)
|
||||
this._overriddenHeadersForRedirect = filterHeadersForRedirect(this._overriddenHeadersForRedirect, this.httpChannel.requestMethod, status);
|
||||
let remoteIPAddress = undefined;
|
||||
let remotePort = undefined;
|
||||
try {
|
||||
@@ -574,7 +597,7 @@ class NetworkRequest {
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkObserver {
|
||||
export class NetworkObserver {
|
||||
static instance() {
|
||||
return NetworkObserver._instance || null;
|
||||
}
|
||||
@@ -774,6 +797,10 @@ function clearRequestHeaders(httpChannel) {
|
||||
// We cannot remove the "host" header.
|
||||
if (header.name.toLowerCase() === 'host')
|
||||
continue;
|
||||
// Keep the "cookie" header. If there is an override, it will be set anyway.
|
||||
// Otherwise, we may delete a cookie that was set for a redirect.
|
||||
if (header.name.toLowerCase() === 'cookie')
|
||||
continue;
|
||||
httpChannel.setRequestHeader(header.name, '', false /* merge */);
|
||||
}
|
||||
}
|
||||
@@ -783,6 +810,18 @@ function overrideRequestHeaders(httpChannel, headers) {
|
||||
appendExtraHTTPHeaders(httpChannel, headers);
|
||||
}
|
||||
|
||||
const redirectStatus = [301, 302, 303, 307, 308];
|
||||
|
||||
function filterHeadersForRedirect(headers, requestMethod, status) {
|
||||
// HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
|
||||
if ((status === 301 || status === 302) && requestMethod === 'POST' ||
|
||||
status === 303 && !['GET', 'HEAD'].includes(requestMethod)) {
|
||||
const requestBodyHeaders = ['content-encoding', 'content-language', 'content-length', 'content-location', 'content-type'];
|
||||
return headers.filter(header => !requestBodyHeaders.includes(header.name.toLowerCase()));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
function causeTypeToString(causeType) {
|
||||
for (let key in Ci.nsIContentPolicy) {
|
||||
if (Ci.nsIContentPolicy[key] === causeType)
|
||||
@@ -870,7 +909,7 @@ function setPostData(httpChannel, postData, headers) {
|
||||
return;
|
||||
const synthesized = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
const body = atob(postData);
|
||||
synthesized.setByteStringData(body, body.length);
|
||||
synthesized.setByteStringData(body);
|
||||
|
||||
const overriddenHeader = (lowerCaseName) => {
|
||||
if (headers) {
|
||||
@@ -902,7 +941,7 @@ function convertString(s, source, dest) {
|
||||
const is = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
|
||||
Ci.nsIStringInputStream
|
||||
);
|
||||
is.setByteStringData(s, s.length);
|
||||
is.setByteStringData(s);
|
||||
const listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
|
||||
Ci.nsIStreamLoader
|
||||
);
|
||||
@@ -962,6 +1001,3 @@ PageNetwork.Events = {
|
||||
RequestFailed: Symbol('PageNetwork.Events.RequestFailed'),
|
||||
};
|
||||
|
||||
var EXPORTED_SYMBOLS = ['NetworkObserver', 'PageNetwork'];
|
||||
this.NetworkObserver = NetworkObserver;
|
||||
this.PageNetwork = PageNetwork;
|
||||
@@ -79,7 +79,7 @@ class SimpleChannel {
|
||||
|
||||
_setTimeout(cb, timeout) {
|
||||
// Lazy load on first call.
|
||||
this._setTimeout = ChromeUtils.import('resource://gre/modules/Timer.jsm').setTimeout;
|
||||
this._setTimeout = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs').setTimeout;
|
||||
this._setTimeout(cb, timeout);
|
||||
}
|
||||
|
||||
@@ -251,6 +251,3 @@ class SimpleChannel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['SimpleChannel'];
|
||||
this.SimpleChannel = SimpleChannel;
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
/* 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/. */
|
||||
* 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/. */
|
||||
|
||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
|
||||
const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
|
||||
const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm");
|
||||
const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
|
||||
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
const {Preferences} = ChromeUtils.importESModule("resource://gre/modules/Preferences.sys.mjs");
|
||||
const {ContextualIdentityService} = ChromeUtils.importESModule("resource://gre/modules/ContextualIdentityService.sys.mjs");
|
||||
const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs');
|
||||
const {AppConstants} = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs");
|
||||
|
||||
const Cr = Components.results;
|
||||
|
||||
const helper = new Helper();
|
||||
|
||||
const IDENTITY_NAME = 'Camoufox ';
|
||||
const IDENTITY_NAME = 'JUGGLER ';
|
||||
const HUNDRED_YEARS = 60 * 60 * 24 * 365 * 100;
|
||||
|
||||
const ALL_PERMISSIONS = [
|
||||
@@ -22,6 +21,9 @@ const ALL_PERMISSIONS = [
|
||||
];
|
||||
|
||||
let globalTabAndWindowActivationChain = Promise.resolve();
|
||||
// This is a workaround for https://github.com/microsoft/playwright/issues/34586
|
||||
let didCreateFirstPage = false;
|
||||
let globalNewPageChain = Promise.resolve();
|
||||
|
||||
class DownloadInterceptor {
|
||||
constructor(registry) {
|
||||
@@ -102,7 +104,7 @@ class DownloadInterceptor {
|
||||
|
||||
const screencastService = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService);
|
||||
|
||||
class TargetRegistry {
|
||||
export class TargetRegistry {
|
||||
static instance() {
|
||||
return TargetRegistry._instance || null;
|
||||
}
|
||||
@@ -308,6 +310,16 @@ class TargetRegistry {
|
||||
}
|
||||
|
||||
async newPage({browserContextId}) {
|
||||
// When creating the very first page, we cannot create multiple in parallel.
|
||||
// See https://github.com/microsoft/playwright/issues/34586.
|
||||
if (didCreateFirstPage)
|
||||
return this._newPageInternal({browserContextId});
|
||||
const result = globalNewPageChain.then(() => this._newPageInternal({browserContextId}));
|
||||
globalNewPageChain = result.catch(error => { /* swallow errors to keep chain running */ });
|
||||
return result;
|
||||
}
|
||||
|
||||
async _newPageInternal({browserContextId}) {
|
||||
const browserContext = this.browserContextForId(browserContextId);
|
||||
const features = "chrome,dialog=no,all";
|
||||
// See _callWithURIToLoad in browser.js for the structure of window.arguments
|
||||
@@ -356,6 +368,7 @@ class TargetRegistry {
|
||||
if (await target.hasFailedToOverrideTimezone())
|
||||
throw new Error('Failed to override timezone');
|
||||
}
|
||||
didCreateFirstPage = true;
|
||||
return target.id();
|
||||
}
|
||||
|
||||
@@ -372,7 +385,7 @@ class TargetRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
class PageTarget {
|
||||
export class PageTarget {
|
||||
constructor(registry, win, tab, browserContext, opener) {
|
||||
helper.decorateAsEventEmitter(this);
|
||||
|
||||
@@ -384,16 +397,7 @@ class PageTarget {
|
||||
this._linkedBrowser = tab.linkedBrowser;
|
||||
this._browserContext = browserContext;
|
||||
this._viewportSize = undefined;
|
||||
// Set the viewport size to Camoufox's default value.
|
||||
if (
|
||||
ChromeUtils.camouGetInt("window.innerWidth")
|
||||
|| ChromeUtils.camouGetInt("window.innerHeight")
|
||||
) {
|
||||
this._viewportSize = {
|
||||
width: ChromeUtils.camouGetInt("window.innerWidth") || 1280,
|
||||
height: ChromeUtils.camouGetInt("window.innerHeight") || 720,
|
||||
};
|
||||
}
|
||||
this._zoom = 1;
|
||||
this._initialDPPX = this._linkedBrowser.browsingContext.overrideDPPX;
|
||||
this._url = 'about:blank';
|
||||
this._openerId = opener ? opener.id() : undefined;
|
||||
@@ -506,9 +510,11 @@ class PageTarget {
|
||||
this.updateUserAgent(browsingContext);
|
||||
this.updatePlatform(browsingContext);
|
||||
this.updateDPPXOverride(browsingContext);
|
||||
this.updateZoom(browsingContext);
|
||||
this.updateEmulatedMedia(browsingContext);
|
||||
this.updateColorSchemeOverride(browsingContext);
|
||||
this.updateReducedMotionOverride(browsingContext);
|
||||
this.updateContrastOverride(browsingContext);
|
||||
this.updateForcedColorsOverride(browsingContext);
|
||||
this.updateForceOffline(browsingContext);
|
||||
this.updateCacheDisabled(browsingContext);
|
||||
@@ -544,7 +550,16 @@ class PageTarget {
|
||||
}
|
||||
|
||||
updateDPPXOverride(browsingContext = undefined) {
|
||||
(browsingContext || this._linkedBrowser.browsingContext).overrideDPPX = this._browserContext.deviceScaleFactor || this._initialDPPX;
|
||||
browsingContext ||= this._linkedBrowser.browsingContext;
|
||||
const dppx = this._zoom * (this._browserContext.deviceScaleFactor || this._initialDPPX);
|
||||
browsingContext.overrideDPPX = dppx;
|
||||
}
|
||||
|
||||
async updateZoom(browsingContext = undefined) {
|
||||
browsingContext ||= this._linkedBrowser.browsingContext;
|
||||
// Update dpr first, and then UI zoom.
|
||||
this.updateDPPXOverride(browsingContext);
|
||||
browsingContext.fullZoom = this._zoom;
|
||||
}
|
||||
|
||||
_updateModalDialogs() {
|
||||
@@ -579,19 +594,7 @@ class PageTarget {
|
||||
// The "default size" (1) is only respected when the page is opened.
|
||||
// Otherwise, explicitly set page viewport prevales over browser context
|
||||
// default viewport.
|
||||
|
||||
// Camoufox is already handling viewport size, so we don't need to set it here.
|
||||
if (
|
||||
ChromeUtils.camouGetInt("window.outerWidth") ||
|
||||
ChromeUtils.camouGetInt("window.outerHeight") ||
|
||||
ChromeUtils.camouGetInt("window.innerWidth") ||
|
||||
ChromeUtils.camouGetInt("window.innerHeight")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportSize = this._viewportSize || this._browserContext.defaultViewportSize;
|
||||
|
||||
if (viewportSize) {
|
||||
const {width, height} = viewportSize;
|
||||
this._linkedBrowser.style.setProperty('width', width + 'px');
|
||||
@@ -606,7 +609,7 @@ class PageTarget {
|
||||
const toolbarTop = stackRect.y;
|
||||
this._window.resizeBy(width - this._window.innerWidth, height + toolbarTop - this._window.innerHeight);
|
||||
|
||||
await this._channel.connect('').send('awaitViewportDimensions', { width, height });
|
||||
await this._channel.connect('').send('awaitViewportDimensions', { width: width / this._zoom, height: height / this._zoom });
|
||||
} else {
|
||||
this._linkedBrowser.style.removeProperty('width');
|
||||
this._linkedBrowser.style.removeProperty('height');
|
||||
@@ -618,8 +621,8 @@ class PageTarget {
|
||||
|
||||
const actualSize = this._linkedBrowser.getBoundingClientRect();
|
||||
await this._channel.connect('').send('awaitViewportDimensions', {
|
||||
width: actualSize.width,
|
||||
height: actualSize.height,
|
||||
width: actualSize.width / this._zoom,
|
||||
height: actualSize.height / this._zoom,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -639,7 +642,7 @@ class PageTarget {
|
||||
}
|
||||
|
||||
updateColorSchemeOverride(browsingContext = undefined) {
|
||||
(browsingContext || this._linkedBrowser.browsingContext).prefersColorSchemeOverride = this.colorScheme || 'dark';
|
||||
(browsingContext || this._linkedBrowser.browsingContext).prefersColorSchemeOverride = this.colorScheme || this._browserContext.colorScheme || 'none';
|
||||
}
|
||||
|
||||
setReducedMotion(reducedMotion) {
|
||||
@@ -651,6 +654,15 @@ class PageTarget {
|
||||
(browsingContext || this._linkedBrowser.browsingContext).prefersReducedMotionOverride = this.reducedMotion || this._browserContext.reducedMotion || 'none';
|
||||
}
|
||||
|
||||
setContrast(contrast) {
|
||||
this.contrast = fromProtocolContrast(contrast);
|
||||
this.updateContrastOverride();
|
||||
}
|
||||
|
||||
updateContrastOverride(browsingContext = undefined) {
|
||||
(browsingContext || this._linkedBrowser.browsingContext).prefersContrastOverride = this.contrast || this._browserContext.contrast || 'none';
|
||||
}
|
||||
|
||||
setForcedColors(forcedColors) {
|
||||
this.forcedColors = fromProtocolForcedColors(forcedColors);
|
||||
this.updateForcedColorsOverride();
|
||||
@@ -672,6 +684,14 @@ class PageTarget {
|
||||
await this.updateViewportSize();
|
||||
}
|
||||
|
||||
async setZoom(zoom) {
|
||||
// This is default range from the ZoomManager.
|
||||
if (zoom < 0.3 || zoom > 5)
|
||||
throw new Error('Invalid zoom value, must be between 0.3 and 5');
|
||||
this._zoom = zoom;
|
||||
await this.updateZoom();
|
||||
}
|
||||
|
||||
close(runBeforeUnload = false) {
|
||||
this._gBrowser.removeTab(this._tab, {
|
||||
skipPermitUnload: !runBeforeUnload,
|
||||
@@ -878,6 +898,14 @@ function fromProtocolReducedMotion(reducedMotion) {
|
||||
throw new Error('Unknown reduced motion: ' + reducedMotion);
|
||||
}
|
||||
|
||||
function fromProtocolContrast(contrast) {
|
||||
if (contrast === 'more' || contrast === 'less' || contrast === 'custom' || contrast === 'no-preference')
|
||||
return contrast;
|
||||
if (contrast === null)
|
||||
return undefined;
|
||||
throw new Error('Unknown contrast: ' + contrast);
|
||||
}
|
||||
|
||||
function fromProtocolForcedColors(forcedColors) {
|
||||
if (forcedColors === 'active' || forcedColors === 'none')
|
||||
return forcedColors;
|
||||
@@ -918,6 +946,7 @@ class BrowserContext {
|
||||
this.colorScheme = 'none';
|
||||
this.forcedColors = 'none';
|
||||
this.reducedMotion = 'none';
|
||||
this.contrast = 'none';
|
||||
this.videoRecordingOptions = undefined;
|
||||
this.crossProcessCookie = {
|
||||
initScripts: [],
|
||||
@@ -944,6 +973,12 @@ class BrowserContext {
|
||||
page.updateReducedMotionOverride();
|
||||
}
|
||||
|
||||
setContrast(contrast) {
|
||||
this.contrast = fromProtocolContrast(contrast);
|
||||
for (const page of this.pages)
|
||||
page.updateContrastOverride();
|
||||
}
|
||||
|
||||
setForcedColors(forcedColors) {
|
||||
this.forcedColors = fromProtocolForcedColors(forcedColors);
|
||||
for (const page of this.pages)
|
||||
@@ -988,9 +1023,9 @@ class BrowserContext {
|
||||
if (ignoreHTTPSErrors) {
|
||||
Preferences.set("network.stricttransportsecurity.preloadlist", false);
|
||||
Preferences.set("security.cert_pinning.enforcement_level", 0);
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(true, this.userContextId);
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyDataForUserContext(this.userContextId, true);
|
||||
} else {
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(false, this.userContextId);
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyDataForUserContext(this.userContextId, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,15 +1060,6 @@ class BrowserContext {
|
||||
}
|
||||
|
||||
async setDefaultViewport(viewport) {
|
||||
// Camoufox: only override the set viewport if a new one was passed
|
||||
if (
|
||||
ChromeUtils.camouGetInt("window.innerWidth")
|
||||
|| ChromeUtils.camouGetInt("window.innerHeight")
|
||||
) {
|
||||
if (viewport.viewportSize?.width == 1280 && viewport.viewportSize?.height == 720) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;
|
||||
this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined;
|
||||
await Promise.all(Array.from(this.pages).map(page => page.updateViewportSize()));
|
||||
@@ -1098,7 +1124,8 @@ class BrowserContext {
|
||||
|
||||
setCookies(cookies) {
|
||||
const protocolToSameSite = {
|
||||
[undefined]: Ci.nsICookie.SAMESITE_NONE,
|
||||
[undefined]: Ci.nsICookie.SAMESITE_UNSET,
|
||||
'None': Ci.nsICookie.SAMESITE_UNSET,
|
||||
'Lax': Ci.nsICookie.SAMESITE_LAX,
|
||||
'Strict': Ci.nsICookie.SAMESITE_STRICT,
|
||||
};
|
||||
@@ -1126,7 +1153,7 @@ class BrowserContext {
|
||||
secure,
|
||||
cookie.httpOnly || false,
|
||||
cookie.expires === undefined || cookie.expires === -1 /* isSession */,
|
||||
cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires,
|
||||
cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires * 1000,
|
||||
{ userContextId: this.userContextId || undefined } /* originAttributes */,
|
||||
protocolToSameSite[cookie.sameSite],
|
||||
Ci.nsICookie.SCHEME_UNSET
|
||||
@@ -1141,6 +1168,7 @@ class BrowserContext {
|
||||
getCookies() {
|
||||
const result = [];
|
||||
const sameSiteToProtocol = {
|
||||
[Ci.nsICookie.SAMESITE_UNSET]: 'None',
|
||||
[Ci.nsICookie.SAMESITE_NONE]: 'None',
|
||||
[Ci.nsICookie.SAMESITE_LAX]: 'Lax',
|
||||
[Ci.nsICookie.SAMESITE_STRICT]: 'Strict',
|
||||
@@ -1155,7 +1183,7 @@ class BrowserContext {
|
||||
value: cookie.value,
|
||||
domain: cookie.host,
|
||||
path: cookie.path,
|
||||
expires: cookie.isSession ? -1 : cookie.expiry,
|
||||
expires: cookie.isSession ? -1 : cookie.expiry / 1000,
|
||||
size: cookie.name.length + cookie.value.length,
|
||||
httpOnly: cookie.isHttpOnly,
|
||||
secure: cookie.isSecure,
|
||||
@@ -1267,7 +1295,3 @@ TargetRegistry.Events = {
|
||||
DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'),
|
||||
ScreencastStopped: Symbol('TargetRegistry.ScreencastStopped'),
|
||||
};
|
||||
|
||||
var EXPORTED_SYMBOLS = ['TargetRegistry', 'PageTarget'];
|
||||
this.TargetRegistry = TargetRegistry;
|
||||
this.PageTarget = PageTarget;
|
||||
@@ -158,3 +158,4 @@ const jugglerInstance = new Juggler();
|
||||
export var JugglerFactory = function() {
|
||||
return jugglerInstance;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ Classes = [
|
||||
"constructor": "JugglerFactory",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -7,13 +7,11 @@ 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} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
|
||||
const helper = new Helper();
|
||||
|
||||
class FrameTree {
|
||||
export class FrameTree {
|
||||
constructor(rootBrowsingContext) {
|
||||
helper.decorateAsEventEmitter(this);
|
||||
|
||||
@@ -411,11 +409,7 @@ class Frame {
|
||||
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;
|
||||
|
||||
@@ -509,46 +503,18 @@ class Frame {
|
||||
};
|
||||
}
|
||||
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
_createIsolatedContext(name) {
|
||||
const principal = [this.domWindow()]; // extended principal
|
||||
const sandbox = Cu.Sandbox(principal, {
|
||||
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(), this.domWindow());
|
||||
world.mainEquivalent = mainWorld;
|
||||
}
|
||||
this._worldNameToContext.set(name, world);
|
||||
return world;
|
||||
}
|
||||
@@ -582,11 +548,15 @@ class Frame {
|
||||
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);
|
||||
|
||||
this._worldNameToContext.set('', this._runtime.createExecutionContext(this.domWindow(), this.domWindow(), {
|
||||
frameId: this._frameId,
|
||||
name: '',
|
||||
}));
|
||||
for (const [name, world] of this._frameTree._isolatedWorlds) {
|
||||
if (name)
|
||||
this._createIsolatedContext(name);
|
||||
@@ -718,6 +688,3 @@ function channelId(channel) {
|
||||
}
|
||||
|
||||
|
||||
var EXPORTED_SYMBOLS = ['FrameTree'];
|
||||
this.FrameTree = FrameTree;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const { Helper } = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const { initialize } = ChromeUtils.import('chrome://juggler/content/content/main.js');
|
||||
const { Helper } = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
const { initialize } = ChromeUtils.importESModule('chrome://juggler/content/content/main.js');
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const helper = new Helper();
|
||||
@@ -10,7 +10,7 @@ let sameProcessInstanceNumber = 0;
|
||||
|
||||
const topBrowingContextToAgents = new Map();
|
||||
|
||||
class JugglerFrameChild extends JSWindowActorChild {
|
||||
export class JugglerFrameChild extends JSWindowActorChild {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -83,4 +83,3 @@ class JugglerFrameChild extends JSWindowActorChild {
|
||||
receiveMessage() { }
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['JugglerFrameChild'];
|
||||
|
||||
@@ -8,9 +8,9 @@ const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
|
||||
const {setTimeout} = ChromeUtils.import('resource://gre/modules/Timer.jsm');
|
||||
const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs');
|
||||
const {setTimeout} = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs');
|
||||
|
||||
const dragService = Cc["@mozilla.org/widget/dragservice;1"].getService(
|
||||
Ci.nsIDragService
|
||||
@@ -51,7 +51,7 @@ class WorkerData {
|
||||
}
|
||||
}
|
||||
|
||||
class PageAgent {
|
||||
export class PageAgent {
|
||||
constructor(browserChannel, frameTree) {
|
||||
this._browserChannel = browserChannel;
|
||||
this._browserPage = browserChannel.connect('page');
|
||||
@@ -549,7 +549,7 @@ class PageAgent {
|
||||
false /*aIgnoreRootScrollFrame*/,
|
||||
0.0 /*pressure*/,
|
||||
0 /*inputSource*/,
|
||||
false /*isDOMEventSynthesized*/,
|
||||
true /*isDOMEventSynthesized*/,
|
||||
false /*isWidgetEventSynthesized*/,
|
||||
0 /*buttons*/,
|
||||
win.windowUtils.DEFAULT_MOUSE_POINTER_ID /* pointerIdentifier */,
|
||||
@@ -575,7 +575,7 @@ class PageAgent {
|
||||
// We crash by using js-ctypes and dereferencing
|
||||
// a bad pointer. The crash should happen immediately
|
||||
// upon loading this frame script.
|
||||
const { ctypes } = ChromeUtils.import('resource://gre/modules/ctypes.jsm');
|
||||
const { ctypes } = ChromeUtils.importESModule('resource://gre/modules/ctypes.sys.mjs');
|
||||
ChromeUtils.privateNoteIntentionalCrash();
|
||||
const zero = new ctypes.intptr_t(8);
|
||||
const badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
|
||||
@@ -709,6 +709,3 @@ class PageAgent {
|
||||
}
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['PageAgent'];
|
||||
this.PageAgent = PageAgent;
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
if (!this.Debugger) {
|
||||
// Worker has a Debugger defined already.
|
||||
const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {});
|
||||
addDebuggerToGlobal(Components.utils.getGlobalForObject(this));
|
||||
const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
|
||||
addDebuggerToGlobal(Components.utils.getGlobalForObject(globalThis));
|
||||
}
|
||||
|
||||
let lastId = 0;
|
||||
@@ -99,32 +99,6 @@ class Runtime {
|
||||
const executionContext = this.findExecutionContext(executionContextId);
|
||||
if (!executionContext)
|
||||
throw new Error('Failed to find execution context with id = ' + executionContextId);
|
||||
|
||||
// Hijack the utilityScript.evaluate function to evaluate in the main world
|
||||
if (
|
||||
ChromeUtils.camouGetBool('allowMainWorld', false) &&
|
||||
functionDeclaration.includes('utilityScript.evaluate') &&
|
||||
args.length >= 4 &&
|
||||
args[3].value &&
|
||||
typeof args[3].value === 'string' &&
|
||||
args[3].value.startsWith('mw:')) {
|
||||
ChromeUtils.camouDebug(`Evaluating in main world: ${args[3].value}`);
|
||||
const mainWorldScript = args[3].value.substring(3);
|
||||
|
||||
// Get the main world execution context
|
||||
const mainContext = executionContext.mainEquivalent;
|
||||
if (!mainContext) {
|
||||
throw new Error(`Main world injection is not enabled.`);
|
||||
}
|
||||
// Extract arguments for the main world function
|
||||
const functionArgs = args[5]?.value?.a || [];
|
||||
const exceptionDetails = {};
|
||||
const result = mainContext.executeInGlobal(mainWorldScript, functionArgs, exceptionDetails);
|
||||
if (!result)
|
||||
return {exceptionDetails};
|
||||
return {result};
|
||||
}
|
||||
|
||||
const exceptionDetails = {};
|
||||
let result = await executionContext.evaluateFunction(functionDeclaration, args, exceptionDetails);
|
||||
if (!result)
|
||||
@@ -190,7 +164,7 @@ class Runtime {
|
||||
emitEvent(this.events.onRuntimeError, {
|
||||
executionContext,
|
||||
message: message.errorMessage,
|
||||
stack: message.stack?.toString() || '',
|
||||
stack: message.stack.toString(),
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -308,11 +282,6 @@ class Runtime {
|
||||
return context;
|
||||
}
|
||||
|
||||
createMW(domWindow, contextGlobal) {
|
||||
const context = new MainWorldContext(this, domWindow, contextGlobal);
|
||||
return context;
|
||||
}
|
||||
|
||||
findExecutionContext(executionContextId) {
|
||||
const executionContext = this._executionContexts.get(executionContextId);
|
||||
if (!executionContext)
|
||||
@@ -337,68 +306,6 @@ class Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
class MainWorldContext {
|
||||
constructor(runtime, domWindow, contextGlobal) {
|
||||
this._runtime = runtime;
|
||||
this._domWindow = domWindow;
|
||||
this._contextGlobal = contextGlobal;
|
||||
this._debuggee = runtime._debugger.addDebuggee(contextGlobal);
|
||||
}
|
||||
|
||||
_getResult(completionValue, exceptionDetails = {}) {
|
||||
if (!completionValue) {
|
||||
exceptionDetails.text = "Evaluation terminated";
|
||||
return {success: false, obj: null};
|
||||
}
|
||||
|
||||
if (completionValue.throw) {
|
||||
const result = this._debuggee.executeInGlobalWithBindings(`
|
||||
(function(error) {
|
||||
try {
|
||||
if (error instanceof Error) {
|
||||
return error.toString();
|
||||
}
|
||||
return String(error);
|
||||
} catch(e) {
|
||||
return "Unknown error occurred";
|
||||
}
|
||||
})(e)
|
||||
`, { e: completionValue.throw });
|
||||
|
||||
exceptionDetails.text = result.return || "Unknown error";
|
||||
return {success: false, obj: null};
|
||||
}
|
||||
|
||||
return {success: true, obj: completionValue.return};
|
||||
}
|
||||
|
||||
executeInGlobal(script, args = [], exceptionDetails = {}) {
|
||||
try {
|
||||
const wrappedScript = `
|
||||
(() => {
|
||||
let _s = (${script});
|
||||
let _r = typeof _s === 'function'
|
||||
? _s(${args.map(arg => JSON.stringify(arg)).join(', ')})
|
||||
: _s;
|
||||
return JSON.stringify({value: _r});
|
||||
})()
|
||||
`;
|
||||
|
||||
const result = this._debuggee.executeInGlobal(wrappedScript);
|
||||
|
||||
let {success, obj} = this._getResult(result, exceptionDetails);
|
||||
if (!success) {
|
||||
return {exceptionDetails};
|
||||
}
|
||||
return JSON.parse(obj);
|
||||
} catch (e) {
|
||||
exceptionDetails.text = e.message;
|
||||
exceptionDetails.stack = e.stack;
|
||||
return {exceptionDetails};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExecutionContext {
|
||||
constructor(runtime, domWindow, contextGlobal, auxData) {
|
||||
this._runtime = runtime;
|
||||
@@ -431,8 +338,6 @@ class ExecutionContext {
|
||||
|
||||
return hasSymbol ? undefined : result;
|
||||
}).bind(null, JSON.stringify.bind(JSON))`).return;
|
||||
|
||||
this.mainEquivalent = undefined;
|
||||
}
|
||||
|
||||
id() {
|
||||
@@ -691,5 +596,5 @@ function emitEvent(event, ...args) {
|
||||
listener.call(null, ...args);
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['Runtime'];
|
||||
this.Runtime = Runtime;
|
||||
// Export Runtime to global.
|
||||
globalThis.Runtime = Runtime;
|
||||
|
||||
@@ -2,20 +2,26 @@
|
||||
* 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/. */
|
||||
|
||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js');
|
||||
const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
|
||||
const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js');
|
||||
// Load SimpleChannel and Runtime in content process's global.
|
||||
// NOTE: since these have to exist in both Worker and main threads, and we do
|
||||
// not know a way to load ES Modules in worker threads, we have to use the loadSubScript
|
||||
// utility instead.
|
||||
Services.scriptloader.loadSubScript('chrome://juggler/content/SimpleChannel.js');
|
||||
Services.scriptloader.loadSubScript('chrome://juggler/content/content/Runtime.js');
|
||||
|
||||
const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
const {FrameTree} = ChromeUtils.importESModule('chrome://juggler/content/content/FrameTree.js');
|
||||
const {PageAgent} = ChromeUtils.importESModule('chrome://juggler/content/content/PageAgent.js');
|
||||
|
||||
const helper = new Helper();
|
||||
|
||||
function initialize(browsingContext, docShell) {
|
||||
export function initialize(browsingContext, docShell) {
|
||||
const data = { channel: undefined, pageAgent: undefined, frameTree: undefined, failedToOverrideTimezone: false };
|
||||
|
||||
const applySetting = {
|
||||
geolocation: (geolocation) => {
|
||||
if (geolocation) {
|
||||
docShell.setGeolocationOverride({
|
||||
browsingContext.setGeolocationServiceOverride({
|
||||
coords: {
|
||||
latitude: geolocation.latitude,
|
||||
longitude: geolocation.longitude,
|
||||
@@ -25,14 +31,12 @@ function initialize(browsingContext, docShell) {
|
||||
heading: NaN,
|
||||
speed: NaN,
|
||||
},
|
||||
address: null,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now() + 24 * 60 * 60 * 1000, // Make sure it does not expire for a day.
|
||||
});
|
||||
} else {
|
||||
docShell.setGeolocationOverride(null);
|
||||
browsingContext.setGeolocationServiceOverride();
|
||||
}
|
||||
},
|
||||
|
||||
bypassCSP: (bypassCSP) => {
|
||||
docShell.bypassCSPEnabled = bypassCSP;
|
||||
},
|
||||
@@ -95,16 +99,20 @@ function initialize(browsingContext, docShell) {
|
||||
},
|
||||
|
||||
async awaitViewportDimensions({width, height}) {
|
||||
const win = docShell.domWindow;
|
||||
if (win.innerWidth === width && win.innerHeight === height)
|
||||
return;
|
||||
await new Promise(resolve => {
|
||||
const listener = helper.addEventListener(win, 'resize', () => {
|
||||
if (win.innerWidth === width && win.innerHeight === height) {
|
||||
helper.removeListeners([listener]);
|
||||
const listeners = [];
|
||||
const check = () => {
|
||||
helper.removeListeners(listeners);
|
||||
if (docShell.domWindow.innerWidth === width && docShell.domWindow.innerHeight === height) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
});
|
||||
// Note: "domWindow" listeners are often removed upon navigation, as specced.
|
||||
// To survive viewport changes across navigations, re-install listeners upon commit.
|
||||
listeners.push(helper.addEventListener(docShell.domWindow, 'resize', check));
|
||||
listeners.push(helper.addEventListener(data.frameTree, 'navigationcommitted', check));
|
||||
};
|
||||
check();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -114,6 +122,3 @@ function initialize(browsingContext, docShell) {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['initialize'];
|
||||
this.initialize = initialize;
|
||||
|
||||
@@ -9,6 +9,7 @@ juggler.jar:
|
||||
|
||||
content/Helper.js (Helper.js)
|
||||
content/NetworkObserver.js (NetworkObserver.js)
|
||||
content/ChannelEventSink.sys.mjs (ChannelEventSink.sys.mjs)
|
||||
content/TargetRegistry.js (TargetRegistry.js)
|
||||
content/SimpleChannel.js (SimpleChannel.js)
|
||||
content/JugglerFrameParent.jsm (JugglerFrameParent.jsm)
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
|
||||
const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js");
|
||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {PageHandler} = ChromeUtils.import("chrome://juggler/content/protocol/PageHandler.js");
|
||||
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
const {AddonManager} = ChromeUtils.importESModule("resource://gre/modules/AddonManager.sys.mjs");
|
||||
const {XPIProvider} = ChromeUtils.importESModule("resource://gre/modules/addons/XPIProvider.sys.mjs");
|
||||
const {TargetRegistry} = ChromeUtils.importESModule("chrome://juggler/content/TargetRegistry.js");
|
||||
const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
const {PageHandler} = ChromeUtils.importESModule("chrome://juggler/content/protocol/PageHandler.js");
|
||||
const {AppConstants} = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs");
|
||||
|
||||
const helper = new Helper();
|
||||
|
||||
class BrowserHandler {
|
||||
export class BrowserHandler {
|
||||
constructor(session, dispatcher, targetRegistry, startCompletePromise, onclose) {
|
||||
this._session = session;
|
||||
this._dispatcher = dispatcher;
|
||||
@@ -147,6 +148,10 @@ class BrowserHandler {
|
||||
]);
|
||||
}
|
||||
await this._startCompletePromise;
|
||||
await Promise.all([
|
||||
...XPIProvider.startupPromises,
|
||||
...XPIProvider.enabledAddonsStartupPromises,
|
||||
]);
|
||||
this._onclose();
|
||||
Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
|
||||
}
|
||||
@@ -166,7 +171,9 @@ class BrowserHandler {
|
||||
['Browser.clearCache']() {
|
||||
// Clearing only the context cache does not work: https://bugzilla.mozilla.org/show_bug.cgi?id=1819147
|
||||
Services.cache2.clear();
|
||||
ChromeUtils.clearStyleSheetCache();
|
||||
ChromeUtils.clearResourceCache({
|
||||
types: ["stylesheet"],
|
||||
});
|
||||
}
|
||||
|
||||
['Browser.setHTTPCredentials']({browserContextId, credentials}) {
|
||||
@@ -220,7 +227,7 @@ class BrowserHandler {
|
||||
}
|
||||
|
||||
async ['Browser.setContrast']({browserContextId, contrast}) {
|
||||
return; // TODO: Implement
|
||||
await this._targetRegistry.browserContextForId(browserContextId).setContrast(nullToUndefined(contrast));
|
||||
}
|
||||
|
||||
async ['Browser.setVideoRecordingOptions']({browserContextId, options}) {
|
||||
@@ -313,6 +320,3 @@ async function waitForWindowClosed(browserWindow) {
|
||||
function nullToUndefined(value) {
|
||||
return value === null ? undefined : value;
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['BrowserHandler'];
|
||||
this.BrowserHandler = BrowserHandler;
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
* 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/. */
|
||||
|
||||
const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/protocol/Protocol.js");
|
||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {protocol} = ChromeUtils.importESModule("chrome://juggler/content/protocol/Protocol.js");
|
||||
const {checkScheme} = ChromeUtils.importESModule("chrome://juggler/content/protocol/PrimitiveTypes.js");
|
||||
const {Helper} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
|
||||
const helper = new Helper();
|
||||
// Camoufox: Exclude redundant internal events from logs.
|
||||
const EXCLUDED_DBG = ['Page.navigationStarted', 'Page.frameAttached', 'Runtime.executionContextCreated', 'Runtime.console', 'Page.navigationAborted', 'Page.eventFired'];
|
||||
|
||||
class Dispatcher {
|
||||
export class Dispatcher {
|
||||
/**
|
||||
* @param {Connection} connection
|
||||
*/
|
||||
@@ -46,11 +45,6 @@ class Dispatcher {
|
||||
|
||||
async _dispatch(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (ChromeUtils.isCamouDebug())
|
||||
ChromeUtils.camouDebug(`[${new Date().toLocaleString()}]`
|
||||
+ `\nReceived message: ${safeJsonStringify(data)}`);
|
||||
|
||||
const id = data.id;
|
||||
const sessionId = data.sessionId;
|
||||
delete data.sessionId;
|
||||
@@ -93,13 +87,6 @@ class Dispatcher {
|
||||
|
||||
_emitEvent(sessionId, eventName, params) {
|
||||
const [domain, eName] = eventName.split('.');
|
||||
|
||||
// Camoufox: Log internal events
|
||||
if (ChromeUtils.isCamouDebug() && !EXCLUDED_DBG.includes(eventName) && domain !== 'Network') {
|
||||
ChromeUtils.camouDebug(`[${new Date().toLocaleString()}]`
|
||||
+ `\nInternal event: ${eventName}\nParams: ${JSON.stringify(params, null, 2)}`);
|
||||
}
|
||||
|
||||
const scheme = protocol.domains[domain] ? protocol.domains[domain].events[eName] : null;
|
||||
if (!scheme)
|
||||
throw new Error(`ERROR: event '${eventName}' is not supported`);
|
||||
@@ -146,48 +133,3 @@ class ProtocolSession {
|
||||
return await this._handler[method](params);
|
||||
}
|
||||
}
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['Dispatcher'];
|
||||
this.Dispatcher = Dispatcher;
|
||||
|
||||
|
||||
function formatDate(date) {
|
||||
const pad = (num) => String(num).padStart(2, '0');
|
||||
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
}
|
||||
|
||||
function truncateObject(obj, maxDepth = 8, maxLength = 100) {
|
||||
if (maxDepth < 0) return '[Max Depth Reached]';
|
||||
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return typeof obj === 'string' ? truncateString(obj, maxLength) : obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.slice(0, 10).map(item => truncateObject(item, maxDepth - 1, maxLength));
|
||||
}
|
||||
|
||||
const truncated = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (Object.keys(truncated).length >= 10) {
|
||||
truncated['...'] = '[Truncated]';
|
||||
break;
|
||||
}
|
||||
truncated[key] = truncateObject(value, maxDepth - 1, maxLength);
|
||||
}
|
||||
return truncated;
|
||||
}
|
||||
|
||||
function truncateString(str, maxLength) {
|
||||
if (str.length <= maxLength) return str;
|
||||
ChromeUtils.camouDebug(`String length: ${str.length}`);
|
||||
return str.substr(0, maxLength) + '... [truncated]';
|
||||
}
|
||||
|
||||
function safeJsonStringify(data) {
|
||||
try {
|
||||
return JSON.stringify(truncateObject(data), null, 2);
|
||||
} catch (error) {
|
||||
return `[Unable to stringify: ${error.message}]`;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
/* 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/. */
|
||||
* 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 {Helper, EventWatcher} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||
const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
|
||||
const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js');
|
||||
const {PageTarget} = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js');
|
||||
const {setTimeout} = ChromeUtils.import('resource://gre/modules/Timer.jsm');
|
||||
const {Helper, EventWatcher} = ChromeUtils.importESModule('chrome://juggler/content/Helper.js');
|
||||
const {NetUtil} = ChromeUtils.importESModule('resource://gre/modules/NetUtil.sys.mjs');
|
||||
const {NetworkObserver, PageNetwork} = ChromeUtils.importESModule('chrome://juggler/content/NetworkObserver.js');
|
||||
const {PageTarget} = ChromeUtils.importESModule('chrome://juggler/content/TargetRegistry.js');
|
||||
const {setTimeout} = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs');
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
@@ -65,7 +65,7 @@ class WorkerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class PageHandler {
|
||||
export class PageHandler {
|
||||
constructor(target, session, contentChannel) {
|
||||
this._session = session;
|
||||
this._contentChannel = contentChannel;
|
||||
@@ -80,17 +80,7 @@ class PageHandler {
|
||||
}
|
||||
|
||||
this._isDragging = false;
|
||||
|
||||
// Camoufox: set a random default cursor position
|
||||
let random_val = (max_val) => Math.floor(Math.random() * max_val);
|
||||
|
||||
// Try to fetch the viewport size
|
||||
this._defaultCursorPos = {
|
||||
x: random_val(this._pageTarget._viewportSize?.width || 1280),
|
||||
y: random_val(this._pageTarget._viewportSize?.height || 720),
|
||||
};
|
||||
this._lastMousePosition = { ...this._defaultCursorPos };
|
||||
this._lastTrackedPos = { ...this._defaultCursorPos };
|
||||
this._lastMousePosition = { x: 0, y: 0 };
|
||||
|
||||
this._reportedFrameIds = new Set();
|
||||
this._networkEventsForUnreportedFrameIds = new Map();
|
||||
@@ -250,6 +240,10 @@ class PageHandler {
|
||||
await this._pageTarget.setViewportSize(viewportSize === null ? undefined : viewportSize);
|
||||
}
|
||||
|
||||
async ['Page.setZoom']({zoom}) {
|
||||
await this._pageTarget.setZoom(zoom);
|
||||
}
|
||||
|
||||
async ['Runtime.evaluate'](options) {
|
||||
return await this._contentPage.send('evaluate', options);
|
||||
}
|
||||
@@ -308,10 +302,11 @@ class PageHandler {
|
||||
return await this._contentPage.send('setFileInputFiles', options);
|
||||
}
|
||||
|
||||
async ['Page.setEmulatedMedia']({colorScheme, type, reducedMotion, forcedColors}) {
|
||||
async ['Page.setEmulatedMedia']({colorScheme, type, reducedMotion, forcedColors, contrast}) {
|
||||
this._pageTarget.setColorScheme(colorScheme || null);
|
||||
this._pageTarget.setReducedMotion(reducedMotion || null);
|
||||
this._pageTarget.setForcedColors(forcedColors || null);
|
||||
this._pageTarget.setContrast(contrast || null);
|
||||
this._pageTarget.setEmulatedMedia(type);
|
||||
}
|
||||
|
||||
@@ -432,14 +427,6 @@ class PageHandler {
|
||||
});
|
||||
unsubscribe();
|
||||
|
||||
if (ChromeUtils.camouGetBool('memorysaver', false)) {
|
||||
ChromeUtils.camouDebug('Clearing all memory...');
|
||||
Services.obs.notifyObservers(null, "child-gc-request");
|
||||
Cu.forceGC();
|
||||
Services.obs.notifyObservers(null, "child-cc-request");
|
||||
Cu.forceCC();
|
||||
}
|
||||
|
||||
return {
|
||||
navigationId: sameDocumentNavigation ? null : navigationId,
|
||||
};
|
||||
@@ -518,43 +505,28 @@ class PageHandler {
|
||||
await helper.awaitTopic('apz-repaints-flushed');
|
||||
|
||||
const watcher = new EventWatcher(this._pageEventSink, types, this._pendingEventWatchers);
|
||||
const sendMouseEvent = async (eventType, eventX, eventY) => {
|
||||
const promises = [];
|
||||
for (const type of types) {
|
||||
// This dispatches to the renderer synchronously.
|
||||
const jugglerEventId = win.windowUtils.jugglerSendMouseEvent(
|
||||
eventType,
|
||||
eventX + boundingBox.left,
|
||||
eventY + boundingBox.top,
|
||||
type,
|
||||
x + boundingBox.left,
|
||||
y + boundingBox.top,
|
||||
button,
|
||||
clickCount,
|
||||
modifiers,
|
||||
false /* aIgnoreRootScrollFrame */,
|
||||
0.0 /* pressure */,
|
||||
0 /* inputSource */,
|
||||
false /* isDOMEventSynthesized */,
|
||||
true /* isDOMEventSynthesized */,
|
||||
false /* isWidgetEventSynthesized */,
|
||||
buttons,
|
||||
win.windowUtils.DEFAULT_MOUSE_POINTER_ID /* pointerIdentifier */,
|
||||
false /* disablePointerEvent */
|
||||
);
|
||||
await watcher.ensureEvent(eventType, eventObject => eventObject.jugglerEventId === jugglerEventId);
|
||||
};
|
||||
for (const type of types) {
|
||||
if (type === 'mousemove' && ChromeUtils.camouGetBool('humanize', false)) {
|
||||
let trajectory = ChromeUtils.camouGetMouseTrajectory(this._lastTrackedPos.x, this._lastTrackedPos.y, x, y);
|
||||
for (let i = 2; i < trajectory.length - 2; i += 2) {
|
||||
let currentX = trajectory[i];
|
||||
let currentY = trajectory[i + 1];
|
||||
// Skip movement that is out of bounds
|
||||
if (currentX < 0 || currentY < 0 || currentX > boundingBox.width || currentY > boundingBox.height) {
|
||||
continue;
|
||||
}
|
||||
await sendMouseEvent(type, currentX, currentY);
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
} else {
|
||||
// Call the function for the current event
|
||||
await sendMouseEvent(type, x, y);
|
||||
}
|
||||
promises.push(watcher.ensureEvent(type, eventObject => eventObject.jugglerEventId === jugglerEventId));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
await watcher.dispose();
|
||||
};
|
||||
|
||||
@@ -575,15 +547,15 @@ class PageHandler {
|
||||
// NOTE: since this won't go inside the renderer, there's no need to wait for ACK.
|
||||
win.windowUtils.sendMouseEvent(
|
||||
'mousemove',
|
||||
this._defaultCursorPos.x,
|
||||
this._defaultCursorPos.y,
|
||||
0 /* x */,
|
||||
0 /* y */,
|
||||
button,
|
||||
clickCount,
|
||||
modifiers,
|
||||
false /* aIgnoreRootScrollFrame */,
|
||||
0.0 /* pressure */,
|
||||
0 /* inputSource */,
|
||||
false /* isDOMEventSynthesized */,
|
||||
true /* isDOMEventSynthesized */,
|
||||
false /* isWidgetEventSynthesized */,
|
||||
buttons,
|
||||
win.windowUtils.DEFAULT_MOUSE_POINTER_ID /* pointerIdentifier */,
|
||||
@@ -612,7 +584,6 @@ class PageHandler {
|
||||
|
||||
const watcher = new EventWatcher(this._pageEventSink, ['dragstart', 'juggler-drag-finalized'], this._pendingEventWatchers);
|
||||
await sendEvents(['mousemove']);
|
||||
this._lastTrackedPos = { x, y };
|
||||
|
||||
// The order of events after 'mousemove' is sent:
|
||||
// 1. [dragstart] - might or might NOT be emitted
|
||||
@@ -720,6 +691,3 @@ class PageHandler {
|
||||
return await worker.sendMessage(JSON.parse(message));
|
||||
}
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ['PageHandler'];
|
||||
this.PageHandler = PageHandler;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
const t = {};
|
||||
export const t = {};
|
||||
|
||||
t.String = function(x, details = {}, path = ['<root>']) {
|
||||
if (typeof x === 'string' || typeof x === 'String')
|
||||
@@ -96,7 +96,7 @@ function beauty(path, obj) {
|
||||
return `property "${path.join('.')}" - ${JSON.stringify(obj, null, 2)}`;
|
||||
}
|
||||
|
||||
function checkScheme(scheme, x, details = {}, path = ['<root>']) {
|
||||
export function checkScheme(scheme, x, details = {}, path = ['<root>']) {
|
||||
if (!scheme)
|
||||
throw new Error(`ILLDEFINED SCHEME: ${path.join('.')}`);
|
||||
if (typeof scheme === 'object') {
|
||||
@@ -142,6 +142,3 @@ test(t.Either(t.String, t.Number), {});
|
||||
|
||||
*/
|
||||
|
||||
this.t = t;
|
||||
this.checkScheme = checkScheme;
|
||||
this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js');
|
||||
const {t} = ChromeUtils.importESModule('chrome://juggler/content/protocol/PrimitiveTypes.js');
|
||||
|
||||
// Protocol-specific types.
|
||||
const browserTypes = {};
|
||||
@@ -800,6 +800,11 @@ const Page = {
|
||||
viewportSize: t.Nullable(pageTypes.Size),
|
||||
},
|
||||
},
|
||||
'setZoom': {
|
||||
params: {
|
||||
zoom: t.Number,
|
||||
},
|
||||
},
|
||||
'bringToFront': {
|
||||
params: {
|
||||
},
|
||||
@@ -810,6 +815,7 @@ const Page = {
|
||||
colorScheme: t.Optional(t.Enum(['dark', 'light', 'no-preference'])),
|
||||
reducedMotion: t.Optional(t.Enum(['reduce', 'no-preference'])),
|
||||
forcedColors: t.Optional(t.Enum(['active', 'none'])),
|
||||
contrast: t.Optional(t.Enum(['less', 'more', 'custom', 'no-preference'])),
|
||||
},
|
||||
},
|
||||
'setCacheDisabled': {
|
||||
@@ -1012,8 +1018,6 @@ const Accessibility = {
|
||||
}
|
||||
}
|
||||
|
||||
this.protocol = {
|
||||
export const protocol = {
|
||||
domains: {Browser, Heap, Page, Runtime, Network, Accessibility},
|
||||
};
|
||||
this.checkScheme = checkScheme;
|
||||
this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme'];
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "nsScreencastService.h"
|
||||
|
||||
#include "gfxPlatform.h"
|
||||
#include "ScreencastEncoder.h"
|
||||
#include "HeadlessWidget.h"
|
||||
#include "HeadlessWindowCapturer.h"
|
||||
@@ -343,10 +344,17 @@ nsresult nsScreencastService::StartVideoRecording(nsIScreencastServiceClient* aC
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
gfx::IntMargin margin;
|
||||
auto bounds = widget->GetScreenBounds().ToUnknownRect();
|
||||
// Screen bounds is the widget location on screen.
|
||||
auto screenBounds = widget->GetScreenBounds().ToUnknownRect();
|
||||
// Client bounds is the content location, in terms of parent widget.
|
||||
// To use it, we need to translate it to screen coordinates first.
|
||||
auto clientBounds = widget->GetClientBounds().ToUnknownRect();
|
||||
for (auto parent = widget->GetParent(); parent != nullptr; parent = parent->GetParent()) {
|
||||
auto pb = parent->GetClientBounds().ToUnknownRect();
|
||||
clientBounds.MoveBy(pb.X(), pb.Y());
|
||||
}
|
||||
// Crop the image to exclude frame (if any).
|
||||
margin = bounds - clientBounds;
|
||||
margin = screenBounds - clientBounds;
|
||||
// Crop the image to exclude controls.
|
||||
margin.top += offsetTop;
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# Playwright Maintenance Guide
|
||||
|
||||
This document describes how to maintain Playwright integration in Camoufox.
|
||||
|
||||
## Overview
|
||||
|
||||
Camoufox integrates Playwright's browser automation capabilities through patches and additional files. These need to be kept in sync with upstream Playwright development.
|
||||
|
||||
## Patch Files
|
||||
|
||||
Location: `patches/playwright/`
|
||||
|
||||
| File | Purpose |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `0-playwright.patch` | Playwright's upstream patches. Must be kept up to date with [bootstrap.diff](https://github.com/microsoft/playwright/blob/main/browser_patches/firefox/patches/bootstrap.diff) |
|
||||
| `1-leak-fixes.patch` | Undos certain patches from `0-playwright.patch` to fix memory leaks |
|
||||
|
||||
## Addition Files
|
||||
|
||||
Location: `additions/juggler/`
|
||||
|
||||
The `juggler` directory contains Playwright's Juggler protocol implementation. These files must be kept in sync with:
|
||||
|
||||
**Upstream Source:** https://github.com/microsoft/playwright/tree/main/browser_patches/firefox/juggler
|
||||
|
||||
### Key Files
|
||||
|
||||
- **`components/Juggler.js`** - Main Juggler component (legacy JSM format)
|
||||
- **`components/Juggler.sys.mjs`** - ESM wrapper for Firefox 146+ compatibility
|
||||
- **`components/components.conf`** - XPCOM component registration
|
||||
|
||||
### Firefox 146 ESM Migration
|
||||
|
||||
Firefox 146 removed JSM (JavaScript Module) support in favor of ESM (ES Modules). To maintain compatibility:
|
||||
|
||||
1. **`components.conf`** uses `esModule` field instead of deprecated `jsm` field:
|
||||
```python
|
||||
{
|
||||
"esModule": "chrome://juggler/content/components/Juggler.sys.mjs",
|
||||
"constructor": "JugglerFactory",
|
||||
}
|
||||
```
|
||||
|
||||
2. **`Juggler.sys.mjs`** acts as an ESM wrapper that imports the legacy JSM file:
|
||||
```javascript
|
||||
const { JugglerFactory } = ChromeUtils.import(
|
||||
"chrome://juggler/content/components/Juggler.js"
|
||||
);
|
||||
export { JugglerFactory };
|
||||
```
|
||||
|
||||
This maintains backward compatibility while satisfying Firefox 146's static component generator requirements.
|
||||
|
||||
## Updating Playwright Integration
|
||||
|
||||
### 1. Update Upstream Patches
|
||||
|
||||
Compare the current `patches/playwright/0-playwright.patch` with Playwright's [bootstrap.diff](https://github.com/microsoft/playwright/blob/main/browser_patches/firefox/patches/bootstrap.diff).
|
||||
|
||||
If changes are needed:
|
||||
```bash
|
||||
# Download latest bootstrap.diff
|
||||
curl -o patches/playwright/0-playwright.patch \
|
||||
https://raw.githubusercontent.com/microsoft/playwright/main/browser_patches/firefox/patches/bootstrap.diff
|
||||
|
||||
# Test the build
|
||||
make clean && make dir && make build
|
||||
```
|
||||
|
||||
### 2. Update Juggler Files
|
||||
|
||||
Sync `additions/juggler/` with upstream:
|
||||
|
||||
```bash
|
||||
# Clone Playwright repository
|
||||
git clone https://github.com/microsoft/playwright.git /tmp/playwright
|
||||
|
||||
# Compare directories
|
||||
diff -r additions/juggler/ /tmp/playwright/browser_patches/firefox/juggler/
|
||||
|
||||
# Copy updated files (example)
|
||||
cp -r /tmp/playwright/browser_patches/firefox/juggler/* additions/juggler/
|
||||
|
||||
# IMPORTANT: Preserve Firefox 146 ESM compatibility
|
||||
# - Keep additions/juggler/components/Juggler.sys.mjs
|
||||
# - Keep additions/juggler/components/components.conf with esModule field
|
||||
```
|
||||
|
||||
### 3. Verify ESM Wrapper Compatibility
|
||||
|
||||
After updating from upstream, ensure the ESM wrapper remains functional:
|
||||
|
||||
1. Check that `Juggler.js` still exports `JugglerFactory`:
|
||||
```javascript
|
||||
var EXPORTED_SYMBOLS = ["Juggler", "JugglerFactory"];
|
||||
var JugglerFactory = function() { /* ... */ };
|
||||
```
|
||||
|
||||
2. If upstream changed the export name, update `Juggler.sys.mjs` accordingly.
|
||||
|
||||
3. Verify `components.conf` matches the format above (not upstream's format).
|
||||
|
||||
### 4. Test Build
|
||||
|
||||
```bash
|
||||
# Clean build to verify component registration
|
||||
cd camoufox-146.0.1-beta.25
|
||||
make clean
|
||||
cd ..
|
||||
make dir
|
||||
cd camoufox-146.0.1-beta.25
|
||||
./mach build
|
||||
```
|
||||
|
||||
**Expected:** No linker errors about `mozCreateComponent<nsICommandLineHandler>`.
|
||||
|
||||
**Common Error:** If you see:
|
||||
```
|
||||
ld64.lld: error: undefined symbol: already_AddRefed<nsISupports> mozCreateComponent<nsICommandLineHandler>()
|
||||
```
|
||||
|
||||
This means `components.conf` is not using ESM format. Fix by ensuring it has:
|
||||
- `"esModule"` field (not `"jsm"`)
|
||||
- `"constructor": "JugglerFactory"` field (not `"type"`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Component Registration Errors
|
||||
|
||||
**Error:** `Externally-constructed components may not specify 'constructor' or 'legacy_constructor' properties`
|
||||
|
||||
**Cause:** Using `"jsm"` field which is unsupported in Firefox 146.
|
||||
|
||||
**Fix:** Use `"esModule"` field instead.
|
||||
|
||||
---
|
||||
|
||||
**Error:** `Externally-constructed components must specify a type other than nsISupports`
|
||||
|
||||
**Cause:** Using external component without proper type specification.
|
||||
|
||||
**Fix:** Convert to ESM component with constructor.
|
||||
|
||||
---
|
||||
|
||||
**Error:** `JavaScript components must specify a constructor`
|
||||
|
||||
**Cause:** ESM component missing constructor field.
|
||||
|
||||
**Fix:** Add `"constructor": "JugglerFactory"` to components.conf.
|
||||
|
||||
### Build Failures
|
||||
|
||||
If the build fails after updating Juggler files:
|
||||
|
||||
1. Check that all JSM imports in `Juggler.js` are still valid
|
||||
2. Verify the ESM wrapper exports match what's imported
|
||||
3. Ensure no file paths changed in upstream
|
||||
4. Check for Firefox API changes that might require patches
|
||||
|
||||
## References
|
||||
|
||||
- [Playwright Firefox Patches](https://github.com/microsoft/playwright/tree/main/browser_patches/firefox)
|
||||
- [Firefox 146 Component Registration](https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/basics.html)
|
||||
- [Firefox ESM Migration Guide](https://firefox-source-docs.mozilla.org/dom/script_loader/index.html)
|
||||
Reference in New Issue
Block a user