diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfdc555c7..957d55c62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,17 +96,18 @@ jobs: timeout-minutes: 5 env: DEBUG: pw:install - run: | - npm install --prefix "${RUNNER_TEMP}/behaviour-browser" --no-save --package-lock=false playwright@1.60.0 - npm install --prefix "${RUNNER_TEMP}/behaviour-core" --no-save --package-lock=false playwright-core@1.59.1 - "${RUNNER_TEMP}/behaviour-browser/node_modules/.bin/playwright" install chromium --with-deps --only-shell + run: ./scripts/test-behaviour.sh inputs - name: JavaScript behavior tests - env: - NODE_PATH: ${{ runner.temp }}/behaviour-browser/node_modules - BROWSER_OPERATOR_PLAYWRIGHT_CORE: ${{ runner.temp }}/behaviour-core/node_modules/playwright-core run: | python3 scripts/browser-vm-linux-network.test.py - node --test --test-timeout=60000 elastos/esp/projections.test.mjs scripts/*.test.mjs scripts/build/*.test.mjs scripts/lib/*.test.mjs capsules/*/browser/*.test.mjs capsules/*/browser/src/*.test.mjs scripts/home-fixture-contracts-smoke.mjs scripts/documents-save-conflict-smoke.mjs scripts/gba-save-conflict-smoke.mjs + ./scripts/test-behaviour.sh + - name: Browser startup status screenshots + if: always() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + with: + name: browser-startup-status + path: ${{ runner.temp }}/browser-status-screenshots/ + if-no-files-found: ignore # One input for every ARM consumer. PRs restore their develop base's cache; # only a develop push can save a verified recipe result in this namespace. @@ -250,15 +251,6 @@ jobs: cargo test --workspace -- --list | tee "${RUNNER_TEMP}/elastos-test-list.txt" total=$(awk '/: test$/ {n++} END {print n+0}' "${RUNNER_TEMP}/elastos-test-list.txt") echo "Workspace listed tests: ${total}" | tee -a "${GITHUB_STEP_SUMMARY}" - - name: native network isolation and loopback proxy - timeout-minutes: 5 - run: | - set -euo pipefail - cargo build --quiet --manifest-path elastos/tools/browser-engine-supervisor/Cargo.toml - cargo build --quiet --manifest-path elastos/tools/browser-native-proxy-engine/Cargo.toml - git rev-parse HEAD HEAD^{tree} - sha256sum elastos/tools/browser-engine-supervisor/target/debug/browser-engine-supervisor elastos/tools/browser-native-proxy-engine/target/debug/browser-native-proxy-engine - sudo -n bash scripts/browser-native-supervisor-proxy-smoke.sh --prebuilt --require-isolation - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '26' @@ -600,6 +592,10 @@ jobs: mkdir -p "${RUNNER_TEMP}/source-home-logs" USER_HOME="${HOME}" SOURCE_HOME="${RUNNER_TEMP}/elastos-source-home" + export ELASTOS_NODE_BIN="$(command -v node)" + export ELASTOS_BROWSER_VM_TURNSERVER_BIN="$(command -v turnserver)" + [[ "${ELASTOS_NODE_BIN}" == /* && -x "${ELASTOS_NODE_BIN}" ]] || { echo 'CI requires its installed Node executable.' >&2; exit 1; } + [[ "${ELASTOS_BROWSER_VM_TURNSERVER_BIN}" == /* && -x "${ELASTOS_BROWSER_VM_TURNSERVER_BIN}" ]] || { echo 'CI requires its installed TURN executable.' >&2; exit 1; } mkdir -p "${SOURCE_HOME}" export CARGO_HOME="${CARGO_HOME:-${USER_HOME}/.cargo}" export RUSTUP_HOME="${RUSTUP_HOME:-${USER_HOME}/.rustup}" @@ -830,6 +826,10 @@ jobs: mkdir -p "${RUNNER_TEMP}/source-home-logs" USER_HOME="${HOME}" MAC_TEST_HOME="${RUNNER_TEMP}/elastos-mac-test-home" + export ELASTOS_NODE_BIN="$(command -v node)" + export ELASTOS_BROWSER_VM_TURNSERVER_BIN="$(command -v turnserver)" + [[ "${ELASTOS_NODE_BIN}" == /* && -x "${ELASTOS_NODE_BIN}" ]] || { echo 'CI requires its installed Node executable.' >&2; exit 1; } + [[ "${ELASTOS_BROWSER_VM_TURNSERVER_BIN}" == /* && -x "${ELASTOS_BROWSER_VM_TURNSERVER_BIN}" ]] || { echo 'CI requires its installed TURN executable.' >&2; exit 1; } mkdir -p "${MAC_TEST_HOME}" export CARGO_HOME="${CARGO_HOME:-${USER_HOME}/.cargo}" export RUSTUP_HOME="${RUSTUP_HOME:-${USER_HOME}/.rustup}" diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml index 5b36819a2..b9a018af2 100644 --- a/.github/workflows/release-package.yml +++ b/.github/workflows/release-package.yml @@ -19,6 +19,18 @@ on: description: Update version N+1, built with N's support required: true type: string + browser_image_cid: + description: Qualified common ARM64 guest archive CID (served as its immutable filename) + required: true + type: string + browser_image_sha256: + description: Independent SHA-256 of that guest archive + required: true + type: string + browser_image_size: + description: Exact archive bytes + required: true + type: string seed_package: description: Also build the Linux seed package (only to upgrade the seed Runtime) type: boolean @@ -35,6 +47,12 @@ env: SOURCE_COMMIT: ${{ inputs.source_commit }} INSTALL_VERSION: ${{ inputs.install_version }} UPDATE_VERSION: ${{ inputs.update_version }} + BROWSER_IMAGE_CID: ${{ inputs.browser_image_cid }} + BROWSER_IMAGE_SHA256: ${{ inputs.browser_image_sha256 }} + BROWSER_IMAGE_SIZE: ${{ inputs.browser_image_size }} + BROWSER_IMAGE_PUBLISHER_DID: ${{ vars.BROWSER_IMAGE_PUBLISHER_DID }} + BROWSER_IMAGE_PUBLISHER_NODE_ID: ${{ vars.BROWSER_IMAGE_PUBLISHER_NODE_ID }} + BROWSER_IMAGE_CONNECT_TICKET: ${{ secrets.BROWSER_IMAGE_CONNECT_TICKET }} CARGO_BUILD_JOBS: '4' PYTHONDONTWRITEBYTECODE: '1' # Shipped release builds stay hermetic: no compiler wrapper or cache. @@ -65,6 +83,16 @@ jobs: # Preparation may run beside CI. Publication still requires green CI # for this exact source and the release acceptance gate. echo "source_tree=$tree" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + ref: ${{ inputs.source_commit }} + persist-credentials: false + - name: admit the common immutable image selector + run: | + set -euo pipefail + python3 -I -S scripts/browser-image-cid-input.py "$BROWSER_IMAGE_CID" "$BROWSER_IMAGE_SHA256" "$BROWSER_IMAGE_SIZE" + [[ "$BROWSER_IMAGE_PUBLISHER_DID" == did:key:* && "$BROWSER_IMAGE_PUBLISHER_NODE_ID" =~ ^[0-9a-f]{64}$ ]] \ + || { echo 'Configure the explicit Browser image publisher DID and Carrier node ID' >&2; exit 1; } mac: name: Apple silicon N and N+1 @@ -107,7 +135,7 @@ jobs: timeout-minutes: 20 run: | set -euo pipefail - brew install e2fsprogs coturn pkg-config openssl@3 + brew install e2fsprogs coturn pkg-config openssl@3 autoconf automake libtool zstd python3 -I -S ../workflow/.github/scripts/release-mac-capacity.py - name: keep the capacity record if: always() && env.RELEASE_ROOT != '' @@ -131,8 +159,10 @@ jobs: [[ ! -e "$CARGO_TARGET_DIR" && ! -L "$CARGO_TARGET_DIR" && ! -e "$CARGO_BUILD_BUILD_DIR" && ! -L "$CARGO_BUILD_BUILD_DIR" ]] reuse=() [[ "$name" == N ]] || reuse=(--reuse-support "$inputs/N") + image=() + [[ "$name" != N ]] || image=(--browser-vm-image-cid "$BROWSER_IMAGE_CID" --browser-vm-image-sha256 "$BROWSER_IMAGE_SHA256" --browser-vm-image-size "$BROWSER_IMAGE_SIZE") HOME="$RELEASE_ROOT/home" scripts/prepare-release-platform.sh \ - --version "$version" --output "$inputs/$name" ${reuse[@]+"${reuse[@]}"} + --version "$version" --output "$inputs/$name" ${reuse[@]+"${reuse[@]}"} ${image[@]+"${image[@]}"} python3 -I -S scripts/release-platform-input.py verify "$inputs/$name" > /dev/null rm -rf "$CARGO_TARGET_DIR" "$CARGO_BUILD_BUILD_DIR" done @@ -297,7 +327,7 @@ jobs: timeout-minutes: 15 run: | sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 update - sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 install -y build-essential cmake coturn e2fsprogs ffmpeg musl-tools nasm pkg-config + sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 install -y build-essential cmake coturn e2fsprogs ffmpeg musl-tools nasm pkg-config autoconf automake libtool libclang-dev linux-libc-dev zstd - name: download pinned ARM64 engine input if: matrix.platform == 'aarch64-linux' uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 @@ -319,8 +349,10 @@ jobs: [[ ! -e "$CARGO_TARGET_DIR" && ! -L "$CARGO_TARGET_DIR" && ! -e "$CARGO_BUILD_BUILD_DIR" && ! -L "$CARGO_BUILD_BUILD_DIR" ]] reuse=() [[ "$name" == N ]] || reuse=(--reuse-support "$inputs/N") + image=() + [[ "$name" != N || "$RELEASE_PLATFORM" != aarch64-linux ]] || image=(--browser-vm-image-cid "$BROWSER_IMAGE_CID" --browser-vm-image-sha256 "$BROWSER_IMAGE_SHA256" --browser-vm-image-size "$BROWSER_IMAGE_SIZE") HOME="$RELEASE_ROOT/home" scripts/prepare-release-platform.sh \ - --version "$version" --output "$inputs/$name" ${reuse[@]+"${reuse[@]}"} + --version "$version" --output "$inputs/$name" ${reuse[@]+"${reuse[@]}"} ${image[@]+"${image[@]}"} python3 -I -S scripts/release-platform-input.py verify "$inputs/$name" > /dev/null rm -rf "$CARGO_TARGET_DIR" "$CARGO_BUILD_BUILD_DIR" done diff --git a/AGENTS.md b/AGENTS.md index 6d6294b8a..e77eb0041 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -428,12 +428,13 @@ Use target roles consistently: - Mac: staging, macOS VZ Browser proof, and cross-platform proof; - Jetson: Linux/crosvm native Browser target and intended main device proof. -Mac staging requires durable SSH before serious testing. `tmate` is acceptable +Browser staging on this local Mac uses direct execution. Remote Mac staging +requires durable SSH before serious testing. `tmate` is acceptable only as a break-glass bootstrap channel. During that bootstrap, create or reuse a dedicated staging account, install an agent-owned public key in the target account's authorized-keys file, disable password assumptions, record a local SSH host alias, and verify non-interactive commands work. If no durable SSH is available, -say the Mac is blocked instead of implying it was verified. +say remote Mac staging is blocked instead of implying it was verified. Do not commit staging aliases, private key names, reverse-tunnel ports, local worktree paths, or operator usernames. Keep those details in private operational diff --git a/capsules/browser-engine-adapter/Cargo.lock b/capsules/browser-engine-adapter/Cargo.lock index db43ee2e4..f8e6dbb58 100644 --- a/capsules/browser-engine-adapter/Cargo.lock +++ b/capsules/browser-engine-adapter/Cargo.lock @@ -85,6 +85,7 @@ name = "elastos-common" version = "0.7.1" dependencies = [ "hex", + "libc", "serde", "serde_json", "sha2", diff --git a/capsules/browser-engine-adapter/Cargo.toml b/capsules/browser-engine-adapter/Cargo.toml index dc154beb1..80f5ae913 100644 --- a/capsules/browser-engine-adapter/Cargo.toml +++ b/capsules/browser-engine-adapter/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" description = "ElastOS Browser Engine Adapter provider capsule" license = "MIT" +[features] +default = [] +test-engine = [] + [[bin]] name = "browser-engine-adapter" path = "src/main.rs" diff --git a/capsules/browser-engine-adapter/src/supervisor.rs b/capsules/browser-engine-adapter/src/supervisor.rs index abde163cb..574e79da1 100644 --- a/capsules/browser-engine-adapter/src/supervisor.rs +++ b/capsules/browser-engine-adapter/src/supervisor.rs @@ -4,6 +4,20 @@ use std::io::{Read, Write}; use std::process::{Command, Stdio}; use std::time::{Duration, Instant}; +// The adapter owns the writer; transient launchers pass only the reader on. +fn configure_helper_owner(command: &mut Command) -> std::io::Result<()> { + use elastos_common::process_lifetime::ParentLifetime; + use std::sync::OnceLock; + if ParentLifetime::configure_inherited(command)? { + return Ok(()); + } + static OWNER: OnceLock> = OnceLock::new(); + match OWNER.get_or_init(ParentLifetime::new) { + Ok(owner) => owner.configure(command), + Err(error) => Err(std::io::Error::new(error.kind(), error.to_string())), + } +} + const MAX_BROWSER_ENGINE_LAUNCH_REQUEST_BYTES: usize = 64 * 1024; #[derive(Debug)] @@ -115,6 +129,8 @@ pub(super) fn run_supervisor_launch( if transport.is_none() { command.env("ELASTOS_BROWSER_ENGINE_REQUEST", &serialized); } + configure_helper_owner(&mut command) + .map_err(|err| SupervisorLaunchError::process(err.to_string()))?; let mut child = command .spawn() .map_err(|err| SupervisorLaunchError::process(err.to_string()))?; @@ -203,15 +219,16 @@ fn kill_and_reap(child: &mut std::process::Child) { } pub(super) fn run_supervisor_prewarm(supervisor: &EngineSupervisorConfig) -> Result { - let mut child = Command::new(&supervisor.program) + let mut command = Command::new(&supervisor.program); + command .args(&supervisor.args) .envs(&supervisor.env) .env("ELASTOS_BROWSER_VM_PREWARM_CONTROL_SERVICE", "1") .stdin(Stdio::null()) .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|err| err.to_string())?; + .stderr(Stdio::piped()); + configure_helper_owner(&mut command).map_err(|err| err.to_string())?; + let mut child = command.spawn().map_err(|err| err.to_string())?; let deadline = Instant::now() + Duration::from_millis(supervisor.timeout_ms.min(30_000)); let status = loop { match child.try_wait() { diff --git a/capsules/browser-engine-adapter/src/validation.rs b/capsules/browser-engine-adapter/src/validation.rs index dbd15db01..7e1d7ba26 100644 --- a/capsules/browser-engine-adapter/src/validation.rs +++ b/capsules/browser-engine-adapter/src/validation.rs @@ -25,6 +25,12 @@ pub(super) fn looks_like_bridge_provider_config(config: &Value) -> bool { } pub(super) fn validate_adapter(adapter: &AdapterConfig) -> Result<(), String> { + validate_product_engine(adapter.kind, cfg!(any(test, feature = "test-engine")))?; + if adapter.kind == AdapterKind::ChromiumMicrovm + && adapter.display_modes != vec![BrowserDisplayMode::WebrtcRemoteDisplay] + { + return Err("The VM Browser Engine requires webrtc_remote_display".to_string()); + } if !is_safe_id(&adapter.id) { return Err("adapter id must be a safe identifier".to_string()); } @@ -57,6 +63,13 @@ pub(super) fn validate_adapter(adapter: &AdapterConfig) -> Result<(), String> { Ok(()) } +fn validate_product_engine(kind: AdapterKind, test_engine: bool) -> Result<(), String> { + if kind != AdapterKind::ChromiumMicrovm && !test_engine { + return Err("Browser uses the VM Engine. Select a compatible local VM or an approved remote Engine. Host-native and Playwright engines are test-only.".to_string()); + } + Ok(()) +} + pub(super) fn validate_supervisor(supervisor: &EngineSupervisorConfig) -> Result<(), String> { if supervisor.program.is_empty() || !supervisor.program.starts_with('/') { return Err("browser engine supervisor program must be absolute".to_string()); @@ -282,3 +295,26 @@ pub(super) fn validate_relay_ipc(endpoint: &RelayIpcEndpoint) -> Result<(), Stri } Ok(()) } + +#[cfg(test)] +mod product_engine_tests { + use super::*; + + #[test] + fn production_accepts_only_the_vm_engine() { + assert!(validate_product_engine(AdapterKind::ChromiumMicrovm, false).is_ok()); + for kind in [ + AdapterKind::Cef, + AdapterKind::ChromiumHeadless, + AdapterKind::SelkiesGstreamer, + AdapterKind::HostedRemoteBrowser, + AdapterKind::ContractProof, + AdapterKind::Webview2, + AdapterKind::Geckoview, + AdapterKind::Wkwebview, + ] { + assert!(validate_product_engine(kind, false).is_err()); + assert!(validate_product_engine(kind, true).is_ok()); + } + } +} diff --git a/capsules/browser/browser/browser-status.js b/capsules/browser/browser/browser-status.js index 6576a5d53..afe4b9c57 100644 --- a/capsules/browser/browser/browser-status.js +++ b/capsules/browser/browser/browser-status.js @@ -72,7 +72,7 @@ export function friendlyOpenError(error) { case "artifact_invalid": return "Browser Engine files need repair. Prepare this Engine or choose another approved Engine."; case "host_unsupported": - return "The selected Engine needs a compatible host. Choose another approved Engine."; + return "Browser virtualization is unavailable on the selected host. Use a host with Apple Virtualization or KVM, or choose another approved Engine."; case "readiness_unsupported": return "Browser Engine needs an update to report readiness. Update it or choose another approved Engine."; case "control_unavailable": @@ -94,6 +94,14 @@ export function friendlyOpenError(error) { return "Browser Engine is unavailable. Choose an available approved Engine."; } } + if ( + error?.payload?.code === "profile_recovery_required" && + ["terminal_pre_effect_failure", "terminal_post_effect_cleanup"].includes(outcome?.state) && + outcome.effects.page_acquired === false && + outcome.effects.vm_acquired === false + ) { + return "Browser profile setup was interrupted. In Settings, select Reset profile to start again. Reset clears this account's Browser data."; + } if (outcome?.state === "terminal_pre_effect_failure") { if (error.status === 403) { return "This page was blocked by your Exit Node settings."; diff --git a/capsules/browser/browser/browser-status.test.mjs b/capsules/browser/browser/browser-status.test.mjs index 904384568..8e16ac641 100644 --- a/capsules/browser/browser/browser-status.test.mjs +++ b/capsules/browser/browser/browser-status.test.mjs @@ -70,6 +70,28 @@ test("pre-effect profile placement keeps the Runtime transfer sentence", () => { assert.equal(friendlyOpenError(error), message); }); +test("interrupted profile setup gives reset guidance after terminal cleanup", () => { + for (const state of ["terminal_pre_effect_failure", "terminal_post_effect_cleanup"]) { + const error = openError(state); + error.payload.code = "profile_recovery_required"; + assert.equal( + friendlyOpenError(error), + "Browser profile setup was interrupted. In Settings, select Reset profile to start again. Reset clears this account's Browser data.", + ); + } +}); + +test("profile recovery guidance keeps pending or acquired ownership visible", () => { + for (const error of [ + openError("cleanup_pending"), + openError("terminal_post_effect_cleanup", { page_acquired: true, vm_acquired: true }), + new Error("profile_recovery_required"), + ]) { + error.payload = { ...error.payload, code: "profile_recovery_required" }; + assert.doesNotMatch(friendlyOpenError(error), /select Reset profile/); + } +}); + test("pre-effect viewer ingress capacity keeps the Runtime capacity sentence", () => { const message = "Runtime viewer ingress capacity unavailable"; const error = openError("terminal_pre_effect_failure"); @@ -224,7 +246,7 @@ test("WebRTC diagnostics expose only selected pair types and inbound counters", test("readiness failures explain the repair while cleanup retains priority", () => { const expected = new Map([ ["artifact_invalid", /files need repair/], - ["host_unsupported", /needs a compatible host/], + ["host_unsupported", /Browser virtualization is unavailable on the selected host/], ["readiness_unsupported", /update to report readiness/], ["control_unavailable", /Restore its connection/], ["preparation_required", /needs preparation/], diff --git a/capsules/browser/browser/browser.js b/capsules/browser/browser/browser.js index 8f45dbe15..2835eb8a9 100644 --- a/capsules/browser/browser/browser.js +++ b/capsules/browser/browser/browser.js @@ -47,7 +47,7 @@ const GUARANTEE_OPERATOR_RBI = "operator_rbi"; const GUARANTEE_POLICY_WEBVIEW = "policy_webview"; const LOCAL_EXIT_LABEL = "This device"; const LOCAL_EXIT_SUMMARY = "Use this device's Exit Node for Browser traffic."; -const DEFAULT_ENGINE_LABEL = "Automatic"; +const DEFAULT_ENGINE_LABEL = "Local VM"; const DEFAULT_ENGINE_SUMMARY = "Runtime chooses a compatible Browser Engine."; const BROWSER_WINDOW_CLOSE_REQUEST_TYPE = "elastos.browser.window-close.request/v1"; @@ -85,6 +85,9 @@ const backButton = document.querySelector("#browser-back"); const forwardButton = document.querySelector("#browser-forward"); const refreshButton = document.querySelector("#browser-refresh"); const profileResetButton = document.querySelector("#browser-profile-reset"); +const profileResetConfirmation = document.querySelector("#browser-profile-reset-confirmation"); +const profileResetCancel = document.querySelector("#browser-profile-reset-cancel"); +const profileResetCommit = document.querySelector("#browser-profile-reset-commit"); const settingsButton = document.querySelector("#browser-settings"); const settingsPanel = document.querySelector("#browser-settings-panel"); const settingsCloseButton = document.querySelector("#browser-settings-close"); @@ -146,6 +149,7 @@ function setSettingsOpen(open) { return; } settingsPanel.hidden = !open; + if (!open) profileResetConfirmation.hidden = true; settingsButton.setAttribute("aria-expanded", open ? "true" : "false"); } @@ -386,6 +390,7 @@ function setLoading(loading) { if (profileResetButton) { profileResetButton.disabled = loading; } + if (profileResetCommit) profileResetCommit.disabled = loading; renderEmpty.hidden = true; refreshButton.disabled = loading || !currentPage; updateNavState(); @@ -2086,7 +2091,7 @@ async function waitForRuntimeOpen(response, { engineLabel, exitLabel }) { while (Date.now() - startedAt < BROWSER_OPEN_POLL_TIMEOUT_MS) { const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000)); showStatus( - `Opening ${engineLabel} with ${exitLabel}... ${elapsedSeconds}s`, + `Browser is preparing ${engineLabel} with ${exitLabel} (${elapsedSeconds}s). Wait for preparation to finish, then close Browser if needed.`, { sticky: true }, ); await wait(BROWSER_OPEN_POLL_INTERVAL_MS); @@ -2481,9 +2486,6 @@ async function resetBrowserProfile() { showStatus("Browser profile reset requires a Browser launch token.", { sticky: true }); return; } - if (!window.confirm("Reset Browser cookies, local storage, history, and cache for this account?")) { - return; - } const activePage = currentPage; const activeGeneration = currentPageGeneration; const activeOwner = runtimePageOwner(activePage, activeGeneration); @@ -2534,6 +2536,18 @@ async function resetBrowserProfile() { } profileResetButton?.addEventListener("click", () => { + profileResetConfirmation.hidden = false; + profileResetCancel.focus({ preventScroll: true }); +}); + +profileResetCancel?.addEventListener("click", () => { + profileResetConfirmation.hidden = true; + profileResetButton.focus({ preventScroll: true }); +}); + +profileResetCommit?.addEventListener("click", () => { + if (profileResetConfirmation.hidden || profileResetCommit.disabled) return; + profileResetConfirmation.hidden = true; resetBrowserProfile().catch((error) => { showStatus(friendlyOpenError(error), { sticky: true }); }); @@ -2733,14 +2747,11 @@ const requestedStartupUrl = params.get("url"); const initialUrl = requestedStartupUrl || DEFAULT_URL; addressInput.value = initialUrl; setLoading(true); +showStatus("Browser is checking its Engine. Wait, or reopen Browser from Home to retry.", { sticky: true }); fetchBrowserSummary({ remoteServices: false }) .then(async (summary) => { if (await restoreRuntimePageViewer(summary)) return; - if (!requestedStartupUrl) { - setLoading(false); - return; - } - return requestRuntimeOpen(requestedStartupUrl, { history: "replace" }); + return requestRuntimeOpen(initialUrl, { history: "replace" }); }) .catch((error) => { if (unloadCleanupStarted || homeWindowCloseInFlight || homeWindowTerminalCloseConfirmed) return; diff --git a/capsules/browser/browser/index.html b/capsules/browser/browser/index.html index 33e32b8f2..469c315ad 100644 --- a/capsules/browser/browser/index.html +++ b/capsules/browser/browser/index.html @@ -66,9 +66,9 @@ -

Use the best Browser Engine available.

+

Select this device’s VM Engine or an approved remote Engine.