Mobile Agentic Browsers: Auto‑Agent Pipelines for Android Chrome/WebView and iOS WKWebView
Shipping AI browser agents on mobile is both compelling and tricky. Phones are the dominant personal computing platform, and browsers are the universal UI. But the mobile runtime is heavily sandboxed, user-gesture-gated, and power-constrained. If you want a reliable, secure, power-aware mobile “agentic browser” that executes tasks for a user, you must meet the platform where it is — not where desktop automation frameworks like CDP and Playwright have conditioned us to be.
This article presents an opinionated guide to architecting agentic browsing pipelines on Android (Chrome, WebView) and iOS (WKWebView), with a focus on:
- Automation gaps and realities across Chrome/WebView and WKWebView
- User-Agent reduction, Client Hints, and mobile identification quirks
- Gesture/input constraints and workarounds
- Backgrounding and scheduling on resource-constrained mobile OSes
- Identity flows: FedCM, third-party cookies, and passkeys/WebAuthn
- Secure, power-aware pipelines for shipping production agents
My thesis: you can ship competent, useful mobile browser agents today — but they must be app-embedded (WebView/WKWebView), task-scoped, privacy-preserving, and power-aware. Most “headless human” ideas (full mouse/keyboard emulation, background browsing) don’t survive App Store policies or OS constraints. The winning approach is a hybrid pipeline: native scheduling + sandboxed web runtime + constrained JS automation, with clear fallbacks to system browsers for identity and payments.
The landscape: what you can and can’t control
Before talking automation, understand your runtime choices:
-
Android Chrome (standalone app)
- Pros: Full Chrome features (FedCM, CH, latest web APIs), DevTools Protocol for debugging.
- Cons: No programmatic control from your app in production; CDP remote debugging is for development, not app distribution. No sanctioned way to drive a user’s Chrome in the background.
-
Android WebView (embedded in your app)
- Pros: You control lifecycle, JS injection, storage partitioning, cookies, and network headers (via interceptors). Can toggle permissions and some gesture gatings (e.g., media playback).
- Cons: Feature lag vs. Chrome; FedCM not supported in WebView; third-party cookies default handling differs; background execution limited; agent code must run in-app.
-
iOS WKWebView (embedded in your app)
- Pros: Production-supported, modern WebKit engine; passkeys/WebAuthn supported at top-level; powerful JS bridge; content blockers.
- Cons: No CDP; strict user-gesture gating; strong backgrounding limits; ITP affects storage/cookies; FedCM not broadly available for WKWebView; push/web workers/background sync not available.
-
System browsers for identity
- Android: Chrome Custom Tabs (CCT)
- iOS: SFSafariViewController or ASWebAuthenticationSession
- These are best for OAuth/federation/passkeys due to cookie jar continuity and security UX.
The consequence for agent design: use an embedded webview for deterministic automation, and bounce to the system browser for identity flows or when you need the full browser cookie jar and UX primitives.
Automation reality check
- There is no supported way to inject synthesized “trusted” user gestures from JS in either WebView or WKWebView.
- On Android, you can generate real gestures with an AccessibilityService, but it requires explicit user opt-in and is heavy (and typically rejected if used to drive other apps without accessibility justification).
- On iOS, touch injection is only available to XCTest; it’s not available to production apps.
- Many APIs are user-activation-gated (clipboard read, file dialogs, autoplay, popups). You must design flows that either avoid these APIs or explicitly request a user tap at the right time.
- True “headless browsing” in the background isn’t available on mobile. Your agent must run in the foreground or within tight background windows, and it must be miserly with CPU/network.
Opinion: Embrace task-oriented, semi-assisted flows. Ask for a single deliberate tap to authorize a block of work, use settings that relax gesture requirements where allowed (e.g., media playback), and precompute as much as possible off the main thread. Don’t fight the OS.
User-Agent reduction and Client Hints on mobile
Desktop bots historically spoofed navigator.userAgent. That world is ending. Chromium is reducing UA entropy and moving to User-Agent Client Hints (UA-CH). On mobile you also juggle platform-specific quirks:
- Android WebView UA includes the
wvtoken to flag in-app browsing. Many sites use this to alter behavior (or block). - UA reduction in Chrome removes granular version and platform details from the UA string; entropy moves to CH (e.g., Sec-CH-UA, Sec-CH-UA-Platform, Sec-CH-UA-Model).
- UA-CH is request-header-based and origin-controlled. High-entropy hints require opt-in: server sends
Accept-CHand optionallyCritical-CH, then the browser includes hints on subsequent requests. JS can request high entropy vianavigator.userAgentData.getHighEntropyValues(Chromium only). - iOS Safari/WKWebView do not implement UA-CH broadly. Rely on the classic UA string.
Practical advice:
- Do not rely on UA sniffing to decide critical app behaviors; feature-detect instead.
- On Android WebView, if you must emulate standard Chrome, you can override the WebView UA string to remove
wv, but this can break sites that expect in-app behavior and may be frowned upon by some services. Use sparingly and transparently. - If your agent’s server needs device metadata, use client-side JS to fetch UA-CH (Chromium) and pass it in a request body to your own origin. Do not expect CH to appear on first navigation.
Example: probing UA and UA-CH from your agent’s JS shim
jsasync function probeUA() { const ua = navigator.userAgent; const res = { ua }; // Chromium-only UA-CH if (navigator.userAgentData && navigator.userAgentData.getHighEntropyValues) { try { const he = await navigator.userAgentData.getHighEntropyValues([ 'platform', 'platformVersion', 'model', 'architecture', 'bitness', 'fullVersionList' ]); res.uaCh = { brands: navigator.userAgentData.brands, mobile: navigator.userAgentData.mobile, ...he }; } catch (e) { res.uaChError = String(e); } } else { res.uaCh = null; } return res; }
Android-specific note: on WebView, UA-CH support largely follows Chromium versioning, but you won’t get hints to third-party origins without server opt-in. Use your own domain as a relay if you need structured device metadata.
References:
- Chromium UA Reduction: https://www.chromium.org/updates/ua-reduction/
- UA-CH Explainer: https://wicg.github.io/ua-client-hints/
Gesture and input: design for user activation
What you can do:
- Programmatically click DOM elements via JS (
element.click()), fill forms, read/write DOM state, intercept XHR/fetch, scroll containers, and screenshot/record via native APIs (e.g., on Android you can capture the WebView view hierarchy). - Set webview preferences to relax some gating: on Android
setMediaPlaybackRequiresUserGesture(false), on iOSmediaTypesRequiringUserActionForPlayback = []. - Request a single real user tap to unlock tasks that require activation (clipboard read/paste, download dialogs, window.open, audio autoplay). Have clear UI indicating “Tap to continue.”
What you cannot do from production apps:
- Synthesize “trusted” gestures in JS that pass
Event.isTrustedchecks. - On iOS, inject touches programmatically outside test frameworks.
- Trigger file pickers or permission prompts without a recent user activation.
Android-only option: AccessibilityService for gesture injection
- You can implement an AccessibilityService that performs global gestures (
dispatchGesture). This is a heavy permission requiring explicit user opt-in in Settings. Use only if your core value proposition is accessibility. For consumer automation, this is usually a non-starter.
Example (Android WebView) relaxing media gating
kotlinwebView.settings.javaScriptEnabled = true webView.settings.mediaPlaybackRequiresUserGesture = false webView.settings.domStorageEnabled = true // Consider disabling file access from file URLs for security webView.settings.allowFileAccessFromFileURLs = false webView.settings.allowUniversalAccessFromFileURLs = false
Example (iOS WKWebView) allowing media playback
swiftlet cfg = WKWebViewConfiguration() if #available(iOS 10.0, *) { cfg.mediaTypesRequiringUserActionForPlayback = [] // allow inline playback } let webView = WKWebView(frame: .zero, configuration: cfg) webView.configuration.preferences.javaScriptEnabled = true
Design pattern: agent-visible prompt
- Show a small native overlay prompting the user to tap when a user-activation is required. After the tap, immediately call into JS to perform the gated action. Keep the window small and bounded (e.g., 5 seconds) to respect the spirit of user activation.
Backgrounding: the hard boundary
iOS:
- WKWebView is paused when your app backgrounds. There’s no supported way to keep scripting or timers alive.
- Use BGAppRefreshTaskRequest/BGProcessingTaskRequest for limited background work — but these cannot host a live WKWebView session. They’re for network/compute tasks via URLSession or native code.
- Plan your agent loop to checkpoint state on
sceneWillResignActiveand resume gracefully on foreground.
Android:
- Background execution limits apply. If you need long-running tasks, use a Foreground Service with a user-visible notification.
- WebView should not be instantiated offscreen for long periods. Render only when needed; use WorkManager/JobScheduler to wake up and perform bounded tasks.
- Pre-warm a WebView (hidden) shortly before displaying it to reduce TTFI, then destroy when done to free memory.
Practical patterns:
- Split orchestration (small native service) from browsing (WebView/WKWebView in foreground). The service wakes up with WorkManager/BGTaskScheduler, evaluates if work is pending, and asks the user to foreground for execution.
- For pure data fetching, prefer native URLSession/OkHttp with site APIs when feasible, rather than headless browsing.
Identity: FedCM, third-party cookies, and passkeys
Identity is the sharp edge where WebViews diverge most from full browsers.
FedCM (Federated Credential Management API):
- Chrome (standalone) supports FedCM and is rolling out features that reduce reliance on third-party cookies.
- Android WebView: FedCM is not supported. Assume redirect-based OAuth.
- iOS Safari: FedCM support has been evolving; do not assume FedCM in WKWebView in production at this time.
Implication: run identity flows via system browsers
- Android: launch Chrome Custom Tabs for OAuth/social login. You get shared cookies, safe UX, and robust redirects back to your app.
- iOS: use SFSafariViewController or ASWebAuthenticationSession for OAuth/OpenID Connect.
Third-party cookies and ITP:
- Android WebView: third-party cookies may be disabled by default on some versions. You can control this per-WebView:
kotlinCookieManager.getInstance().setAcceptThirdPartyCookies(webView, true)
- iOS WKWebView: Safari’s ITP (Intelligent Tracking Prevention) behaviors apply broadly. Third-party cookies and storage are restricted. The Storage Access API can help third-parties request access after a user interaction, but integration is nuanced.
Passkeys/WebAuthn:
- Android Chrome/WebView: WebAuthn is supported. Platform authenticators (passkeys) are available on modern Android. WebView support for WebAuthn has matured; ensure you’re on a recent WebView (Chromium m100+).
- iOS WKWebView: WebAuthn (including passkeys) works at top-level contexts on iOS 16+. User gestures and HTTPS are required; iframes and cross-origin constraints apply.
Example: invoking passkey creation in-page
jsasync function registerPasskey(challenge, rp, user) { const publicKey = { challenge: Uint8Array.from(atob(challenge), c => c.charCodeAt(0)), rp: { name: rp.name, id: rp.id }, user: { id: Uint8Array.from(atob(user.id), c => c.charCodeAt(0)), name: user.name, displayName: user.displayName, }, pubKeyCredParams: [ { type: 'public-key', alg: -7 }, { type: 'public-key', alg: -257 } ], authenticatorSelection: { residentKey: 'required', userVerification: 'required' }, attestation: 'none' }; const cred = await navigator.credentials.create({ publicKey }); // Serialize for your server const attObj = cred.response.attestationObject; const clientDataJSON = cred.response.clientDataJSON; return { id: cred.id, rawId: btoa(String.fromCharCode(...new Uint8Array(cred.rawId))), type: cred.type, response: { attestationObject: btoa(String.fromCharCode(...new Uint8Array(attObj))), clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(clientDataJSON))) } }; }
Production advice:
- Keep WebAuthn inside the webview if you control the page and it’s top-level. If you don’t, prefer system browser-based flows (CCT/Safari) to avoid surprises from ITP or cross-origin constraints.
- For OAuth on iOS, prefer ASWebAuthenticationSession: it shares cookies with Safari and is the platform-recommended UX for login.
References:
- FedCM: https://fedidcg.github.io/FedCM/
- WebAuthn: https://www.w3.org/TR/webauthn-3/
- ITP: https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/
Secure JS bridging and sandboxing
WebView and WKWebView are powerful — and dangerous if you expose native capabilities broadly.
Android WebView:
- JS bridge:
addJavascriptInterface(obj, "Android"). Only methods annotated with@JavascriptInterfaceare exposed. On Android < 4.2 there were known vulnerabilities; on modern Android it’s safe if you narrow the surface. - Do not expose file system or intents directly to JS. Whitelist operations and validate inputs.
- Use
shouldInterceptRequestto block/modify requests (e.g., enforce CSP, add headers for your origin). - Prefer
setAllowFileAccessFromFileURLs(false)andsetAllowUniversalAccessFromFileURLs(false). UsesetMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW)for security.
Example: minimal Android JS bridge
kotlinclass AgentBridge(private val context: Context) { @JavascriptInterface fun postMessage(json: String) { // Validate schema and enqueue to native pipeline // Never execute commands blindly } } webView.addJavascriptInterface(AgentBridge(this), "Agent")
iOS WKWebView:
- Use
WKUserContentControllerandWKScriptMessageHandlerto receive messages from JS. Content scripts can be injected at document start/end and in isolated worlds (WKContentWorld.pagevs.defaultClienton newer iOS). - Apply content rule lists (WKContentRuleList) to block trackers or disallowed hosts.
- Use
WKWebsiteDataStore.nonPersistent()for ephemeral sessions that shouldn’t persist cookies or storage.
Example: WKWebView JS bridge
swiftclass AgentHandler: NSObject, WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == "agent" { guard let body = message.body as? String else { return } // Validate and handle JSON } } } let cfg = WKWebViewConfiguration() let ucc = WKUserContentController() ucc.add(AgentHandler(), name: "agent") cfg.userContentController = ucc cfg.websiteDataStore = .nonPersistent() let webView = WKWebView(frame: .zero, configuration: cfg)
Security checklist:
- Narrow, schema-validate every message from JS.
- Keep a strict allowlist of navigable hosts; block everything else.
- Disable inline JS and eval where possible with CSP for pages you control.
- Avoid persistent storage for sensitive tasks; use ephemeral data stores.
- Treat the webview as untrusted: never trust its content merely because it’s in your app.
Power-aware pipeline design
Mobile power is the scarcest resource. Agents must be respectful.
Core strategies:
- Batch work and coalesce network requests. Use HTTP/2 or HTTP/3 and keep-alive where possible. Avoid polling; use server push or long polling with backoff.
- Gate heavy tasks on device state: unmetered network, charging, screen-on.
- Pre-render only when needed; pause JS timers when offscreen (the OS will anyway). Tear down webviews after tasks to free memory.
- Serialize large DOM inspections or ML inference off the main thread (Web Workers on web; native background threads on app side). In WebView, workers may be limited — consider moving CPU-bound steps into native.
Android implementation sketch (WorkManager + ForegroundService + WebView host):
kotlinclass AgentWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params) { override suspend fun doWork(): Result { // Check constraints: network, battery saver val bm = applicationContext.getSystemService(BatteryManager::class.java) val powerSave = (applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).isPowerSaveMode if (powerSave) return Result.retry() // Notify UI to present foreground webview if needed AgentController.requestExecution() return Result.success() } } // Somewhere in UI layer fun executeInWebView(task: AgentTask) { // Pre-warm webview webView.loadUrl("about:blank") injectAgentScripts() // Navigate and run webView.loadUrl(task.url) // Evaluate JS once loaded webView.evaluateJavascript("window.Agent.runTask(${task.toJson()})") {} }
iOS implementation sketch (BGTaskScheduler + foreground WKWebView):
swift// Register once dispatchPrecondition(condition: .onQueue(.main)) BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.agent.refresh", using: nil) { task in scheduleNextRefresh() // Decide whether to ask user to open app now (e.g., local notification) task.setTaskCompleted(success: true) } func scheduleNextRefresh() { let req = BGAppRefreshTaskRequest(identifier: "com.example.agent.refresh") req.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 15) try? BGTaskScheduler.shared.submit(req) } // In foreground, drive WKWebView as needed
Low-power signals:
- Android:
PowerManager.isPowerSaveMode,BatteryManager.isCharging,JobSchedulerconstraints. - iOS:
ProcessInfo.processInfo.isLowPowerModeEnabled,NSProcessInfoPowerStateDidChange.
Throttle heuristics:
- If low power: suspend non-critical agent runs; degrade to metadata-only checks; push notification asking the user to run later.
- On metered networks: postpone heavy browsing; fetch only small sitemaps.
Putting it together: an agent pipeline that ships
Architecture overview:
-
Native orchestrator
- Schedules work (WorkManager/BGTaskScheduler)
- Gathers device signals (power, network)
- Securely stores credentials/tokens (Keychain/Keystore)
- Decides whether to run now or defer
-
Webview executor (Android WebView / iOS WKWebView)
- Loads task-specific pages
- Injects a strict content script that:
- Observes DOM, extracts structured features
- Proposes a minimal set of actions (clicks, form fills)
- Executes via whitelisted operations and reports outcomes
- Persists ephemeral session data only as needed
-
Identity broker
- For OAuth/federation: opens CCT/Safari/ASWebAuthenticationSession
- Returns to app with token, stored in native keystore
-
Policy and safety gate
- Checks host allowlist and prohibited actions (e.g., file uploads, downloads)
- Enforces rate limits per domain and per user
- Requires explicit user tap for any action crossing a policy boundary (clipboard, payments)
Example: simple JS agent core you inject
jswindow.Agent = (function(){ const state = { log: [], lastActionTs: 0 }; function log(msg){ state.log.push({ t: Date.now(), msg }); if (state.log.length > 2000) state.log.shift(); if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.agent){ window.webkit.messageHandlers.agent.postMessage(JSON.stringify({ type: 'log', msg })); } else if (window.AgentNative) { window.AgentNative.postMessage(JSON.stringify({ type: 'log', msg })); } } function qs(sel){ return document.querySelector(sel); } async function runTask(task){ log(`Running task ${task.kind} on ${location.href}`); switch (task.kind){ case 'loginStatus': return detectLogin(); case 'fillForm': return fillForm(task.payload); case 'scrape': return scrape(task.payload); default: return { ok: false, error: 'unknown task' }; } } function detectLogin(){ const selectors = ['a[href*="logout" i]', 'button[aria-label*="log out" i]']; const loggedIn = selectors.some(sel => qs(sel)); return { ok: true, loggedIn }; } function click(el){ if (!el) return false; el.scrollIntoView({ block: 'center', behavior: 'instant' }); el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); el.click(); return true; } async function fillForm({ fields, submit }){ for (const f of fields){ const el = qs(f.selector); if (!el) return { ok: false, error: `missing ${f.selector}` }; el.focus(); el.value = f.value; el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); } if (submit){ click(qs(submit)); } return { ok: true }; } async function scrape({ selectors }){ const out = {}; for (const [k, sel] of Object.entries(selectors)){ const el = qs(sel); out[k] = el ? (el.textContent || '').trim() : null; } return { ok: true, data: out }; } return { runTask }; })();
Native invokes it:
- Android:
kotlinwebView.evaluateJavascript("window.Agent.runTask(${json})") { resultJson -> // Handle result }
- iOS:
swiftwebView.evaluateJavaScript("window.Agent.runTask(\(taskJson))") { result, error in // Handle result }
Note: all actions above are DOM-level. They won’t bypass user-activation gates. When a gate is hit (e.g., clipboard read), report it to native and present a UX to get a real tap.
Networking and headers in WebViews
Android WebView gives more control over HTTP than WKWebView.
Android:
- Use
shouldInterceptRequestto inject headers (e.g., your request-id) or to block tracking domains. - For HTML you control, send
Accept-CHresponse headers to bootstrap UA-CH on subsequent navigations.
kotlinoverride fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest): WebResourceResponse? { val url = request.url.toString() if (isTracker(url)) { return WebResourceResponse("text/plain", "utf-8", 204, "Blocked", mapOf(), ByteArrayInputStream(ByteArray(0))) } return super.shouldInterceptRequest(view, request) }
iOS WKWebView:
- There is no symmetric
shouldInterceptRequest; you can useWKURLSchemeHandlerfor custom schemes or injectfetch/XHR wrappers client-side. - Content rule lists (JSON) provide declarative blocking (like Safari content blockers) without JS overhead.
Testing and CI
- Android: use Espresso for UI, Robolectric for unit tests of WebView host logic. For Web content, you can stand up Playwright-driven tests that mirror the DOM your agent expects.
- iOS: use XCUITest for the host app. WKWebView internals are hard to drive from tests; instrument JS via evaluateJavaScript and assert results.
- End-to-end: run integration tests on device farms (Firebase Test Lab, AWS Device Farm) to capture performance and crash metrics.
Pitfalls and mitigations
- Sites detect WebView (
wvtoken) and degrade or block. Mitigation: transparently route identity or sensitive flows through system browsers; for content consumption, consider per-site SSR or API-based data paths. - Iframes and cross-origin restrictions limit DOM access. Mitigation: accept top-level only automation; where necessary, target landing pages rather than embedded flows.
- Captchas. Mitigation: avoid initiating flows that trigger bot detection; prefer user-initiated runs and throttle frequency; integrate with legitimate verification flows where permitted.
- Passkey UX regressions in iframes. Mitigation: ensure top-level same-origin when invoking WebAuthn; otherwise bounce to system browser.
What about driving Chrome via CDP on Android?
For clarity: Chrome’s DevTools Protocol is fantastic for development and desktop automation. On Android, you can attach over USB/Wi-Fi for debugging. Shipping an app that silently attaches to a user’s Chrome or man-in-the-middles Chrome traffic is not supported and would be a serious privacy violation. Don’t do it.
If you need full-browser features for a flow (FedCM, existing cookies, stored passkeys), open a system browser surface (CCT/Safari). If you need deterministic automation, keep it in your embedded webview under tight control.
Performance: measuring and tuning
- Measure FCP/LCP/TTI for your agent tasks using the PerformanceObserver API and report to native.
- On Android, enable hardware acceleration (default) and consider
setLayerType(LAYER_TYPE_HARDWARE, null)only when rendering bugs force software fallback. - Reduce JS bundle size for your agent script; keep it under 30–50 KB. Load lazily only when on allowed hosts.
- Use requestIdleCallback (Chromium) to schedule low-priority DOM scans when the main thread is free.
Example: measuring LCP in an agent
jsfunction observeLCP(cb){ if (!('PerformanceObserver' in window)) return; const po = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'largest-contentful-paint') { cb(entry); } } }); po.observe({ type: 'largest-contentful-paint', buffered: true }); } observeLCP(entry => { const payload = { t: entry.startTime, size: entry.size }; // Ship to native bridge });
Opinionated recommendations
- Use WebView/WKWebView for automation; use system browsers for identity, payments, and anything requiring robust cookie jar continuity or FedCM.
- Do not fight gesture gating. Design humane prompts that ask for one tap to unlock tasks.
- Embrace UA reduction; avoid UA sniffing. If you need device metadata, use JS UA-CH at runtime on Chromium and pass it via your own origin.
- Keep agents small, host-allowlisted, and auditable. Provide a log the user can inspect and a kill switch.
- Be power-aware by default. If the user is on low power or metered network, postpone non-urgent tasks.
- Treat WKWebView and Android WebView as different products. Build a thin compatibility layer, but ship platform-specific toggles and fallbacks.
Quick capability matrix (2025 reality check)
-
FedCM:
- Chrome: yes
- Android WebView: no
- iOS WKWebView: assume no for production
-
UA-CH:
- Chrome/WebView: yes (with server opt-in for high-entropy hints)
- WKWebView: no (use classic UA)
-
Passkeys/WebAuthn:
- Chrome/WebView: yes (top-level)
- WKWebView: yes (top-level, iOS 16+)
-
Background browsing:
- Chrome/WebView/WKWebView: no (foreground only; restricted background work without live webviews)
-
Gesture injection:
- Android: possible via AccessibilityService (heavy, user opt-in)
- iOS: not available to production apps
References and further reading
- Chromium UA Reduction overview: https://www.chromium.org/updates/ua-reduction/
- UA Client Hints draft: https://wicg.github.io/ua-client-hints/
- FedCM spec draft: https://fedidcg.github.io/FedCM/
- WebAuthn Level 3: https://www.w3.org/TR/webauthn-3/
- WebKit ITP: https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/
- Android WebView docs: https://developer.android.com/guide/webapps/webview
- WKWebView docs: https://developer.apple.com/documentation/webkit/wkwebview
- BGTaskScheduler: https://developer.apple.com/documentation/backgroundtasks
- WorkManager: https://developer.android.com/topic/libraries/architecture/workmanager
Closing
Mobile agentic browsing is not about brute-forcing desktop automation onto phones. It’s about composing a respectful pipeline: native orchestrator + web sandbox + identity via system browsers + careful power and privacy budgets. Work with the grain of Android and iOS, and you can ship agents that feel native, stay within policy lines, and quietly do useful work on behalf of users.