Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9ce4f38
feat: add device.webview.* method stubs
gmegidish May 11, 2026
8dabdb1
feat: align webview methods with trimmed OpenRPC spec
gmegidish May 12, 2026
3945a67
feat: add webview CLI commands
gmegidish May 12, 2026
36729b8
feat: implement WebViewListCommand for Android
gmegidish May 12, 2026
cef8b30
fix: remove hardcoded DEX_PATH from jvmti_agent.c
gmegidish May 12, 2026
559d39d
feat: implement webview goto, evaluate (url + title) for Android
gmegidish May 12, 2026
a6e7d1e
fix: webview url/title/eval returning empty result
gmegidish May 12, 2026
37687ee
feat: implement webview back, forward, content
gmegidish May 12, 2026
d8109a6
fix: ensure bare expressions are wrapped with return in WebViewEvaluate
gmegidish May 12, 2026
46f452c
feat: implement webview reload
gmegidish May 12, 2026
e6a0c33
fix: handle null evalJs result and ClassCastException in evaluateExpr…
gmegidish May 12, 2026
98d936d
build: wire agents/android into root Makefile
gmegidish May 12, 2026
7808755
feat: implement webview waitForLoadState
gmegidish May 12, 2026
31386a7
refactor: replace map[string]any with typed result structs
gmegidish May 12, 2026
4abe35b
chore: add agent sources, Makefile, and adb client
gmegidish May 12, 2026
f42b53c
chore: remove built agent binaries from git, add to gitignore
gmegidish May 12, 2026
581330f
fix: cross-platform agent Makefile and CI agent build job
gmegidish May 12, 2026
b1cfde6
feat: iOS simulator webview agent (list, url, goto)
gmegidish May 12, 2026
c6d1eea
no dylib in git
gmegidish May 12, 2026
49d63ee
feat: complete iOS simulator webview support + WebViewable interface
gmegidish May 12, 2026
c74d7b7
feat: iOS real device webview list (inject via lldb, forward via go-ios)
gmegidish May 12, 2026
c0e2d01
fix: iOS 26 SDK compat for real device webview injection via LLDB
gmegidish May 13, 2026
0391621
performance
gmegidish May 13, 2026
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
72 changes: 72 additions & 0 deletions commands/webview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package commands

import "fmt"

// ─── Request types ────────────────────────────────────────────

type WebViewListRequest struct {
DeviceID string
}

// WebViewRequest is the base for all webview operations that target a specific webview.
type WebViewRequest struct {
DeviceID string
WebViewID string
}

type WebViewGotoRequest struct {
DeviceID string
WebViewID string
URL string
WaitUntil string
}

type WebViewReloadRequest struct {
DeviceID string
WebViewID string
WaitUntil string
}

type WebViewEvaluateRequest struct {
DeviceID string
WebViewID string
Expression string
Args []any
Comment on lines +25 to +38
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both WaitUntil fields (here and in WebViewReloadRequest) and the Args field here are dead from the CLI layer — cli/webview.go registers no --wait-until or --arg flags, so they will always be zero-value when called from the CLI.

Either add the flags, or remove the fields from these command-layer structs and keep them only in the server-layer params structs (WebViewGotoParams, etc.), which already carry them correctly.

}

type WebViewWaitForLoadStateRequest struct {
DeviceID string
WebViewID string
State string
Timeout int
}

// ─── Stubs ────────────────────────────────────────────────────

func WebViewListCommand(req WebViewListRequest) *CommandResponse {
return NewErrorResponse(fmt.Errorf("not implemented"))
}

func WebViewGotoCommand(req WebViewGotoRequest) *CommandResponse {
return NewErrorResponse(fmt.Errorf("not implemented"))
}

func WebViewReloadCommand(req WebViewReloadRequest) *CommandResponse {
return NewErrorResponse(fmt.Errorf("not implemented"))
}

func WebViewGoBackCommand(req WebViewRequest) *CommandResponse {
return NewErrorResponse(fmt.Errorf("not implemented"))
}

func WebViewGoForwardCommand(req WebViewRequest) *CommandResponse {
return NewErrorResponse(fmt.Errorf("not implemented"))
}

func WebViewEvaluateCommand(req WebViewEvaluateRequest) *CommandResponse {
return NewErrorResponse(fmt.Errorf("not implemented"))
}

