Skip to content

Commit 1712f25

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 1712f25

13 files changed

Lines changed: 3894 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: 259 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -111,49 +111,196 @@ 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;
216+
}
217+
218+
interface WebViewInfo {
219+
id: string;
220+
mode: WebViewMode;
221+
incognito: boolean;
222+
isolated: boolean;
223+
destroyed: boolean;
224+
profileName?: string;
225+
url: string;
226+
title: string;
227+
progress: number;
228+
loading: boolean;
229+
canGoBack: boolean;
230+
canGoForward: boolean;
231+
userAgent?: string | null;
138232
}
139233

234+
type WebViewEventName =
235+
| "pageStarted"
236+
| "pageCommitVisible"
237+
| "pageFinished"
238+
| "progressChanged"
239+
| "titleChanged"
240+
| "faviconChanged"
241+
| "historyChanged"
242+
| "scrollChanged"
243+
| "renderProcessGone"
244+
| "closed"
245+
| "navigationRequested"
246+
| "navigationBlocked"
247+
| "resourceError"
248+
| "httpError"
249+
| "sslError"
250+
| "consoleMessage"
251+
| "jsAlert"
252+
| "jsConfirm"
253+
| "jsPrompt"
254+
| "jsBeforeUnload"
255+
| "permissionRequest"
256+
| "geolocationPermissionRequest"
257+
| "fullscreenChanged"
258+
| "windowRequested"
259+
| "downloadRequested"
260+
| "downloadStarted"
261+
| "downloadFailed"
262+
| "shown"
263+
| "hidden"
264+
| "destroyed";
265+
266+
type WebViewEventHandler<T = unknown> = (data: T, event: string) => void;
267+
type WebViewAnyEventHandler = (event: string, data: unknown) => void;
268+
type WebViewMessageHandler = (message: unknown) => void;
269+
140270
interface AcodeWebView {
141271
readonly id: string;
142272
readonly options: WebViewOptions;
273+
readonly capabilities: WebViewCapabilities;
274+
readonly isDestroyed: boolean;
275+
readonly isIncognito: boolean;
276+
/** True when the instance runs in its own isolated WebView profile. */
277+
readonly isIsolated: boolean;
278+
readonly profileName: string | null;
279+
readonly url: string;
280+
readonly title: string;
281+
readonly progress: number;
282+
readonly loading: boolean;
283+
readonly canGoBack: boolean;
284+
readonly canGoForward: boolean;
143285
/** 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;
286+
loadURL(url: string, headers?: Record<string, string>): Promise<void>;
287+
loadHTML(html: string, options?: WebViewHTMLLoadOptions): Promise<void>;
288+
evaluate(js: string): Promise<string | null>;
289+
postMessage(message: unknown): Promise<void>;
290+
reload(): Promise<void>;
291+
stopLoading(): Promise<void>;
292+
/** Navigates back; resolves true when a navigation happened. */
293+
goBack(): Promise<boolean>;
294+
goForward(): Promise<boolean>;
295+
/** Re-reads the back/forward flags from the native WebView. */
296+
refreshHistory(): Promise<{ canGoBack: boolean; canGoForward: boolean }>;
149297
/**
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.
298+
* Resolves when the main frame finishes loading, or rejects on a main-frame
299+
* load error, close/destroy, or timeout (ms; 0 disables the timeout).
300+
* Attach it before starting the navigation, since loadURL()/loadHTML()
301+
* resolve as soon as the load is initiated.
153302
*/
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>;
303+
waitForLoad(timeout?: number): Promise<{ url: string; title: string }>;
157304
/**
158305
* Show the WebView. Only fullscreen instances can be shown; rejects for
159306
* "hidden" mode.
@@ -165,10 +312,98 @@ interface AcodeWebView {
165312
* No-op for "hidden" mode.
166313
*/
167314
hide(): Promise<void>;
168-
reload(): Promise<void>;
315+
clearCache(includeDiskFiles?: boolean): Promise<void>;
316+
clearHistory(): Promise<void>;
317+
clearFormData(): Promise<void>;
318+
setUserAgent(
319+
userAgent: string | null,
320+
mode?: WebViewUserAgentMode,
321+
): Promise<WebViewSettings>;
322+
getUserAgent(): Promise<string | null>;
323+
/** Applies a partial batch of web settings. */
324+
configure(settings: WebViewSettings): Promise<WebViewSettings>;
325+
getSettings(): Promise<WebViewSettings>;
326+
getInfo(): Promise<WebViewInfo>;
327+
on(event: WebViewEventName | "*", callback: WebViewEventHandler): this;
328+
off(event: WebViewEventName | "*", callback: WebViewEventHandler): this;
329+
once(event: WebViewEventName | "*", callback: WebViewEventHandler): this;
330+
onAny(callback: WebViewAnyEventHandler): this;
331+
offAny(callback: WebViewAnyEventHandler): this;
332+
removeAllListeners(event?: WebViewEventName | "*"): this;
333+
onMessage(callback: WebViewMessageHandler): this;
334+
offMessage(callback: WebViewMessageHandler): this;
169335
destroy(): Promise<void>;
170336
}
171337

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

0 commit comments

Comments
 (0)