Skip to content

Commit 30ab290

Browse files
feat(webview): incognito profiles, configurable settings and richer events
Rework the Acode WebView API end to end. - Incognito mode backed by AndroidX WebKit multi-profile isolation, with reference-counted profiles, automatic cleanup and a documented best-effort fallback when the provider lacks multi-profile support. - Configurable user agent (explicit string or desktop/mobile presets) and a broad set of WebSettings, applied at creation and at runtime on the UI thread. - Expanded event stream (page lifecycle, progress, navigation, errors, console, dialogs, permissions, fullscreen, downloads, scroll) plus navigation/settings helpers, capability probes and registry utilities. - Split the native plugin into WebViewPlugin/WebViewInstance/WebViewOptions/ ProfileManager and the JS into a thin bridge plus a public AcodeWebView API. - Harden profile names (reject non-canonical or reserved names instead of rewriting them) and use the originating profile's cookie jar for downloads.
1 parent b57a418 commit 30ab290

13 files changed

Lines changed: 4049 additions & 560 deletions

File tree

‎package-lock.json‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@
4646
"cordova-plugin-iap": {},
4747
"com.foxdebug.acode.rk.customtabs": {},
4848
"cordova-plugin-crashhandler": {},
49-
"cordova-plugin-advanced-http": {
50-
"ANDROIDBLACKLISTSECURESOCKETPROTOCOLS": "SSLv3,TLSv1"
51-
},
49+
"cordova-plugin-advanced-http": {},
5250
"cordova-plugin-acode-webview": {},
5351
"cordova-plugin-system": {}
5452
},

‎src/index.d.ts‎

Lines changed: 271 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -111,49 +111,207 @@ declare global {
111111
var Executor: Executor | undefined;
112112
}
113113

