v0.0.7 - Disable perf probe by default (fix 322% WebProcess CPU), gate behind setting, top-frame only, quiet console logging
This commit is contained in:
Generated
+1
-1
@@ -3056,7 +3056,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sovereign_browser"
|
||||
version = "0.0.5"
|
||||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "sovereign_browser"
|
||||
version = "0.0.6"
|
||||
version = "0.0.7"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "A Linux x86 web browser built on WebKitGTK with Nostr identity"
|
||||
|
||||
@@ -4,10 +4,15 @@ set -e
|
||||
# run.sh - Build (only if needed) and run the sovereign_browser binary.
|
||||
#
|
||||
# Usage:
|
||||
# ./run.sh [args...] # build if stale, then run with args
|
||||
# ./run.sh [args...] # build if stale (debug), then run with args
|
||||
# ./run.sh --release # build/run the optimized release binary instead
|
||||
# ./run.sh --build # force a rebuild, then run
|
||||
# ./run.sh --no-build # run without building (fails if missing)
|
||||
#
|
||||
# The default profile is DEBUG: it rebuilds in a few seconds, which is what
|
||||
# you want while iterating. Pass --release for the optimized binary (much
|
||||
# slower to build, faster at runtime).
|
||||
#
|
||||
# The binary is rebuilt only when it is missing or when any source file
|
||||
# (src/**, Cargo.toml, Cargo.lock, www/**) is newer than the binary.
|
||||
|
||||
@@ -29,15 +34,18 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
BIN_NAME="sovereign_browser"
|
||||
BIN_PATH="target/release/${BIN_NAME}"
|
||||
|
||||
# --- Args ---------------------------------------------------------------
|
||||
FORCE_BUILD=false
|
||||
NO_BUILD=false
|
||||
RELEASE=false
|
||||
RUN_ARGS=()
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--release)
|
||||
RELEASE=true
|
||||
;;
|
||||
--build)
|
||||
FORCE_BUILD=true
|
||||
;;
|
||||
@@ -50,6 +58,17 @@ for arg in "$@"; do
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Profile ------------------------------------------------------------
|
||||
# Debug is the default; --release selects the optimized profile.
|
||||
if [[ "$RELEASE" == true ]]; then
|
||||
PROFILE_DIR="release"
|
||||
CARGO_PROFILE_ARGS=(--release)
|
||||
else
|
||||
PROFILE_DIR="debug"
|
||||
CARGO_PROFILE_ARGS=()
|
||||
fi
|
||||
BIN_PATH="target/${PROFILE_DIR}/${BIN_NAME}"
|
||||
|
||||
# --- Determine whether a build is needed --------------------------------
|
||||
needs_build() {
|
||||
# No binary yet -> must build.
|
||||
@@ -70,12 +89,12 @@ if [[ "$NO_BUILD" == true ]]; then
|
||||
fi
|
||||
print_status "Skipping build (--no-build)."
|
||||
elif [[ "$FORCE_BUILD" == true ]]; then
|
||||
print_status "Forcing rebuild of ${BIN_NAME} (release)..."
|
||||
cargo build --release
|
||||
print_status "Forcing rebuild of ${BIN_NAME} (${PROFILE_DIR})..."
|
||||
cargo build "${CARGO_PROFILE_ARGS[@]}"
|
||||
print_success "Build complete."
|
||||
elif needs_build; then
|
||||
print_status "Sources changed or binary missing; building ${BIN_NAME} (release)..."
|
||||
cargo build --release
|
||||
print_status "Sources changed or binary missing; building ${BIN_NAME} (${PROFILE_DIR})..."
|
||||
cargo build "${CARGO_PROFILE_ARGS[@]}"
|
||||
print_success "Build complete."
|
||||
else
|
||||
print_status "Binary is up to date; skipping build."
|
||||
|
||||
+179
-31
@@ -62,8 +62,12 @@ fn handle_sovereign_scheme(request: &URISchemeRequest) {
|
||||
let route = path.split('?').next().unwrap_or(path);
|
||||
|
||||
// ── nostr API ───────────────────────────────────────────────────
|
||||
if route.starts_with("nostr/") {
|
||||
handle_nostr_api(request, &route[6..]);
|
||||
// NOTE: pass `path` (which still includes the query string), not
|
||||
// `route`. The JS shim encodes the request body as `?body=<json>`,
|
||||
// so stripping the query here would make every body-bearing method
|
||||
// (signEvent, nip04/nip44) fail with "Invalid request body".
|
||||
if path.starts_with("nostr/") {
|
||||
handle_nostr_api(request, &path[6..]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -527,6 +531,16 @@ fn handle_settings_set(request: &URISchemeRequest, query: &str) {
|
||||
let _ = db::db_kv_set("agent_server_enabled", new);
|
||||
(new == "true", true)
|
||||
}
|
||||
"perf_probe_enabled" => {
|
||||
// Toggle the per-tab performance probe. OFF by default; the
|
||||
// probe patches hot JS APIs and runs a permanent rAF loop.
|
||||
// Takes effect on the next tab (or reload).
|
||||
let mut s = settings::settings_get();
|
||||
s.perf_probe_enabled = !s.perf_probe_enabled;
|
||||
settings::settings_update(&serde_json::json!({"perf_probe_enabled": s.perf_probe_enabled}));
|
||||
settings::settings_save();
|
||||
(s.perf_probe_enabled, false)
|
||||
}
|
||||
_ => {
|
||||
respond_error_json(request, 400, "Unknown feature");
|
||||
return;
|
||||
@@ -687,6 +701,7 @@ fn handle_settings_config_json(request: &URISchemeRequest) {
|
||||
"dev_extras": true,
|
||||
"file_access": true,
|
||||
"universal_access": true,
|
||||
"perf_probe_enabled": s.perf_probe_enabled,
|
||||
"search_engines": search_engines,
|
||||
"shortcuts": shortcuts,
|
||||
});
|
||||
@@ -721,21 +736,27 @@ fn handle_nostr_api(request: &URISchemeRequest, method: &str) {
|
||||
respond_error_json(request, 403, "Read-only mode");
|
||||
return;
|
||||
}
|
||||
if let Some(ref signer) = state.signer {
|
||||
match serde_json::from_str::<nostr_core::types::Event>(&event_json) {
|
||||
Ok(event) => {
|
||||
match signer.sign_event(&event) {
|
||||
Ok(signed) => {
|
||||
let result = serde_json::to_string(&signed).unwrap_or_default();
|
||||
respond_json(request, &result);
|
||||
}
|
||||
Err(e) => respond_error_json(request, 500, &e.to_string()),
|
||||
}
|
||||
}
|
||||
Err(e) => respond_error_json(request, 400, &e.to_string()),
|
||||
let signer = match &state.signer {
|
||||
Some(s) => s.clone(),
|
||||
None => {
|
||||
respond_error_json(request, 500, "No signer available");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
respond_error_json(request, 500, "No signer available");
|
||||
};
|
||||
let pubkey_hex = state.pubkey_hex.clone();
|
||||
drop(state);
|
||||
|
||||
match parse_unsigned_event(&event_json, &pubkey_hex) {
|
||||
Ok(event) => {
|
||||
match signer.sign_event(&event) {
|
||||
Ok(signed) => {
|
||||
let result = serde_json::to_string(&signed).unwrap_or_default();
|
||||
respond_json(request, &result);
|
||||
}
|
||||
Err(e) => respond_error_json(request, 500, &e.to_string()),
|
||||
}
|
||||
}
|
||||
Err(e) => respond_error_json(request, 400, &e),
|
||||
}
|
||||
}
|
||||
"getRelays" => {
|
||||
@@ -817,22 +838,31 @@ fn handle_nostr_api(request: &URISchemeRequest, method: &str) {
|
||||
let plaintext = params.get("plaintext").and_then(|v| v.as_str()).unwrap_or("");
|
||||
match signer.nip44_encrypt(&peer_pk, plaintext) {
|
||||
Ok(ciphertext) => {
|
||||
let json = serde_json::json!({ "result": ciphertext });
|
||||
// NIP-44 ciphertext is raw bytes; the NIP-07 API
|
||||
// (and the C bridge) represent it as a base64
|
||||
// string. Serializing the Vec<u8> directly would
|
||||
// emit a JSON array of numbers instead.
|
||||
let b64 = nostr_core::util::base64_encode(&ciphertext);
|
||||
let json = serde_json::json!({ "result": b64 });
|
||||
respond_json(request, &serde_json::to_string(&json).unwrap_or_default());
|
||||
}
|
||||
Err(e) => respond_error_json(request, 500, &e.to_string()),
|
||||
}
|
||||
}
|
||||
"nip44Decrypt" => {
|
||||
let ciphertext_bytes: Vec<u8> = params.get("ciphertext").map(|v| {
|
||||
if let Some(s) = v.as_str() {
|
||||
s.as_bytes().to_vec()
|
||||
} else if let Some(arr) = v.as_array() {
|
||||
arr.iter().filter_map(|b| b.as_u64().map(|n| n as u8)).collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
// The JS shim sends the ciphertext as a base64 string
|
||||
// (NIP-07 convention). The signer expects the raw binary
|
||||
// payload, so base64-decode it. Passing the base64 text's
|
||||
// ASCII bytes through unchanged corrupts the payload and
|
||||
// makes the remote signer reject it with invalid_params.
|
||||
let ciphertext_b64 = params.get("ciphertext").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let ciphertext_bytes = match nostr_core::util::base64_decode(ciphertext_b64) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
respond_error_json(request, 400, &format!("Invalid base64 ciphertext: {}", e));
|
||||
return;
|
||||
}
|
||||
}).unwrap_or_default();
|
||||
};
|
||||
match signer.nip44_decrypt(&peer_pk, &ciphertext_bytes) {
|
||||
Ok(plaintext) => {
|
||||
let json = serde_json::json!({ "result": plaintext });
|
||||
@@ -862,8 +892,14 @@ fn extract_query_param(query: &str, key: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
/// Simple URL percent-decoding.
|
||||
///
|
||||
/// Decodes into a byte buffer first, then interprets the result as UTF-8.
|
||||
/// This is required for correctness: `encodeURIComponent` percent-encodes
|
||||
/// each byte of a multi-byte UTF-8 sequence separately, so pushing each
|
||||
/// decoded byte as a `char` (the previous behaviour) would corrupt any
|
||||
/// non-ASCII content (e.g. emoji or accented characters in a post body).
|
||||
fn url_decode(s: &str) -> String {
|
||||
let mut result = String::with_capacity(s.len());
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(s.len());
|
||||
let mut chars = s.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '%' {
|
||||
@@ -871,18 +907,22 @@ fn url_decode(s: &str) -> String {
|
||||
let h2 = chars.next();
|
||||
if let (Some(a), Some(b)) = (h1, h2) {
|
||||
if let Ok(byte) = u8::from_str_radix(&format!("{}{}", a, b), 16) {
|
||||
result.push(byte as char);
|
||||
bytes.push(byte);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.push(c);
|
||||
// Malformed escape — emit the literal '%' and the consumed chars.
|
||||
bytes.push(b'%');
|
||||
if let Some(a) = h1 { bytes.extend_from_slice(a.to_string().as_bytes()); }
|
||||
if let Some(b) = h2 { bytes.extend_from_slice(b.to_string().as_bytes()); }
|
||||
} else if c == '+' {
|
||||
result.push(' ');
|
||||
bytes.push(b' ');
|
||||
} else {
|
||||
result.push(c);
|
||||
let mut buf = [0u8; 4];
|
||||
bytes.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
|
||||
}
|
||||
}
|
||||
result
|
||||
String::from_utf8_lossy(&bytes).into_owned()
|
||||
}
|
||||
|
||||
/// Parse a hex pubkey string into a PublicKey.
|
||||
@@ -895,6 +935,114 @@ fn parse_pubkey_hex(hex_str: &str) -> Option<nostr_core::types::PublicKey> {
|
||||
Some(nostr_core::types::PublicKey::from_bytes(arr))
|
||||
}
|
||||
|
||||
/// Parse an unsigned event JSON body from the JS shim into a
|
||||
/// `nostr_core::types::Event`, injecting the active identity's pubkey.
|
||||
///
|
||||
/// The JS shim sends an *unsigned* event — typically
|
||||
/// `{kind, content, tags, created_at}` with no `pubkey`/`id`/`sig`.
|
||||
/// `nostr_core::types::Event` requires `pubkey` (it is not an `Option`),
|
||||
/// so deserializing the raw body fails with "missing field `pubkey`".
|
||||
/// This fills the pubkey in from the signed-in user, mirroring the C
|
||||
/// bridge. An explicit non-empty `pubkey` in the body is preserved.
|
||||
fn parse_unsigned_event(
|
||||
event_json: &str,
|
||||
pubkey_hex: &str,
|
||||
) -> Result<nostr_core::types::Event, String> {
|
||||
let mut value: serde_json::Value =
|
||||
serde_json::from_str(event_json).map_err(|e| format!("Invalid JSON: {}", e))?;
|
||||
|
||||
if let Some(obj) = value.as_object_mut() {
|
||||
let needs_pubkey = obj
|
||||
.get("pubkey")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.is_empty())
|
||||
.unwrap_or(true);
|
||||
if needs_pubkey {
|
||||
obj.insert(
|
||||
"pubkey".to_string(),
|
||||
serde_json::Value::String(pubkey_hex.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::from_value::<nostr_core::types::Event>(value).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const PK: &str = "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d";
|
||||
|
||||
#[test]
|
||||
fn injects_pubkey_when_missing() {
|
||||
// Exactly what the JS shim sends for a kind-1 post.
|
||||
let body = r#"{"kind":1,"content":"Hello world","tags":[],"created_at":1700000000}"#;
|
||||
let event = parse_unsigned_event(body, PK).expect("should parse");
|
||||
assert_eq!(event.pubkey.to_hex(), PK);
|
||||
assert_eq!(event.content, "Hello world");
|
||||
assert_eq!(event.kind.as_u64(), 1);
|
||||
assert_eq!(event.created_at, 1700000000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_explicit_pubkey() {
|
||||
let other = "0000000000000000000000000000000000000000000000000000000000000001";
|
||||
let body = format!(
|
||||
r#"{{"pubkey":"{}","kind":1,"content":"x","tags":[],"created_at":1}}"#,
|
||||
other
|
||||
);
|
||||
let event = parse_unsigned_event(&body, PK).expect("should parse");
|
||||
assert_eq!(event.pubkey.to_hex(), other);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_tags_and_imeta() {
|
||||
let body = r#"{"kind":1,"content":"hi","tags":[["imeta","url https://example.com/a.png"]],"created_at":5}"#;
|
||||
let event = parse_unsigned_event(body, PK).expect("should parse");
|
||||
assert_eq!(event.tags.len(), 1);
|
||||
assert_eq!(event.tags[0].0[0], "imeta");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_json() {
|
||||
assert!(parse_unsigned_event("not json", PK).is_err());
|
||||
}
|
||||
|
||||
/// Round-trip the NIP-44 base64 encoding the bridge uses.
|
||||
///
|
||||
/// The JS shim sends the ciphertext as a base64 string; the bridge must
|
||||
/// base64-decode it before handing raw bytes to the signer, and must
|
||||
/// base64-encode the signer's raw output before returning it. This test
|
||||
/// exercises that exact path with two real local signers.
|
||||
#[test]
|
||||
fn nip44_base64_round_trip() {
|
||||
use nostr_signer::local::LocalSigner;
|
||||
use nostr_signer::traits::NostrSigner;
|
||||
|
||||
let (sk_a, pk_a) = nostr_core::crypto::keys::generate_keypair();
|
||||
let (sk_b, pk_b) = nostr_core::crypto::keys::generate_keypair();
|
||||
let signer_a = LocalSigner::new(sk_a).expect("signer a");
|
||||
let signer_b = LocalSigner::new(sk_b).expect("signer b");
|
||||
|
||||
let plaintext = "Hello, giftwrap!";
|
||||
|
||||
// Encrypt as A→B, then base64-encode (what the bridge returns).
|
||||
let raw_ct = signer_a.nip44_encrypt(&pk_b, plaintext).expect("encrypt");
|
||||
let b64_ct = nostr_core::util::base64_encode(&raw_ct);
|
||||
|
||||
// The base64 string must not be valid raw ciphertext bytes — this is
|
||||
// exactly the bug: passing the ASCII of the base64 text to the signer
|
||||
// fails. Decoding first must succeed.
|
||||
let decoded = nostr_core::util::base64_decode(&b64_ct).expect("decode");
|
||||
assert_eq!(decoded, raw_ct);
|
||||
|
||||
// Decrypt as B←A using the decoded raw bytes.
|
||||
let decrypted = signer_b.nip44_decrypt(&pk_a, &decoded).expect("decrypt");
|
||||
assert_eq!(String::from_utf8(decrypted).unwrap(), plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
fn respond_json(request: &URISchemeRequest, json: &str) {
|
||||
let body = json.to_string().into_bytes();
|
||||
let stream = MemoryInputStream::from_bytes(&Bytes::from_owned(body.clone()));
|
||||
|
||||
+6
-3
@@ -188,19 +188,22 @@ pub fn nostr_inject_create_content_manager_with_probe(tab_index: i32) -> UserCon
|
||||
manager.add_script(&script);
|
||||
|
||||
// Perf probe preamble (skips sovereign:// pages, sets tab index).
|
||||
// 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 preamble = UserScript::new(
|
||||
&perf_probe_preamble(tab_index),
|
||||
UserContentInjectedFrames::AllFrames,
|
||||
UserContentInjectedFrames::TopFrame,
|
||||
UserScriptInjectionTime::Start,
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
manager.add_script(&preamble);
|
||||
|
||||
// Perf probe body.
|
||||
// Perf probe body (top frame only — see above).
|
||||
let probe = UserScript::new(
|
||||
PERF_PROBE_JS,
|
||||
UserContentInjectedFrames::AllFrames,
|
||||
UserContentInjectedFrames::TopFrame,
|
||||
UserScriptInjectionTime::Start,
|
||||
&[],
|
||||
&[],
|
||||
|
||||
@@ -38,6 +38,12 @@ pub struct BrowserSettings {
|
||||
pub enable_smooth_scrolling: bool,
|
||||
pub enable_accelerated_2d_canvas: bool,
|
||||
pub enable_write_console_messages_to_stdout: bool,
|
||||
/// Per-tab performance probe injection. OFF by default: the probe
|
||||
/// patches addEventListener/setTimeout/setInterval/XHR and runs a
|
||||
/// permanent rAF loop, which adds enough main-thread overhead to
|
||||
/// break scrolling on listener-heavy pages. Mirrors the C default
|
||||
/// (settings.c:136).
|
||||
pub perf_probe_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for BrowserSettings {
|
||||
@@ -69,7 +75,12 @@ impl Default for BrowserSettings {
|
||||
enable_media_source: true,
|
||||
enable_smooth_scrolling: true,
|
||||
enable_accelerated_2d_canvas: true,
|
||||
// Console-to-stdout is OFF by default: the browser's stdout is
|
||||
// usually a terminal, where writes are slow and can block the
|
||||
// WebProcess. Enable only when debugging.
|
||||
enable_write_console_messages_to_stdout: false,
|
||||
// Perf probe OFF by default (see field docs).
|
||||
perf_probe_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,6 +133,12 @@ pub fn settings_update(json: &serde_json::Value) {
|
||||
if let Some(v) = json.get("theme_dark").and_then(|v| v.as_bool()) {
|
||||
settings.theme_dark = v;
|
||||
}
|
||||
if let Some(v) = json.get("perf_probe_enabled").and_then(|v| v.as_bool()) {
|
||||
settings.perf_probe_enabled = v;
|
||||
}
|
||||
if let Some(v) = json.get("enable_write_console_messages_to_stdout").and_then(|v| v.as_bool()) {
|
||||
settings.enable_write_console_messages_to_stdout = v;
|
||||
}
|
||||
}
|
||||
|
||||
/// Load settings from the database.
|
||||
@@ -149,6 +166,8 @@ pub fn settings_save() {
|
||||
"agent_port": settings.agent_port,
|
||||
"session_restore": settings.session_restore,
|
||||
"theme_dark": settings.theme_dark,
|
||||
"perf_probe_enabled": settings.perf_probe_enabled,
|
||||
"enable_write_console_messages_to_stdout": settings.enable_write_console_messages_to_stdout,
|
||||
});
|
||||
|
||||
if let Ok(json_str) = serde_json::to_string(&json) {
|
||||
|
||||
+10
-2
@@ -218,8 +218,16 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti
|
||||
state.active_tab_id = tab_id;
|
||||
drop(state);
|
||||
|
||||
// Create the webview with perf probe injected (tab index baked into preamble).
|
||||
let content_manager = crate::nostr_inject::nostr_inject_create_content_manager_with_probe(tab_id);
|
||||
// Create the webview. The perf probe is injected only when explicitly
|
||||
// enabled: it patches addEventListener/setTimeout/setInterval/XHR and
|
||||
// runs a permanent rAF loop, which adds enough main-thread overhead to
|
||||
// break scrolling on listener-heavy pages. Mirrors the C gate
|
||||
// (tab_manager.c:1349). Default OFF.
|
||||
let content_manager = if s.perf_probe_enabled {
|
||||
crate::nostr_inject::nostr_inject_create_content_manager_with_probe(tab_id)
|
||||
} else {
|
||||
crate::nostr_inject::nostr_inject_create_content_manager()
|
||||
};
|
||||
let webview = WebView::builder()
|
||||
.user_content_manager(&content_manager)
|
||||
.web_context(ctx)
|
||||
|
||||
+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.6";
|
||||
pub const VERSION: &str = "v0.0.7";
|
||||
|
||||
/// 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 = 6;
|
||||
pub const VERSION_PATCH: u32 = 7;
|
||||
|
||||
+7
-4
@@ -50,8 +50,11 @@ pub fn web_context_configure_settings(settings: &webkit2gtk::Settings) {
|
||||
// Deprecated since 2.32 with no replacement; keep for explicitness.
|
||||
#[allow(deprecated)]
|
||||
settings.set_enable_accelerated_2d_canvas(true);
|
||||
// Enable console messages to stdout so we can debug window.nostr injection
|
||||
// and page-level issues. The C version connects to console-message signal;
|
||||
// this is simpler for now.
|
||||
settings.set_enable_write_console_messages_to_stdout(true);
|
||||
// Console-to-stdout is OFF by default. The browser's stdout is usually a
|
||||
// terminal, where writes are slow and can block the WebProcess; on a
|
||||
// chatty page this is a real CPU cost. Enable via the
|
||||
// `enable_write_console_messages_to_stdout` setting when debugging.
|
||||
settings.set_enable_write_console_messages_to_stdout(
|
||||
crate::settings::settings_get().enable_write_console_messages_to_stdout,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,6 +145,17 @@
|
||||
onclick="toggle('universal_access')"></div>
|
||||
</div>
|
||||
|
||||
<div class="setting">
|
||||
<div>
|
||||
<div class="setting-name">Performance Probe</div>
|
||||
<div class="setting-desc">Per-tab CPU/FPS instrumentation. Adds main-thread
|
||||
overhead (patches timers, listeners, XHR; runs a rAF loop). Off by
|
||||
default. Takes effect on new tabs.</div>
|
||||
</div>
|
||||
<div class="toggle" data-feature="perf_probe_enabled"
|
||||
onclick="toggle('perf_probe_enabled')"></div>
|
||||
</div>
|
||||
|
||||
<div class="setting">
|
||||
<div><div class="setting-name">CORS Enforcement</div></div>
|
||||
<div class="toggle off" style="opacity:0.5"></div>
|
||||
|
||||
+2
-1
@@ -49,7 +49,8 @@ function applyConfig(d) {
|
||||
agent_server_enabled: d.agent_server_enabled,
|
||||
dev_extras: d.dev_extras,
|
||||
file_access: d.file_access,
|
||||
universal_access: d.universal_access
|
||||
universal_access: d.universal_access,
|
||||
perf_probe_enabled: d.perf_probe_enabled
|
||||
};
|
||||
document.querySelectorAll('.toggle[data-feature]').forEach(function(el) {
|
||||
var key = el.getAttribute('data-feature');
|
||||
|
||||
Reference in New Issue
Block a user