func WebViewWaitForLoadStateCommand(req WebViewWaitForLoadStateRequest) *CommandResponse {
return NewErrorResponse(fmt.Errorf("not implemented"))
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
7 changes: 7 additions & 0 deletions server/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func GetMethodRegistry() map[string]HandlerFunc {
"device.screenrecord.stop": handleScreenRecordStop,
"device.crashes.list": handleCrashesList,
"device.crashes.get": handleCrashesGet,
"device.webview.list": handleWebViewList,
"device.webview.goto": handleWebViewGoto,
"device.webview.reload": handleWebViewReload,
"device.webview.goBack": handleWebViewGoBack,
"device.webview.goForward": handleWebViewGoForward,
Comment on lines +45 to +46
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dispatch key naming breaks the existing convention. Every other key uses lowercase (device.io.tap, device.apps.launch, device.screenrecord.stop) but these two use camelCase. This will force the mobilewright driver to special-case them.

Suggest renaming to match the CLI subcommand names:

"device.webview.back":    handleWebViewGoBack,
"device.webview.forward": handleWebViewGoForward,

or device.webview.go_back / device.webview.go_forward if you want the verb.

"device.webview.evaluate": handleWebViewEvaluate,
"device.webview.waitForLoadState": handleWebViewWaitForLoadState,
Comment on lines +42 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t register webview RPC methods until command implementations exist.

These methods are discoverable now but all currently resolve to stubbed command errors, so clients get guaranteed runtime failures instead of clear method unavailability.

Minimal safe change
-		"device.webview.list":             handleWebViewList,
-		"device.webview.goto":             handleWebViewGoto,
-		"device.webview.reload":           handleWebViewReload,
-		"device.webview.goBack":           handleWebViewGoBack,
-		"device.webview.goForward":        handleWebViewGoForward,
-		"device.webview.evaluate":         handleWebViewEvaluate,
-		"device.webview.waitForLoadState": handleWebViewWaitForLoadState,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"device.webview.list": handleWebViewList,
"device.webview.goto": handleWebViewGoto,
"device.webview.reload": handleWebViewReload,
"device.webview.goBack": handleWebViewGoBack,
"device.webview.goForward": handleWebViewGoForward,
"device.webview.evaluate": handleWebViewEvaluate,
"device.webview.waitForLoadState": handleWebViewWaitForLoadState,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/dispatch.go` around lines 42 - 48, The RPC map in dispatch.go is
registering webview methods that map to stubbed handlers (handleWebViewList,
handleWebViewGoto, handleWebViewReload, handleWebViewGoBack,
handleWebViewGoForward, handleWebViewEvaluate, handleWebViewWaitForLoadState);
remove or stop registering those entries until their real command
implementations exist so clients won't discover and call methods that always
return stub errors. Edit the registration block to either delete/comment out the
"device.webview.*" entries or gate their addition behind a
feature-flag/implementation-check so only fully implemented handlers are
exposed.

"server.info": handleServerInfo,
"server.shutdown": handleServerShutdown,
}
Expand Down
192 changes: 192 additions & 0 deletions server/webview_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package server

import (
"encoding/json"
"fmt"

"github.com/mobile-next/mobilecli/commands"
)

// ─── Params structs ───────────────────────────────────────────

type WebViewListParams struct {
DeviceID string `json:"deviceId"`
}

type WebViewParams struct {
DeviceID string `json:"deviceId"`
WebViewID string `json:"id"`
}

type WebViewGotoParams struct {
DeviceID string `json:"deviceId"`
WebViewID string `json:"id"`
URL string `json:"url"`
WaitUntil string `json:"waitUntil,omitempty"`
}

type WebViewReloadParams struct {
DeviceID string `json:"deviceId"`
WebViewID string `json:"id"`
WaitUntil string `json:"waitUntil,omitempty"`
}

type WebViewEvaluateParams struct {
DeviceID string `json:"deviceId"`
WebViewID string `json:"id"`
Expression string `json:"expression"`
Args []any `json:"args,omitempty"`
}

type WebViewWaitForLoadStateParams struct {
DeviceID string `json:"deviceId"`
WebViewID string `json:"id"`
State string `json:"state,omitempty"`
Timeout int `json:"timeout,omitempty"`
}
Comment on lines +41 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

timeout should preserve omission vs explicit zero.

Using int with omitempty collapses “not provided” and 0 into the same value. That makes default timeout behavior ambiguous in waitForLoadState.

Suggested fix
 type WebViewWaitForLoadStateParams struct {
 	DeviceID  string `json:"deviceId"`
 	WebViewID string `json:"id"`
 	State     string `json:"state,omitempty"`
-	Timeout   int    `json:"timeout,omitempty"`
+	Timeout   *int   `json:"timeout,omitempty"`
 }
 return voidOf(commands.WebViewWaitForLoadStateCommand(commands.WebViewWaitForLoadStateRequest{
 	DeviceID:  p.DeviceID,
 	WebViewID: p.WebViewID,
 	State:     p.State,
 	Timeout:   p.Timeout,
 }))

And align commands.WebViewWaitForLoadStateRequest.Timeout to *int as well.

Also applies to: 186-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/webview_handlers.go` around lines 41 - 46, Change the Timeout fields
from concrete int to pointer *int so omission vs explicit zero is preserved:
update the struct WebViewWaitForLoadStateParams (field Timeout int -> Timeout
*int) and also align commands.WebViewWaitForLoadStateRequest.Timeout to *int;
ensure JSON tags remain `json:"timeout,omitempty"` and adjust any code that
reads Timeout to handle nil (meaning omitted) versus non-nil zero (explicit 0).


// ─── Shared helpers ───────────────────────────────────────────

func unmarshal[T any](params json.RawMessage) (T, error) {
var p T
if err := json.Unmarshal(params, &p); err != nil {
return p, fmt.Errorf("invalid parameters: %w", err)
}
return p, nil
}

func resultOf(resp *commands.CommandResponse) (any, error) {
if resp.Status == "error" {
return nil, fmt.Errorf("%s", resp.Error)
}
return resp.Data, nil
}

func voidOf(resp *commands.CommandResponse) (any, error) {
if resp.Status == "error" {
return nil, fmt.Errorf("%s", resp.Error)
}
return okResponse, nil
}
Comment on lines +50 to +70
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unmarshal[T], resultOf, and voidOf are useful helpers but they only exist in this file, while server.go does inline json.Unmarshal in every handler. This creates a style split in the same package — a contributor adding a new handler in server.go will not know these exist.

Either move these helpers to a shared file (e.g. server/handlers.go) so server.go can adopt them too, or keep the inline pattern for consistency.


func requireWebViewParams(deviceID, webViewID string) error {
if deviceID == "" {
return fmt.Errorf("deviceId is required")
}
if webViewID == "" {
return fmt.Errorf("id is required")
}
return nil
}

// ─── Handlers ─────────────────────────────────────────────────

func handleWebViewList(params json.RawMessage) (any, error) {
p, err := unmarshal[WebViewListParams](params)
if err != nil {
return nil, err
}
if p.DeviceID == "" {
return nil, fmt.Errorf("deviceId is required")
}
return resultOf(commands.WebViewListCommand(commands.WebViewListRequest{
DeviceID: p.DeviceID,
}))
}

func handleWebViewGoto(params json.RawMessage) (any, error) {
p, err := unmarshal[WebViewGotoParams](params)
if err != nil {
return nil, err
}
if err := requireWebViewParams(p.DeviceID, p.WebViewID); err != nil {
return nil, err
}
if p.URL == "" {
return nil, fmt.Errorf("url is required")
}
return voidOf(commands.WebViewGotoCommand(commands.WebViewGotoRequest{
DeviceID: p.DeviceID,
WebViewID: p.WebViewID,
URL: p.URL,
WaitUntil: p.WaitUntil,
}))
}

func handleWebViewReload(params json.RawMessage) (any, error) {
p, err := unmarshal[WebViewReloadParams](params)
if err != nil {
return nil, err
}
if err := requireWebViewParams(p.DeviceID, p.WebViewID); err != nil {
return nil, err
}
return voidOf(commands.WebViewReloadCommand(commands.WebViewReloadRequest{
DeviceID: p.DeviceID,
WebViewID: p.WebViewID,
WaitUntil: p.WaitUntil,
}))
}

func handleWebViewGoBack(params json.RawMessage) (any, error) {
p, err := unmarshal[WebViewParams](params)
if err != nil {
return nil, err
}
if err := requireWebViewParams(p.DeviceID, p.WebViewID); err != nil {
return nil, err
}
return voidOf(commands.WebViewGoBackCommand(commands.WebViewRequest{
DeviceID: p.DeviceID,
WebViewID: p.WebViewID,
}))
}

func handleWebViewGoForward(params json.RawMessage) (any, error) {
p, err := unmarshal[WebViewParams](params)
if err != nil {
return nil, err
}
if err := requireWebViewParams(p.DeviceID, p.WebViewID); err != nil {
return nil, err
}
return voidOf(commands.WebViewGoForwardCommand(commands.WebViewRequest{
DeviceID: p.DeviceID,
WebViewID: p.WebViewID,
}))
}

func handleWebViewEvaluate(params json.RawMessage) (any, error) {
p, err := unmarshal[WebViewEvaluateParams](params)
if err != nil {
return nil, err
}
if err := requireWebViewParams(p.DeviceID, p.WebViewID); err != nil {
return nil, err
}
if p.Expression == "" {
return nil, fmt.Errorf("expression is required")
}
return resultOf(commands.WebViewEvaluateCommand(commands.WebViewEvaluateRequest{
DeviceID: p.DeviceID,
WebViewID: p.WebViewID,
Expression: p.Expression,
Args: p.Args,
}))
}

func handleWebViewWaitForLoadState(params json.RawMessage) (any, error) {
p, err := unmarshal[WebViewWaitForLoadStateParams](params)
if err != nil {
return nil, err
}
if err := requireWebViewParams(p.DeviceID, p.WebViewID); err != nil {
return nil, err
}
return voidOf(commands.WebViewWaitForLoadStateCommand(commands.WebViewWaitForLoadStateRequest{
DeviceID: p.DeviceID,
WebViewID: p.WebViewID,
State: p.State,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When device.webview.waitForLoadState is called directly via RPC with state omitted (empty string), this passes the empty string through to the command. When the implementation lands, normalize it here before dispatch:

if p.State == "" {
    p.State = "load"
}

The CLI defaults --state to "load" (via flags.go), but RPC callers bypassing the CLI get no default.

Timeout: p.Timeout,
}))
}
Loading