@swiftbrowser/native 0.17.4 → 0.17.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,8 +13,11 @@ three things:
13
13
  `console`, `performance`, `fetch` with `Headers`, `Request`, `Response`
14
14
  and `AbortController`, `URL`, `URLSearchParams`, `TextEncoder`,
15
15
  `TextDecoder`, `atob`, `btoa`, `crypto.getRandomValues` and `randomUUID`,
16
- `localStorage`, `structuredClone`, `navigator`). React's scheduler reads
17
- some of these when it loads, so the bundle starts with the runtime.
16
+ `localStorage`, `structuredClone`, `navigator`, `Image` over the host's
17
+ image loader, for the size of a picture without drawing it, which is how
18
+ `AsyncImage`'s `placeholder` and `failure` show natively). React's
19
+ scheduler reads some of these when it loads, so the bundle starts with the
20
+ runtime.
18
21
  - **The bundler**: `swiftbrowser-native build` writes one `app.js` for the
19
22
  Xcode target, and `swiftbrowser-native dev` serves it with rebuild on
20
23
  save for the app's `NativeApp.watch(url:)`.
package/dist/bridge.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * The native side of the bridge, as Swift installs it on `__sbNative`
3
3
  * (Native/Sources/SwiftBrowserNative/Host/HostBridge.swift). Every call is
4
4
  * synchronous; the ones that answer later do so by calling a global this
5
- * package defines (`__sbTimerFired`, `__sbFetchResult`, `__sbWorkerMessage`,
6
- * `__sbOpenURL`).
5
+ * package defines (`__sbTimerFired`, `__sbFetchResult`, `__sbImageProbed`,
6
+ * `__sbWorkerMessage`, `__sbOpenURL`).
7
7
  */
