@gjsify/iframe 0.3.13 → 0.3.14

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.
@@ -1,131 +1,133 @@
1
- var _a, _b, _c;
1
+ import { iframeWidget, loaded, windowProxy } from "./property-symbol.js";
2
2
  import { HTMLElement, PropertySymbol } from "@gjsify/dom-elements";
3
3
  import { Event } from "@gjsify/dom-events";
4
- import * as PS from "./property-symbol.js";
4
+
5
+ //#region src/html-iframe-element.ts
6
+ let _PS$iframeWidget, _PS$windowProxy, _PS$loaded;
5
7
  const { tagName, localName, namespaceURI } = PropertySymbol;
6
- class HTMLIFrameElement extends HTMLElement {
7
- constructor() {
8
- super();
9
- /** @internal The backing IFrameBridge (set by IFrameBridge when it creates this element) */
10
- this[_c] = null;
11
- /** @internal The contentWindow proxy */
12
- this[_b] = null;
13
- /** @internal Whether content has been loaded */
14
- this[_a] = false;
15
- this[tagName] = "IFRAME";
16
- this[localName] = "iframe";
17
- this[namespaceURI] = "http://www.w3.org/1999/xhtml";
18
- }
19
- // -- Attribute-backed string properties --
20
- get src() {
21
- return this.getAttribute("src") ?? "";
22
- }
23
- set src(value) {
24
- const old = this.getAttribute("src");
25
- this.setAttribute("src", value);
26
- if (value !== old && value && this[PS.iframeWidget]) {
27
- this[PS.iframeWidget].loadUri(value);
28
- }
29
- }
30
- get srcdoc() {
31
- return this.getAttribute("srcdoc") ?? "";
32
- }
33
- set srcdoc(value) {
34
- const old = this.getAttribute("srcdoc");
35
- this.setAttribute("srcdoc", value);
36
- if (value !== old && value && this[PS.iframeWidget]) {
37
- this[PS.iframeWidget].loadHtml(value);
38
- }
39
- }
40
- get name() {
41
- return this.getAttribute("name") ?? "";
42
- }
43
- set name(value) {
44
- this.setAttribute("name", value);
45
- }
46
- get sandbox() {
47
- return this.getAttribute("sandbox") ?? "";
48
- }
49
- set sandbox(value) {
50
- this.setAttribute("sandbox", value);
51
- }
52
- get allow() {
53
- return this.getAttribute("allow") ?? "";
54
- }
55
- set allow(value) {
56
- this.setAttribute("allow", value);
57
- }
58
- get referrerPolicy() {
59
- return this.getAttribute("referrerpolicy") ?? "";
60
- }
61
- set referrerPolicy(value) {
62
- this.setAttribute("referrerpolicy", value);
63
- }
64
- get loading() {
65
- const value = this.getAttribute("loading");
66
- if (value === "lazy" || value === "eager") return value;
67
- return "eager";
68
- }
69
- set loading(value) {
70
- this.setAttribute("loading", value);
71
- }
72
- // -- Attribute-backed string properties (width/height are strings per spec) --
73
- get width() {
74
- return this.getAttribute("width") ?? "";
75
- }
76
- set width(value) {
77
- this.setAttribute("width", value);
78
- }
79
- get height() {
80
- return this.getAttribute("height") ?? "";
81
- }
82
- set height(value) {
83
- this.setAttribute("height", value);
84
- }
85
- // -- Content access --
86
- /**
87
- * Returns the window proxy for the iframe's content.
88
- * Available after the IFrameBridge has loaded content.
89
- */
90
- get contentWindow() {
91
- return this[PS.windowProxy];
92
- }
93
- /**
94
- * Always returns null — cross-context boundary.
95
- * The WebView content runs in a separate process; direct document access
96
- * is not feasible. Use postMessage() for communication.
97
- */
98
- get contentDocument() {
99
- return null;
100
- }
101
- // -- Methods --
102
- /**
103
- * Returns a promise that resolves to the iframe's src URL.
104
- */
105
- getSVGDocument() {
106
- return null;
107
- }
108
- cloneNode(deep = false) {
109
- const clone = super.cloneNode(deep);
110
- clone[PS.iframeWidget] = null;
111
- clone[PS.windowProxy] = null;
112
- clone[PS.loaded] = false;
113
- return clone;
114
- }
115
- get [(_c = PS.iframeWidget, _b = PS.windowProxy, _a = PS.loaded, Symbol.toStringTag)]() {
116
- return "HTMLIFrameElement";
117
- }
118
- // -- Internal: called by IFrameBridge --
119
- /** @internal Fire load event */
120
- _onLoad() {
121
- this[PS.loaded] = true;
122
- this.dispatchEvent(new Event("load"));
123
- }
124
- /** @internal Fire error event */
125
- _onError() {
126
- this.dispatchEvent(new Event("error"));
127
- }
128
- }
129
- export {
130
- HTMLIFrameElement
8
+ /**
9
+ * HTML IFrame Element.
10
+ *
11
+ * Backed by WebKit.WebView when connected to an IFrameBridge.
12
+ * Without a backing widget, behaves as a pure DOM element with attribute storage.
13
+ *
14
+ * Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement
15
+ */
16
+ var HTMLIFrameElement = class extends HTMLElement {
17
+ constructor() {
18
+ super();
19
+ this[_PS$iframeWidget] = null;
20
+ this[_PS$windowProxy] = null;
21
+ this[_PS$loaded] = false;
22
+ this[tagName] = "IFRAME";
23
+ this[localName] = "iframe";
24
+ this[namespaceURI] = "http://www.w3.org/1999/xhtml";
25
+ }
26
+ get src() {
27
+ return this.getAttribute("src") ?? "";
28
+ }
29
+ set src(value) {
30
+ const old = this.getAttribute("src");
31
+ this.setAttribute("src", value);
32
+ if (value !== old && value && this[iframeWidget]) {
33
+ this[iframeWidget].loadUri(value);
34
+ }
35
+ }
36
+ get srcdoc() {
37
+ return this.getAttribute("srcdoc") ?? "";
38
+ }
39
+ set srcdoc(value) {
40
+ const old = this.getAttribute("srcdoc");
41
+ this.setAttribute("srcdoc", value);
42
+ if (value !== old && value && this[iframeWidget]) {
43
+ this[iframeWidget].loadHtml(value);
44
+ }
45
+ }
46
+ get name() {
47
+ return this.getAttribute("name") ?? "";
48
+ }
49
+ set name(value) {
50
+ this.setAttribute("name", value);
51
+ }
52
+ get sandbox() {
53
+ return this.getAttribute("sandbox") ?? "";
54
+ }
55
+ set sandbox(value) {
56
+ this.setAttribute("sandbox", value);
57
+ }
58
+ get allow() {
59
+ return this.getAttribute("allow") ?? "";
60
+ }
61
+ set allow(value) {
62
+ this.setAttribute("allow", value);
63
+ }
64
+ get referrerPolicy() {
65
+ return this.getAttribute("referrerpolicy") ?? "";
66
+ }
67
+ set referrerPolicy(value) {
68
+ this.setAttribute("referrerpolicy", value);
69
+ }
70
+ get loading() {
71
+ const value = this.getAttribute("loading");
72
+ if (value === "lazy" || value === "eager") return value;
73
+ return "eager";
74
+ }
75
+ set loading(value) {
76
+ this.setAttribute("loading", value);
77
+ }
78
+ get width() {
79
+ return this.getAttribute("width") ?? "";
80
+ }
81
+ set width(value) {
82
+ this.setAttribute("width", value);
83
+ }
84
+ get height() {
85
+ return this.getAttribute("height") ?? "";
86
+ }
87
+ set height(value) {
88
+ this.setAttribute("height", value);
89
+ }
90
+ /**
91
+ * Returns the window proxy for the iframe's content.
92
+ * Available after the IFrameBridge has loaded content.
93
+ */
94
+ get contentWindow() {
95
+ return this[windowProxy];
96
+ }
97
+ /**
98
+ * Always returns null cross-context boundary.
99
+ * The WebView content runs in a separate process; direct document access
100
+ * is not feasible. Use postMessage() for communication.
101
+ */
102
+ get contentDocument() {
103
+ return null;
104
+ }
105
+ /**
106
+ * Returns a promise that resolves to the iframe's src URL.
107
+ */
108
+ getSVGDocument() {
109
+ return null;
110
+ }
111
+ cloneNode(deep = false) {
112
+ const clone = super.cloneNode(deep);
113
+ clone[iframeWidget] = null;
114
+ clone[windowProxy] = null;
115
+ clone[loaded] = false;
116
+ return clone;
117
+ }
118
+ get [(_PS$iframeWidget = iframeWidget, _PS$windowProxy = windowProxy, _PS$loaded = loaded, Symbol.toStringTag)]() {
119
+ return "HTMLIFrameElement";
120
+ }
121
+ /** @internal Fire load event */
122
+ _onLoad() {
123
+ this[loaded] = true;
124
+ this.dispatchEvent(new Event("load"));
125
+ }
126
+ /** @internal Fire error event */
127
+ _onError() {
128
+ this.dispatchEvent(new Event("error"));
129
+ }
131
130
  };
131
+
132
+ //#endregion
133
+ export { HTMLIFrameElement };
@@ -1,107 +1,130 @@
1
- import GObject from "gi://GObject";
2
- import WebKit from "gi://WebKit?version=6.0";
1
+ import { iframeWidget, windowProxy } from "./property-symbol.js";
3
2
  import { HTMLIFrameElement } from "./html-iframe-element.js";
4
3
  import { IFrameWindowProxy } from "./iframe-window-proxy.js";
5
4
  import { MessageBridge } from "./message-bridge.js";
6
- import * as PS from "./property-symbol.js";
7
- const IFrameBridge = GObject.registerClass(
8
- { GTypeName: "GjsifyIFrameBridge" },
9
- class IFrameBridge2 extends WebKit.WebView {
10
- constructor(options) {
11
- const { enableDeveloperExtras, enableJavascript, ...webViewProps } = options ?? {};
12
- const userContentManager = new WebKit.UserContentManager();
13
- const settings = new WebKit.Settings();
14
- settings.enable_javascript = enableJavascript ?? true;
15
- settings.enable_developer_extras = enableDeveloperExtras ?? true;
16
- super({
17
- ...webViewProps,
18
- user_content_manager: userContentManager,
19
- settings
20
- });
21
- this._readyCallbacks = [];
22
- this._options = { enableDeveloperExtras, enableJavascript };
23
- this._iframe = new HTMLIFrameElement();
24
- this._iframe[PS.iframeWidget] = this;
25
- this._messageBridge = new MessageBridge(this);
26
- const windowProxy = new IFrameWindowProxy(this._messageBridge);
27
- this._iframe[PS.windowProxy] = windowProxy;
28
- this._messageBridge.setWindowProxy(windowProxy);
29
- this.connect("load-changed", (_webView, event) => {
30
- switch (event) {
31
- case WebKit.LoadEvent.COMMITTED: {
32
- const uri = this.get_uri();
33
- if (uri) this._messageBridge.updateUri(uri);
34
- break;
35
- }
36
- case WebKit.LoadEvent.FINISHED:
37
- this._iframe._onLoad();
38
- for (const cb of this._readyCallbacks) {
39
- cb(this._iframe);
40
- }
41
- this._readyCallbacks = [];
42
- break;
43
- }
44
- });
45
- this.connect("load-failed", () => {
46
- this._iframe._onError();
47
- return false;
48
- });
49
- this.connect("unrealize", () => {
50
- this._messageBridge.destroy();
51
- const proxy = this._iframe[PS.windowProxy];
52
- if (proxy) {
53
- proxy._close();
54
- }
55
- this._iframe[PS.iframeWidget] = null;
56
- this._iframe[PS.windowProxy] = null;
57
- });
58
- }
59
- /** The HTMLIFrameElement wrapping this WebView. */
60
- get iframeElement() {
61
- return this._iframe;
62
- }
63
- /**
64
- * Register a callback to be invoked when content has loaded.
65
- * If content is already loaded, the callback fires on next load.
66
- */
67
- onReady(cb) {
68
- this._readyCallbacks.push(cb);
69
- }
70
- /**
71
- * Load a URI into the WebView.
72
- * Also updates the iframe element's src attribute.
73
- */
74
- loadUri(uri) {
75
- this._iframe.setAttribute("src", uri);
76
- this.load_uri(uri);
77
- }
78
- /**
79
- * Load inline HTML into the WebView.
80
- * Also updates the iframe element's srcdoc attribute.
81
- */
82
- loadHtml(html, baseUri) {
83
- this._iframe.setAttribute("srcdoc", html);
84
- this.load_html(html, baseUri ?? "about:srcdoc");
85
- }
86
- /**
87
- * Send a message to the WebView content via the standard postMessage API.
88
- * Equivalent to `this.iframeElement.contentWindow.postMessage(message, targetOrigin)`.
89
- */
90
- postMessage(message, targetOrigin = "*") {
91
- this._messageBridge.sendToWebView(message, targetOrigin);
92
- }
93
- /**
94
- * Set `globalThis.HTMLIFrameElement` to the gjsify implementation.
95
- */
96
- installGlobals() {
97
- Object.defineProperty(globalThis, "HTMLIFrameElement", {
98
- value: HTMLIFrameElement,
99
- writable: true,
100
- configurable: true
101
- });
102
- }
103
- }
104
- );
105
- export {
106
- IFrameBridge
107
- };
5
+ import GObject from "gi://GObject";
6
+ import WebKit from "gi://WebKit?version=6.0";
7
+
8
+ //#region src/iframe-bridge.ts
9
+ /**
10
+ * A `WebKit.WebView` subclass that handles iframe bootstrapping:
11
+ * - Sets up WebKit settings (JavaScript, developer extras)
12
+ * - Creates an `HTMLIFrameElement` wrapping this WebView
13
+ * - Sets up postMessage bridge for GJS ↔ WebView communication
14
+ * - Fires `onReady()` callbacks with the iframe element once loaded
15
+ * - `installGlobals()` sets `globalThis.HTMLIFrameElement`
16
+ *
17
+ * Usage:
18
+ * ```ts
19
+ * const iframeWidget = new IFrameBridge();
20
+ * iframeWidget.installGlobals();
21
+ * iframeWidget.onReady((iframe) => {
22
+ * iframe.contentWindow?.addEventListener('message', (e) => {
23
+ * console.log('Message from iframe:', e.data);
24
+ * });
25
+ * });
26
+ * iframeWidget.iframeElement.src = 'https://example.com';
27
+ * window.set_child(iframeWidget);
28
+ * ```
29
+ */
30
+ const IFrameBridge = GObject.registerClass({ GTypeName: "GjsifyIFrameBridge" }, class IFrameBridge extends WebKit.WebView {
31
+ constructor(options) {
32
+ const { enableDeveloperExtras, enableJavascript, ...webViewProps } = options ?? {};
33
+ const userContentManager = new WebKit.UserContentManager();
34
+ const settings = new WebKit.Settings();
35
+ settings.enable_javascript = enableJavascript ?? true;
36
+ settings.enable_developer_extras = enableDeveloperExtras ?? true;
37
+ super({
38
+ ...webViewProps,
39
+ user_content_manager: userContentManager,
40
+ settings
41
+ });
42
+ this._readyCallbacks = [];
43
+ this._options = {
44
+ enableDeveloperExtras,
45
+ enableJavascript
46
+ };
47
+ this._iframe = new HTMLIFrameElement();
48
+ this._iframe[iframeWidget] = this;
49
+ this._messageBridge = new MessageBridge(this);
50
+ const windowProxy$1 = new IFrameWindowProxy(this._messageBridge);
51
+ this._iframe[windowProxy] = windowProxy$1;
52
+ this._messageBridge.setWindowProxy(windowProxy$1);
53
+ this.connect("load-changed", (_webView, event) => {
54
+ switch (event) {
55
+ case WebKit.LoadEvent.COMMITTED: {
56
+ const uri = this.get_uri();
57
+ if (uri) this._messageBridge.updateUri(uri);
58
+ break;
59
+ }
60
+ case WebKit.LoadEvent.FINISHED:
61
+ this._iframe._onLoad();
62
+ for (const cb of this._readyCallbacks) {
63
+ cb(this._iframe);
64
+ }
65
+ this._readyCallbacks = [];
66
+ break;
67
+ }
68
+ });
69
+ this.connect("load-failed", () => {
70
+ this._iframe._onError();
71
+ return false;
72
+ });
73
+ this.connect("unrealize", () => {
74
+ this._messageBridge.destroy();
75
+ const proxy = this._iframe[windowProxy];
76
+ if (proxy) {
77
+ proxy._close();
78
+ }
79
+ this._iframe[iframeWidget] = null;
80
+ this._iframe[windowProxy] = null;
81
+ });
82
+ }
83
+ /** The HTMLIFrameElement wrapping this WebView. */
84
+ get iframeElement() {
85
+ return this._iframe;
86
+ }
87
+ /**
88
+ * Register a callback to be invoked when content has loaded.
89
+ * If content is already loaded, the callback fires on next load.
90
+ */
91
+ onReady(cb) {
92
+ this._readyCallbacks.push(cb);
93
+ }
94
+ /**
95
+ * Load a URI into the WebView.
96
+ * Also updates the iframe element's src attribute.
97
+ */
98
+ loadUri(uri) {
99
+ this._iframe.setAttribute("src", uri);
100
+ this.load_uri(uri);
101
+ }
102
+ /**
103
+ * Load inline HTML into the WebView.
104
+ * Also updates the iframe element's srcdoc attribute.
105
+ */
106
+ loadHtml(html, baseUri) {
107
+ this._iframe.setAttribute("srcdoc", html);
108
+ this.load_html(html, baseUri ?? "about:srcdoc");
109
+ }
110
+ /**
111
+ * Send a message to the WebView content via the standard postMessage API.
112
+ * Equivalent to `this.iframeElement.contentWindow.postMessage(message, targetOrigin)`.
113
+ */
114
+ postMessage(message, targetOrigin = "*") {
115
+ this._messageBridge.sendToWebView(message, targetOrigin);
116
+ }
117
+ /**
118
+ * Set `globalThis.HTMLIFrameElement` to the gjsify implementation.
119
+ */
120
+ installGlobals() {
121
+ Object.defineProperty(globalThis, "HTMLIFrameElement", {
122
+ value: HTMLIFrameElement,
123
+ writable: true,
124
+ configurable: true
125
+ });
126
+ }
127
+ });
128
+
129
+ //#endregion
130
+ export { IFrameBridge };
@@ -1,58 +1,73 @@
1
1
  import { EventTarget } from "@gjsify/dom-events";
2
- class IFrameWindowProxy extends EventTarget {
3
- constructor(bridge) {
4
- super();
5
- this._closed = false;
6
- this._bridge = bridge;
7
- }
8
- /**
9
- * Send a message to the iframe content.
10
- *
11
- * @param message - Data to send (must be JSON-serializable)
12
- * @param targetOrigin - Target origin for the message. Default: '*'
13
- */
14
- postMessage(message, targetOrigin = "*") {
15
- if (this._closed) return;
16
- this._bridge.sendToWebView(message, targetOrigin);
17
- }
18
- /**
19
- * Read-only location reflecting the current WebView URI.
20
- */
21
- get location() {
22
- return this._bridge.getLocation();
23
- }
24
- /**
25
- * Reference to the host (parent) window — in GJS this is globalThis.
26
- */
27
- get parent() {
28
- return globalThis;
29
- }
30
- /**
31
- * Reference to the top-level window — in GJS this is globalThis.
32
- */
33
- get top() {
34
- return globalThis;
35
- }
36
- /**
37
- * The window itself (self-reference per spec).
38
- */
39
- get self() {
40
- return this;
41
- }
42
- get window() {
43
- return this;
44
- }
45
- get closed() {
46
- return this._closed;
47
- }
48
- /** @internal Mark as closed when the WebView is destroyed */
49
- _close() {
50
- this._closed = true;
51
- }
52
- get [Symbol.toStringTag]() {
53
- return "IFrameWindowProxy";
54
- }
55
- }
56
- export {
57
- IFrameWindowProxy
2
+
3
+ //#region src/iframe-window-proxy.ts
4
+ /**
5
+ * Lightweight Window-like proxy returned by `HTMLIFrameElement.contentWindow`.
6
+ *
7
+ * Supports the subset of the Window API needed for cross-origin iframe communication:
8
+ * - `postMessage()` for sending messages to the iframe content
9
+ * - `addEventListener('message', ...)` for receiving messages from the iframe content
10
+ * - `location` (read-only) reflecting the current URI
11
+ * - `parent` reference to the host window
12
+ * - `closed` status
13
+ *
14
+ * This is intentionally NOT a full BrowserWindow — just enough for standard
15
+ * postMessage-based communication patterns.
16
+ */
17
+ var IFrameWindowProxy = class extends EventTarget {
18
+ constructor(bridge) {
19
+ super();
20
+ this._closed = false;
21
+ this._bridge = bridge;
22
+ }
23
+ /**
24
+ * Send a message to the iframe content.
25
+ *
26
+ * @param message - Data to send (must be JSON-serializable)
27
+ * @param targetOrigin - Target origin for the message. Default: '*'
28
+ */
29
+ postMessage(message, targetOrigin = "*") {
30
+ if (this._closed) return;
31
+ this._bridge.sendToWebView(message, targetOrigin);
32
+ }
33
+ /**
34
+ * Read-only location reflecting the current WebView URI.
35
+ */
36
+ get location() {
37
+ return this._bridge.getLocation();
38
+ }
39
+ /**
40
+ * Reference to the host (parent) window — in GJS this is globalThis.
41
+ */
42
+ get parent() {
43
+ return globalThis;
44
+ }
45
+ /**
46
+ * Reference to the top-level window — in GJS this is globalThis.
47
+ */
48
+ get top() {
49
+ return globalThis;
50
+ }
51
+ /**
52
+ * The window itself (self-reference per spec).
53
+ */
54
+ get self() {
55
+ return this;
56
+ }
57
+ get window() {
58
+ return this;
59
+ }
60
+ get closed() {
61
+ return this._closed;
62
+ }
63
+ /** @internal Mark as closed when the WebView is destroyed */
64
+ _close() {
65
+ this._closed = true;
66
+ }
67
+ get [Symbol.toStringTag]() {
68
+ return "IFrameWindowProxy";
69
+ }
58
70
  };
71
+
72
+ //#endregion
73
+ export { IFrameWindowProxy };
package/lib/esm/index.js CHANGED
@@ -1,18 +1,16 @@
1
1
  import { HTMLIFrameElement } from "./html-iframe-element.js";
2
- import { IFrameBridge } from "./iframe-bridge.js";
3
2
  import { IFrameWindowProxy } from "./iframe-window-proxy.js";
4
3
  import { MessageBridge } from "./message-bridge.js";
4
+ import { IFrameBridge } from "./iframe-bridge.js";
5
5
  import { Document } from "@gjsify/dom-elements";
6
- import { HTMLIFrameElement as HTMLIFrameElement2 } from "./html-iframe-element.js";
7
- Document.registerElementFactory("iframe", () => new HTMLIFrameElement2());
6
+
7
+ //#region src/index.ts
8
+ Document.registerElementFactory("iframe", () => new HTMLIFrameElement());
8
9
  Object.defineProperty(globalThis, "HTMLIFrameElement", {
9
- value: HTMLIFrameElement2,
10
- writable: true,
11
- configurable: true
10
+ value: HTMLIFrameElement,
11
+ writable: true,
12
+ configurable: true
12
13
  });
13
- export {
14
- HTMLIFrameElement,
15
- IFrameBridge,
16
- IFrameWindowProxy,
17
- MessageBridge
18
- };
14
+
15
+ //#endregion
16
+ export { HTMLIFrameElement, IFrameBridge, IFrameWindowProxy, MessageBridge };
@@ -1,8 +1,19 @@
1
- import Gio from "gi://Gio?version=2.0";
2
- import WebKit from "gi://WebKit?version=6.0";
3
1
  import { MessageEvent } from "@gjsify/dom-events";
2
+ import WebKit from "gi://WebKit?version=6.0";
3
+ import Gio from "gi://Gio?version=2.0";
4
+
5
+ //#region src/message-bridge.ts
4
6
  Gio._promisify(WebKit.WebView.prototype, "evaluate_javascript", "evaluate_javascript_finish");
5
7
  const CHANNEL_NAME = "gjsify-iframe";
8
+ /**
9
+ * Bootstrap script injected into every WebView page at document start.
10
+ * Provides the `window.parent.postMessage()` bridge from WebView content back to GJS.
11
+ *
12
+ * The script:
13
+ * 1. Gets the WebKit message handler registered under CHANNEL_NAME
14
+ * 2. Creates a parent proxy with a postMessage() that sends via the WebKit handler
15
+ * 3. Overrides window.parent to point to the proxy
16
+ */
6
17
  const BOOTSTRAP_SCRIPT = `(function() {
7
18
  var handler = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers['${CHANNEL_NAME}'];
8
19
  if (!handler) return;
@@ -14,7 +25,7 @@ const BOOTSTRAP_SCRIPT = `(function() {
14
25
  }));
15
26
  }
