Skip to content

Commit 19a98b9

Browse files
committed
Add doc-bot file for addDocumentStartJavaScript
1 parent 19995c7 commit 19a98b9

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
title: "AddDocumentStartJavaScript"
3+
description: "How we use AddDocumentStartJavaScript"
4+
keywords: ["adddocumentstartjavascript", "addDocumentStartJavaScript", "AddDocumentStartJavaScript", "add document start javascript", "css", "c-s-s", "CSS", "C-S-S", "WebView", "webview", "contentscopescripts", "content-scope-scripts", "ContentScopeScripts", "contentScopeScripts"]
5+
alwaysApply: false
6+
---
7+
8+
# Adding a new script using `addDocumentStartJavaScript`
9+
10+
There are 2 ways of adding a new script using
11+
1. (Recommended) Using `AddDocumentStartScriptDelegate` to implement `AddDocumentStartJavaScript` via delegation pattern. This approach already takes care of
12+
preventing adding the same script more than once, and removing the previous script and adding a new one if needed. It also adds checks on the WebView lifecycle to minimize native crashes.
13+
2. (Manual approach, only recommended if more flexibility than the one provided by the delegate is needed) Manually implementing `AddDocumentStartJavaScript`
14+
15+
## Using `AddDocumentStartScriptDelegate` to implement `AddDocumentStartJavaScript` via delegation pattern
16+
```
17+
class <YourFeature>AddDocumentStartJavaScript @Inject constructor(
18+
scriptInjectorDelegate: AddDocumentStartScriptDelegate,
19+
) : AddDocumentStartJavaScript by scriptInjectorDelegate.createPlugin(
20+
object : AddDocumentStartJavaScriptScriptStrategy {
21+
override suspend fun canInject(): Boolean {
22+
TODO("Implement logic to determine if the script can be added (i.e. checking RC flags or user settings)" +
23+
"or return true if always applicable")
24+
}
25+
26+
override suspend fun getScriptString(): String {
27+
TODO("Return the script to be injected")
28+
}
29+
30+
override val allowedOriginRules: Set<String>
31+
get() = TODO("Return the set of allowed origin rules. For example:" +
32+
"- if the script should be injected on all origins, return setOf(\"*\")" +
33+
"- if the script should be injected only on specific origins, return setOf(\"https://example.com\", \"https://another.com\")" +
34+
"- if the script should be injected on all subdomains of a domain, return setOf(\"https://*.example.com\")")
35+
36+
override val context: String
37+
get() = TODO("Return a string representing the context of this script, e.g., \"YourFeature\"")
38+
},
39+
)
40+
```
41+
42+
## Manually implementing `AddDocumentStartJavaScript`
43+
Since the `AddDocumentStartScriptDelegate` already solves most of the issues and dangers of working with the `addDocumentStartJavaScript` API, manual implementation isn't recommended. If absolutely necessary, having a look at `RealAddDocumentStartScriptDelegate` is recommended, in order to replicate some best practices:
44+
* If a script has already been added, don't add it again unless the content has changed
45+
* If the content has changed, remove the previous `ScriptHandler` before adding the new script. Call `remove` on the main thread
46+
* Use `WebViewCompatWrapper` instead of calling `WebViewCompat` directly, as it includes several checks on the `WebView` lifecycle and ensures proper threading is used
47+
48+
# Adding a new script to the browser (DuckDuckGoWebView/BrowserTabFragment)
49+
50+
If you need your script to be executed on the main browser WebView, you need to create a browser plugin that wraps your `AddDocumentStartJavaScript` implementation.
51+
52+
## Step 1: Create the core implementation
53+
54+
Follow the patterns described in the [delegation pattern section](#using-adddocumentstartscriptdelegate-to-implement-adddocumentstartjavascript-via-delegation-pattern) above.
55+
56+
## Step 2: Create the browser plugin wrapper
57+
58+
```kotlin
59+
@ContributesMultibinding(FragmentScope::class)
60+
class <YourFeature>AddDocumentStartJavaScriptBrowserPlugin @Inject constructor(
61+
private val <yourFeature>AddDocumentStartJavaScript: <YourFeature>AddDocumentStartJavaScript,
62+
) : AddDocumentStartJavaScriptBrowserPlugin {
63+
override fun addDocumentStartJavaScript(): AddDocumentStartJavaScript =
64+
<yourFeature>AddDocumentStartJavaScript
65+
}
66+
```
67+
68+
## How it works
69+
70+
The browser automatically calls your script's `addDocumentStartJavaScript(webView)` method when:
71+
- A new page loads
72+
- If you also need it to be called when Privacy protections are updated, you need to add your implementation context to `BrowserTabViewModel#privacyProtectionsUpdated`
73+
- If you need your script to be re-added in any other circumstances, follow the same pattern as `BrowserTabViewModel#privacyProtectionsUpdated`, so we only update the necessary scripts, not all of them. However, this is strongly discouraged by the Chromium team, and should only be done if messaging is not viable (for example, out of performance concerns)
74+
75+
The `AddDocumentStartScriptDelegate` handles lifecycle management, script deduplication, and WebView safety checks.
76+
77+
## Important Notes
78+
79+
- Use appropriate scoping and consider using `@SingleInstanceIn(<Scope>)` with appropriate scoping to make sure only one instance of `WebMessaging` exists per `WebView`

0 commit comments

Comments
 (0)