114-
interface WebViewOptions {
114+
type WebViewMode = "fullscreen" | "hidden";
115+
type WebViewCacheMode =
116+
| "default"
117+
| "no-cache"
118+
| "cache-only"
119+
| "cache-else-network";
120+
type WebViewMixedContentMode =
121+
| "never-allow"
122+
| "compatibility"
123+
| "always-allow";
124+
type WebViewUserAgentMode = "default" | "mobile" | "desktop";
125+
type WebViewAlgorithmicDarkening = "auto" | "on" | "off";
126+
127+
/** Web settings that can be set at creation and changed at runtime. */
128+
interface WebViewSettings {
129+
/** Exact user-agent string. Takes precedence over userAgentMode. */
130+
userAgent?: string | null;
131+
/** Preset user agent: platform default, mobile, or desktop. */
132+
userAgentMode?: WebViewUserAgentMode;
133+
javaScript?: boolean;
134+
domStorage?: boolean;
135+
databaseStorage?: boolean;
136+
cacheMode?: WebViewCacheMode;
137+
mediaPlaybackRequiresUserGesture?: boolean;
138+
loadImages?: boolean;
139+
mixedContentMode?: WebViewMixedContentMode;
140+
/** Page text zoom in percent (25-500). */
141+
textZoom?: number;
142+
/** Initial viewport scale in percent (0 = WebView default). */
143+
initialScale?: number;
144+
useWideViewPort?: boolean;
145+
loadWithOverviewMode?: boolean;
146+
supportZoom?: boolean;
147+
builtInZoomControls?: boolean;
148+
displayZoomControls?: boolean;
149+
geolocation?: boolean;
150+
safeBrowsing?: boolean;
151+
algorithmicDarkening?: WebViewAlgorithmicDarkening;
152+
acceptThirdPartyCookies?: boolean;
153+
/** CSS color (e.g. "#101418") painted behind the page. */
154+
backgroundColor?: string;
155+
}
156+
157+
interface WebViewOptions extends WebViewSettings {
115158
/** Title applied to the hosting activity in fullscreen mode. */
116159
title?: string;
117160
/**
118161
* "fullscreen" displays the WebView in its own activity, "hidden" runs it
119162
* headless (never displayed). Defaults to "hidden".
120163
*/
121-
mode?: "fullscreen" | "hidden";
122-
/**
123-
* Allow in-WebView navigation. Defaults to true. Only http(s) targets
124-
* ever load; other schemes are always blocked for isolation.
125-
*/
126-
allowNavigation?: boolean;
127-
/**
128-
* Ask the user with a confirmation dialog before downloading files via
129-
* the system DownloadManager. Defaults to false.
130-
*/
131-
allowDownloads?: boolean;
164+
mode?: WebViewMode;
132165
/**
133166
* Show immediately after creation. Defaults to true. Only meaningful for
134167
* fullscreen mode: when false, the activity launch is deferred until
135168
* show() is called.
136169
*/
137170
visible?: boolean;
171+
/**
172+
* Run in a private, ephemeral profile: separate cookies/localStorage (when
173+
* the WebView provider supports multi-profile), no cache, and all data is
174+
* cleared when the instance is destroyed. Defaults to false.
175+
*/
176+
incognito?: boolean;
177+
/**
178+
* Named WebView profile used for isolation. Incognito instances get an
179+
* auto-generated ephemeral name when this is omitted; a named incognito
180+
* profile is shared by every instance that uses the same name.
181+
*
182+
* Must be 1-64 characters from `A-Z a-z 0-9 _ -`. Names are used verbatim
183+
* and invalid or reserved (`acode_incognito_*`) names are rejected so two
184+
* different requests can never select the same profile.
185+
*/
186+
profileName?: string;
187+
/**
188+
* Allow in-WebView navigation. Defaults to true. Only http(s) targets ever
189+
* load; other schemes are always blocked for isolation.
190+
*/
191+
allowNavigation?: boolean;
192+
/**
193+
* Ask the user with a confirmation dialog before downloading files via the
194+
* system DownloadManager. Defaults to false.
195+
*/
196+
allowDownloads?: boolean;
197+
/** Resolved by the native layer: whether profile isolation is active. */
198+
readonly isolated?: boolean;
199+
}
200+
201+
interface WebViewHTMLLoadOptions {
202+
baseUrl?: string;
203+
mimeType?: string;
204+
encoding?: string;
205+
historyUrl?: string;
206+
}
207+
208+
interface WebViewCapabilities {
209+
version: string;
210+
/** Multi-profile support: required for true incognito isolation. */
211+
multiProfile: boolean;
212+
deleteBrowsingData: boolean;
213+
documentStartScript: boolean;
214+
safeBrowsing: boolean;
215+
algorithmicDarkening: boolean;
138216
}
139217

218+
interface WebViewInfo {
219+
id: string;
220+
mode: WebViewMode;
221+
incognito: boolean;
222+
isolated: boolean;
223+
destroyed: boolean;
224+
ready: boolean;
225+
profileName?: string;
226+
url: string;
227+
title: string;
228+
progress: number;
229+
loading: boolean;
230+
canGoBack: boolean;
231+
canGoForward: boolean;
232+
userAgent?: string | null;
233+
}
234+
235+
type WebViewEventName =
236+
| "pageStarted"
237+
| "pageCommitVisible"
238+
| "pageFinished"
239+
| "progressChanged"
240+
| "titleChanged"
241+
| "faviconChanged"
242+
| "historyChanged"
243+
| "scrollChanged"
244+
| "renderProcessGone"
245+
| "ready"
246+
| "closed"
247+
| "navigationRequested"
248+
| "navigationBlocked"
249+
| "resourceError"
250+
| "httpError"
251+
| "sslError"
252+
| "consoleMessage"
253+
| "jsAlert"
254+
| "jsConfirm"
255+
| "jsPrompt"
256+
| "jsBeforeUnload"
257+
| "permissionRequest"
258+
| "geolocationPermissionRequest"
259+
| "fullscreenChanged"
260+
| "windowRequested"
261+
| "downloadRequested"
262+
| "downloadStarted"
263+
| "downloadFailed"
264+
| "shown"
265+
| "hidden"
266+
| "destroyed";
267+
268+
type WebViewEventHandler<T = unknown> = (data: T, event: string) => void;
269+
type WebViewAnyEventHandler = (event: string, data: unknown) => void;
270+
type WebViewMessageHandler = (message: unknown) => void;
271+
140272
interface AcodeWebView {
141273
readonly id: string;
142274
readonly options: WebViewOptions;
275+
readonly capabilities: WebViewCapabilities;
276+
readonly isDestroyed: boolean;
277+
/** True once the native WebView exists (see waitForReady). */
278+
readonly ready: boolean;
279+
readonly isIncognito: boolean;
280+
/** True when the instance runs in its own isolated WebView profile. */
281+
readonly isIsolated: boolean;
282+
readonly profileName: string | null;
283+
readonly url: string;
284+
readonly title: string;
285+
readonly progress: number;
286+
readonly loading: boolean;
287+
readonly canGoBack: boolean;
288+
readonly canGoForward: boolean;
143289
/** Load an http(s) URL. Other schemes are rejected. */
144-
loadURL(url: string): Promise<void>;
145-
loadHTML(html: string): Promise<void>;
146-
evaluate(js: string): Promise<string>;
147-
onMessage(callback: (message: unknown) => void): void;
148-
offMessage(callback: (message: unknown) => void): void;
290+
loadURL(url: string, headers?: Record<string, string>): Promise<void>;
291+
loadHTML(html: string, options?: WebViewHTMLLoadOptions): Promise<void>;
292+
evaluate(js: string): Promise<string | null>;
293+
postMessage(message: unknown): Promise<void>;
294+
reload(): Promise<void>;
295+
stopLoading(): Promise<void>;
296+
/** Navigates back; resolves true when a navigation happened. */
297+
goBack(): Promise<boolean>;
298+
goForward(): Promise<boolean>;
299+
/** Re-reads the back/forward flags from the native WebView. */
300+
refreshHistory(): Promise<{ canGoBack: boolean; canGoForward: boolean }>;
149301
/**
150-
* Subscribe to lifecycle events: "pageFinished", "titleChanged" and
151-
* "closed" (fullscreen closed by the user or the system). After "closed"
152-
* the instance is destroyed and cannot be reused.
302+
* Resolves once the native WebView exists. Hidden instances are ready by the
303+
* time create() resolves; fullscreen instances become ready when their
304+
* activity builds the WebView, so await this before evaluate() on a
305+
* fullscreen instance. Rejects on close/destroy or timeout (ms; 0 disables).
153306
*/
154-
on(event: string, callback: (event: string, data?: unknown) => void): void;
155-
off(event: string, callback: (event: string, data?: unknown) => void): void;
156-
postMessage(message: unknown): Promise<void>;
307+
waitForReady(timeout?: number): Promise<void>;
308+
/**
309+
* Resolves when the main frame finishes loading, or rejects on a main-frame
310+
* load error, close/destroy, or timeout (ms; 0 disables the timeout).
311+
* Attach it before starting the navigation, since loadURL()/loadHTML()
312+
* resolve as soon as the load is initiated.
313+
*/
314+
waitForLoad(timeout?: number): Promise<{ url: string; title: string }>;
157315
/**
158316
* Show the WebView. Only fullscreen instances can be shown; rejects for
159317
* "hidden" mode.
@@ -165,10 +323,99 @@ interface AcodeWebView {
165323
* No-op for "hidden" mode.
166324
*/
167325
hide(): Promise<void>;
168-
reload(): Promise<void>;
326+
clearCache(includeDiskFiles?: boolean): Promise<void>;
327+
clearHistory(): Promise<void>;
328+
clearFormData(): Promise<void>;
329+
setUserAgent(
330+
userAgent: string | null,
331+
mode?: WebViewUserAgentMode,
332+
): Promise<WebViewSettings>;
333+
getUserAgent(): Promise<string | null>;
334+
/** Applies a partial batch of web settings. */
335+
configure(settings: WebViewSettings): Promise<WebViewSettings>;
336+
getSettings(): Promise<WebViewSettings>;
337+
getInfo(): Promise<WebViewInfo>;
338+
on(event: WebViewEventName | "*", callback: WebViewEventHandler): this;
339+
off(event: WebViewEventName | "*", callback: WebViewEventHandler): this;
340+
once(event: WebViewEventName | "*", callback: WebViewEventHandler): this;
341+
onAny(callback: WebViewAnyEventHandler): this;
342+
offAny(callback: WebViewAnyEventHandler): this;
343+
removeAllListeners(event?: WebViewEventName | "*"): this;
344+
onMessage(callback: WebViewMessageHandler): this;
345+
offMessage(callback: WebViewMessageHandler): this;
169346
destroy(): Promise<void>;
170347
}
171348

349+
interface AcodeWebViewEvents {
350+
readonly PAGE_STARTED: "pageStarted";
351+
readonly PAGE_COMMIT_VISIBLE: "pageCommitVisible";
352+
readonly PAGE_FINISHED: "pageFinished";
353+
readonly PROGRESS: "progressChanged";
354+
readonly TITLE_CHANGED: "titleChanged";
355+
readonly FAVICON_CHANGED: "faviconChanged";
356+
readonly HISTORY_CHANGED: "historyChanged";
357+
readonly SCROLL_CHANGED: "scrollChanged";
358+
readonly RENDER_PROCESS_GONE: "renderProcessGone";
359+
readonly READY: "ready";
360+
readonly CLOSED: "closed";
361+
readonly NAVIGATION_REQUESTED: "navigationRequested";
362+
readonly NAVIGATION_BLOCKED: "navigationBlocked";
363+
readonly RESOURCE_ERROR: "resourceError";
364+
readonly HTTP_ERROR: "httpError";
365+
readonly SSL_ERROR: "sslError";
366+
readonly CONSOLE_MESSAGE: "consoleMessage";
367+
readonly JS_ALERT: "jsAlert";
368+
readonly JS_CONFIRM: "jsConfirm";
369+
readonly JS_PROMPT: "jsPrompt";
370+
readonly JS_BEFORE_UNLOAD: "jsBeforeUnload";
371+
readonly PERMISSION_REQUEST: "permissionRequest";
372+
readonly GEOLOCATION_PERMISSION: "geolocationPermissionRequest";
373+
readonly FULLSCREEN_CHANGED: "fullscreenChanged";
374+
readonly WINDOW_REQUESTED: "windowRequested";
375+
readonly DOWNLOAD_REQUESTED: "downloadRequested";
376+
readonly DOWNLOAD_STARTED: "downloadStarted";
377+
readonly DOWNLOAD_FAILED: "downloadFailed";
378+
readonly SHOWN: "shown";
379+
readonly HIDDEN: "hidden";
380+
readonly DESTROYED: "destroyed";
381+
}
382+
172383
interface AcodeWebViewAPI {
173384
create(options?: WebViewOptions): Promise<AcodeWebView>;
385+
/** Feature support of the installed WebView provider. */
386+
getCapabilities(): Promise<WebViewCapabilities>;
387+
/** Live instance for an id, or null when it no longer exists. */
388+
get(id: string): AcodeWebView | null;
389+
/** Ids of all live instances. */
390+
list(): string[];
391+
/** Destroys every live instance. */
392+
destroyAll(): Promise<void>;
393+
readonly AcodeWebView: new (
394+
id: string,
395+
options?: WebViewOptions,
396+
capabilities?: WebViewCapabilities,
397+
) => AcodeWebView;
398+
readonly events: AcodeWebViewEvents;
399+
readonly modes: { readonly FULLSCREEN: "fullscreen"; readonly HIDDEN: "hidden" };
400+
readonly cacheModes: {
401+
readonly DEFAULT: "default";
402+
readonly NO_CACHE: "no-cache";
403+
readonly CACHE_ONLY: "cache-only";
404+
readonly CACHE_ELSE_NETWORK: "cache-else-network";
405+
};
406+
readonly mixedContentModes: {
407+
readonly NEVER_ALLOW: "never-allow";
408+
readonly COMPATIBILITY: "compatibility";
409+
readonly ALWAYS_ALLOW: "always-allow";
410+
};
411+
readonly userAgentModes: {
412+
readonly DEFAULT: "default";
413+
readonly MOBILE: "mobile";
414+
readonly DESKTOP: "desktop";
415+
};
416+
readonly algorithmicDarkeningModes: {
417+
readonly AUTO: "auto";
418+
readonly ON: "on";
419+
readonly OFF: "off";
420+
};
174421
}

0 commit comments

Comments
 (0)