16
27
  // In a WebKit.WebView loaded via srcdoc, window.parent === window (no real iframe nesting).
17
- // window.parent is [LegacyUnforgeable] \u2014 cannot be redefined with defineProperty.
28
+ // window.parent is [LegacyUnforgeable] cannot be redefined with defineProperty.
18
29
  // Instead, override window.postMessage directly. Since window.parent === window,
19
30
  // calls to window.parent.postMessage() will use our override.
20
31
  var origPostMessage = window.postMessage;
@@ -24,106 +35,98 @@ const BOOTSTRAP_SCRIPT = `(function() {
24
35
  // Also expose on a safe namespace for explicit use
25
36
  window.__gjsifyBridge = { postMessage: bridgePostMessage, origPostMessage: origPostMessage };
26
37
  })();`;
27
- class MessageBridge {
28
- constructor(webView) {
29
- this._windowProxy = null;
30
- this._currentUri = "about:blank";
31
- this._signalId = null;
32
- this._webView = webView;
33
- this._userContentManager = webView.get_user_content_manager();
34
- this._setupReceiver();
35
- this._injectBootstrapScript();
36
- }
37
- /** Connect the IFrameWindowProxy that will receive messages from the WebView */
38
- setWindowProxy(proxy) {
39
- this._windowProxy = proxy;
40
- }
41
- /** Update current URI (called by IFrameBridge on load-changed) */
42
- updateUri(uri) {
43
- this._currentUri = uri;
44
- }
45
- /** Get current location info for the IFrameWindowProxy */
46
- getLocation() {
47
- let origin;
48
- try {
49
- const url = new URL(this._currentUri);
50
- origin = url.origin;
51
- } catch {
52
- origin = "null";
53
- }
54
- return { href: this._currentUri, origin };
55
- }
56
- /**
57
- * Send a message from GJS to the WebView content.
58
- * Dispatches a standard MessageEvent on the WebView's window object.
59
- */
60
- sendToWebView(data, _targetOrigin) {
61
- const serialized = JSON.stringify(data);
62
- const origin = JSON.stringify("gjsify");
63
- const script = `window.dispatchEvent(new MessageEvent('message', { data: JSON.parse(${JSON.stringify(serialized)}), origin: ${origin} }));`;
64
- this._webView.evaluate_javascript(
65
- script,
66
- -1,
67
- // length (-1 = null-terminated)
68
- null,
69
- // world name (null = default)
70
- null,
71
- // source URI
72
- null
73
- // cancellable
74
- ).catch(() => {
75
- });
76
- }
77
- /** Clean up signal handlers */
78
- destroy() {
79
- if (this._signalId !== null) {
80
- this._userContentManager.disconnect(this._signalId);
81
- this._signalId = null;
82
- }
83
- this._userContentManager.unregister_script_message_handler(CHANNEL_NAME, null);
84
- this._windowProxy = null;
85
- }
86
- /**
87
- * Set up the receiver for messages coming from the WebView.
88
- * Registers a script message handler and connects to the signal.
89
- */
90
- _setupReceiver() {
91
- this._userContentManager.register_script_message_handler(CHANNEL_NAME, null);
92
- this._signalId = this._userContentManager.connect(
93
- `script-message-received::${CHANNEL_NAME}`,
94
- (_ucm, jsValue) => {
95
- if (!this._windowProxy) return;
96
- try {
97
- const json = jsValue.to_string();
98
- const envelope = JSON.parse(json);
99
- const event = new MessageEvent("message", {
100
- data: envelope.data,
101
- origin: envelope.origin
102
- });
103
- this._windowProxy.dispatchEvent(event);
104
- } catch (error) {
105
- console.error("[IFrame MessageBridge] Error processing message:", error);
106
- }
107
- }
108
- );
109
- }
110
- /**
111
- * Inject the bootstrap script into the WebView so that
112
- * window.parent.postMessage() bridges back to GJS.
113
- */
114
- _injectBootstrapScript() {
115
- const script = new WebKit.UserScript(
116
- BOOTSTRAP_SCRIPT,
117
- WebKit.UserContentInjectedFrames.ALL_FRAMES,
118
- WebKit.UserScriptInjectionTime.START,
119
- null,
120
- // allow list (null = all)
121
- null
122
- // block list (null = none)
123
- );
124
- this._userContentManager.add_script(script);
125
- }
126
- }
127
- export {
128
- MessageBridge
38
+ /**
39
+ * Manages bidirectional postMessage communication between GJS and a WebKit.WebView.
40
+ *
41
+ * Direction 1 — GJS → WebView:
42
+ * Uses webView.evaluate_javascript() to dispatch a MessageEvent on the WebView's window.
43
+ *
44
+ * Direction 2 — WebView → GJS:
45
+ * Bootstrap script overrides window.parent.postMessage to call
46
+ * webkit.messageHandlers[CHANNEL_NAME].postMessage(), which triggers
47
+ * the UserContentManager 'script-message-received' signal in GJS.
48
+ */
49
+ var MessageBridge = class {
50
+ constructor(webView) {
51
+ this._windowProxy = null;
52
+ this._currentUri = "about:blank";
53
+ this._signalId = null;
54
+ this._webView = webView;
55
+ this._userContentManager = webView.get_user_content_manager();
56
+ this._setupReceiver();
57
+ this._injectBootstrapScript();
58
+ }
59
+ /** Connect the IFrameWindowProxy that will receive messages from the WebView */
60
+ setWindowProxy(proxy) {
61
+ this._windowProxy = proxy;
62
+ }
63
+ /** Update current URI (called by IFrameBridge on load-changed) */
64
+ updateUri(uri) {
65
+ this._currentUri = uri;
66
+ }
67
+ /** Get current location info for the IFrameWindowProxy */
68
+ getLocation() {
69
+ let origin;
70
+ try {
71
+ const url = new URL(this._currentUri);
72
+ origin = url.origin;
73
+ } catch {
74
+ origin = "null";
75
+ }
76
+ return {
77
+ href: this._currentUri,
78
+ origin
79
+ };
80
+ }
81
+ /**
82
+ * Send a message from GJS to the WebView content.
83
+ * Dispatches a standard MessageEvent on the WebView's window object.
84
+ */
85
+ sendToWebView(data, _targetOrigin) {
86
+ const serialized = JSON.stringify(data);
87
+ const origin = JSON.stringify("gjsify");
88
+ const script = `window.dispatchEvent(new MessageEvent('message', { data: JSON.parse(${JSON.stringify(serialized)}), origin: ${origin} }));`;
89
+ this._webView.evaluate_javascript(script, -1, null, null, null).catch(() => {});
90
+ }
91
+ /** Clean up signal handlers */
92
+ destroy() {
93
+ if (this._signalId !== null) {
94
+ this._userContentManager.disconnect(this._signalId);
95
+ this._signalId = null;
96
+ }
97
+ this._userContentManager.unregister_script_message_handler(CHANNEL_NAME, null);
98
+ this._windowProxy = null;
99
+ }
100
+ /**
101
+ * Set up the receiver for messages coming from the WebView.
102
+ * Registers a script message handler and connects to the signal.
103
+ */
104
+ _setupReceiver() {
105
+ this._userContentManager.register_script_message_handler(CHANNEL_NAME, null);
106
+ this._signalId = this._userContentManager.connect(`script-message-received::${CHANNEL_NAME}`, (_ucm, jsValue) => {
107
+ if (!this._windowProxy) return;
108
+ try {
109
+ const json = jsValue.to_string();
110
+ const envelope = JSON.parse(json);
111
+ const event = new MessageEvent("message", {
112
+ data: envelope.data,
113
+ origin: envelope.origin
114
+ });
115
+ this._windowProxy.dispatchEvent(event);
116
+ } catch (error) {
117
+ console.error("[IFrame MessageBridge] Error processing message:", error);
118
+ }
119
+ });
120
+ }
121
+ /**
122
+ * Inject the bootstrap script into the WebView so that
123
+ * window.parent.postMessage() bridges back to GJS.
124
+ */
125
+ _injectBootstrapScript() {
126
+ const script = new WebKit.UserScript(BOOTSTRAP_SCRIPT, WebKit.UserContentInjectedFrames.ALL_FRAMES, WebKit.UserScriptInjectionTime.START, null, null);
127
+ this._userContentManager.add_script(script);
128
+ }
129
129
  };
130
+
131
+ //#endregion
132
+ export { MessageBridge };
@@ -1,8 +1,7 @@
1
- const iframeWidget = /* @__PURE__ */ Symbol("iframeWidget");
2
- const windowProxy = /* @__PURE__ */ Symbol("windowProxy");
3
- const loaded = /* @__PURE__ */ Symbol("loaded");
4
- export {
5
- iframeWidget,
6
- loaded,
7
- windowProxy
8
- };
1
+ //#region src/property-symbol.ts
2
+ const iframeWidget = Symbol("iframeWidget");
3
+ const windowProxy = Symbol("windowProxy");
4
+ const loaded = Symbol("loaded");
5
+
6
+ //#endregion
7
+ export { iframeWidget, loaded, windowProxy };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/iframe",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "HTMLIFrameElement for GJS, backed by WebKit.WebView",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -30,16 +30,16 @@
30
30
  "dom"
31
31
  ],
32
32
  "dependencies": {
33
- "@girs/gjs": "^4.0.0-rc.9",
34
- "@girs/gtk-4.0": "^4.23.0-4.0.0-rc.9",
33
+ "@girs/gjs": "4.0.0-rc.9",
34
+ "@girs/gtk-4.0": "4.23.0-4.0.0-rc.9",
35
35
  "@girs/javascriptcore-6.0": "2.52.1-4.0.0-rc.9",
36
36
  "@girs/webkit-6.0": "2.52.1-4.0.0-rc.9",
37
- "@gjsify/dom-elements": "^0.3.13",
38
- "@gjsify/dom-events": "^0.3.13"
37
+ "@gjsify/dom-elements": "^0.3.14",
38
+ "@gjsify/dom-events": "^0.3.14"
39
39
  },
40
40
  "devDependencies": {
41
- "@gjsify/cli": "^0.3.13",
42
- "@gjsify/unit": "^0.3.13",
41
+ "@gjsify/cli": "^0.3.14",
42
+ "@gjsify/unit": "^0.3.14",
43
43
  "@types/node": "^25.6.0",
44
44
  "typescript": "^6.0.3"
45
45
  }