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:

  1. JavaScript to Native: Chromium registers message handlers using WKScriptMessageHandler. When an injected JavaScript function wants to send data back to the browser, it calls window.webkit.messageHandlers.<HandlerName>.postMessage(). The native Objective-C layer intercepts this JSON payload and routes it to the appropriate C++ controller.
  2. Native to JavaScript: When the native browser needs to trigger an action on the page, it calls the evaluateJavaScript:inFrame:inContentWorld:completionHandler: method on the WKWebView. This executes functions previously defined on the __gCrWeb object 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 like type="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 postMessage bridge.
  • 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 evaluateJavaScript to pass this payload to a __gCrWeb.autofill.fillForm() function.
  • DOM Manipulation: The injected JavaScript locates the specific DOM input elements, sets their .value properties to the user's credentials, and programmatically dispatches input and change events 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 nodeValue of the original text nodes in the DOM, seamlessly updating the language on the screen while preserving the underlying HTML structure.