8
8
  export interface NativeBridge {
9
9
  /** Applies a render pass's ops (a JSON array). Returns an error message, or nothing. */
@@ -15,6 +15,14 @@ export interface NativeBridge {
15
15
  clearTimer(id: number): void;
16
16
  /** Starts a request; the answer arrives at `__sbFetchResult(id, result)`. */
17
17
  fetch(id: number, request: string | NativeFetchRequest): void;
18
+ /**
19
+ * Starts loading the image at `url` for its pixel size (image.ts: the
20
+ * runtime's `Image`), keeping the bytes for the `image` element that
21
+ * follows; the answer arrives at `__sbImageProbed(id, result)`. Hosts
22
+ * before 0.18 lack it, and so does the page's host for the round trip,
23
+ * which has a real `Image`.
24
+ */
25
+ probeImage?(id: number, url: string): void;
18
26
  storageGet(key: string): string | null;
19
27
  storageSet(key: string, value: string): void;
20
28
  storageRemove(key: string): void;
@@ -56,6 +64,11 @@ export interface NativeFetchRequest {
56
64
  /** Base64, or null. */
57
65
  body: string | null;
58
66
  }
67
+ export interface NativeImageProbeResult {
68
+ width?: number;
69
+ height?: number;
70
+ error?: string;
71
+ }
59
72
  export interface NativeFetchResult {
60
73
  status?: number;
61
74
  statusText?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,+BAA+B;IAC/B,GAAG,IAAI,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;IAC9D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,IAAI,MAAM,EAAE,CAAC;IACxB,YAAY,IAAI,IAAI,CAAC;IACrB,sCAAsC;IACtC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,gDAAgD;IAChD,MAAM,IAAI,MAAM,CAAC;IACjB,+FAA+F;IAC/F,eAAe,CAAC,IAAI,MAAM,EAAE,CAAC;IAM7B;;;;;OAKG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzF,0FAA0F;IAC1F,UAAU,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,eAAe,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,sEAAsE;IACtE,iBAAiB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;AAEjD,kEAAkE;AAClE,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAG1C;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,uBAAuB;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,CAAC,GAAE,OAAO,UAAuB,GAAG,YAAY,GAAG,SAAS,CAExF"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAwDA,kEAAkE;AAClE,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,OAAO,MAAM,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,CAAC;AAC3C,CAAC;AAoBD,yFAAyF;AACzF,MAAM,UAAU,YAAY,CAAC,IAAuB,UAAU;IAC5D,OAAQ,CAA8C,CAAC,UAAU,CAAC;AACpE,CAAC"}
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAgEA,kEAAkE;AAClE,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,OAAO,MAAM,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,CAAC;AAC3C,CAAC;AA0BD,yFAAyF;AACzF,MAAM,UAAU,YAAY,CAAC,IAAuB,UAAU;IAC5D,OAAQ,CAA8C,CAAC,UAAU,CAAC;AACpE,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `Image` over the bridge's `probeImage`: what a page's `new Image()` gives
3
+ * code that loads a picture to learn its size without drawing it (React's
4
+ * `AsyncImage`, which shows its placeholder meanwhile). Setting `src` starts
5
+ * the probe; `onload` (or a `load` listener) comes with `naturalWidth` and
6
+ * `naturalHeight`, `onerror` when the image could not be loaded or read.
7
+ * Swift fetches the bytes through its image loader, so the `image` element
8
+ * that follows draws from the cache. Nothing else of an `HTMLImageElement`
9
+ * is here: no drawing, no DOM.
10
+ */
11
+ import type { NativeBridge, NativeImageProbeResult } from './bridge';
12
+ interface ImageEvent {
13
+ type: 'load' | 'error';
14
+ target: SimpleImage;
15
+ }
16
+ type Listener = (event: ImageEvent) => void;
17
+ /** Binds `Image` to `native`'s `probeImage`; returns the globals to install. */
18
+ export declare function createImageGlobals(native: NativeBridge): {
19
+ Image: typeof SimpleImage;
20
+ __sbImageProbed: typeof receiveImageProbe;
21
+ };
22
+ /** The bridge's `__sbImageProbed(id, result)`. */
23
+ export declare function receiveImageProbe(id: number, result: NativeImageProbeResult | string): void;
24
+ export declare class SimpleImage {
25
+ naturalWidth: number;
26
+ naturalHeight: number;
27
+ width: number;
28
+ height: number;
29
+ /** True once the load settled, either way, as on the web. */
30
+ complete: boolean;
31
+ onload: Listener | null;
32
+ onerror: Listener | null;
33
+ crossOrigin: string | null;
34
+ decoding: string;
35
+ loading: string;
36
+ referrerPolicy: string;
37
+ alt: string;
38
+ private source;
39
+ private probe;
40
+ private readonly listeners;
41
+ private waiting;
42
+ constructor(width?: number, height?: number);
43
+ get src(): string;
44
+ /** Starts loading; an earlier load still under way is forgotten. */
45
+ set src(value: string);
46
+ addEventListener(type: string, listener: Listener): void;
47
+ removeEventListener(type: string, listener: Listener): void;
48
+ /** Resolves once the image loaded, rejects when it could not. */
49
+ decode(): Promise<void>;
50
+ /** @internal The bridge's answer. */
51
+ settle(result: NativeImageProbeResult): void;
52
+ private dispatch;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAErE,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;CACrB;AACD,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;AAM5C,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG;IAAE,KAAK,EAAE,OAAO,WAAW,CAAC;IAAC,eAAe,EAAE,OAAO,iBAAiB,CAAA;CAAE,CAKjI;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAK3F;AAED,qBAAa,WAAW;IACtB,YAAY,SAAK;IACjB,aAAa,SAAK;IAClB,KAAK,SAAK;IACV,MAAM,SAAK;IACX,6DAA6D;IAC7D,QAAQ,UAAS;IACjB,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAC/B,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAClC,QAAQ,SAAU;IAClB,OAAO,SAAW;IAClB,cAAc,SAAM;IACpB,GAAG,SAAM;IACT,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D,OAAO,CAAC,OAAO,CAAiE;gBAEpE,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAK3C,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,oEAAoE;IACpE,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,EAmBpB;IAED,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IASxD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAI3D,iEAAiE;IACjE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,qCAAqC;IACrC,MAAM,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAgB5C,OAAO,CAAC,QAAQ;CAKjB"}
package/dist/image.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * `Image` over the bridge's `probeImage`: what a page's `new Image()` gives
3
+ * code that loads a picture to learn its size without drawing it (React's
4
+ * `AsyncImage`, which shows its placeholder meanwhile). Setting `src` starts
5
+ * the probe; `onload` (or a `load` listener) comes with `naturalWidth` and
6
+ * `naturalHeight`, `onerror` when the image could not be loaded or read.
7
+ * Swift fetches the bytes through its image loader, so the `image` element
8
+ * that follows draws from the cache. Nothing else of an `HTMLImageElement`
9
+ * is here: no drawing, no DOM.
10
+ */
11
+ let bridge = null;
12
+ let nextId = 1;
13
+ const pending = new Map();
14
+ /** Binds `Image` to `native`'s `probeImage`; returns the globals to install. */
15
+ export function createImageGlobals(native) {
16
+ bridge = native;
17
+ nextId = 1;
18
+ pending.clear();
19
+ return { Image: SimpleImage, __sbImageProbed: receiveImageProbe };
20
+ }
21
+ /** The bridge's `__sbImageProbed(id, result)`. */
22
+ export function receiveImageProbe(id, result) {
23
+ const image = pending.get(Number(id));
24
+ if (!image)
25
+ return;
26
+ pending.delete(Number(id));
27
+ image.settle(typeof result === 'string' ? JSON.parse(result) : result);
28
+ }
29
+ export class SimpleImage {
30
+ naturalWidth = 0;
31
+ naturalHeight = 0;
32
+ width = 0;
33
+ height = 0;
34
+ /** True once the load settled, either way, as on the web. */
35
+ complete = false;
36
+ onload = null;
37
+ onerror = null;
38
+ crossOrigin = null;
39
+ decoding = 'auto';
40
+ loading = 'eager';
41
+ referrerPolicy = '';
42
+ alt = '';
43
+ source = '';
44
+ probe = null;
45
+ listeners = new Map();
46
+ waiting = [];
47
+ constructor(width, height) {
48
+ if (width !== undefined)
49
+ this.width = Number(width);
50
+ if (height !== undefined)
51
+ this.height = Number(height);
52
+ }
53
+ get src() {
54
+ return this.source;
55
+ }
56
+ /** Starts loading; an earlier load still under way is forgotten. */
57
+ set src(value) {
58
+ const url = String(value);
59
+ this.source = url;
60
+ this.complete = false;
61
+ this.naturalWidth = 0;
62
+ this.naturalHeight = 0;
63
+ if (this.probe !== null) {
64
+ pending.delete(this.probe);
65
+ this.probe = null;
66
+ }
67
+ if (url === '')
68
+ return;
69
+ if (!bridge?.probeImage) {
70
+ this.settle({ error: 'no image loader' });
71
+ return;
72
+ }
73
+ const id = nextId++;
74
+ this.probe = id;
75
+ pending.set(id, this);
76
+ bridge.probeImage(id, url);
77
+ }
78
+ addEventListener(type, listener) {
79
+ let set = this.listeners.get(type);
80
+ if (!set) {
81
+ set = new Set();
82
+ this.listeners.set(type, set);
83
+ }
84
+ set.add(listener);
85
+ }
86
+ removeEventListener(type, listener) {
87
+ this.listeners.get(type)?.delete(listener);
88
+ }
89
+ /** Resolves once the image loaded, rejects when it could not. */
90
+ decode() {
91
+ if (this.complete)
92
+ return this.naturalWidth > 0 ? Promise.resolve() : Promise.reject(new Error('The image could not be decoded'));
93
+ return new Promise((resolve, reject) => this.waiting.push({ resolve, reject }));
94
+ }
95
+ /** @internal The bridge's answer. */
96
+ settle(result) {
97
+ this.probe = null;
98
+ this.complete = true;
99
+ const loaded = !result.error && Number(result.width) > 0 && Number(result.height) > 0;
100
+ if (loaded) {
101
+ this.naturalWidth = Number(result.width);
102
+ this.naturalHeight = Number(result.height);
103
+ if (this.width === 0)
104
+ this.width = this.naturalWidth;
105
+ if (this.height === 0)
106
+ this.height = this.naturalHeight;
107
+ }
108
+ const waiting = this.waiting;
109
+ this.waiting = [];
110
+ for (const { resolve, reject } of waiting)
111
+ loaded ? resolve() : reject(new Error(result.error ?? 'The image could not be decoded'));
112
+ this.dispatch(loaded ? 'load' : 'error');
113
+ }
114
+ dispatch(type) {
115
+ const event = { type, target: this };
116
+ (type === 'load' ? this.onload : this.onerror)?.(event);
117
+ for (const listener of this.listeners.get(type) ?? [])
118
+ listener(event);
119
+ }
120
+ }
121
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"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"}
package/dist/runtime.d.ts CHANGED
@@ -11,9 +11,11 @@
11
11
  * `atob`, `btoa`, `fetch` with `Headers`, `Request`, `Response`,
12
12
  * `AbortController` and `AbortSignal`, `URL`, `URLSearchParams`, `crypto`
13
13
  * (`getRandomValues`, `randomUUID`), `localStorage`, `structuredClone`,
14
- * `navigator`, `Worker` (worker.ts) with the bridge's `__sbWorkerMessage`
15
- * and `__sbWorkerError`, `__sbOpenURL` (openURL.ts), and the bridge's
16
- * callbacks `__sbTimerFired` and `__sbFetchResult`. In a worker's own context (its bridge has
14
+ * `navigator`, `Image` (image.ts, on a bridge with `probeImage`) with the
15
+ * bridge's `__sbImageProbed`, `Worker` (worker.ts) with the bridge's
16
+ * `__sbWorkerMessage` and `__sbWorkerError`, `__sbOpenURL` (openURL.ts),
17
+ * and the bridge's callbacks `__sbTimerFired` and `__sbFetchResult`. In a
18
+ * worker's own context (its bridge has
17
19
  * `workerPostMessage`), `self`, `postMessage`, `addEventListener` and
18
20
  * `__sbWorkerReceive` instead of `Worker`. `window` and `document` are not
19
21
  * defined: code that checks for them takes its headless path, as it should
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAO3D,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAQzC,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,GAAE,SAA8C,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,EAAE,CA8KnJ"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAQ3D,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAQzC,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,GAAE,SAA8C,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,EAAE,CAsLnJ"}
package/dist/runtime.js CHANGED
@@ -11,9 +11,11 @@
11
11
  * `atob`, `btoa`, `fetch` with `Headers`, `Request`, `Response`,
