@gjsify/iframe 0.1.0
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 +34 -0
- package/lib/esm/html-iframe-element.js +131 -0
- package/lib/esm/iframe-widget.js +107 -0
- package/lib/esm/iframe-window-proxy.js +58 -0
- package/lib/esm/index.js +18 -0
- package/lib/esm/message-bridge.js +129 -0
- package/lib/esm/property-symbol.js +8 -0
- package/lib/esm/types/index.js +0 -0
- package/lib/types/html-iframe-element.d.ts +59 -0
- package/lib/types/iframe-widget.d.ts +646 -0
- package/lib/types/iframe-window-proxy.d.ts +51 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/message-bridge.d.ts +47 -0
- package/lib/types/property-symbol.d.ts +3 -0
- package/lib/types/types/index.d.ts +15 -0
- package/package.json +46 -0
- package/src/html-iframe-element.ts +177 -0
- package/src/iframe-widget.ts +158 -0
- package/src/iframe-window-proxy.ts +86 -0
- package/src/index.spec.ts +323 -0
- package/src/index.ts +24 -0
- package/src/message-bridge.ts +175 -0
- package/src/property-symbol.ts +11 -0
- package/src/test.mts +6 -0
- package/src/types/index.ts +19 -0
- package/tmp/.tsbuildinfo +1 -0
- package/tsconfig.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @gjsify/iframe
|
|
2
|
+
|
|
3
|
+
GJS implementation of HTMLIFrameElement using WebKit 6.0. Provides IFrameWidget extending WebKit.WebView with postMessage bridge.
|
|
4
|
+
|
|
5
|
+
Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gjsify/iframe
|
|
11
|
+
# or
|
|
12
|
+
yarn add @gjsify/iframe
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { IFrameWidget } from '@gjsify/iframe';
|
|
19
|
+
|
|
20
|
+
const widget = new IFrameWidget();
|
|
21
|
+
|
|
22
|
+
widget.onReady((iframe) => {
|
|
23
|
+
iframe.contentWindow?.addEventListener('message', (event) => {
|
|
24
|
+
console.log(event.data);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
widget.iframeElement.srcdoc = '<h1>Hello</h1>';
|
|
29
|
+
window.set_child(widget);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
var _a, _b, _c;
|
|
2
|
+
import { HTMLElement, PropertySymbol } from "@gjsify/dom-elements";
|
|
3
|
+
import { Event } from "@gjsify/dom-events";
|
|
4
|
+
import * as PS from "./property-symbol.js";
|
|
5
|
+
const { tagName, localName, namespaceURI } = PropertySymbol;
|
|
6
|
+
class HTMLIFrameElement extends HTMLElement {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
/** @internal The backing IFrameWidget (set by IFrameWidget 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 IFrameWidget 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 IFrameWidget --
|
|
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
|
|
131
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import GObject from "gi://GObject";
|
|
2
|
+
import WebKit from "gi://WebKit?version=6.0";
|
|
3
|
+
import { HTMLIFrameElement } from "./html-iframe-element.js";
|
|
4
|
+
import { IFrameWindowProxy } from "./iframe-window-proxy.js";
|
|
5
|
+
import { MessageBridge } from "./message-bridge.js";
|
|
6
|
+
import * as PS from "./property-symbol.js";
|
|
7
|
+
const IFrameWidget = GObject.registerClass(
|
|
8
|
+
{ GTypeName: "GjsifyIFrameWidget" },
|
|
9
|
+
class IFrameWidget2 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
|
+
IFrameWidget
|
|
107
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
|
58
|
+
};
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HTMLIFrameElement } from "./html-iframe-element.js";
|
|
2
|
+
import { IFrameWidget } from "./iframe-widget.js";
|
|
3
|
+
import { IFrameWindowProxy } from "./iframe-window-proxy.js";
|
|
4
|
+
import { MessageBridge } from "./message-bridge.js";
|
|
5
|
+
import { Document } from "@gjsify/dom-elements";
|
|
6
|
+
import { HTMLIFrameElement as HTMLIFrameElement2 } from "./html-iframe-element.js";
|
|
7
|
+
Document.registerElementFactory("iframe", () => new HTMLIFrameElement2());
|
|
8
|
+
Object.defineProperty(globalThis, "HTMLIFrameElement", {
|
|
9
|
+
value: HTMLIFrameElement2,
|
|
10
|
+
writable: true,
|
|
11
|
+
configurable: true
|
|
12
|
+
});
|
|
13
|
+
export {
|
|
14
|
+
HTMLIFrameElement,
|
|
15
|
+
IFrameWidget,
|
|
16
|
+
IFrameWindowProxy,
|
|
17
|
+
MessageBridge
|
|
18
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import Gio from "gi://Gio?version=2.0";
|
|
2
|
+
import WebKit from "gi://WebKit?version=6.0";
|
|
3
|
+
import { MessageEvent } from "@gjsify/dom-events";
|
|
4
|
+
Gio._promisify(WebKit.WebView.prototype, "evaluate_javascript", "evaluate_javascript_finish");
|
|
5
|
+
const CHANNEL_NAME = "gjsify-iframe";
|
|
6
|
+
const BOOTSTRAP_SCRIPT = `(function() {
|
|
7
|
+
var handler = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers['${CHANNEL_NAME}'];
|
|
8
|
+
if (!handler) return;
|
|
9
|
+
function bridgePostMessage(data, targetOrigin) {
|
|
10
|
+
handler.postMessage(JSON.stringify({
|
|
11
|
+
data: data,
|
|
12
|
+
targetOrigin: targetOrigin || '*',
|
|
13
|
+
origin: location.origin
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
// 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.
|
|
18
|
+
// Instead, override window.postMessage directly. Since window.parent === window,
|
|
19
|
+
// calls to window.parent.postMessage() will use our override.
|
|
20
|
+
var origPostMessage = window.postMessage;
|
|
21
|
+
window.postMessage = function(data, targetOrigin) {
|
|
22
|
+
bridgePostMessage(data, targetOrigin);
|
|
23
|
+
};
|
|
24
|
+
// Also expose on a safe namespace for explicit use
|
|
25
|
+
window.__gjsifyBridge = { postMessage: bridgePostMessage, origPostMessage: origPostMessage };
|
|
26
|
+
})();`;
|
|
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 IFrameWidget 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
|
|
129
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { HTMLElement } from '@gjsify/dom-elements';
|
|
2
|
+
import * as PS from './property-symbol.js';
|
|
3
|
+
import type { IFrameWindowProxy } from './iframe-window-proxy.js';
|
|
4
|
+
/**
|
|
5
|
+
* HTML IFrame Element.
|
|
6
|
+
*
|
|
7
|
+
* Backed by WebKit.WebView when connected to an IFrameWidget.
|
|
8
|
+
* Without a backing widget, behaves as a pure DOM element with attribute storage.
|
|
9
|
+
*
|
|
10
|
+
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement
|
|
11
|
+
*/
|
|
12
|
+
export declare class HTMLIFrameElement extends HTMLElement {
|
|
13
|
+
/** @internal The backing IFrameWidget (set by IFrameWidget when it creates this element) */
|
|
14
|
+
[PS.iframeWidget]: import('./iframe-widget.js').IFrameWidget | null;
|
|
15
|
+
/** @internal The contentWindow proxy */
|
|
16
|
+
[PS.windowProxy]: IFrameWindowProxy | null;
|
|
17
|
+
/** @internal Whether content has been loaded */
|
|
18
|
+
[PS.loaded]: boolean;
|
|
19
|
+
constructor();
|
|
20
|
+
get src(): string;
|
|
21
|
+
set src(value: string);
|
|
22
|
+
get srcdoc(): string;
|
|
23
|
+
set srcdoc(value: string);
|
|
24
|
+
get name(): string;
|
|
25
|
+
set name(value: string);
|
|
26
|
+
get sandbox(): string;
|
|
27
|
+
set sandbox(value: string);
|
|
28
|
+
get allow(): string;
|
|
29
|
+
set allow(value: string);
|
|
30
|
+
get referrerPolicy(): string;
|
|
31
|
+
set referrerPolicy(value: string);
|
|
32
|
+
get loading(): string;
|
|
33
|
+
set loading(value: string);
|
|
34
|
+
get width(): string;
|
|
35
|
+
set width(value: string);
|
|
36
|
+
get height(): string;
|
|
37
|
+
set height(value: string);
|
|
38
|
+
/**
|
|
39
|
+
* Returns the window proxy for the iframe's content.
|
|
40
|
+
* Available after the IFrameWidget has loaded content.
|
|
41
|
+
*/
|
|
42
|
+
get contentWindow(): IFrameWindowProxy | null;
|
|
43
|
+
/**
|
|
44
|
+
* Always returns null — cross-context boundary.
|
|
45
|
+
* The WebView content runs in a separate process; direct document access
|
|
46
|
+
* is not feasible. Use postMessage() for communication.
|
|
47
|
+
*/
|
|
48
|
+
get contentDocument(): null;
|
|
49
|
+
/**
|
|
50
|
+
* Returns a promise that resolves to the iframe's src URL.
|
|
51
|
+
*/
|
|
52
|
+
getSVGDocument(): null;
|
|
53
|
+
cloneNode(deep?: boolean): HTMLIFrameElement;
|
|
54
|
+
get [Symbol.toStringTag](): string;
|
|
55
|
+
/** @internal Fire load event */
|
|
56
|
+
_onLoad(): void;
|
|
57
|
+
/** @internal Fire error event */
|
|
58
|
+
_onError(): void;
|
|
59
|
+
}
|