Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,704 changes: 1,238 additions & 466 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions src/apps/desktop/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"windows": ["main", "agent-companion-pet", "spotlight"],
"permissions": [
"log:default",
"autostart:default",
"core:default",
"core:path:default",
"core:event:default",
Expand Down Expand Up @@ -45,12 +44,6 @@
"core:window:allow-unmaximize",
"core:window:allow-unminimize",
"core:window:allow-set-min-size",
"dialog:default",
"dialog:allow-open",
"dialog:allow-save",
"dialog:allow-ask",
"dialog:allow-confirm",
"dialog:allow-message",
"opener:default",
"opener:allow-open-url",
{
Expand Down
2 changes: 1 addition & 1 deletion src/apps/desktop/src/api/browser_control_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn default_cdp_port() -> u16 {
/// no live endpoint to reattach to, it does nothing and leaves the on-demand
/// path to handle it.
pub fn init_on_startup() {
tokio::spawn(async {
tauri::async_runtime::spawn(async {
if !auto_connect_on_startup_enabled().await {
return;
}
Expand Down
37 changes: 30 additions & 7 deletions src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,24 @@ export default class EntryAbility extends RustAbility {
});
RustModule.registerArktsFunction('set_decorations_ohos', async (err: Error, arg: string): Promise<string> => {
const req: SetDecorationsArg = JSON.parse(arg);
const mainWin = windowStage.getMainWindowSync();
try {
await windowStage.getMainWindowSync().setWindowDecorVisible(req.visible);
// Showing native title buttons makes HarmonyOS fall back to hover-only
// window decorations. Re-apply the same custom title-bar configuration
// used at startup after restoring the buttons.
await mainWin.setWindowTitleButtonVisible(
req.visible,
req.visible,
req.visible,
);
if (req.visible) {
mainWin.setWindowDecorHeight(44);
mainWin.setWindowTitleMoveEnabled(false);
mainWin.setWindowDecorVisible(false);
}
} catch (e) {
const be = e as BusinessError;
hilog.warn(DOMAIN, TAG, `setWindowDecorVisible failed: ${be.message}`);
hilog.warn(DOMAIN, TAG, `setWindowTitleButtonVisible failed: ${be.message}`);
}
return '';
});
Expand Down Expand Up @@ -334,17 +347,27 @@ export default class EntryAbility extends RustAbility {
dispWidth = disp.width;
dispHeight = disp.height;
scale = disp.densityPixels > 0 ? disp.densityPixels : 1;
// A floating window may not intersect the system bar when this query
// runs, in which case getWindowAvoidArea() reports zero. The display's
// available bounds still exclude the desktop bar, so keep that
// right/bottom reservation as a monitor-level fallback.
if (disp.availableWidth > 0 && disp.availableWidth <= disp.width) {
avoidRight = Math.max(avoidRight, disp.width - disp.availableWidth);
}
if (disp.availableHeight > 0 && disp.availableHeight <= disp.height) {
avoidBottom = Math.max(avoidBottom, disp.height - disp.availableHeight);
}
} catch (e) {
const be = e as BusinessError;
hilog.warn(DOMAIN, TAG, `getDefaultDisplaySync failed: ${be.message}`);
}
try {
const avoidArea = windowStage.getMainWindowSync()
.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
avoidTop = avoidArea.topRect.height;
avoidBottom = avoidArea.bottomRect.height;
avoidLeft = avoidArea.leftRect.width;
avoidRight = avoidArea.rightRect.width;
avoidTop = Math.max(avoidTop, avoidArea.topRect.height);
avoidBottom = Math.max(avoidBottom, avoidArea.bottomRect.height);
avoidLeft = Math.max(avoidLeft, avoidArea.leftRect.width);
avoidRight = Math.max(avoidRight, avoidArea.rightRect.width);
} catch (e) {
const be = e as BusinessError;
hilog.warn(DOMAIN, TAG, `getWindowAvoidArea failed: ${be.message}`);
Expand Down Expand Up @@ -389,7 +412,7 @@ export default class EntryAbility extends RustAbility {
});
RustModule.registerArktsFunction('maximize_ohos', async (err: Error, arg: string): Promise<string> => {
try {
await windowStage.getMainWindowSync().maximize();
await windowStage.getMainWindowSync().maximize(window.MaximizePresentation.EXIT_IMMERSIVE);
} catch (e) {
const be = e as BusinessError;
hilog.warn(DOMAIN, TAG, `maximize failed: ${be.message}`);
Expand Down
26 changes: 25 additions & 1 deletion src/crates/adapters/webdriver/src/platform/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ mod imp {
}
}

#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
mod imp {
use super::*;

Expand All @@ -594,6 +594,30 @@ mod imp {
}
}

#[cfg(target_env = "ohos")]
mod imp {
use super::*;

pub(super) async fn take_screenshot<R: Runtime>(
_webview: Webview<R>,
_timeout_ms: u64,
) -> Result<String, WebDriverErrorResponse> {
Err(WebDriverErrorResponse::unsupported_operation(
"HarmonyOS ArkWeb screenshots require a native capture provider",
))
}

pub(super) async fn print_page<R: Runtime>(
_webview: Webview<R>,
_timeout_ms: u64,
_options: &PrintOptions,
) -> Result<String, WebDriverErrorResponse> {
Err(WebDriverErrorResponse::unsupported_operation(
"HarmonyOS ArkWeb printing requires a native print provider",
))
}
}

#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
mod imp {
use super::*;
Expand Down
5 changes: 5 additions & 0 deletions src/crates/adapters/webdriver/src/platform/evaluator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ pub(crate) async fn evaluate_script<R: tauri::Runtime>(
async_mode: bool,
frame_context: &Value,
) -> Result<Value, WebDriverErrorResponse> {
// OpenHarmony's ArkWeb host currently reaches this generic evaluator via
// the Tauri fork's Webview::eval implementation. The injected bridge is
// pure page JavaScript, so pointer/wheel/key actions remain shared. A
// future ArkWeb-native provider can replace only this branch when it needs
// a direct runJavaScript/result callback instead of Tauri IPC.
#[cfg(target_os = "macos")]
{
let _ = state;
Expand Down
25 changes: 24 additions & 1 deletion src/crates/adapters/webdriver/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,28 @@ pub(crate) async fn run_script(
let timeout_ms = session.timeouts.script.max(5_000);

let frame_context = script::serialize_frame_context(&session.frame_context);
Err(WebDriverErrorResponse::no_such_window("No such windows"))
let window = state
.app
.get_webview_window(&session.current_window)
.ok_or_else(|| {
WebDriverErrorResponse::no_such_window(format!(
"Window not found: {}",
session.current_window
))
})?;

// The JavaScript bridge is platform-neutral. Each host only supplies the
// WebView evaluator and result transport in `platform::evaluator` (the
// OHOS target uses the generic Tauri bridge until an ArkWeb-native host
// adapter is wired in).
platform::evaluator::evaluate_script(
state,
window.as_ref().clone(),
timeout_ms,
script_source,
&args,
async_mode,
&frame_context,
)
.await
}
20 changes: 20 additions & 0 deletions src/crates/adapters/webdriver/src/runtime/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,23 @@ if (!window.__bitfunWd) {{
core_tail = core::tail()
)
}

#[cfg(test)]
mod tests {
use super::bridge_helper_script;

#[test]
fn bridge_includes_harmony_safe_pointer_actions() {
let bridge = bridge_helper_script();

for symbol in [
"performPointerSourceActions",
"performWheelSourceActions",
"performKeySourceActions",
"dispatchPointerClick",
"releaseActions",
] {
assert!(bridge.contains(symbol), "missing injected symbol: {symbol}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub async fn create(
let set_window_rect = cfg!(any(
target_os = "macos",
target_os = "windows",
target_os = "linux"
all(target_os = "linux", not(target_env = "ohos"))
));

Ok(WebDriverResponse::success(json!({
Expand Down
1 change: 0 additions & 1 deletion src/crates/assembly/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ tokio-util = { workspace = true, optional = true }
napi-ohos = { workspace = true }
napi-derive-ohos = { workspace = true }
tokio-stream = { workspace = true }
tokio-util = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true, optional = true }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::sync::Arc;
use bitfun_product_domains::appearance_market::{
AppearanceAdminSubmissionDetail, AppearanceCursorPage, AppearanceMarketListingDetail,
AppearanceMarketListingSummary, AppearanceMarketSort, AppearanceMarketSubmission,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,6 @@ $transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}

.bitfun-toolbar-mode--ohos .bitfun-toolbar-mode__header-overflow {
display: none;
}

.bitfun-toolbar-mode__header-collapsed-actions {
display: flex;
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { isOpenHarmonyRuntime } from '@/infrastructure/runtime';
import { createLogger } from '@/shared/utils/logger';
import {
MAIN_WINDOW_DEFAULT_SIZE,
Expand Down Expand Up @@ -36,6 +37,9 @@ const setMainWindowTransientGeometry = async (transient: boolean): Promise<void>
await systemAPI.setMainWindowTransientGeometry(transient);
};

const waitForAnimationFrame = (): Promise<void> =>
new Promise((resolve) => window.requestAnimationFrame(() => resolve()));

const restoreMainWindowFromToolbarMode = async (
ops: WindowOps,
saved: SavedWindowState | null,
Expand Down Expand Up @@ -79,6 +83,16 @@ const restoreMainWindowFromToolbarMode = async (
await ops.center();
}

if (isOpenHarmonyRuntime()) {
try {
// Geometry changes, especially maximize(), can reset HarmonyOS to its
// hover-only immersive chrome. Restore the normal title bar last.
await ops.setDecorations(saved?.isDecorated ?? true);
} catch (error) {
log.debug('Failed to re-apply HarmonyOS window decorations (ignored)', error);
}
}

await ops.setMinSizeLogical({
width: MAIN_WINDOW_MIN_SIZE.width,
height: MAIN_WINDOW_MIN_SIZE.height,
Expand Down Expand Up @@ -198,14 +212,31 @@ export const ToolbarModeProvider: React.FC<ToolbarModeProviderProps> = ({ childr
const disableToolbarMode = useCallback(async () => {
const ops = createWindowOps();
const isMacOS = isMacOSPlatform();
const documentElement = isOpenHarmonyRuntime() ? document.documentElement : null;
const previousVisibility = documentElement?.style.visibility ?? '';

try {
if (documentElement) {
documentElement.style.visibility = 'hidden';
await waitForAnimationFrame();
}

await restoreMainWindowFromToolbarMode(ops, savedWindowStateRef.current, isMacOS);
setIsToolbarMode(false);
setIsExpanded(false);
await restoreMainWindowFromToolbarMode(ops, savedWindowStateRef.current, isMacOS);
savedWindowStateRef.current = null;

if (documentElement) {
// Let React commit the normal layout before revealing the restored window.
await waitForAnimationFrame();
await waitForAnimationFrame();
}
} catch (error) {
log.error('Failed to disable toolbar mode', error);
} finally {
if (documentElement) {
documentElement.style.visibility = previousVisibility;
}
}
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ const ohosOps = (): WindowOps => ({
return await workspaceAPI.window_is_maximized();
},
async isDecorated(): Promise<boolean | undefined> {
return false;
// OHOS main window ships with native window decorations (min/max/close)
// visible by default; report true so exiting toolbar mode restores them.
return true;
},
async unmaximize(): Promise<void> {
await workspaceAPI.unmaximizeOhos();
Expand Down
Loading