@gjsify/iframe 0.3.13 → 0.3.15
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/lib/esm/html-iframe-element.js +129 -127
- package/lib/esm/iframe-bridge.js +127 -104
- package/lib/esm/iframe-window-proxy.js +71 -56
- package/lib/esm/index.js +10 -12
- package/lib/esm/message-bridge.js +108 -105
- package/lib/esm/property-symbol.js +7 -8
- package/package.json +7 -7
|
@@ -1,131 +1,133 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
4
|
+
|
|
5
|
+
//#region src/html-iframe-element.ts
|
|
6
|
+
let _PS$iframeWidget, _PS$windowProxy, _PS$loaded;
|
|
5
7
|
const { tagName, localName, namespaceURI } = PropertySymbol;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 };
|
package/lib/esm/iframe-bridge.js
CHANGED
|
@@ -1,107 +1,130 @@
|
|
|
1
|
-
import
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
//#region src/index.ts
|
|
8
|
+
Document.registerElementFactory("iframe", () => new HTMLIFrameElement());
|
|
8
9
|
Object.defineProperty(globalThis, "HTMLIFrameElement", {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
value: HTMLIFrameElement,
|
|
11
|
+
writable: true,
|
|
12
|
+
configurable: true
|
|
12
13
|
});
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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]
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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.
|
|
3
|
+
"version": "0.3.15",
|
|
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": "
|
|
34
|
-
"@girs/gtk-4.0": "
|
|
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.
|
|
38
|
-
"@gjsify/dom-events": "^0.3.
|
|
37
|
+
"@gjsify/dom-elements": "^0.3.15",
|
|
38
|
+
"@gjsify/dom-events": "^0.3.15"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@gjsify/cli": "^0.3.
|
|
42
|
-
"@gjsify/unit": "^0.3.
|
|
41
|
+
"@gjsify/cli": "^0.3.15",
|
|
42
|
+
"@gjsify/unit": "^0.3.15",
|
|
43
43
|
"@types/node": "^25.6.0",
|
|
44
44
|
"typescript": "^6.0.3"
|
|
45
45
|
}
|