12
12
  * `AbortController` and `AbortSignal`, `URL`, `URLSearchParams`, `crypto`
13
13
  * (`getRandomValues`, `randomUUID`), `localStorage`, `structuredClone`,
14
- * `navigator`, `Worker` (worker.ts) with the bridge's `__sbWorkerMessage`
15
- * and `__sbWorkerError`, `__sbOpenURL` (openURL.ts), and the bridge's
16
- * callbacks `__sbTimerFired` and `__sbFetchResult`. In a worker's own context (its bridge has
14
+ * `navigator`, `Image` (image.ts, on a bridge with `probeImage`) with the
15
+ * bridge's `__sbImageProbed`, `Worker` (worker.ts) with the bridge's
16
+ * `__sbWorkerMessage` and `__sbWorkerError`, `__sbOpenURL` (openURL.ts),
17
+ * and the bridge's callbacks `__sbTimerFired` and `__sbFetchResult`. In a
18
+ * worker's own context (its bridge has
17
19
  * `workerPostMessage`), `self`, `postMessage`, `addEventListener` and
18
20
  * `__sbWorkerReceive` instead of `Worker`. `window` and `document` are not
19
21
  * defined: code that checks for them takes its headless path, as it should
@@ -22,6 +24,7 @@
22
24
  import { nativeBridge } from './bridge';
23
25
  import { atobPolyfill, base64Decode, btoaPolyfill, SimpleTextDecoder, SimpleTextEncoder } from './encoding';
24
26
  import { createFetch } from './fetch';
27
+ import { createImageGlobals } from './image';
25
28
  import { SimpleURL, SimpleURLSearchParams } from './url';
26
29
  import { installWorkerScope, NativeWorker } from './worker';
27
30
  import { receiveOpenURL } from './openURL';
