From 021b2f895dfd4f60df86bf61027a026542f800fd Mon Sep 17 00:00:00 2001 From: daijro Date: Wed, 11 Sep 2024 04:06:05 -0500 Subject: [PATCH] Juggler: Add logging & fix frame execution issues #3 #6 - Add back frame execution contexts. Fixes leaks by using an isolated context. #3 #6 - Fixed other small errors in Juggler (viewport size & error message stack) - Add debugging functionality to Juggler - Add launcher argument to write stderr to a log file - Bumped to v130.0-beta.5 --- .gitignore | 1 + Makefile | 2 + additions/juggler/TargetRegistry.js | 3 +- additions/juggler/components/Juggler.js | 5 ++ additions/juggler/content/FrameTree.js | 14 ++---- additions/juggler/content/Runtime.js | 2 +- additions/juggler/protocol/Dispatcher.js | 55 +++++++++++++++++++++ launcher/exec.go | 14 +++++- launcher/main.go | 17 ++++++- patches/debug.patch | 63 ++++++++++++++++++++++++ settings/camoufox.cfg | 3 +- upstream.sh | 2 +- 12 files changed, 164 insertions(+), 17 deletions(-) create mode 100644 patches/debug.patch diff --git a/.gitignore b/.gitignore index 87197cd..d89faf4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ __pycache__/ *.pyc wget-log *.kate-swp +*.log diff --git a/Makefile b/Makefile index b57a3f1..16033cf 100644 --- a/Makefile +++ b/Makefile @@ -140,11 +140,13 @@ package-windows: --fonts macos linux run-launcher: + rm -rf $(cf_source_dir)/obj-x86_64-pc-linux-gnu/dist/bin/launch; make build-launcher arch=x86_64 os=linux; cp launcher/dist/launch $(cf_source_dir)/obj-x86_64-pc-linux-gnu/dist/bin/launch; $(cf_source_dir)/obj-x86_64-pc-linux-gnu/dist/bin/launch run-pw: + rm -rf $(cf_source_dir)/obj-x86_64-pc-linux-gnu/dist/bin/launch; make build-launcher arch=x86_64 os=linux; python3 scripts/run-pw.py \ --version $(version) \ diff --git a/additions/juggler/TargetRegistry.js b/additions/juggler/TargetRegistry.js index 4ed4189..64da225 100644 --- a/additions/juggler/TargetRegistry.js +++ b/additions/juggler/TargetRegistry.js @@ -571,6 +571,8 @@ class PageTarget { // default viewport. // Do not allow default viewport size if Camoufox set it first + const viewportSize = this._viewportSize || this._browserContext.defaultViewportSize; + if ( !viewportSize && this._browserContext.defaultViewportSize && ( @@ -582,7 +584,6 @@ class PageTarget { return; } - const viewportSize = this._viewportSize || this._browserContext.defaultViewportSize; if (viewportSize) { const {width, height} = viewportSize; this._linkedBrowser.style.setProperty('width', width + 'px'); diff --git a/additions/juggler/components/Juggler.js b/additions/juggler/components/Juggler.js index d919935..acc38b2 100644 --- a/additions/juggler/components/Juggler.js +++ b/additions/juggler/components/Juggler.js @@ -131,15 +131,20 @@ class Juggler { }, }; pipe.init(connection); + ChromeUtils.camouDebug('Juggler pipe initialized'); const dispatcher = new Dispatcher(connection); + ChromeUtils.camouDebug('Dispatcher created'); browserHandler = new BrowserHandler(dispatcher.rootSession(), dispatcher, targetRegistry, browserStartupFinishedPromise, () => { + ChromeUtils.camouDebug('BrowserHandler cleanup callback called'); if (this._silent) Services.startup.exitLastWindowClosingSurvivalArea(); connection.onclose(); pipe.stop(); pipeStopped = true; }); + ChromeUtils.camouDebug('BrowserHandler created'); dispatcher.rootSession().setHandler(browserHandler); + ChromeUtils.camouDebug('BrowserHandler set as root session handler'); loadStyleSheet(); dump(`\nJuggler listening to the pipe\n`); break; diff --git a/additions/juggler/content/FrameTree.js b/additions/juggler/content/FrameTree.js index 939cbec..c83844e 100644 --- a/additions/juggler/content/FrameTree.js +++ b/additions/juggler/content/FrameTree.js @@ -564,15 +564,11 @@ class Frame { webSocketService.removeListener(this._webSocketListenerInnerWindowId, this._webSocketListener); this._webSocketListenerInnerWindowId = this.domWindow().windowGlobalChild.innerWindowId; webSocketService.addListener(this._webSocketListenerInnerWindowId, this._webSocketListener); - // Camoufox: Causes leaks. - // for (const context of this._worldNameToContext.values()) - // this._runtime.destroyExecutionContext(context); - // this._worldNameToContext.clear(); - - // this._worldNameToContext.set('', this._runtime.createExecutionContext(this.domWindow(), this.domWindow(), { - // frameId: this._frameId, - // name: '', - // })); + 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(''); for (const [name, world] of this._frameTree._isolatedWorlds) { if (name) this._createIsolatedContext(name); diff --git a/additions/juggler/content/Runtime.js b/additions/juggler/content/Runtime.js index a5fa7a0..89fdc7f 100644 --- a/additions/juggler/content/Runtime.js +++ b/additions/juggler/content/Runtime.js @@ -164,7 +164,7 @@ class Runtime { emitEvent(this.events.onRuntimeError, { executionContext, message: message.errorMessage, - stack: message.stack.toString(), + stack: message.stack?.toString() || '', }); } }, diff --git a/additions/juggler/protocol/Dispatcher.js b/additions/juggler/protocol/Dispatcher.js index 8542461..cf24b89 100644 --- a/additions/juggler/protocol/Dispatcher.js +++ b/additions/juggler/protocol/Dispatcher.js @@ -6,6 +6,8 @@ const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/pro const {Helper} = ChromeUtils.import('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 { /** @@ -44,6 +46,11 @@ 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; @@ -86,6 +93,13 @@ 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`); @@ -136,3 +150,44 @@ class ProtocolSession { 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}]`; + } +} \ No newline at end of file diff --git a/launcher/exec.go b/launcher/exec.go index 95fa6c1..9fde13c 100644 --- a/launcher/exec.go +++ b/launcher/exec.go @@ -67,7 +67,7 @@ func filterOutput(r io.Reader, w io.Writer) { } // Run Camoufox -func runCamoufox(execName string, args []string, addonsList []string) { +func runCamoufox(execName string, args []string, addonsList []string, stderrPath string) { // If addons are specified, get the debug port var debugPortInt int if len(addonsList) > 0 { @@ -130,8 +130,18 @@ func runCamoufox(execName string, args []string, addonsList []string) { done <- true }() go func() { + // If stderrPath is not empty, write to the file + fmt.Printf("Setting stderr to file: %s\n", stderrPath) + if stderrPath != "" { + file, err := os.Create(stderrPath) + if err != nil { + fmt.Printf("Error creating stderr file: %v\n", err) + os.Exit(1) + } + defer file.Close() + filterOutput(stderr, file) + } filterOutput(stderr, os.Stderr) - done <- true }() <-done diff --git a/launcher/main.go b/launcher/main.go index 8b34fd4..0851cb6 100644 --- a/launcher/main.go +++ b/launcher/main.go @@ -18,6 +18,7 @@ func main() { configPath := parseArgs("--config", "{}", &args, true) addons := parseArgs("--addons", "[]", &args, true) excludeAddons := parseArgs("--exclude-addons", "[]", &args, true) + stderrPath := parseArgs("--stderr", "", &args, true) //*** PARSE CONFIG ***// @@ -26,6 +27,11 @@ func main() { parseJson(configPath, &configMap) validateConfig(configMap) + // Add "debug: True" to the config + if stderrPath != "" { + configMap["debug"] = true + } + //*** PARSE ADDONS ***// // If addons are passed, handle them @@ -58,7 +64,7 @@ func main() { fmt.Printf("Error setting executable permissions: %v\n", err) os.Exit(1) } - runCamoufox(execName, args, addonsList) + runCamoufox(execName, args, addonsList, stderrPath) } // Returns the absolute path relative to the launcher @@ -93,13 +99,20 @@ func parseArgs(param string, defaultValue string, args *[]string, removeFromArgs return defaultValue } +// fileExists checks if a file exists +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + // Parses a JSON string or file into a map func parseJson(argv string, target interface{}) { // Unmarshal the config input into a map var data []byte + var err error // Check if the input is a file path or inline JSON - if _, err := os.Stat(argv); err == nil { + if fileExists(argv) { data, err = os.ReadFile(argv) if err != nil { fmt.Printf("Error reading config file: %v\n", err) diff --git a/patches/debug.patch b/patches/debug.patch new file mode 100644 index 0000000..71f2890 --- /dev/null +++ b/patches/debug.patch @@ -0,0 +1,63 @@ +diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp +index 71d897a0d0..2234834f3e 100644 +--- a/dom/base/ChromeUtils.cpp ++++ b/dom/base/ChromeUtils.cpp +@@ -2069,6 +2069,25 @@ bool ChromeUtils::IsDarkBackground(GlobalObject&, Element& aElement) { + return nsNativeTheme::IsDarkBackground(f); + } + ++/* static */ ++void ChromeUtils::CamouDebug(GlobalObject& aGlobal, ++ const nsAString& aVarName) { ++ if (auto value = MaskConfig::GetBool("debug"); ++ value.has_value() && !value.value()) { ++ return; ++ } ++ NS_ConvertUTF16toUTF8 utf8VarName(aVarName); ++ printf_stderr("DEBUG: %s\n", utf8VarName.get()); ++} ++ ++bool ChromeUtils::IsCamouDebug(GlobalObject& aGlobal) { ++ if (auto value = MaskConfig::GetBool("debug"); ++ value.has_value() && value.value()) { ++ return true; ++ } ++ return false; ++} ++ + double ChromeUtils::DateNow(GlobalObject&) { return JS_Now() / 1000.0; } + + /* static */ +diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h +index 42b74131d6..9f151ca2e7 100644 +--- a/dom/base/ChromeUtils.h ++++ b/dom/base/ChromeUtils.h +@@ -301,6 +301,10 @@ class ChromeUtils { + + static bool IsDarkBackground(GlobalObject&, Element&); + ++ static void CamouDebug(GlobalObject& aGlobal, const nsAString& aVarName); ++ ++ static bool IsCamouDebug(GlobalObject& aGlobal); ++ + static double DateNow(GlobalObject&); + + static void EnsureJSOracleStarted(GlobalObject&); +diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl +index f761be86a4..c6409bd56e 100644 +--- a/dom/chrome-webidl/ChromeUtils.webidl ++++ b/dom/chrome-webidl/ChromeUtils.webidl +@@ -746,6 +746,13 @@ partial namespace ChromeUtils { + */ + boolean isDarkBackground(Element element); + ++ /** ++ * Camoufox debug commands ++ */ ++ undefined camouDebug(DOMString varName); ++ ++ boolean isCamouDebug(); ++ + /** + * Starts the JSOracle process for ORB JavaScript validation, if it hasn't started already. + */ diff --git a/settings/camoufox.cfg b/settings/camoufox.cfg index ee995e1..76593b8 100644 --- a/settings/camoufox.cfg +++ b/settings/camoufox.cfg @@ -19,6 +19,7 @@ pref("media.peerconnection.ice.no_host", true); // Force enable content isolation (WAFs can detect this!) pref("fission.autostart", true); +pref("fission.webContentIsolationStrategy", 2); // Use dark theme by default pref("ui.systemUsesDarkTheme", 1); @@ -450,7 +451,7 @@ pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true); pref("network.auth.use_redirect_for_retries", false); // Disable cross-process iframes, but not cross-process navigations. -pref("fission.webContentIsolationStrategy", 0); +// pref("fission.webContentIsolationStrategy", 0); // Disable BFCache in parent process. // We also separately disable BFCache in content via docSchell property. diff --git a/upstream.sh b/upstream.sh index 5bd295f..689bf07 100644 --- a/upstream.sh +++ b/upstream.sh @@ -1,2 +1,2 @@ version=130.0 -release=beta.4 +release=beta.5