feat(ohos): pluginized bridge architecture with lifecycle serialization and plugin migrations - #46
Open
ljy9812 wants to merge 29 commits into
Open
feat(ohos): pluginized bridge architecture with lifecycle serialization and plugin migrations#46ljy9812 wants to merge 29 commits into
ljy9812 wants to merge 29 commits into
Conversation
The four newly-added bridge methods (requestRedraw, requestUserAttention, setImePosition, setWindowDraggable) are invoked from Rust via synchronous func.call (NAPI-reentrant context). Their synchronous catch blocks (and the window-not-found early-return in setWindowDraggable) used bare hilog.warn/info, which can throw 'Argc mismatch' and mask the original error (ohos-constraints 2.3). Switched those sync paths to safeLogError. Async .then()/.catch() Promise callbacks are left on hilog (not reentrant).
- window/mod.rs: bridge set_window_topmost/title/limits/ime_position/draggable/ redraw/request_user_attention; instanceKey for specified launchType - app.rs: notify_window_status NAPI queue + drain (windowStatusChange backfill) - event.rs: FoldDisplayModeChange MainEvent - ArkHelper.ets: ArkTS impls for the new window ops - NativeAbility.ets + FloatPage.ets: windowStatusChange registration + seed; FloatPage title/min/max/close buttons, startMoving onTouch - WindowManager.ets: showMainAbility(startAbility instanceKey=main), setPointerStyle real windowId, decoration flag interception (closable/ maximizable/minimizable/resizable), setWindowTitle/Limits/Topmost - type.ets: ArkHelper interface + UIAbilityWantParams.instanceKey
…otif id, startMoving guard Addresses the 4 findings in PR#45 review #4948263922. F1 (🟡 main-window show non-functional under standard launchType): showMainAbility() did startAbility(instanceKey='main'), but instanceKey is ignored under launchType "standard" (tauri-cli template) → every call spawned a DUPLICATE EntryAbility. The demo masked this (omits launchType → singleton → onNewWant). Replaced the main-window show path with restore() — the documented inverse of minimize() (the minimize() doc routes main-window restoration through restore(), not showWindow()). Guarded by isMinimized() so a redundant set_visible(true) on a visible maximized window doesn't un-maximize it. Removed the now-unused Want import and the misleading onAcceptWant comments. F2 (🔵 FloatPage isMaximized local @State desyncs from system/tao triggers): Drive isMaximized from the existing windowStatusChange callback (+ initial seed) instead of an optimistic button-click toggle, so the maximize/restore icon stays in sync when maximize/restore is triggered by the system or the tao mirror (apply_window_status). isMaximized = (status === WindowStatusType.MAXIMIZE). F3 (🔵 request_user_attention hardcoded notif id:1 collides): Concurrent calls shared id:1 → second overwrote first. Now uses a closure-level monotonic counter so each call gets a unique id. (Signature kept no-arg: tao's request_user_attention does not forward windowId, so per-window id needs a full-chain param addition — monotonic id fixes the collision today.) F4 (🔵 FloatPage startMoving API14+ with no guard): startMoving is API14+ but compatibleSdkVersion is API12 → on API<14 the method is undefined and threw on every touch-down. Added deviceInfo.sdkApiVersion<14 guard (matches sibling setWindowTopmost/restoreWindow). No PanGesture fallback attached: a simultaneously bound PanGesture consumes the Down event and blocks startMoving, so the two cannot coexist — drag targets API14+ desktop.
…indowId helper
set_cursor_grab resolves tao's window id to the real OHOS window id via a
synchronous getRealWindowId ArkTS helper, then calls
OH_WindowManager_LockCursor/UnlockCursor from libnative_window_manager.so.
The library is dlopen'd lazily (never statically linked — compatibleSdk is
API 12 and older images lack the symbols; dlsym-null doubles as the version
guard → NotSupported). Typed CursorGrabError {NotSupported, OsCode, Bridge}
lets tao map errors without string matching; unlock is idempotent
(1300002 after focus-loss auto-release maps to Ok). Also declares
LOCK_WINDOW_CURSOR in the HAR module.json5 (self-documenting; HAP-side
declaration is authoritative).
…uard 0cac4c3 replaced WebViewStyle width/height with "100%" natural layout to fix the main-webview resize relayout bug, which silently broke child webview geometry: a child created at (x, y, w, h) rendered window-sized at (x, y), so its bottom-right overflow was clipped by the window edge (clipped amount == position offset). Restore data.style.width/height in WebBuilder/EmbeddedWebBuilder, and add a naturalLayout flag (set at creation when style.width is absent): natural webviews — the main webview of each window — have runtime set_bounds width/height stripped in updateWebviewStyle, keeping "100%" sizing so the 0cac4c3 fix does not regress. Explicit-bounds child webviews accept width/height at creation and at runtime. Verified on device (HUAWEI MateBook Pro 2in1): create_webview multi-webview manual test renders the exact 300x200 @(50,50) rect.
…on and plugin migrations - pluginize native bridge capabilities (harmony-contrib#67), normalize node surface (harmony-contrib#70), transparent XComponent background (harmony-contrib#73), webview controller release safety (harmony-contrib#69) - plugin module agnostic refactor (harmony-contrib#75), serialize ability plugin lifecycle (harmony-contrib#74) - account/updater bridge migration and clipboard write-html API - dead code cleanup superseded by the bridge plugin architecture - deps upgrade, format + git hook Co-Authored-By: Claude <noreply@anthropic.com>
…ableDrag, fix setPointerStyle C5 - instanceKey: 'specified launchType' mechanism doesn't exist (template is standard, SDK 12 has no abilityStage/onAcceptWant) — delete the whole passthrough (window/mod.rs want.set, ArkHelper.ets, type.ets field) and correct the type.ets comment back to launchType:standard - setWindowDraggable: enableDrag is API20+; add deviceInfo.sdkApiVersion < 20 guard (safeLogError + return) matching the startMoving/setWindowTopmost pattern - setPointerStyle: bare hilog in the NAPI-reentrant sync body throws 'Argc mismatch' (C5) — success log misreported failures, catch log swallowed the original err; switch to console fallback like safeLogError/getRealWindowId
feat(ohos): window ops bridge + status readback + FloatPage decorations
…on and plugin migrations - pluginize native bridge capabilities (harmony-contrib#67), normalize node surface (harmony-contrib#70), transparent XComponent background (harmony-contrib#73), webview controller release safety (harmony-contrib#69) - plugin module agnostic refactor (harmony-contrib#75), serialize ability plugin lifecycle (harmony-contrib#74) - account/updater bridge migration and clipboard write-html API - dead code cleanup superseded by the bridge plugin architecture - deps upgrade, format + git hook Co-Authored-By: Claude <noreply@anthropic.com>
Replace AppInner.window_rect (single shared field) with
window_rects: HashMap<i64, Rect> keyed by windowId (0 = main window,
>0 = Float sub-window via NEXT_WINDOW_ID). Add window_rect_for /
set_window_rect; release_render_owner clears only key 0.
ArkTS: wrap native window.RectChangeOptions / size-change options into
{ windowId, reason, rect } before invoking the Rust lifecycle closures
(NativeAbility.ets main window id=0, BridgeHost.ets component-window
second registration id=0). Float sub-windows never reach attachComponent
(DefaultXComponent early-returns), so register windowRectChange in
WindowManager.createSubWindow with callback injection
(registerRectChangeCallback) and off() cleanup in removeWindow.
Rust closures (lifecycle.rs) read windowId from the wrapped options and
store each rect under its own key; MainEvent::WindowResize and
ContentRect now carry window_id for tao event routing (Phase 3).
Part of openspec change p1-window-state-per-window-rect (Phase 2, D1-D5).
Co-Authored-By: Claude <noreply@anthropic.com>
…+trailing) openspec p2-mainthread-event-hygiene layer 2: high-frequency window operations (30+ subwindow create/destroy bursts) previously queued 12+ Immediate/Low events on the main thread and could trip the 6s watchdog even without lock contention. - WindowManager: per-windowId leading+trailing 16ms throttle for windowRectChange/windowSizeChange (rect and size have independent throttle state; leading keeps first-event immediacy, trailing guarantees final-state delivery — required for window-state cache freshness), WindowEntry.destroying flag drops events for windows being torn down, timer cleanup in removeWindow/unregisterUIAbilityStage - NativeAbility/BridgeHost: all four registration points routed through the same throttledRectDispatch; RECOVER→menubar restore stays synchronous (not throttled) - typed WindowSizeEventWrap/WindowRectEventWrap interfaces replace ESObject (arkts-limited-esobj WARN eliminated) Verified on device: THROTTLE 104 IN / 98 OUT (5.8% suppression, 0 failures), full suite 281 pass / 1 fail / 1 skip, zero new appfreeze. Co-Authored-By: Claude <noreply@anthropic.com>
Feature-gated behind `fault-injection` (zero overhead when off): - Rust wire types (FaultRuleWire etc.) + OpenHarmonyApp facade (set_fault_rule/clear_fault_rules, auto-enables the registry on first call) + call_fault_injection on the bridge client - ArkTS FaultInjection.ets: always compiled but short-circuits at match() when disabled — injects failures into bridge calls to light up error-handling branches that never run naturally Co-Authored-By: Claude <noreply@anthropic.com>
…ge ramp-up) Appended to existing test modules: expect_engine_phase / engine_scheme_pairs variants, plugin-window and mouse-event pure-transform cases — host-side inputs for branches that never occur naturally on device. Co-Authored-By: Claude <noreply@anthropic.com>
Rebase of upstream/ohdev (through a25848a) onto the pluginized local architecture; upstream features were built on the old ArkHelper TSFN framework, so they are ported semantically instead of textually: - window/mod.rs: cursor grab FFI (dlopen libnative_window_manager.so, OH_WindowManager_LockCursor/UnlockCursor) with set_cursor_grab taking the real OHOS window id directly - app.rs: notify_window_status NAPI + PENDING_WINDOW_STATUS queue, mirroring the notify_window_close drain pattern - WindowPlugin.ets: 7 new bridge actions — set-topmost, set-title, set-limits (delegate to ported WindowManager methods), request-user-attention (static notificationManager import, monotonic id, 1600004 enable-retry), set-ime-position (await updateCursor, result returned directly — no poll), set-draggable (API20 guard), get-real-window-id - plugin-window WindowClient: matching async facade methods - DefaultWebview: naturalLayout guard ported from upstream d530828 — explicit child webview bounds restored, natural-layout webviews keep 100% sizing and strip set_bounds width/height - windowCommand 'show': delegate to WindowManager.showWindowMethod so a minimized main window is restored instead of no-op showWindow() cargo check passes on host and aarch64-unknown-linux-ohos (0 errors). Co-Authored-By: Claude <noreply@anthropic.com>
…e actions Two facade actions tao needs beyond the PR#45 port set: - set-cursor-icon: delegates to WindowManager.setPointerStyle (resolves the real OHOS window id internally, pointer.setPointerStyleSync). Fire-and-forget — per-window failures are logged on the ArkTS side, the ack only confirms dispatch (tao's setter is void). - set-decoration-flags: applies the FLAG bit-field (closable=1, maximizable=2, minimizable=4, resizable=8) to a Float sub-window's LocalStorage; the UIAbility main window is a system-managed no-op. WindowClient gains the matching async methods; requests are validated windowId + integer style / flags in [0, 15]. Co-Authored-By: Claude <noreply@anthropic.com>
…e escape pack.bat invokes this script as "...pack-plugins.ps1" "%SCRIPT_DIR%"; the trailing backslash escapes the closing quote in cmd's parser, so arrives with a literal trailing quote. Join-Path bakes that quote into every derived path and Test-Path fails with ItemExistsArgumentError, aborting the whole pack at "plugin aggregation failed". Trim both quotes and backslashes from the incoming value. Co-Authored-By: Claude <noreply@anthropic.com>
…usChange The rebase-brought windowStatusChange registration in onWindowStageCreate referenced a bare 'windowId' identifier (upstream defines it via readWindowId() for multi-UIAbility; local single-UIAbility architecture has no such binding), failing ArkTS compilation. The main window is always tao windowId=0 (same sentinel as the surrounding WindowSizeEventWrap/WindowRectEventWrap wraps), and runtime-wry routes by tao window_id, so notify with the literal 0. Float sub-windows keep using the NEXT_WINDOW_ID(>=1) virtual id via FloatPage. Co-Authored-By: Claude <noreply@anthropic.com>
The WM rect (windowRectChange) and the XComponent surface rect update asynchronously; a live window_rect - content_rect diff read in the gap produced garbage decor estimates (824/770/292 instead of the real 146), which corrupted tao's inner_size reads and compounded through window-state save/restore into a shrinking main window. - latch decor height ONLY on surface events (activate/update), where both rects are consistent: diff==0 -> 0, 0<diff<=320 -> latch, otherwise keep the previous estimate - add decor_change_callbacks: listeners fired while the app write lock is held whenever the latched value actually changes (contract: lock-free listeners only — channel sends/atomics); returning false removes the listener after the call - expose register/remove_decor_change_callback for tao's event-driven set_inner_size self-correction - unit tests for the latch rules and the callback firing semantics Co-Authored-By: Claude <noreply@anthropic.com>
…en to WindowManager 1. Restore CURSOR_POSITION_X/Y atomics + update_cursor_position NAPI in app.rs (dropped during the pluginize refactor, restored from 5941dfb). The ArkTS MainPage.onMouse handler is the only viable cursor tracking source — the NDK DispatchMouseEvent path never fires while the cursor is over the WebView. tao's cursor_position() reads these atomics. 2. WindowPlugin.ets set-fullscreen: delegate to WindowManager.setFullscreen instead of the inline setWindowLayoutFullScreen mobile-only path the pluginize migration left behind. WindowManager implements the dual path (desktop/2in1: maximize(ENTER_IMMERSIVE) + setTitleAndDockHoverShown; mobile: setWindowLayoutFullScreen + setWindowSystemBarEnable with serializeOp race guard); the inline version was a visual no-op on desktop while still acking OK. Verified on HUAWEI MateBook Pro (2in1): cursorPosition() non-zero; fullscreen toggle enters (ENTER_IMMERSIVE) and exits (recover()). Co-Authored-By: Claude <noreply@anthropic.com>
Remote-only commit is the pre-rebase push of work that was replayed (and further refined) during the upstream-ohdev rebase; local content is authoritative (-s ours). See upstream-ohdev-rebase-window-ops.
…swap JsStatusbarManager builds its ScbServerMessageReceiver asynchronously (~14ms) after the sync addToStatusBar() call returns. Registering click handlers on the sync return binds them to the previous receiver, which the new receiver then replaces - rightMenuClick and statusBarIconClick listeners were silently dropped on re-add (set_title/set_visible/set_quick_operation all re-run add). Switch to the AsyncCallback overload (API 14+) and re-register in the completion callback, which runs strictly after the new receiver exists. The sync registration is kept as a baseline (harmless off+on) in case the callback never fires. Co-Authored-By: Claude <noreply@anthropic.com>
Predefined menu fullscreen (tray/menubar Fullscreen items) used an inline implementation that only did a bare immersive maximize on desktop - the system title bar / Dock stayed visible, diverging from WindowPlugin set-fullscreen. Delegate both 'fullscreen' and 'recover' predefined actions to WindowManager.setFullscreen so every entry point shares the dual-path (desktop maximize(ENTER_IMMERSIVE) + hide title bar/Dock hover, mobile layout fullscreen + hide system bars) and the serializeOp race guard. WindowManager.setFullscreen now also drives the Tauri menubar via MW-5 callbacks (macOS semantics, design.md D3): hide on entry, restore on exit. Previously only the predefined path hid the menubar, so the window-API Toggle Fullscreen button kept it visible - and Esc could not exit because the MainPage Esc handler is gated on menubar-invisible. Centralizing the callbacks makes Esc exit work for all fullscreen flavours through the existing condition. The Esc recoverFn (NativeAbility) delegates to setFullscreen(0, false) so the title bar / Dock hidden on entry are restored as well. Verified on device (MateBook Pro): button/tray/menubar fullscreen all hide system chrome + menubar; Esc and button toggle both restore. Co-Authored-By: Claude <noreply@anthropic.com>
Restores set_cursor_visible, which the bridge facade migration (tao 73212e1e) dropped to a no-op on the Rust side while the ArkTS WindowManager.setPointerVisible implementation survived. Adds the set-cursor-visible action to the WindowPlugin (delegating to WindowManager.setPointerVisible) and a CursorVisibleRequest facade method. The request carries no window id - pointer.setPointerVisible is process-wide (global vs window-level semantics documented as 遗留问题六). Verified on device (MateBook Pro): setCursorVisible(false) (3s) hides the pointer globally and restores it after 3s. Co-Authored-By: Claude <noreply@anthropic.com>
…store - createSubWindow: on API19+ use createSubWindowWithOptions with maximizeSupported:true — sub-window win.maximize() is rejected by WMS (1300004) unless maximize capability was declared at creation; decorEnabled:false keeps FloatPage's custom title bar - FloatPage un-maximize: use recoverWindow() (MAXIMIZE/FULL_SCREEN → FLOATING, the documented inverse of maximize) instead of restoreWindow() which is main-window-only Co-Authored-By: Claude <noreply@anthropic.com>
…g while maximized - WMS recover() re-anchors the floating rect to the pointer (GetFullScreenToFloatingRect), so programmatic restore landed the window near the cursor instead of its pre-maximize position. Snapshot the floating rect before maximize (preMaximizeRects, has-guard against double-maximize overwriting with the fullscreen rect) and moveTo() back after recover, on both the FloatPage button path and the WindowPlugin bridge path (shared snapshotPreMaximizeRect/restorePreMaximizeRect helpers; the bridge keeps its await-completion semantics and cannot delegate to the fire-and-forget maximizeWindow). - FloatPage title-bar onTouch(Down)->startMoving() also fires for child button touches (ArkUI touch bubbling); while maximized it triggered WMS drag-off-maximize at touch-down, moving the window away so the touch-up landed out of region and the click gesture was rejected — onClick never ran. Skip startMoving while maximized (drag-off restore sacrificed; restore via the button). Device-verified 2026-08-27: maximizeWindow 1 OK -> recoverWindow 1 OK (restored pre-maximize rect), window returns to its original position; bridge path verified via WMS rect chain [0,0]->[0,0,3120,1955]->[0,0,1140,760]. Co-Authored-By: Claude <noreply@anthropic.com>
Bridge-side counterpart to the wry https-scheme fix. The previous bridge migration dropped the create-time https_intercept_protocols field from WebviewCreateRequest, causing the ArkTS httpsInterceptProtocols Set to be empty at first loadUrl and onInterceptRequest to early-return null. - WebviewCreateRequest (lib.rs:385): add https_intercept_protocol_list: Option<Vec<String>>, populated by wry when use_https && protocols exist. - WebviewCreatePayload (WebviewPlugin.ets:148): add matching httpsInterceptProtocolList field; seed httpsInterceptProtocols Set at create (WebviewPlugin.ets:1625) so the Set is non-empty before loadUrl. - callbacks.rs / WebviewPlugin.ets: add full [bridge https-intercept] and [https-intercept] hilog chain (received/enter/extracted/reverted/calling/ returned/success/passthrough) for observability — the previous path was silent, masking the empty-Set early-return. - plugin-webview Cargo.toml: add log = "0.4" dependency (callbacks.rs/lib.rs use log::warn!/log::info! but the crate never declared log -> E0433). Verified 2026-08-28 on HUAWEI MateBook Pro desktop with the wry fix: all 4 https-scheme cases PASS (page-load/secure-context/subresource/external). Co-Authored-By: Claude <noreply@anthropic.com>
…t want.uri The file manager (com.huawei.hmos.filemanager MainAbility) reads the target from want.parameters.fileUri + want.parameters.external_storage_uuid, NOT from top-level want.uri. The previous code put the virtual uri in want.uri, which FM ignores -> FM defaulted to home (stayed on the homepage instead of navigating to the parent directory). This was the real root cause of §22-3 failing; the earlier "subPath empty" hypothesis was wrong (subPath-non-empty paths also stayed on home). Fix: move the mapped uri into parameters.fileUri and add external_storage_uuid = "LOCAL" (the local disk id, verified via FM diskNameArray; cloud = "cloud"). Device experiment (2026-08-28, aa start matrix): with parameters.fileUri + external_storage_uuid="LOCAL", FM does a two-stage setCurrentUri (myPC intermediate -> target uri) and navigates to Documents/Download/Desktop. In app semantics Rust takes path.parent() so reveal always opens the top-level public folder containing the file — this is within OHOS FM capabilities (the launch entry only resolves top-level virtual folder names; deeper subpaths hit isFolder error and fall back). Note: the 2026-08-20 "PASS" was a misread — FM actually stayed on the home/My Computer page; "empty uri when get uuid" is per-startup noise, not a URI rejection. Verified 2026-08-28 on HUAWEI MateBook Pro desktop: setCurrentUri reaches file://docs/storage/Users/currentUser/Documents (was: home). Co-Authored-By: Claude <noreply@anthropic.com>
ProcessInitializer.initialize() never set __native_module__ in AppStorage, so every AppStorage.get(NATIVE_MODULE_STORAGE_KEY) reader (FloatPage × button / aboutToDisappear, menu.ets closeWindow, NativeAbility windowStatusChange) returned undefined → notifyWindowClose / notifyWindowStatus were never invoked. On Float sub-window close, the Rust tauri manager kept a zombie entry → getByLabel returned a dead handle → same-label windows couldn't be rebuilt (manual_tests §二十八 close lifecycle defect). Fix: publish the primary native module to AppStorage at the end of ProcessInitializer.initialize(). One write fixes all readers. Verified on HUAWEI MateBook Pro (API 23): hilog shows "Window N close notified via NAPI" (was "not available"); windowId increments 1→2→3 on repeated create/close of the same-label overlay. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Impact
OHOS-only repository. The pluginized architecture is the single ArkTS bridge layer all other repos (tauri/tao/wry/muda/tray-icon) consume; NAPI surface keeps camelCase naming and TSFN-based dispatch.
Testing
Verified end-to-end on HUAWEI MateBook Pro (desktop form, API 23) across the tauri api example: webview create/attach, window ops, menubar/tray, clipboard, print, geolocation permission + watchPosition streaming, notification actions. Local checklist review passed: 0 Blocker / 0 Major.
Related
Supersedes #43. Counterpart PRs: tauri#75, tao#21, wry#21, muda#5, tray-icon#9, plugins-workspace, window-vibrancy.