How iOS Chromium Uses JavaScript Injection in WKWebView
Because Apple requires all third-party browsers on iOS to use the
native WebKit rendering engine via the WKWebView API,
Chromium on iOS cannot use its own Blink engine to directly manipulate
the Document Object Model (DOM) from C++ code. This article explains how
Chromium overcomes these architectural limitations to implement core
browser features like password autofill and page translation. It
achieves this by bundling custom JavaScript scripts, injecting them into
the WKWebView lifecycle, and establishing a two-way
communication bridge between the web page's DOM and Chromium's native
C++ and Objective-C codebase.
The Architecture of
WKWebView and __gCrWeb
In desktop versions of Chromium, the browser has direct C++ access to
the Blink rendering engine, allowing it to inspect and modify HTML
elements natively. On iOS, WKWebView runs in a separate
process and hides the DOM behind a strict security boundary.
To interact with the web page, Chromium uses a JavaScript namespace
called __gCrWeb. During the build process, Chromium
compiles various TypeScript and JavaScript files into bundles. When a
user navigates to a webpage, Chromium uses WebKit's
WKUserContentController to inject these scripts as
WKUserScript objects. These scripts are configured to run
either at the start of the document load (atDocumentStart)
or after the DOM is fully constructed (atDocumentEnd).
To prevent the webpage's own JavaScript from tampering with
Chromium’s injected scripts, Chromium utilizes
WKContentWorld (introduced in iOS 14). This allows the
__gCrWeb scripts to execute in an isolated environment,
separate from the standard web page scripts, while still having full
access to the same DOM.
The Two-Way Communication Bridge
Injecting scripts is only half the solution; the scripts must communicate with Chromium's native C++ features. This is achieved through a bidirectional bridge:
- JavaScript to Native: Chromium registers message
handlers using
WKScriptMessageHandler. When an injected JavaScript function wants to send data back to the browser, it callswindow.webkit.messageHandlers.<HandlerName>.postMessage(). The native Objective-C layer intercepts this JSON payload and routes it to the appropriate C++ controller. - Native to JavaScript: When the native browser needs
to trigger an action on the page, it calls the
evaluateJavaScript:inFrame:inContentWorld:completionHandler:method on theWKWebView. This executes functions previously defined on the__gCrWebobject within the isolated world.
Implementing Password Autofill
Password autofill requires the browser to detect login forms, recognize when a user submits them, and populate the fields with saved credentials. Chromium executes this via the following pipeline:
- Form Detection: Once the page loads, an injected
Autofill script scans the DOM for
<form>and<input>elements. It ignores irrelevant fields and specifically looks for attributes liketype="password",autocomplete="username", and surrounding text labels. - Data Extraction: The script serializes the
structure of the form into a JSON object, assigning unique identifiers
to the input fields, and sends this data to the native layer via the
postMessagebridge. - Native Processing: The native
AutofillManager(written in C++ and shared across all Chromium platforms) analyzes the form structure and checks the local database for matching saved credentials. - Field Population: If a match is found and the user
authenticates (e.g., via FaceID), the native layer constructs a JSON
payload containing the username and password. It calls
evaluateJavaScriptto pass this payload to a__gCrWeb.autofill.fillForm()function. - DOM Manipulation: The injected JavaScript locates
the specific DOM input elements, sets their
.valueproperties to the user's credentials, and programmatically dispatchesinputandchangeevents so the host webpage's JavaScript frameworks (like React or Angular) recognize that the fields have been filled.
Implementing Page Translation
Translating a webpage requires reading all visible text, determining its language, and replacing it with translated text without breaking the page layout or event listeners.
- Language Detection: When a page finishes loading,
an injected script traverses the DOM body, extracting a sample of
visible text nodes while ignoring
<script>,<style>, and<code>tags. This text sample is sent to the native layer via a message handler. - Triggering Translation: The native C++ language detection model determines the language of the extracted text. If it differs from the user's system language, the native UI prompts the user to translate the page.
- Injecting the Translation Engine: If the user
agrees, the native layer fetches the Google Translate element JavaScript
library from the network. Because websites often have strict Content
Security Policies (CSP) that block external scripts, Chromium injects
this translation library directly into its isolated
WKContentWorld, bypassing the page's CSP restrictions. - DOM Replacement: The injected translation script
walks the DOM tree, collects batches of text nodes, and sends them out
for translation via network requests routed through the native browser
process (again, to bypass CORS and CSP limitations). As translated
strings return, the script directly mutates the
nodeValueof the original text nodes in the DOM, seamlessly updating the language on the screen while preserving the underlying HTML structure.