v0.0.14 - Fix tab URL tracking: use main-frame URI instead of decide-policy, sync URL bar on navigation
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "sovereign_browser"
|
||||
version = "0.0.13"
|
||||
version = "0.0.14"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "A Linux x86 web browser built on WebKitGTK with Nostr identity"
|
||||
|
||||
+5
-1
@@ -111,8 +111,12 @@ fn handle_sovereign_scheme(request: &URISchemeRequest) {
|
||||
}
|
||||
|
||||
// ── Processes REST API ─────────────────────────────────────────
|
||||
// Pass `path` (with its query string): probe-report reads
|
||||
// tab_index/body and tab_probe/tab_action read index/action from it.
|
||||
// Passing `route` dropped every parameter, so no probe report was ever
|
||||
// recorded and the Tabs view stayed blank.
|
||||
if route.starts_with("processes/") {
|
||||
handle_processes_api(request, &route[10..]);
|
||||
handle_processes_api(request, &path[10..]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+39
-3
@@ -229,15 +229,19 @@ fn add_scrollbar_visibility_fix(manager: &UserContentManager) {
|
||||
manager.add_style_sheet(&sheet);
|
||||
}
|
||||
|
||||
/// Perf probe preamble — skips sovereign:// and about: pages, sets tab index.
|
||||
/// Perf probe preamble — skips sovereign:// pages, sets tab index.
|
||||
/// The tab index is baked in at injection time as a literal.
|
||||
///
|
||||
/// Only `sovereign:` is skipped. The previous check also skipped `about:`,
|
||||
/// but at document-start the location of a freshly created top-level
|
||||
/// document can still be `about:blank`, which silently disabled the probe
|
||||
/// for every real page.
|
||||
fn perf_probe_preamble(tab_index: i32) -> String {
|
||||
format!(
|
||||
r#"(function(){{
|
||||
'use strict';
|
||||
try {{
|
||||
var u = location.href || '';
|
||||
if (u.indexOf('sovereign://') === 0 || u.indexOf('about:') === 0) {{
|
||||
if (location.protocol === 'sovereign:') {{
|
||||
window.__sbPerfProbeSkip = true;
|
||||
}} else {{
|
||||
window.__sbPerfProbeSkip = false;
|
||||
@@ -250,6 +254,28 @@ fn perf_probe_preamble(tab_index: i32) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// Isolated-world relay for perf probe reports. The probe (main world)
|
||||
/// dispatches a `__sb_probe_report` DOM event carrying the JSON payload as a
|
||||
/// string; this relay performs the synchronous XHR to
|
||||
/// `sovereign://processes/probe-report`. Doing the XHR from an isolated world
|
||||
/// exempts it from the page's Content-Security-Policy (connect-src), which
|
||||
/// otherwise blocks the request on many sites.
|
||||
const PERF_PROBE_RELAY_JS: &str = r#"
|
||||
(function() {
|
||||
'use strict';
|
||||
document.addEventListener('__sb_probe_report', function(ev) {
|
||||
try {
|
||||
var d = JSON.parse(ev.detail);
|
||||
var url = 'sovereign://processes/probe-report?tab_index=' + d.tab_index +
|
||||
'&body=' + encodeURIComponent(ev.detail);
|
||||
var x = new XMLHttpRequest();
|
||||
x.open('GET', url, false);
|
||||
x.send();
|
||||
} catch (e) { /* probe must never break the page */ }
|
||||
});
|
||||
})();
|
||||
"#;
|
||||
|
||||
/// The perf-probe.js script (embedded from www/js/perf-probe.js).
|
||||
const PERF_PROBE_JS: &str = include_str!("../www/js/perf-probe.js");
|
||||
|
||||
@@ -274,6 +300,16 @@ pub fn nostr_inject_create_content_manager_with_probe(tab_index: i32) -> UserCon
|
||||
// Top frame only — mirrors the C version (perf_probe.c:67). Injecting
|
||||
// into all frames would run a separate probe instance (with its own
|
||||
// rAF loop and 1s sync XHR) in every iframe.
|
||||
let probe_relay = UserScript::for_world(
|
||||
PERF_PROBE_RELAY_JS,
|
||||
UserContentInjectedFrames::TopFrame,
|
||||
UserScriptInjectionTime::Start,
|
||||
BRIDGE_WORLD,
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
manager.add_script(&probe_relay);
|
||||
|
||||
let preamble = UserScript::new(
|
||||
&perf_probe_preamble(tab_index),
|
||||
UserContentInjectedFrames::TopFrame,
|
||||
|
||||
+45
-7
@@ -841,11 +841,13 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update tab URL in state for non-rewritten URLs.
|
||||
let mut state = G_TAB_STATE.lock().unwrap();
|
||||
if let Some(t) = state.tabs.iter_mut().find(|t| t.id == tab_id_clone2) {
|
||||
t.url = uri_str;
|
||||
}
|
||||
// NOTE: the tab's URL in state is intentionally NOT
|
||||
// updated here. decide-policy also fires for
|
||||
// sub-frame/iframe navigations (e.g. a PayPal
|
||||
// button) and for not-yet-committed requests, which
|
||||
// would clobber the real page URL. The main-frame
|
||||
// URI is tracked via `notify::uri` below.
|
||||
let _ = tab_id_clone2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -853,6 +855,31 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti
|
||||
true
|
||||
});
|
||||
|
||||
// Track the main-frame URI: keep the tab state and the URL entry in
|
||||
// sync with what the webview is actually showing (covers link clicks,
|
||||
// redirects, history navigation, and Recents/Bookmarks menu loads).
|
||||
let tab_id_uri = tab_id;
|
||||
let url_entry_uri = url_entry.clone();
|
||||
webview.connect_uri_notify(move |wv| {
|
||||
let Some(uri) = wv.uri() else { return };
|
||||
let uri_str = uri.to_string();
|
||||
if uri_str.is_empty() {
|
||||
return;
|
||||
}
|
||||
{
|
||||
let mut state = G_TAB_STATE.lock().unwrap();
|
||||
if let Some(t) = state.tabs.iter_mut().find(|t| t.id == tab_id_uri) {
|
||||
t.url = uri_str.clone();
|
||||
}
|
||||
}
|
||||
if uri_str.starts_with("about:blank") {
|
||||
url_entry_uri.set_text("");
|
||||
} else if url_entry_uri.text().as_str() != uri_str {
|
||||
url_entry_uri.set_text(&uri_str);
|
||||
url_entry_uri.set_position(-1);
|
||||
}
|
||||
});
|
||||
|
||||
// Webview context menu — add "Open Link in New Tab" when right-clicking
|
||||
// a link, and "Open Page in New Tab" when right-clicking the page
|
||||
// background. Mirrors the C version (tab_manager.c:1912).
|
||||
@@ -1669,8 +1696,13 @@ pub fn tab_manager_reload_tab(tab_id: i32) {
|
||||
/// Duplicate a tab by ID (opens the same URL in a new tab in the same window).
|
||||
pub fn tab_manager_duplicate(tab_id: i32) {
|
||||
let state = G_TAB_STATE.lock().unwrap();
|
||||
let url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone());
|
||||
let state_url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone());
|
||||
drop(state);
|
||||
// Prefer the webview's live main-frame URI over the stored one.
|
||||
let url = tab_webview(tab_id)
|
||||
.and_then(|wv| wv.uri().map(|u| u.to_string()))
|
||||
.filter(|u| !u.is_empty())
|
||||
.or(state_url);
|
||||
// Duplicate into the tab's own notebook (its window).
|
||||
let nb = match tab_notebook(tab_id) {
|
||||
Some(n) => n,
|
||||
@@ -1692,7 +1724,13 @@ pub fn tab_manager_open_in_new_window(tab_id: i32) {
|
||||
// Capture the URL and the tab's own notebook before moving.
|
||||
let (url, nb) = {
|
||||
let state = G_TAB_STATE.lock().unwrap();
|
||||
let url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone());
|
||||
let state_url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone());
|
||||
drop(state);
|
||||
// Prefer the webview's live main-frame URI over the stored one.
|
||||
let url = tab_webview(tab_id)
|
||||
.and_then(|wv| wv.uri().map(|u| u.to_string()))
|
||||
.filter(|u| !u.is_empty())
|
||||
.or(state_url);
|
||||
(url, tab_notebook(tab_id))
|
||||
};
|
||||
let nb = match nb {
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
//! Version information for sovereign_browser
|
||||
|
||||
/// The current version of sovereign_browser (with leading 'v').
|
||||
pub const VERSION: &str = "v0.0.13";
|
||||
pub const VERSION: &str = "v0.0.14";
|
||||
|
||||
/// Major version number.
|
||||
pub const VERSION_MAJOR: u32 = 0;
|
||||
@@ -10,4 +10,4 @@ pub const VERSION_MAJOR: u32 = 0;
|
||||
pub const VERSION_MINOR: u32 = 0;
|
||||
|
||||
/// Patch version number.
|
||||
pub const VERSION_PATCH: u32 = 13;
|
||||
pub const VERSION_PATCH: u32 = 14;
|
||||
|
||||
+44
-13
@@ -125,6 +125,8 @@
|
||||
try {
|
||||
var resObs = new PerformanceObserver(function (list) {
|
||||
list.getEntries().forEach(function (e) {
|
||||
// Ignore the probe's own reports and other browser-internal calls.
|
||||
if (e.name.indexOf('sovereign://') === 0) return;
|
||||
netRequestsLastSec.push({ name: e.name, type: e.initiatorType });
|
||||
var key = e.name.split('?')[0];
|
||||
netEndpoints[key] = (netEndpoints[key] || 0) + 1;
|
||||
@@ -135,13 +137,42 @@
|
||||
}
|
||||
|
||||
// ── FPS via requestAnimationFrame ───────────────────────────────
|
||||
// The rAF loop is the probe's only continuous main-thread work. It is
|
||||
// paused whenever the document is hidden (background tab / minimised
|
||||
// window) so a backgrounded tab can never keep the compositor and font
|
||||
// machinery busy. It is also guarded so callbacks can't stack.
|
||||
var rafFrames = 0;
|
||||
var fps = 0;
|
||||
function rafLoop(ts) {
|
||||
var rafScheduled = false;
|
||||
var rafActive = false;
|
||||
|
||||
function rafLoop() {
|
||||
rafScheduled = false;
|
||||
if (!rafActive) return;
|
||||
rafFrames++;
|
||||
scheduleRaf();
|
||||
}
|
||||
function scheduleRaf() {
|
||||
if (rafScheduled || !rafActive) return;
|
||||
rafScheduled = true;
|
||||
requestAnimationFrame(rafLoop);
|
||||
}
|
||||
requestAnimationFrame(rafLoop);
|
||||
function setRafActive(on) {
|
||||
if (on === rafActive) return;
|
||||
rafActive = on;
|
||||
if (on) {
|
||||
scheduleRaf();
|
||||
} else {
|
||||
rafFrames = 0;
|
||||
fps = 0;
|
||||
}
|
||||
}
|
||||
rafActive = !document.hidden;
|
||||
if (rafActive) scheduleRaf();
|
||||
|
||||
var onVisibility = function () { setRafActive(!document.hidden); };
|
||||
onVisibility.__sbInternal = true; // don't count our own listener
|
||||
document.addEventListener('visibilitychange', onVisibility);
|
||||
|
||||
// ── Timer tracking (patched setTimeout/setInterval) ─────────────
|
||||
var activeTimers = {}; // id -> {code, interval_ms, count}
|
||||
@@ -344,21 +375,21 @@
|
||||
netEndpoints = {};
|
||||
timerTop = {};
|
||||
|
||||
// Send. Use sync XHR to sovereign:// (same convention as the
|
||||
// window.nostr shim — WebKit's custom scheme handler doesn't
|
||||
// reliably support fetch() or async XHR for sovereign:// URLs).
|
||||
// Send. The actual sync XHR to sovereign:// runs in an isolated
|
||||
// script world (see PERF_PROBE_RELAY_JS in nostr_inject.rs) so that
|
||||
// pages with a strict CSP can't block it. We hand the payload over as
|
||||
// a JSON string in a DOM CustomEvent (objects don't cross worlds).
|
||||
try {
|
||||
var body = encodeURIComponent(JSON.stringify(payload));
|
||||
var url = 'sovereign://processes/probe-report?tab_index=' + TAB_INDEX +
|
||||
'&body=' + body;
|
||||
var x = new XMLHttpRequest();
|
||||
x.open('GET', url, false); // synchronous
|
||||
x.send();
|
||||
document.dispatchEvent(new CustomEvent('__sb_probe_report', {
|
||||
detail: JSON.stringify(payload)
|
||||
}));
|
||||
} catch (e) { /* swallow — probe must never break the page */ }
|
||||
}
|
||||
|
||||
// FPS counter: count frames in each 1s window.
|
||||
setInterval(function () {
|
||||
// FPS counter: count frames in each 1s window. Use the original
|
||||
// setInterval so the probe doesn't count itself as an active timer.
|
||||
origSetInterval.call(window, function () {
|
||||
if (!rafActive) return;
|
||||
fps = rafFrames;
|
||||
rafFrames = 0;
|
||||
}, 1000);
|
||||
|
||||
Reference in New Issue
Block a user