@@ -155,6 +158,13 @@ export function installRuntime(native, target = globalThis, options = {}) {
155
158
  define('__sbFetchResult', fetchGlobals.__sbFetchResult);
156
159
  define('URL', SimpleURL);
157
160
  define('URLSearchParams', SimpleURLSearchParams);
161
+ // `Image`, for code that loads a picture to learn its size (React's
162
+ // `AsyncImage`): the host reads it through its image loader.
163
+ if (native.probeImage && !native.workerPostMessage) {
164
+ const imageGlobals = createImageGlobals(native);
165
+ define('Image', imageGlobals.Image);
166
+ define('__sbImageProbed', imageGlobals.__sbImageProbed);
167
+ }
158
168
  define('crypto', {
159
169
  getRandomValues: (array) => {
160
170
  const bytes = base64Decode(native.randomBytes(array.byteLength));
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swiftbrowser/native",
3
- "version": "0.17.4",
3
+ "version": "0.17.5",
4
4
  "type": "module",
5
5
  "description": "Run a @swiftbrowser/react app natively: the runtime a JavaScriptCore-hosted app needs (timers, console, fetch and the other web globals over the SwiftBrowser Native bridge), the `run` entry that renders into the native SwiftUI renderer, and the bundler that builds app.js for the Xcode target.",
6
6
  "repository": {
@@ -59,8 +59,8 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@babel/core": "^7.29.7",
62
- "@swiftbrowser/react": "0.17.4",
63
- "@swiftbrowser/web": "0.17.4",
62
+ "@swiftbrowser/react": "0.17.5",
63
+ "@swiftbrowser/web": "0.17.5",
64
64
  "esbuild": "^0.28.2",
65
65
  "react-refresh": "^0.17.0"
66
66
  },
package/src/bridge.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * The native side of the bridge, as Swift installs it on `__sbNative`
3
3
  * (Native/Sources/SwiftBrowserNative/Host/HostBridge.swift). Every call is
4
4
  * synchronous; the ones that answer later do so by calling a global this
5
- * package defines (`__sbTimerFired`, `__sbFetchResult`, `__sbWorkerMessage`,
6
- * `__sbOpenURL`).
5
+ * package defines (`__sbTimerFired`, `__sbFetchResult`, `__sbImageProbed`,
6
+ * `__sbWorkerMessage`, `__sbOpenURL`).
7
7
  */
8
8
  export interface NativeBridge {
9
9
  /** Applies a render pass's ops (a JSON array). Returns an error message, or nothing. */
@@ -15,6 +15,14 @@ export interface NativeBridge {
15
15
  clearTimer(id: number): void;
16
16
  /** Starts a request; the answer arrives at `__sbFetchResult(id, result)`. */
17
17
  fetch(id: number, request: string | NativeFetchRequest): void;
18
+ /**
19
+ * Starts loading the image at `url` for its pixel size (image.ts: the
20
+ * runtime's `Image`), keeping the bytes for the `image` element that
21
+ * follows; the answer arrives at `__sbImageProbed(id, result)`. Hosts
22
+ * before 0.18 lack it, and so does the page's host for the round trip,
23
+ * which has a real `Image`.
24
+ */
25
+ probeImage?(id: number, url: string): void;
18
26
  storageGet(key: string): string | null;
19
27
  storageSet(key: string, value: string): void;
20
28
  storageRemove(key: string): void;
@@ -68,6 +76,12 @@ export interface NativeFetchRequest {
68
76
  body: string | null;
69
77
  }
70
78
 
79
+ export interface NativeImageProbeResult {
80
+ width?: number;
81
+ height?: number;
82
+ error?: string;
83
+ }
84
+
71
85
  export interface NativeFetchResult {
72
86
  status?: number;
73
87
  statusText?: string;
package/src/image.ts ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `Image` over the bridge's `probeImage`: what a page's `new Image()` gives
3
+ * code that loads a picture to learn its size without drawing it (React's
4
+ * `AsyncImage`, which shows its placeholder meanwhile). Setting `src` starts
5
+ * the probe; `onload` (or a `load` listener) comes with `naturalWidth` and
6
+ * `naturalHeight`, `onerror` when the image could not be loaded or read.
7
+ * Swift fetches the bytes through its image loader, so the `image` element
8
+ * that follows draws from the cache. Nothing else of an `HTMLImageElement`
9
+ * is here: no drawing, no DOM.
10
+ */
11
+
12
+ import type { NativeBridge, NativeImageProbeResult } from './bridge';
13
+
14
+ interface ImageEvent {
15
+ type: 'load' | 'error';
16
+ target: SimpleImage;
17
+ }
18
+ type Listener = (event: ImageEvent) => void;
19
+
20
+ let bridge: NativeBridge | null = null;
21
+ let nextId = 1;
22
+ const pending = new Map<number, SimpleImage>();
23
+
24
+ /** Binds `Image` to `native`'s `probeImage`; returns the globals to install. */
25
+ export function createImageGlobals(native: NativeBridge): { Image: typeof SimpleImage; __sbImageProbed: typeof receiveImageProbe } {
26
+ bridge = native;
27
+ nextId = 1;
28
+ pending.clear();
29
+ return { Image: SimpleImage, __sbImageProbed: receiveImageProbe };
30
+ }
31
+
32
+ /** The bridge's `__sbImageProbed(id, result)`. */
33
+ export function receiveImageProbe(id: number, result: NativeImageProbeResult | string): void {
34
+ const image = pending.get(Number(id));
35
+ if (!image) return;
36
+ pending.delete(Number(id));
37
+ image.settle(typeof result === 'string' ? (JSON.parse(result) as NativeImageProbeResult) : result);
38
+ }
39
+
40
+ export class SimpleImage {
41
+ naturalWidth = 0;
42
+ naturalHeight = 0;
43
+ width = 0;
44
+ height = 0;
45
+ /** True once the load settled, either way, as on the web. */
46
+ complete = false;
47
+ onload: Listener | null = null;
48
+ onerror: Listener | null = null;
49
+ crossOrigin: string | null = null;
50
+ decoding = 'auto';
51
+ loading = 'eager';
52
+ referrerPolicy = '';
53
+ alt = '';
54
+ private source = '';
55
+ private probe: number | null = null;
56
+ private readonly listeners = new Map<string, Set<Listener>>();
57
+ private waiting: { resolve: () => void; reject: (error: Error) => void }[] = [];
58
+
59
+ constructor(width?: number, height?: number) {
60
+ if (width !== undefined) this.width = Number(width);
61
+ if (height !== undefined) this.height = Number(height);
62
+ }
63
+
64
+ get src(): string {
65
+ return this.source;
66
+ }
67
+
68
+ /** Starts loading; an earlier load still under way is forgotten. */
69
+ set src(value: string) {
70
+ const url = String(value);
71
+ this.source = url;
72
+ this.complete = false;
73
+ this.naturalWidth = 0;
74
+ this.naturalHeight = 0;
75
+ if (this.probe !== null) {
76
+ pending.delete(this.probe);
77
+ this.probe = null;
78
+ }
79
+ if (url === '') return;
80
+ if (!bridge?.probeImage) {
81
+ this.settle({ error: 'no image loader' });
82
+ return;
83
+ }
84
+ const id = nextId++;
85
+ this.probe = id;
86
+ pending.set(id, this);
87
+ bridge.probeImage(id, url);
88
+ }
89
+
90
+ addEventListener(type: string, listener: Listener): void {
91
+ let set = this.listeners.get(type);
92
+ if (!set) {
93
+ set = new Set();
94
+ this.listeners.set(type, set);
95
+ }
96
+ set.add(listener);
97
+ }
98
+
99
+ removeEventListener(type: string, listener: Listener): void {
100
+ this.listeners.get(type)?.delete(listener);
101
+ }
102
+
103
+ /** Resolves once the image loaded, rejects when it could not. */
104
+ decode(): Promise<void> {
105
+ if (this.complete) return this.naturalWidth > 0 ? Promise.resolve() : Promise.reject(new Error('The image could not be decoded'));
106
+ return new Promise((resolve, reject) => this.waiting.push({ resolve, reject }));
107
+ }
108
+
109
+ /** @internal The bridge's answer. */
110
+ settle(result: NativeImageProbeResult): void {
111
+ this.probe = null;
112
+ this.complete = true;
113
+ const loaded = !result.error && Number(result.width) > 0 && Number(result.height) > 0;
114
+ if (loaded) {
115
+ this.naturalWidth = Number(result.width);
116
+ this.naturalHeight = Number(result.height);
117
+ if (this.width === 0) this.width = this.naturalWidth;
118
+ if (this.height === 0) this.height = this.naturalHeight;
119
+ }
120
+ const waiting = this.waiting;
121
+ this.waiting = [];
122
+ for (const { resolve, reject } of waiting) loaded ? resolve() : reject(new Error(result.error ?? 'The image could not be decoded'));
123
+ this.dispatch(loaded ? 'load' : 'error');
124
+ }
125
+
126
+ private dispatch(type: 'load' | 'error'): void {
127
+ const event: ImageEvent = { type, target: this };
128
+ (type === 'load' ? this.onload : this.onerror)?.(event);
129
+ for (const listener of this.listeners.get(type) ?? []) listener(event);
130
+ }
131
+ }
package/src/runtime.ts CHANGED
@@ -11,9 +11,11 @@
11
11
  * `atob`, `btoa`, `fetch` with `Headers`, `Request`, `Response`,
12
12
  * `AbortController` and `AbortSignal`, `URL`, `URLSearchParams`, `crypto`
13
13
  * (`getRandomValues`, `randomUUID`), `localStorage`, `structuredClone`,
14
- * `navigator`, `Worker` (worker.ts) with the bridge's `__sbWorkerMessage`
15
- * and `__sbWorkerError`, `__sbOpenURL` (openURL.ts), and the bridge's
16
- * callbacks `__sbTimerFired` and `__sbFetchResult`. In a worker's own context (its bridge has
14
+ * `navigator`, `Image` (image.ts, on a bridge with `probeImage`) with the
15
+ * bridge's `__sbImageProbed`, `Worker` (worker.ts) with the bridge's
16
+ * `__sbWorkerMessage` and `__sbWorkerError`, `__sbOpenURL` (openURL.ts),
17
+ * and the bridge's callbacks `__sbTimerFired` and `__sbFetchResult`. In a
18
+ * worker's own context (its bridge has
17
19
  * `workerPostMessage`), `self`, `postMessage`, `addEventListener` and
18
20
  * `__sbWorkerReceive` instead of `Worker`. `window` and `document` are not
19
21
  * defined: code that checks for them takes its headless path, as it should
@@ -23,6 +25,7 @@
23
25
  import { nativeBridge, type NativeBridge } from './bridge';
24
26
  import { atobPolyfill, base64Decode, btoaPolyfill, SimpleTextDecoder, SimpleTextEncoder } from './encoding';
25
27
  import { createFetch } from './fetch';
28
+ import { createImageGlobals } from './image';
26
29
  import { SimpleURL, SimpleURLSearchParams } from './url';
27
30
  import { installWorkerScope, NativeWorker } from './worker';
28
31
  import { receiveOpenURL } from './openURL';
@@ -167,6 +170,14 @@ export function installRuntime(native: NativeBridge, target: AnyGlobal = globalT
167
170
  define('URL', SimpleURL);
168
171
  define('URLSearchParams', SimpleURLSearchParams);
169
172
 
173
+ // `Image`, for code that loads a picture to learn its size (React's
174
+ // `AsyncImage`): the host reads it through its image loader.
175
+ if (native.probeImage && !native.workerPostMessage) {
176
+ const imageGlobals = createImageGlobals(native);
177
+ define('Image', imageGlobals.Image);
178
+ define('__sbImageProbed', imageGlobals.__sbImageProbed);
179
+ }
180
+
170
181
  define('crypto', {
171
182
  getRandomValues: <T extends ArrayBufferView>(array: T): T => {
172
183
  const bytes = base64Decode(native.randomBytes(array.byteLength));
@@ -22,6 +22,7 @@ import FoundationBridge // the shim's Foundation stand-ins (ShimAliases.swift)
22
22
  /// | `now()` | milliseconds, monotonic |
23
23
  /// | `setTimer(id, delayMs, repeats)`, `clearTimer(id)` | timers on the main run loop; fire `__sbTimerFired(id)` |
24
24
  /// | `fetch(id, requestJSON)` | `URLSession`; answers `__sbFetchResult(id, resultJSON)` |
25
+ /// | `probeImage(id, url)` | the image's pixel size through the image loader (ImageLoader.swift), which keeps the bytes for the `image` that follows; answers `__sbImageProbed(id, {width, height} or {error})`. The runtime's `Image` (a page's `new Image()`). Apple platforms only |
25
26
  /// | `storageGet/Set/Remove/Keys/Clear` | `localStorage` over `UserDefaults` |
26
27
  /// | `randomBytes(count)` | base64 of random bytes (`crypto.getRandomValues`) |
27
28
  /// | `openURL(url)` | the system opener |
@@ -126,6 +127,22 @@ final class HostBridge {
126
127
  return nil
127
128
  }
128
129
 
130
+ #if !canImport(FoundationBridge)
131
+ host.install("probeImage") { [weak self] arguments in
132
+ guard let self, let id = arguments[safe: 0]?.int, let string = arguments[safe: 1]?.string else { return nil }
133
+ Task { @MainActor [weak self] in
134
+ let result: JSONValue
135
+ if let url = URL(string: string), let size = await ImageLoader.shared.probe(url) {
136
+ result = .object(["width": .number(Double(size.width)), "height": .number(Double(size.height))])
137
+ } else {
138
+ result = .object(["error": .string("the image could not be loaded")])
139
+ }
140
+ self?.callIn("__sbImageProbed", [.number(Double(id)), result])
141
+ }
142
+ return nil
143
+ }
144
+ #endif
145
+
129
146
  host.install("storageGet") { arguments in
130
147
  guard let key = arguments.first?.string else { return .null }
131
148
  return UserDefaults.standard.string(forKey: HostBridge.storagePrefix + key).map(JSONValue.string) ?? .null
@@ -21,5 +21,7 @@ public typealias URLSessionConfiguration = FoundationBridge.URLSessionConfigurat
21
21
  public typealias URLRequest = FoundationBridge.URLRequest
22
22
  public typealias URLResponse = FoundationBridge.URLResponse
23
23
  public typealias HTTPURLResponse = FoundationBridge.HTTPURLResponse
24
+ public typealias URLCache = FoundationBridge.URLCache
25
+ public typealias CachedURLResponse = FoundationBridge.CachedURLResponse
24
26
  public typealias DispatchQueue = FoundationBridge.DispatchQueue
25
27
  #endif
@@ -0,0 +1,331 @@
1
+ // Apple platforms only: against the shim (the round trip, docs/native.md)
2
+ // the page loads and draws the images itself, and the shim's URL cache and
3
+ // session are stand-ins.
4
+ #if canImport(SwiftUI) && !canImport(FoundationBridge)
5
+ import CoreGraphics
6
+ import Foundation
7
+ import ImageIO
8
+ #if canImport(SwiftDraw)
9
+ import SwiftDraw
10
+ #endif
11
+
12
+ /// How the image loader caches (`ImageLoader`, below): set once at launch,
13
+ /// before the first image loads, with `ImageCache.configure(_:)`.
14
+ ///
15
+ /// ```swift
16
+ /// var options = ImageCacheOptions()
17
+ /// options.diskCapacity = 512 << 20
18
+ /// ImageCache.configure(options)
19
+ /// ```
20
+ public struct ImageCacheOptions: Sendable {
21
+ /// The bytes kept in memory (32 MB) and on disk (256 MB), for as long as the server's cache headers allow.
22
+ public var memoryCapacity: Int = 32 << 20
23
+ public var diskCapacity: Int = 256 << 20
24
+ /// Where the bytes live on disk; nil for `Caches/SwiftBrowserNative/Images`.
25
+ public var directory: URL? = nil
26
+ /// The decoded images kept in memory, in bytes; nil for an eighth of the device's memory, 256 MB at most.
27
+ public var decodedCapacity: Int? = nil
28
+ /// The configuration the images' session is made with (`.default`: the
29
+ /// app's cookies); the loader sets its cache on it. An app whose images
30
+ /// need a header (a bearer token) adds it here.
31
+ public var sessionConfiguration: @Sendable () -> URLSessionConfiguration = { .default }
32
+
33
+ public init() {}
34
+ }
35
+
36
+ /// The caches of the remote images a native app draws (ImageLoader.swift).
37
+ public enum ImageCache {
38
+ /// Sets the caches' sizes and the images' session. Call it at launch,
39
+ /// before the first image loads: it returns false, and changes nothing,
40
+ /// once a session exists.
41
+ @discardableResult
42
+ public static func configure(_ options: ImageCacheOptions) -> Bool {
43
+ ImageLoader.settings.configure(options)
44
+ }
45
+
46
+ /// The options in effect.
47
+ public static var options: ImageCacheOptions { ImageLoader.settings.current }
48
+
49
+ /// Drops every cached byte and decode (a sign-out: the next account's images are its own).
50
+ public static func clear() async {
51
+ await ImageLoader.shared.clear()
52
+ }
53
+ }
54
+
55
+ /// The remote images a native app draws (RemoteImage.swift), cached twice
56
+ /// over, as SwiftUI's `AsyncImage` does not: the bytes in a URL cache of the
57
+ /// loader's own (32 MB in memory, 256 MB on disk, kept across launches, for
58
+ /// as long as the server's cache headers allow), and the decoded images in
59
+ /// memory (up to an eighth of the device's memory, 256 MB at most; the
60
+ /// system evicts them under pressure), each decoded no larger than the box
61
+ /// it is drawn in, through ImageIO's thumbnail path, so a photo drawn as a
62
+ /// 40 pt avatar costs a few kilobytes rather than the tens of megabytes of
63
+ /// its full size. A row that scrolls back on screen, or a screen popped
64
+ /// back to, draws its images at once. `ImageCacheOptions` sizes the caches.
65
+ ///
66
+ /// A request names the URL, the pixel size the longer side may have (nil:
67
+ /// the image's own size) and the scale the result is drawn at. The cache
68
+ /// keeps the largest decode of each image at each scale; a decode at the
69
+ /// image's own size covers every request for it.
70
+ ///
71
+ /// A `probe` reads an image's size from its header for the app (the
72
+ /// bridge's `probeImage`, HostBridge.swift: a page's `new Image()`), keeping
73
+ /// the bytes for the load that follows.
74
+ actor ImageLoader {
75
+ static let shared = ImageLoader()
76
+ static let settings = ImageLoaderSettings()
77
+
78
+ /// The session every image (and SVG) loads through: the app's cookies,
79
+ /// and the loader's own URL cache for the bytes.
80
+ static var session: URLSession { settings.session() }
81
+
82
+ /// The pixel sizes a resizable image is decoded at (its longer side):
83
+ /// a box that grows a little does not decode again, and the decodes of
84
+ /// one image at several sizes stay few.
85
+ static let sizeLadder = [64, 128, 256, 512, 1024, 2048, 4096]
86
+
87
+ /// The size to decode an image for a box of `box` points at `scale`, on
88
+ /// the ladder: enough pixels for the image to cover the box (knowing the
89
+ /// image's own size, `source`, the exact figure; before, the box's
90
+ /// longer side), or nil for the image's own size when it needs more than
91
+ /// the ladder's top. A box with no size yet (before its first layout) is
92
+ /// nil too: nothing to size for.
93
+ static func pixelSize(for box: CGSize, scale: CGFloat, source: CGSize?) -> Int? {
94
+ guard box.width > 0 || box.height > 0 else { return nil }
95
+ var needed = max(box.width, box.height) * scale
96
+ if let source, source.width > 0, source.height > 0 {
97
+ let cover = max(box.width / source.width, box.height / source.height)
98
+ needed = max(source.width, source.height) * cover * scale
99
+ }
100
+ return sizeLadder.first { CGFloat($0) >= needed }
101
+ }
102
+
103
+ private nonisolated let store: DecodedImageStore
104
+ private var inFlight: [String: Task<ImageLoad, Never>] = [:]
105
+ /// The bytes a probe fetched, for the load that follows (a response the
106
+ /// server does not let the URL cache keep would be fetched twice).
107
+ private var probed: [URL: Data] = [:]
108
+ private var probedOrder: [URL] = []
109
+ private static let probedLimit = 32
110
+
111
+ init(decodedCapacity: Int? = nil) {
112
+ store = DecodedImageStore(capacity: decodedCapacity ?? Self.settings.current.decodedCapacity)
113
+ }
114
+
115
+ /// The decode in memory that serves `request`, if any (any task's turn: the store is thread-safe).
116
+ nonisolated func cached(_ request: ImageRequest) -> DecodedImage? {
117
+ guard let decoded = store.cache.object(forKey: request.cacheKey as NSString), decoded.covers(request.maxPixelSize) else { return nil }
118
+ return decoded
119
+ }
120
+
121
+ /// The image for `request`, decoded (and fetched) at most once at a time
122
+ /// per URL and scale, however many views ask.
123
+ func load(_ request: ImageRequest) async -> ImageLoad {
124
+ if let decoded = cached(request) { return .image(decoded) }
125
+ let key = request.cacheKey
126
+ if let running = inFlight[key] {
127
+ let result = await running.value
128
+ if case .image(let decoded) = result, decoded.covers(request.maxPixelSize) { return result }
129
+ // The running decode was for a smaller box: decode again, bigger.
130
+ if inFlight[key] == running { inFlight[key] = nil }
131
+ return await load(request)
132
+ }
133
+ let known = takeProbed(request.url)
134
+ let task = Task<ImageLoad, Never> { await Self.fetchAndDecode(request, data: known) }
135
+ inFlight[key] = task
136
+ let result = await task.value
137
+ if inFlight[key] == task { inFlight[key] = nil }
138
+ if case .image(let decoded) = result { store.cache.setObject(decoded, forKey: key as NSString, cost: decoded.cost) }
139
+ return result
140
+ }
141
+
142
+ private static func fetchAndDecode(_ request: ImageRequest, data known: Data?) async -> ImageLoad {
143
+ let data: Data
144
+ if let known {
145
+ data = known
146
+ } else {
147
+ guard let fetched = await fetch(request.url) else { return .failed }
148
+ data = fetched
149
+ }
150
+ // Decoding is work for a thread of its own, not the loader's turn.
151
+ let task = Task.detached(priority: .userInitiated) { decodeImage(data, url: request.url, maxPixelSize: request.maxPixelSize, scale: request.scale) }
152
+ guard let decoded = await task.value else { return .undecodable }
153
+ return .image(decoded)
154
+ }
155
+
156
+ /// The bytes at `url` through the session, or nil when they cannot be had.
157
+ private static func fetch(_ url: URL) async -> Data? {
158
+ guard let fetched = try? await session.data(from: url) else { return nil }
159
+ if let http = fetched.1 as? HTTPURLResponse, !(200..<300).contains(http.statusCode) { return nil }
160
+ return fetched.0
161
+ }
162
+
163
+ /// The pixel size of the image at `url`, as oriented, read from its
164
+ /// header without a decode (an SVG's from its markup), or nil when it
165
+ /// cannot be fetched or read. The bytes stay for the load that follows.
166
+ func probe(_ url: URL) async -> CGSize? {
167
+ guard let data = await Self.fetch(url) else { return nil }
168
+ guard let size = imageSize(of: data) else { return nil }
169
+ keepProbed(data, for: url)
170
+ return size
171
+ }
172
+
173
+ private func keepProbed(_ data: Data, for url: URL) {
174
+ if probed[url] == nil { probedOrder.append(url) }
175
+ probed[url] = data
176
+ while probedOrder.count > Self.probedLimit {
177
+ probed[probedOrder.removeFirst()] = nil
178
+ }
179
+ }
180
+
181
+ private func takeProbed(_ url: URL) -> Data? {
182
+ guard let data = probed.removeValue(forKey: url) else { return nil }
183
+ probedOrder.removeAll { $0 == url }
184
+ return data
185
+ }
186
+
187
+ /// Drops every cached byte and decode.
188
+ func clear() {
189
+ store.cache.removeAllObjects()
190
+ probed.removeAll()
191
+ probedOrder.removeAll()
192
+ Self.session.configuration.urlCache?.removeAllCachedResponses()
193
+ }
194
+ }
195
+
196
+ /// The loader's options and the session made from them, which fixes them.
197
+ final class ImageLoaderSettings: @unchecked Sendable {
198
+ private let lock = NSLock()
199
+ private var options = ImageCacheOptions()
200
+ private var made: URLSession?
201
+
202
+ /// Takes `options` unless a session exists already; returns whether it did.
203
+ func configure(_ options: ImageCacheOptions) -> Bool {
204
+ lock.lock()
205
+ defer { lock.unlock() }
206
+ guard made == nil else { return false }
207
+ self.options = options
208
+ return true
209
+ }
210
+
211
+ var current: ImageCacheOptions {
212
+ lock.lock()
213
+ defer { lock.unlock() }
214
+ return options
215
+ }
216
+
217
+ /// The images' session, made once from the options.
218
+ func session() -> URLSession {
219
+ lock.lock()
220
+ defer { lock.unlock() }
221
+ if let made { return made }
222
+ let configuration = options.sessionConfiguration()
223
+ let directory = options.directory ?? FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?
224
+ .appendingPathComponent("SwiftBrowserNative", isDirectory: true)
225
+ .appendingPathComponent("Images", isDirectory: true)
226
+ configuration.urlCache = URLCache(memoryCapacity: options.memoryCapacity, diskCapacity: options.diskCapacity, directory: directory)
227
+ configuration.requestCachePolicy = .useProtocolCachePolicy
228
+ let session = URLSession(configuration: configuration)
229
+ made = session
230
+ return session
231
+ }
232
+ }
233
+
234
+ /// The decoded images in memory (`NSCache`: thread-safe, evicted under memory pressure), held where every task reaches them.
235
+ private final class DecodedImageStore: @unchecked Sendable {
236
+ let cache = NSCache<NSString, DecodedImage>()
237
+
238
+ init(capacity: Int?) {
239
+ let memory = ProcessInfo.processInfo.physicalMemory
240
+ cache.totalCostLimit = capacity ?? Int(min(memory / 8, 256 << 20))
241
+ }
242
+ }
243
+
244
+ /// What a remote image is, once drawn: the decode, or the bytes ImageIO could not read (an SVG, maybe), or nothing to show.
245
+ enum ImageLoad: Sendable {
246
+ case image(DecodedImage)
247
+ case undecodable
248
+ case failed
249
+ }
250
+
251
+ struct ImageRequest: Equatable, Hashable, Sendable {
252
+ let url: URL
253
+ /// The longer side's pixels at most; nil for the image's own size.
254
+ let maxPixelSize: Int?
255
+ /// The scale the decode is drawn at: the display's for a resizable image, 1 for one drawn at its own size.
256
+ let scale: CGFloat
257
+
258
+ var cacheKey: String { "\(url.absoluteString)@\(scale)" }
259
+ }
260
+
261
+ /// A decoded image (immutable, so shared freely) with what it was decoded from and for.
262
+ final class DecodedImage: @unchecked Sendable {
263
+ let url: URL
264
+ let cgImage: CGImage
265
+ let scale: CGFloat
266
+ /// The limit it was decoded under; nil for the image's own size, which covers every request.
267
+ let maxPixelSize: Int?
268
+ /// The image's own size in pixels, as oriented.
269
+ let sourceSize: CGSize
270
+
271
+ init(url: URL, cgImage: CGImage, scale: CGFloat, maxPixelSize: Int?, sourceSize: CGSize) {
272
+ self.url = url
273
+ self.cgImage = cgImage
274
+ self.scale = scale
275
+ self.maxPixelSize = maxPixelSize
276
+ self.sourceSize = sourceSize
277
+ }
278
+
279
+ /// Whether a decode this size serves a request for `needed` pixels (nil: the image's own size).
280
+ func covers(_ needed: Int?) -> Bool {
281
+ guard let maxPixelSize else { return true }
282
+ guard let needed else { return false }
283
+ return maxPixelSize >= needed
284
+ }
285
+
286
+ /// The memory the decode holds.
287
+ var cost: Int { cgImage.bytesPerRow * cgImage.height }
288
+ }
289
+
290
+ /// `data` decoded through ImageIO with its longer side at most `maxPixelSize`
291
+ /// pixels (nil, or a limit the image is within: at its own size, which the
292
+ /// result's `maxPixelSize` then reports as nil), oriented as its metadata
293
+ /// says; nil when ImageIO does not read it.
294
+ func decodeImage(_ data: Data, url: URL, maxPixelSize: Int?, scale: CGFloat) -> DecodedImage? {
295
+ guard let source = CGImageSourceCreateWithData(data as CFData, nil), CGImageSourceGetCount(source) > 0 else { return nil }
296
+ let (width, height) = pixelSize(of: source)
297
+ let longest = Int(max(width, height).rounded(.up))
298
+ // The whole image when it is within the limit: the limit is then moot.
299
+ let limit = maxPixelSize.flatMap { longest > 0 && $0 >= longest ? nil : $0 }
300
+ var options: [CFString: Any] = [
301
+ kCGImageSourceCreateThumbnailFromImageAlways: true,
302
+ kCGImageSourceCreateThumbnailWithTransform: true,
303
+ kCGImageSourceShouldCacheImmediately: true,
304
+ ]
305
+ options[kCGImageSourceThumbnailMaxPixelSize] = limit ?? max(longest, 1)
306
+ guard let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else { return nil }
307
+ return DecodedImage(url: url, cgImage: image, scale: scale, maxPixelSize: limit, sourceSize: CGSize(width: width, height: height))
308
+ }
309
+
310
+ /// An image's pixel size as oriented, from its header (EXIF orientations 5 to 8 turn it on its side).
311
+ private func pixelSize(of source: CGImageSource) -> (width: Double, height: Double) {
312
+ let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any] ?? [:]
313
+ var width = (properties[kCGImagePropertyPixelWidth] as? NSNumber)?.doubleValue ?? 0
314
+ var height = (properties[kCGImagePropertyPixelHeight] as? NSNumber)?.doubleValue ?? 0
315
+ if let orientation = (properties[kCGImagePropertyOrientation] as? NSNumber)?.intValue, orientation >= 5 { swap(&width, &height) }
316
+ return (width, height)
317
+ }
318
+
319
+ /// The size of the image `data` holds, without decoding it: ImageIO's
320
+ /// header, else an SVG's own size (SwiftDraw), else nil.
321
+ func imageSize(of data: Data) -> CGSize? {
322
+ if let source = CGImageSourceCreateWithData(data as CFData, nil), CGImageSourceGetCount(source) > 0 {
323
+ let (width, height) = pixelSize(of: source)
324
+ if width > 0, height > 0 { return CGSize(width: width, height: height) }
325
+ }
326
+ #if canImport(SwiftDraw)
327
+ if let svg = SVG(data: data), svg.size.width > 0, svg.size.height > 0 { return svg.size }
328
+ #endif
329
+ return nil
330
+ }
331
+ #endif
@@ -8,11 +8,10 @@ import FoundationBridge // `AsyncImage`, against the shim
8
8
  import SwiftDraw
9
9
  #endif
10
10
 
11
- /// An `image` with a URL: SwiftUI's `AsyncImage`, and for an SVG, which the
12
- /// system's image decoding does not read (a logo found on a website often
13
- /// is one), SwiftDraw's parser and renderer, drawn as vectors at any size.
14
- /// SwiftDraw is the package's dependency on Apple platforms only; the round
15
- /// trip's build of these views has the page, which draws SVG itself.
11
+ #if canImport(FoundationBridge)
12
+ /// An `image` with a URL against the shim (the round trip, docs/native.md):
13
+ /// SwiftUI's `AsyncImage`, which the page loads, draws and caches as a
14
+ /// browser does. On Apple platforms the view below draws it instead.
16
15
  struct RemoteImage: View {
17
16
  let url: URL?
18
17
  let resizable: Bool
@@ -49,11 +48,109 @@ struct RemoteImage: View {
49
48
  #endif
50
49
  }
51
50
  }
51
+ #else
52
+ /// An `image` with a URL, through the package's image loader
53
+ /// (ImageLoader.swift: the bytes and the decodes cached, each decode no
54
+ /// larger than the box it is drawn in), and for an SVG, which the system's
55
+ /// image decoding does not read (a logo found on a website often is one),
56
+ /// SwiftDraw's parser and renderer, drawn as vectors at any size. SwiftDraw
57
+ /// is the package's dependency on Apple platforms only; the round trip's
58
+ /// build of these views has the page, which draws SVG itself.
59
+ ///
60
+ /// A resizable image is decoded for the box its layout gives it (at the
61
+ /// display's scale), and again, larger, should the box grow past what the
62
+ /// decode covers (a photo scaled to fit a width learns its height from the
63
+ /// first decode); one drawn at its own size is decoded whole at scale 1, as
64
+ /// `AsyncImage` drew it.
65
+ struct RemoteImage: View {
66
+ let url: URL?
67
+ let resizable: Bool
68
+ /// The image's own size in pixels when the app knows it (a probe told
69
+ /// it, ImageLoader.swift; 0 before): the box of one drawn at its own
70
+ /// size while it loads, and the shape a resizable one is decoded for.
71
+ let width: Double
72
+ let height: Double
73
+ @Environment(\.displayScale) private var displayScale
74
+ @State private var loaded: Loaded?
75
+ /// The box the layout gives the image; zero before the first layout.
76
+ @State private var box: CGSize = .zero
77
+
78
+ private struct Loaded {
79
+ let url: URL
80
+ let result: ImageLoad
81
+ }
82
+
83
+ private var decoded: DecodedImage? {
84
+ guard let loaded, loaded.url == url, case .image(let decoded) = loaded.result else { return nil }
85
+ return decoded
86
+ }
87
+
88
+ /// What to load for the box the image has now: nothing until a resizable image has a box.
89
+ private var request: ImageRequest? {
90
+ guard let url else { return nil }
91
+ if !resizable { return ImageRequest(url: url, maxPixelSize: nil, scale: 1) }
92
+ guard box.width > 0 || box.height > 0 else { return nil }
93
+ let known = width > 0 && height > 0 ? CGSize(width: width, height: height) : nil
94
+ return ImageRequest(url: url, maxPixelSize: ImageLoader.pixelSize(for: box, scale: displayScale, source: decoded?.sourceSize ?? known), scale: displayScale)
95
+ }
96
+
97
+ var body: some View {
98
+ content
99
+ .onGeometryChange(for: CGSize.self) { proxy in proxy.size } action: { size in box = size }
100
+ .task(id: request) { await load() }
101
+ }
102
+
103
+ @ViewBuilder private var content: some View {
104
+ if let decoded {
105
+ let image = Image(decorative: decoded.cgImage, scale: decoded.scale)
106
+ if resizable { image.resizable() } else { image }
107
+ } else if let loaded, loaded.url == url, case .undecodable = loaded.result, let url {
108
+ undecoded(url)
109
+ } else {
110
+ placeholder
111
+ }
112
+ }
113
+
114
+ private func load() async {
115
+ guard let request else { return }
116
+ // The decode in memory shows at once, without a placeholder in between.
117
+ if let cached = ImageLoader.shared.cached(request) {
118
+ loaded = Loaded(url: request.url, result: .image(cached))
119
+ return
120
+ }
121
+ // What is shown stays up while a larger decode of the same image comes.
122
+ if let decoded, decoded.url == request.url, decoded.covers(request.maxPixelSize) { return }
123
+ let result = await ImageLoader.shared.load(request)
124
+ guard !Task.isCancelled else { return }
125
+ loaded = Loaded(url: request.url, result: result)
126
+ }
127
+
128
+ /// Nothing: at the image's own size when the app knows it and the image
129
+ /// is drawn at that size; else whatever the layout proposes, as a
130
+ /// resizable image takes.
131
+ @ViewBuilder private var placeholder: some View {
132
+ if !resizable, width > 0, height > 0 {
133
+ Color.clear.frame(width: width, height: height)
134
+ } else {
135
+ Color.clear
136
+ }
137
+ }
138
+
139
+ /// What ImageIO could not decode: an SVG, drawn by SwiftDraw, else nothing.
140
+ @ViewBuilder private func undecoded(_ url: URL) -> some View {
141
+ #if canImport(SwiftDraw)
142
+ RemoteSVG(url: url, resizable: resizable) { placeholder }
143
+ #else
144
+ placeholder
145
+ #endif
146
+ }
147
+ }
148
+ #endif
52
149
 
53
150
  #if canImport(SwiftDraw)
54
- /// The SVG at `url`, loaded through the shared session (so the cookies and
55
- /// cache `AsyncImage` used apply), or the placeholder while it loads and
56
- /// when the data is not an SVG.
151
+ /// The SVG at `url`, loaded through the image loader's session (so the
152
+ /// app's cookies apply and the bytes just fetched come from its cache), or
153
+ /// the placeholder while it loads and when the data is not an SVG.
57
154
  struct RemoteSVG<Placeholder: View>: View {
58
155
  let url: URL
59
156
  let resizable: Bool
@@ -80,7 +177,7 @@ struct LoadedSVG: Equatable {
80
177
  }
81
178
 
82
179
  /// The SVG at `url`, or nil when it cannot be loaded or is not an SVG.
83
- func loadSVG(from url: URL, session: URLSession = .shared) async -> SVG? {
180
+ func loadSVG(from url: URL, session: URLSession = ImageLoader.session) async -> SVG? {
84
181
  guard let data = try? await session.data(from: url).0 else { return nil }
85
182
  return SVG(data: data)
86
183
  }
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/wasm/SwiftBrowserNative.wasm
3
- size: 49330000 bytes
4
- sha256: e1e3caa6b59f0db479d22e2969623ed0609ecda85225d254b534cf952a6ca929
3
+ size: 49333823 bytes
4
+ sha256: f14edd41221a10a6f752f899f5eff663bbdbadd186d7503fad5eabe1e94bf916