Skip to content

Add a script injector for debugging#3

Open
incomplete-tree wants to merge 8 commits intoJvr2022:mainfrom
incomplete-tree:injector
Open

Add a script injector for debugging#3
incomplete-tree wants to merge 8 commits intoJvr2022:mainfrom
incomplete-tree:injector

Conversation

@incomplete-tree
Copy link
Copy Markdown
Contributor

@incomplete-tree incomplete-tree commented Mar 19, 2026

Summary

Add a script injector for debugging

What Changed

Added a -i/--inject flag for injecting a javascript script that will run on each page loaded in order to more easily debug problems in the internal web engine.

Verification

Builds on my machine and correctly injects script

Summary by CodeRabbit

  • New Features

    • Add a command-line option to inject a local JavaScript file into pages (path resolved; warns on load failure).
    • Automatically append the SEB version to the browser user agent, with improved detection of the embedded Chrome version.
  • Chores

    • Build script now accepts an explicit mode (debug/release) and prints the selected mode on completion.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

Adds a CLI --inject/-i option and BrowserSettings::injectedScript; when present (QT_DEBUG) SebSession reads that file and registers it as a QWebEngineScript at DocumentReady, and user-agent building now appends a cached SEB version fetched from GitHub releases/latest.

Changes

Cohort / File(s) Summary
Command-line Option Wiring
src/main.cpp
Register --inject/-i option (requires file) under QT_DEBUG; parse it like --url/-u, resolve to absolute path and store into settings.browser.injectedScript.
Settings Structure
src/seb_settings.h
Added QString injectedScript; to seb::BrowserSettings.
Session init, script injection & UA
src/seb_session.cpp
Added getCachedSebVersion() (sync GET to GitHub releases/latest, parse tag_name, cached fallback); on QT_DEBUG, load injectedScript file and register it as QWebEngineScript (DocumentReady, include sub-frames); revised buildUserAgent() to conditionally use custom UA, derive Chrome version fallback, and append SEB/<cached-version>.
Build script
scripts/build.sh
Parse -r/--release to set CONFIG (default debug) and pass CONFIG+=... to qmake6; print selected mode in final message.

Sequence Diagram

sequenceDiagram
    participant User
    participant Main as main.cpp
    participant Settings as BrowserSettings
    participant Session as SebSession
    participant QWE as QWebEngineProfile
    participant GitHub as GitHub API

    User->>Main: start with --inject /path/script.js
    Main->>Main: resolve absolute path
    Main->>Settings: set injectedScript
    Session->>Settings: read injectedScript
    Session->>Session: open & read script file
    Session->>QWE: register QWebEngineScript "InjectScript" (DocumentReady, include sub-frames)
    Session->>GitHub: GET /repos/.../releases/latest
    GitHub-->>Session: JSON { "tag_name": "vX.Y.Z" }
    Session->>Session: parse & cache version
    Session->>Session: build user-agent and append "SEB/<cached-version>"
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Fix user agents #2 — Implements the same CLI option, settings field, session injection, and UA version fetch; strongly related.

Suggested reviewers

  • Jvr2022

Poem

