Skip to content

fix(android): resolve WebViewDialog with webViewId and return result#471

Closed
crosslist-jonaslarsen wants to merge 2 commits intoCap-go:mainfrom
crosslist-jonaslarsen:fix/resolve-WebViewDialog-with-webViewId
Closed

fix(android): resolve WebViewDialog with webViewId and return result#471
crosslist-jonaslarsen wants to merge 2 commits intoCap-go:mainfrom
crosslist-jonaslarsen:fix/resolve-WebViewDialog-with-webViewId

Conversation

@crosslist-jonaslarsen
Copy link

@crosslist-jonaslarsen crosslist-jonaslarsen commented Mar 11, 2026

The webViewId was not returned when the WebViewDialog resolved. If that resolve happened before the one in the plugin then we receive undefined in javascript.

Summary by CodeRabbit

  • Bug Fixes
    • Improved in-app browser initialization timing to avoid immediate premature responses when opening a web view.
    • Ensured consistent delivery of the browser instance identifier when pages finish loading or when the view becomes ready, improving event reliability and integration timing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

The plugin call resolution was moved from immediate return in openWebView to deferred resolution inside WebViewDialog lifecycle events (shouldOverrideUrlLoading and onPageFinished), so the ID result is provided only after relevant WebView events occur.

Changes

Cohort / File(s) Summary
Plugin instantiation
android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java
Removed immediate resolve(...) returning the ID after creating/registering the WebViewDialog; now only calls presentWebView() and defers response.
Deferred resolution points
android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java
Changed two code paths to resolve the plugin call with a JSObject containing "id" in shouldOverrideUrlLoading and onPageFinished (both hidden and non-hidden branches).

Sequence Diagram(s)

sequenceDiagram
    participant Plugin as Plugin (InAppBrowserPlugin)
    participant Dialog as WebViewDialog
    participant WebView as WebViewClient / Page

    Plugin->>Dialog: create & register dialog
    Plugin->>Dialog: presentWebView()  -- no immediate resolve
    Note over Dialog,WebView: WebView lifecycle proceeds
    WebView->>Dialog: shouldOverrideUrlLoading / onPageFinished
    Dialog->>Plugin: resolve({ id: instanceId })
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • riderx

Poem

🐰
I waited in my cozy burrow, small and spry,
Till pages finished loading under sky,
Then hopped with joy to send the ID,
No rush, no leap — just proper timing, whee! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: resolving WebViewDialog with webViewId and returning the result to fix the missing ID issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java (1)

2784-2808: ⚠️ Potential issue | 🔴 Critical

Critical: Same undefined webViewId issue in onPageFinished.

The webViewId variable is not defined in this scope either. Use instanceId instead to match the pattern used elsewhere in this class (e.g., line 2649).

🐛 Proposed fix
                 public void onPageFinished(WebView view, String url) {
                     super.onPageFinished(view, url);
                     if (view == null || _webView == null) {
                         return;
                     }
-                    JSObject result = new JSObject();
-                    result.put("id", webViewId);
+                    JSObject result = new JSObject();
+                    result.put("id", instanceId);
                     if (!isInitialized) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java`
around lines 2784 - 2808, In onPageFinished within WebViewDialog.java the
undefined variable webViewId is used when building the JSObject result and
resolving the PluginCall; replace all uses of webViewId in that block with the
correctly scoped identifier instanceId (same pattern used around line 2649) so
JSObject result.put("id", instanceId) and the call to
_options.getPluginCall().resolve(result) references the result containing
instanceId; ensure no other references to webViewId remain in this method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java`:
- Around line 975-986: The JSObject result is using an undefined variable
webViewId; replace that with the class field instanceId (the
WebViewDialog.instanceId set via setInstanceId()) so the object contains the
correct identifier. Update the code that builds the JSObject in the method shown
to call result.put("id", instanceId) and keep the surrounding logic (isHidden,
getInvisibilityMode, show, applyHiddenMode, getPluginCall().resolve) unchanged
so resolution uses the existing instanceId.
- Around line 975-986: Summarize and document the Android vs iOS
promise-resolution timing difference: in WebViewDialog (methods around
show()/openWebView and the use of _options.getPluginCall().resolve(result))
Android resolves the PluginCall only after the dialog is shown or after page
load when isPresentAfterPageLoad=true, while iOS resolves immediately at the end
of openWebView regardless of isPresentAfterPageLoad; add a concise note to the
plugin API docs (and method docs for openWebView / WebViewDialog and the
isPresentAfterPageLoad option) describing this platform-specific behavior and
advising consumers to account for the different timing.

---

Outside diff comments:
In `@android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java`:
- Around line 2784-2808: In onPageFinished within WebViewDialog.java the
undefined variable webViewId is used when building the JSObject result and
resolving the PluginCall; replace all uses of webViewId in that block with the
correctly scoped identifier instanceId (same pattern used around line 2649) so
JSObject result.put("id", instanceId) and the call to
_options.getPluginCall().resolve(result) references the result containing
instanceId; ensure no other references to webViewId remain in this method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 404fce4c-a150-454c-9787-ba35b02d3811

📥 Commits

Reviewing files that changed from the base of the PR and between 38dcdd3 and c6a90d3.

📒 Files selected for processing (2)
  • android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java
  • android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java

@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java (1)

2784-2808: Consider moving result creation inside the conditional block.

The JSObject result is created on every onPageFinished call, but it's only used when !isInitialized && isPresentAfterPageLoad(). Since onPageFinished is called on each navigation, this creates unnecessary allocations after the first page load.

Suggested optimization
 `@Override`
 public void onPageFinished(WebView view, String url) {
     super.onPageFinished(view, url);
     if (view == null || _webView == null) {
         return;
     }
-    JSObject result = new JSObject();
-    result.put("id", instanceId);
     if (!isInitialized) {
         isInitialized = true;
         _webView.clearHistory();
         if (_options.isPresentAfterPageLoad()) {
+            JSObject result = new JSObject();
+            result.put("id", instanceId);
             boolean usePreShowScript = _options.getPreShowScript() != null && !_options.getPreShowScript().isEmpty();
             if (!usePreShowScript) {
                 show();
                 _options.getPluginCall().resolve(result);
             } else {
+                final JSObject finalResult = result;
                 executorService.execute(
                     new Runnable() {
                         `@Override`
                         public void run() {
                             // ... existing code ...
                             activity.runOnUiThread(
                                 new Runnable() {
                                     `@Override`
                                     public void run() {
                                         show();
-                                        _options.getPluginCall().resolve(result);
+                                        _options.getPluginCall().resolve(finalResult);
                                     }
                                 }
                             );
                         }
                     }
                 );
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java`
around lines 2784 - 2808, Move the creation of JSObject result into the branch
where it is actually used: inside the if (!isInitialized &&
_options.isPresentAfterPageLoad()) block in onPageFinished so it's not allocated
on every navigation; create it immediately before any calls to
_options.getPluginCall().resolve(result) and ensure result is
final/effectively-final so it can be referenced from the nested Runnables used
by injectPreShowScript/show.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java`:
- Around line 975-986: presentWebView() currently resolves the same PluginCall
that onPageFinished() may also resolve, causing duplicate resolution attempts;
add a guard boolean (e.g., private boolean presentResolved) to WebViewDialog and
use it to ensure the PluginCall is resolved only once: before calling
_options.getPluginCall().resolve(result) in presentWebView() and before any
resolve call in onPageFinished(), check if presentResolved is false, then call
resolve and set presentResolved = true; reference the methods presentWebView(),
onPageFinished(), the _options.getPluginCall() call site, and the
isHidden()/isPresentAfterPageLoad() checks when adding this guard.

---

Nitpick comments:
In `@android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java`:
- Around line 2784-2808: Move the creation of JSObject result into the branch
where it is actually used: inside the if (!isInitialized &&
_options.isPresentAfterPageLoad()) block in onPageFinished so it's not allocated
on every navigation; create it immediately before any calls to
_options.getPluginCall().resolve(result) and ensure result is
final/effectively-final so it can be referenced from the nested Runnables used
by injectPreShowScript/show.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9091cbe6-0376-4965-b986-8522056e3bf5

📥 Commits

Reviewing files that changed from the base of the PR and between c6a90d3 and 8b10499.

📒 Files selected for processing (1)
  • android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java

Comment on lines +975 to 986
JSObject result = new JSObject();
result.put("id", instanceId);
if (this._options.isHidden()) {
if (_options.getInvisibilityMode() == Options.InvisibilityMode.FAKE_VISIBLE) {
show();
applyHiddenMode();
}
_options.getPluginCall().resolve();
_options.getPluginCall().resolve(result);
} else if (!this._options.isPresentAfterPageLoad()) {
show();
_options.getPluginCall().resolve();
_options.getPluginCall().resolve(result);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's any mutual exclusion logic between isHidden and isPresentAfterPageLoad
echo "Checking Options class for isHidden/isPresentAfterPageLoad relationship:"
rg -n -A 5 "isPresentAfterPageLoad|isHidden" android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java

echo ""
echo "Checking plugin for how these options are set together:"
rg -n -B 2 -A 5 "setHidden|setPresentAfterPageLoad" android/src/main/java/ee/forgr/capacitor_inappbrowser/

Repository: Cap-go/capacitor-inappbrowser

Length of output: 10943


🏁 Script executed:

#!/bin/bash
# Check the onPageFinished method to see resolution logic
echo "Checking onPageFinished method (around lines 2784-2808):"
sed -n '2780,2820p' android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java

echo ""
echo "Checking for any 'resolved' or 'completed' flags:"
rg -n "resolved|completed|isResolved" android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java | head -20

Repository: Cap-go/capacitor-inappbrowser

Length of output: 2457


🏁 Script executed:

#!/bin/bash
# Search for validation logic that might prevent both flags being true
echo "Checking for validation in Options class:"
sed -n '1,600p' android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java | grep -A 10 -B 5 "setHidden\|setPresentAfterPageLoad" | head -40

echo ""
echo "Checking for validation in InAppBrowserPlugin:"
rg -B 5 -A 10 "setHidden|setPresentAfterPageLoad" android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java | grep -A 10 -B 5 "validate\|check\|if.*hidden\|if.*present"

echo ""
echo "Checking for any assertions or guards about these options together:"
rg -n "isHidden.*isPresentAfterPageLoad|isPresentAfterPageLoad.*isHidden" android/src/main/java/ee/forgr/capacitor_inappbrowser/

Repository: Cap-go/capacitor-inappbrowser

Length of output: 1077


🏁 Script executed:

#!/bin/bash
# Check if PluginCall is defined in this repo or if it's from Capacitor
echo "Searching for PluginCall definition:"
find . -name "*.java" -type f -exec grep -l "interface PluginCall\|class PluginCall" {} \;

echo ""
echo "Checking imports to see where PluginCall comes from:"
rg "import.*PluginCall" android/src/main/java/ee/forgr/capacitor_inappbrowser/ | head -5

echo ""
echo "Checking Capacitor documentation or comments about resolve:"
rg -B 3 -A 3 "getPluginCall.*resolve" android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java | head -20

Repository: Cap-go/capacitor-inappbrowser

Length of output: 1237


🌐 Web query:

Capacitor PluginCall resolve() called multiple times behavior

💡 Result:

By default in Capacitor, a PluginCall/CAPPluginCall is single-shot: once you call call.resolve() or call.reject() the bridge removes its bookkeeping for that callback, so later resolve() calls won’t be delivered back to JS (and may just log/behave like a no-op). [1]

If you intentionally need to send multiple results from one native call, you must mark the call as a callback/stream by setting keepAlive (call.setKeepAlive(true) on Android / call.keepAlive = true on iOS). With keepAlive = true, resolve() can be called repeatedly and each result is sent to JS. [1]

Also, even with keepAlive, you cannot receive multiple results via a normal JS Promise (await plugin.method() / .then(...)), because a promise resolves only once. You must use a callback-style method (or plugin events like notifyListeners) to receive multiple updates. [2]

Sources: [1] Saving/Persisting Plugin Calls (Capacitor docs) (capacitorjs.jp), [2] Ionic Forum: “Help with resolving call multiple times (Call keepAlive)” (forum.ionicframework.com)

Citations:


🏁 Script executed:

#!/bin/bash
# Check if setKeepAlive is used on PluginCall
echo "Searching for setKeepAlive usage:"
rg -n "setKeepAlive" android/src/main/java/ee/forgr/capacitor_inappbrowser/

echo ""
echo "Also check if the PluginCall returned by getPluginCall is modified:"
rg -n "getPluginCall()" android/src/main/java/ee/forgr/capacitor_inappbrowser/ | head -10

Repository: Cap-go/capacitor-inappbrowser

Length of output: 876


Fix correctly includes instanceId in the resolved result.

The implementation properly creates a JSObject with the id field set to instanceId, addressing the issue where JavaScript received undefined for the webViewId.

However, there is a logical issue: if both isHidden() and isPresentAfterPageLoad() are true, presentWebView() will resolve at line 982, and onPageFinished() will attempt to resolve again at lines 2793/2808. While Capacitor's single-shot PluginCall prevents the second resolution from reaching JavaScript, this still represents flawed logic. Either:

  • Ensure isHidden() and isPresentAfterPageLoad() are mutually exclusive through validation, or
  • Add a guard to prevent duplicate resolution attempts (e.g., by checking if the call is already resolved or using a completion flag).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java`
around lines 975 - 986, presentWebView() currently resolves the same PluginCall
that onPageFinished() may also resolve, causing duplicate resolution attempts;
add a guard boolean (e.g., private boolean presentResolved) to WebViewDialog and
use it to ensure the PluginCall is resolved only once: before calling
_options.getPluginCall().resolve(result) in presentWebView() and before any
resolve call in onPageFinished(), check if presentResolved is false, then call
resolve and set presentResolved = true; reference the methods presentWebView(),
onPageFinished(), the _options.getPluginCall() call site, and the
isHidden()/isPresentAfterPageLoad() checks when adding this guard.

@riderx
Copy link
Member

riderx commented Mar 17, 2026

close in favor of #476

@riderx riderx closed this Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants