diff --git a/README.md b/README.md index c9155d8a..f33b62b2 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,9 @@ When you open a webview with this method, a JavaScript interface is automaticall - `window.mobileApp.close()`: Closes the webview from JavaScript - `window.mobileApp.postMessage({detail: {message: "myMessage"}})`: Sends a message from the webview to the app, detail object is the data you want to send to the webview +Promise timing differs by platform when `isPresentAfterPageLoad` is used. +Android resolves with `{ id }` after the dialog is ready to control, while iOS resolves with `{ id }` immediately after creating the native webview. + | Param | Type | | ------------- | ----------------------------------------------------------------- | | **`options`** | OpenWebViewOptions | @@ -868,7 +871,7 @@ And in the AndroidManifest.xml file: | **`backgroundColor`** | BackgroundColor | Background color of the browser | BackgroundColor.BLACK | 0.1.0 | | **`activeNativeNavigationForWebview`** | boolean | If true, enables native navigation gestures within the webview. - Android: Native back button navigates within webview history - iOS: Enables swipe left/right gestures for back/forward navigation | false (Android), true (iOS - enabled by default) | | | **`disableGoBackOnNativeApplication`** | boolean | Disable the possibility to go back on native application, useful to force user to stay on the webview, Android only | false | | -| **`isPresentAfterPageLoad`** | boolean | Open url in a new window fullscreen isPresentAfterPageLoad: if true, the browser will be presented after the page is loaded, if false, the browser will be presented immediately. | false | 0.1.0 | +| **`isPresentAfterPageLoad`** | boolean | Open url in a new window fullscreen isPresentAfterPageLoad: if true, the browser will be presented after the page is loaded, if false, the browser will be presented immediately. Promise timing: on Android, `openWebView()` resolves with the webview id when the webview is ready to be controlled (immediately for hidden/immediate presentation, after the first page load when `isPresentAfterPageLoad` is `true`). On iOS, the promise resolves with the id as soon as the native webview is created, even if presentation is deferred. | false | 0.1.0 | | **`isInspectable`** | boolean | Whether the website in the webview is inspectable or not, ios only | false | | | **`isAnimated`** | boolean | Whether the webview opening is animated or not, ios only | true | | | **`showReloadButton`** | boolean | Shows a reload button that reloads the web page | false | 1.0.15 | diff --git a/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java b/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java index 7a183ea3..e26d7292 100644 --- a/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +++ b/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java @@ -874,9 +874,6 @@ public void run() { dialog.activity = InAppBrowserPlugin.this.getActivity(); registerWebView(webViewId, dialog); dialog.presentWebView(); - JSObject result = new JSObject(); - result.put("id", webViewId); - call.resolve(result); } } ); diff --git a/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java b/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java index bb50c226..78ab8226 100644 --- a/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +++ b/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java @@ -65,6 +65,7 @@ import com.caverock.androidsvg.SVG; import com.caverock.androidsvg.SVGParseException; import com.getcapacitor.JSObject; +import com.getcapacitor.PluginCall; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -135,6 +136,7 @@ public ProxiedRequest() { public static final int FILE_CHOOSER_REQUEST_CODE = 1000; public ValueCallback mUploadMessage; public ValueCallback mFilePathCallback; + private boolean openWebViewResolved; // Temporary URI for storing camera capture public Uri tempCameraUri; @@ -154,6 +156,7 @@ public WebViewDialog(Context context, int theme, Options options, PermissionHand this._context = context; this.permissionHandler = permissionHandler; this.isInitialized = false; + this.openWebViewResolved = false; this.capacitorWebView = capacitorWebView; } @@ -165,6 +168,41 @@ public String getInstanceId() { return instanceId; } + private void resolveOpenWebViewIfNeeded() { + if (openWebViewResolved || _options == null) { + return; + } + PluginCall call = _options.getPluginCall(); + if (call == null) { + Log.e("InAppBrowser", "Cannot resolve openWebView: plugin call is null"); + openWebViewResolved = true; + return; + } + if (instanceId == null || instanceId.isEmpty()) { + call.reject("Cannot resolve openWebView: missing webview id"); + openWebViewResolved = true; + return; + } + JSObject result = new JSObject(); + result.put("id", instanceId); + call.resolve(result); + openWebViewResolved = true; + } + + private void rejectOpenWebViewIfNeeded(String message) { + if (openWebViewResolved || _options == null) { + return; + } + PluginCall call = _options.getPluginCall(); + if (call == null) { + Log.e("InAppBrowser", "Cannot reject openWebView: plugin call is null"); + openWebViewResolved = true; + return; + } + call.reject(message); + openWebViewResolved = true; + } + // Add this class to provide safer JavaScript interface private class JavaScriptInterface { @@ -978,10 +1016,10 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { show(); applyHiddenMode(); } - _options.getPluginCall().resolve(); + resolveOpenWebViewIfNeeded(); } else if (!this._options.isPresentAfterPageLoad()) { show(); - _options.getPluginCall().resolve(); + resolveOpenWebViewIfNeeded(); } } @@ -2467,6 +2505,7 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request // Notify that a page load error occurred if (_options.getCallbacks() != null && request.isForMainFrame()) { _options.getCallbacks().pageLoadError(); + rejectOpenWebViewIfNeeded("No handler available for external URL: " + url); } return true; // prevent WebView from attempting to load the custom scheme } @@ -2796,7 +2835,7 @@ public void onPageFinished(WebView view, String url) { boolean usePreShowScript = _options.getPreShowScript() != null && !_options.getPreShowScript().isEmpty(); if (!usePreShowScript) { show(); - _options.getPluginCall().resolve(); + resolveOpenWebViewIfNeeded(); } else { executorService.execute( new Runnable() { @@ -2811,7 +2850,7 @@ public void run() { @Override public void run() { show(); - _options.getPluginCall().resolve(); + resolveOpenWebViewIfNeeded(); } } ); @@ -2889,6 +2928,11 @@ public void onReceivedError(WebView view, WebResourceRequest request, WebResourc return; } _options.getCallbacks().pageLoadError(); + if (request != null && request.isForMainFrame() && !isInitialized) { + CharSequence description = error != null ? error.getDescription() : null; + String message = description != null ? "Initial page load failed: " + description : "Initial page load failed"; + rejectOpenWebViewIfNeeded(message); + } } @SuppressLint("WebViewClientOnReceivedSslError") diff --git a/src/definitions.ts b/src/definitions.ts index 11f51995..557b07f3 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -400,6 +400,9 @@ export interface OpenWebViewOptions { /** * Open url in a new window fullscreen * isPresentAfterPageLoad: if true, the browser will be presented after the page is loaded, if false, the browser will be presented immediately. + * Promise timing: on Android, `openWebView()` resolves with the webview id when the webview is ready to be controlled + * (immediately for hidden/immediate presentation, after the first page load when `isPresentAfterPageLoad` is `true`). + * On iOS, the promise resolves with the id as soon as the native webview is created, even if presentation is deferred. * @since 0.1.0 * @default false * @example @@ -852,6 +855,9 @@ export interface InAppBrowserPlugin { * - `window.mobileApp.close()`: Closes the webview from JavaScript * - `window.mobileApp.postMessage({detail: {message: "myMessage"}})`: Sends a message from the webview to the app, detail object is the data you want to send to the webview * + * Promise timing differs by platform when `isPresentAfterPageLoad` is used. + * Android resolves with `{ id }` after the dialog is ready to control, while iOS resolves with `{ id }` immediately after creating the native webview. + * * @returns Promise that resolves with the created webview id. * @since 0.1.0 */