🐇
I found a tiny script to bring,
Hopping into pages at DocumentReady's ring,
A cached tag stitched on the UA's tail,
Quietly injected, a subtle trail —
🥕 small hops, big smiles.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main feature being added: a script injector for debugging purposes, which matches the core changeset additions.
Description check ✅ Passed The description covers all required template sections with appropriate detail: Summary identifies the feature, What Changed explains the flag and functionality, and Verification confirms testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.cpp`:
- Around line 90-92: findConfigPath() must be updated to recognize and skip the
new value-taking option(s) so applyEarlyEnvironment() won't misinterpret
injectedScript file paths as the early config/resource; update findConfigPath()
to treat the -i/--inject (and any other newly added options that accept a value)
exactly like -c/--config (skip the next token), and mirror the same change where
a second parsing branch exists (the code around settings.browser.injectedScript
and the duplicate logic at the 142-145 area) so both early and later parsing
paths stay in sync with value-taking options.

In `@src/seb_session.cpp`:
- Around line 123-137: The injector code that reads
settings_.browser.injectedScript and inserts a QWebEngineScript via
profile_->scripts()->insert must be disabled for production; wrap the entire
block in a developer-only guard (e.g. a compile-time DEV_BUILD macro or a
runtime explicit unsafe flag like settings_.unsafeEnableInjectedScript) so it
only runs when that developer/unsafe mode is enabled, and ensure production
sessions (normal mode) never honor settings_.browser.injectedScript;
additionally emit a clear, high-visibility log message when the unsafe injector
is enabled so operators can detect it.
- Around line 39-79: The function getCachedSebVersion currently blocks on a
GitHub request and overwrites cachedVersion with the remote tag, which causes
the User-Agent SEB/ token to claim a version the binary may not have; instead
initialize cachedVersion from the shipped runtime version (use
QApplication::applicationVersion()) and ensure the QNetworkReply handling does
not replace cachedVersion—if you still need the remote release, store it in a
separate variable (e.g. remoteTag or latestRemoteVersion) and make the network
fetch asynchronous (or remove the blocking QEventLoop/timeout) so startup isn't
delayed; also ensure the UserAgentHeader uses cachedVersion for the SEB/ token
and do not change that value based on the reply or tag, referencing
getCachedSebVersion, cachedVersion, manager, request, reply, and the tag
variable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0db49291-cb6c-4aaf-ab0f-b0ee9f6a966c

📥 Commits

Reviewing files that changed from the base of the PR and between ebcac52 and 577a54c.

📒 Files selected for processing (3)
  • src/main.cpp
  • src/seb_session.cpp
  • src/seb_settings.h

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main.cpp (1)

94-97: Consider validating that the inject file exists.

QFileInfo::absoluteFilePath() returns a path regardless of whether the file actually exists. If the user provides a nonexistent path, the error will surface later (presumably in seb_session.cpp). For a better user experience, consider failing early with a clear error message here.

♻️ Optional: Add file existence check
     if (parser.isSet("inject")) {
-        settings.browser.injectedScript = QFileInfo(parser.value("inject")).absoluteFilePath();
+        QFileInfo injectInfo(parser.value("inject"));
+        if (!injectInfo.exists() || !injectInfo.isFile()) {
+            QTextStream err(stderr);
+            err << "Error: Inject script file not found: " << parser.value("inject") << Qt::endl;
+            return 1;
+        }
+        settings.browser.injectedScript = injectInfo.absoluteFilePath();
     }

Note: This would require moving the check to main() after applyCommandLineOverrides() or changing the function signature to return a status.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.cpp` around lines 94 - 97, The code currently sets
settings.browser.injectedScript to
QFileInfo(parser.value("inject")).absoluteFilePath() without verifying the file
exists; update the logic so when parser.isSet("inject") you check
QFileInfo(parser.value("inject")).exists() and if it returns false emit a clear
error and exit (or return a failure status) instead of storing the path; if
applyCommandLineOverrides() cannot fail, move this existence check into main()
after applyCommandLineOverrides() (or change applyCommandLineOverrides() to
return a status) and ensure the error references the missing inject file and
prevents proceeding to seb_session.cpp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main.cpp`:
- Around line 94-97: The code currently sets settings.browser.injectedScript to
QFileInfo(parser.value("inject")).absoluteFilePath() without verifying the file
exists; update the logic so when parser.isSet("inject") you check
QFileInfo(parser.value("inject")).exists() and if it returns false emit a clear
error and exit (or return a failure status) instead of storing the path; if
applyCommandLineOverrides() cannot fail, move this existence check into main()
after applyCommandLineOverrides() (or change applyCommandLineOverrides() to
return a status) and ensure the error references the missing inject file and
prevents proceeding to seb_session.cpp.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23246282-ac02-48b9-884c-ce600f333506

📥 Commits

Reviewing files that changed from the base of the PR and between 577a54c and 1fcea55.

📒 Files selected for processing (1)
  • src/main.cpp

Copy link
Copy Markdown
Owner

@Jvr2022 Jvr2022 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we maybe add this in a custom build instead of the whole program?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/seb_session.cpp (1)

127-133: Verify that the injector is meant to run in an isolated world.

Line 132 uses QWebEngineScript::UserWorld, so hooks to page-owned globals/APIs are invisible to the page’s own JavaScript. If the injected file is supposed to instrument the site itself, this needs MainWorld or a configurable world.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/seb_session.cpp` around lines 127 - 133, The injected script currently
uses QWebEngineScript::UserWorld (see script.setWorldId(...)), which isolates it
from page scripts; if the intent is to instrument or interact with the page’s
globals/APIs switch to QWebEngineScript::MainWorld or make the world
configurable so callers choose UserWorld vs MainWorld; update the creation logic
around QWebEngineScript script (script.setName("InjectScript"),
script.setInjectionPoint(...), script.setWorldId(...)) to accept a parameter or
configuration and set script.setWorldId(QWebEngineScript::MainWorld) when
instrumentation of the page is required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/build.sh`:
- Around line 9-15: The script currently defaults CONFIG="debug" which makes
debug mode opt-out; change the default to CONFIG="release" and update the arg
parsing loop to set CONFIG="debug" only when an explicit debug flag is passed
(e.g., "-d" or "--debug"); keep the existing "-r"/"--release" handling so it
sets CONFIG="release" if present, and ensure you reference the CONFIG variable
and the argument checks in the for loop when implementing this change.
- Line 17: The qmake6 invocation reuses the same ${ROOT_DIR}/build directory
regardless of CONFIG causing mixed debug/release objects; modify the build.sh
logic around the qmake6 CONFIG+="${CONFIG}" ../seb-linux-qt.pro call to either
(A) select a per-configuration build directory (e.g., use
BUILD_DIR="${ROOT_DIR}/build/${CONFIG}" and run qmake6 from there) or (B) detect
when the previous CONFIG differs from the current CONFIG and run a clean (make
clean) before re-running qmake6, ensuring the build directory is created/changed
appropriately and old object files are removed when CONFIG changes.

In `@src/seb_session.cpp`:
- Around line 443-445: Trim settings_.browser.customUserAgent before deciding to
use it: compute a trimmed string (reuse the local variable agent or a temporary)
and test trimmed.isEmpty() together with settings_.browser.useCustomUserAgent so
whitespace-only values fall back to the default; if non-empty after trimming,
assign the trimmed value to agent (instead of trimming only after the check).

---

Nitpick comments:
In `@src/seb_session.cpp`:
- Around line 127-133: The injected script currently uses
QWebEngineScript::UserWorld (see script.setWorldId(...)), which isolates it from
page scripts; if the intent is to instrument or interact with the page’s
globals/APIs switch to QWebEngineScript::MainWorld or make the world
configurable so callers choose UserWorld vs MainWorld; update the creation logic
around QWebEngineScript script (script.setName("InjectScript"),
script.setInjectionPoint(...), script.setWorldId(...)) to accept a parameter or
configuration and set script.setWorldId(QWebEngineScript::MainWorld) when
instrumentation of the page is required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 970da325-2e9d-4227-b220-f0c4816ec0af

📥 Commits

Reviewing files that changed from the base of the PR and between 1fcea55 and cc77c06.

📒 Files selected for processing (3)
  • scripts/build.sh
  • src/main.cpp
  • src/seb_session.cpp

fi
done

qmake6 CONFIG+="${CONFIG}" ../seb-linux-qt.pro
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n scripts/build.sh | head -40

Repository: Jvr2022/seb-linux

Length of output: 697


🏁 Script executed:

# Search for any documentation or comments in the repo about build configuration
rg -i "config|debug|release" scripts/ -A 2 -B 2

Repository: Jvr2022/seb-linux

Length of output: 675


🌐 Web query:

qmake6 CONFIG mode changes same build directory object reuse detection

💡 Result:

qmake (including qmake6 in Qt6) does not have a specific CONFIG option to control object file reuse detection when changing CONFIG modes (like switching between debug/release) in the same build directory. qmake generates dependency information based on source files, headers (via DEPENDPATH), and Makefile regeneration triggers, but CONFIG changes primarily affect which Makefile is used (e.g., Makefile.Debug vs Makefile.Release when CONFIG += debug_and_release). Key behaviors: - When using CONFIG += debug_and_release (default on Windows, optional elsewhere), qmake generates separate Makefiles for each mode: Makefile, Makefile.Debug, Makefile.Release. Building 'debug' uses Makefile.Debug (which builds objects with debug flags), 'release' uses Makefile.Release (release flags). Objects are typically placed in mode-specific directories if configured via OBJECTS_DIR in CONFIG(debug,debug|release) scopes (e.g., OBJECTS_DIR = .obj/debug). Thus, objects are not shared/reused across modes due to different compiler flags and usually separate paths. - In the same build directory without debug_and_release, changing CONFIG (e.g., adding/removing 'debug') invalidates the Makefile, forcing qmake re-run and full rebuild (no incremental object reuse across config changes). - No evidence of qmake6-specific changes to this behavior; Qt6 docs confirm qmake remains supported for apps with same core logic. - To avoid rebuilds on CONFIG changes, use separate build directories per config (shadow builds, recommended). For object reuse across subprojects, object files are not automatically shared; each subdir rebuilds unless using static libs. - Use 'qmake -nodepend' to disable dependency generation (not recommended, breaks incremental builds). No official CONFIG like 'object_reuse_detection' exists. Best practice: separate OBJECTS_DIR per config, separate build dirs.

Citations:


🏁 Script executed:

find . -name "seb-linux-qt.pro" -o -name "*.pro" | head -5

Repository: Jvr2022/seb-linux

Length of output: 151


🏁 Script executed:

cat -n seb-linux-qt.pro | head -50

Repository: Jvr2022/seb-linux

Length of output: 2506


🏁 Script executed:

cat seb-linux-qt.pro | grep -i "debug_and_release\|CONFIG.*debug\|CONFIG.*release\|OBJECTS_DIR.*CONFIG"

Repository: Jvr2022/seb-linux

Length of output: 43


🏁 Script executed:

wc -l seb-linux-qt.pro && tail -50 seb-linux-qt.pro

Repository: Jvr2022/seb-linux

Length of output: 2692


🏁 Script executed:

grep -n "CONFIG\|debug_and_release" seb-linux-qt.pro

Repository: Jvr2022/seb-linux

Length of output: 120


🏁 Script executed:

grep -n "include\|qt_module" seb-linux-qt.pro | head -20

Repository: Jvr2022/seb-linux

Length of output: 43


🏁 Script executed:

# Check if there are any cache/state files generated by qmake that might track CONFIG
find . -name ".qmake*" -o -name "*.stash" -o -name "Makefile*" | grep -v ".git" | head -20

Repository: Jvr2022/seb-linux

Length of output: 43


🌐 Web query:

Qt6 qmake CONFIG change detection same build directory rebuild behavior

💡 Result:

In Qt6, qmake does not automatically detect changes to the CONFIG variable (or other .pro file changes) in the same build directory during incremental builds with make/nmake. The generated Makefile lacks a dependency on the .pro file itself, so changes to CONFIG (e.g., adding debug, release, or custom features) do not trigger qmake regeneration or rebuilds of affected targets. To apply CONFIG changes: - Manually run qmake in the build directory to regenerate the Makefile. - In Qt Creator: Right-click project > Run qmake, or use Build > Run qmake, or delete Makefile(s) to force regeneration. - Use 'make clean' followed by 'make', but this performs a full rebuild. This behavior persists from Qt5 to Qt6 as qmake remains supported for applications. Official docs confirm qmake usage but note CMake preference for Qt6 builds. Qt Creator may not always auto-run qmake on .pro changes (known longstanding issue). Shadow builds (separate build dir) are recommended to avoid source pollution, but the issue remains for .pro changes.

Citations:


🏁 Script executed:

# Check what happens when qmake runs with CONFIG changes
# Show the build.sh logic again for clarity
cat -n scripts/build.sh

Repository: Jvr2022/seb-linux

Length of output: 697


Use separate build directories or force clean rebuild when CONFIG changes.

Line 17 reconfigures qmake6 in the same ${ROOT_DIR}/build directory for both debug and release modes. Qt6 qmake does not track CONFIG variable changes in the Makefile, so switching from debug to release (or vice versa) will reuse object files compiled with the previous configuration's compiler flags. This results in binaries with mixed debug/release code and unintended debug overhead.

Solutions: (1) Use separate build directories per mode (e.g., build/debug, build/release), or (2) Detect CONFIG changes and perform make clean before rebuilding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build.sh` at line 17, The qmake6 invocation reuses the same
${ROOT_DIR}/build directory regardless of CONFIG causing mixed debug/release
objects; modify the build.sh logic around the qmake6 CONFIG+="${CONFIG}"
../seb-linux-qt.pro call to either (A) select a per-configuration build
directory (e.g., use BUILD_DIR="${ROOT_DIR}/build/${CONFIG}" and run qmake6 from
there) or (B) detect when the previous CONFIG differs from the current CONFIG
and run a clean (make clean) before re-running qmake6, ensuring the build
directory is created/changed appropriately and old object files are removed when
CONFIG changes.

Comment on lines 443 to +445
if (settings_.browser.useCustomUserAgent && !settings_.browser.customUserAgent.isEmpty()) {
agent = settings_.browser.customUserAgent.trimmed();
} else {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Trim before deciding whether to honor customUserAgent.

Line 443 only checks isEmpty(). A whitespace-only customUserAgent therefore skips the default UA, then Line 444 trims it to empty and the header degrades to just SEB/... instead of a valid browser UA.

🛠️ Proposed fix
-    if (settings_.browser.useCustomUserAgent && !settings_.browser.customUserAgent.isEmpty()) {
-        agent = settings_.browser.customUserAgent.trimmed();
+    const QString customAgent = settings_.browser.customUserAgent.trimmed();
+    if (settings_.browser.useCustomUserAgent && !customAgent.isEmpty()) {
+        agent = customAgent;
     } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (settings_.browser.useCustomUserAgent && !settings_.browser.customUserAgent.isEmpty()) {
agent = settings_.browser.customUserAgent.trimmed();
} else {
const QString customAgent = settings_.browser.customUserAgent.trimmed();
if (settings_.browser.useCustomUserAgent && !customAgent.isEmpty()) {
agent = customAgent;
} else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/seb_session.cpp` around lines 443 - 445, Trim
settings_.browser.customUserAgent before deciding to use it: compute a trimmed
string (reuse the local variable agent or a temporary) and test
trimmed.isEmpty() together with settings_.browser.useCustomUserAgent so
whitespace-only values fall back to the default; if non-empty after trimming,
assign the trimmed value to agent (instead of trimming only after the check).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants