@opensumi/ide-webview 2.21.6 → 2.21.7-rc-1669799333.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.
@@ -0,0 +1,156 @@
1
+ import { Injectable, Autowired } from '@opensumi/di';
2
+ import {
3
+ Disposable,
4
+ DomListener,
5
+ getDebugLogger,
6
+ IDisposable,
7
+ AppConfig,
8
+ electronEnv,
9
+ } from '@opensumi/ide-core-browser';
10
+
11
+ import { WebviewScheme } from '../common';
12
+
13
+ import { AbstractWebviewPanel } from './abstract-webview';
14
+ import { IWebview, IWebviewContentOptions } from './types';
15
+
16
+ @Injectable({ multiple: true })
17
+ export class ElectronWebviewWebviewPanel extends AbstractWebviewPanel implements IWebview {
18
+ private webview: Electron.WebviewTag;
19
+
20
+ private _needReload = false;
21
+
22
+ private _iframeDisposer: Disposable | null = new Disposable();
23
+
24
+ private _isReady: boolean;
25
+
26
+ @Autowired(AppConfig)
27
+ config: AppConfig;
28
+
29
+ constructor(public readonly id: string, options: IWebviewContentOptions = {}) {
30
+ super(id, options);
31
+
32
+ this.webview = document.createElement('webview');
33
+ this.webview.src = `${WebviewScheme}://index.html`;
34
+ this.webview.preload = electronEnv.webviewPreload;
35
+ this.webview.style.border = 'none';
36
+ this.webview.style.width = '100%';
37
+ this.webview.style.position = 'absolute';
38
+ this.webview.style.height = '100%';
39
+ this.webview.style.zIndex = '2';
40
+ super.init();
41
+ }
42
+
43
+ prepareContainer() {
44
+ this.clear();
45
+ this._iframeDisposer = new Disposable();
46
+ this._ready = new Promise<void>((resolve) => {
47
+ // tslint:disable-next-line: no-unused-variable
48
+ const disposer = this._onWebviewMessage('webview-ready', () => {
49
+ if (this._isReady) {
50
+ // 这种情况一般是由于iframe在dom中的位置变动导致了重载。
51
+ // 此时我们需要重新初始化
52
+ // electronWebview不需要重新监听事件
53
+ this.updateStyle();
54
+ this.doUpdateContent();
55
+ }
56
+ this._isReady = true;
57
+ resolve();
58
+ });
59
+ });
60
+ this._needReload = false;
61
+ }
62
+
63
+ getDomNode() {
64
+ return this.webview;
65
+ }
66
+
67
+ protected _sendToWebview(channel: string, data: any) {
68
+ if (!this._isListening) {
69
+ return;
70
+ }
71
+ this._ready
72
+ .then(() => {
73
+ if (!this.webview) {
74
+ return;
75
+ }
76
+ this.webview.send(channel, data);
77
+ })
78
+ .catch((err) => {
79
+ getDebugLogger().error(err);
80
+ });
81
+ }
82
+
83
+ protected _onWebviewMessage(channel: string, listener: (data: any) => any): IDisposable {
84
+ return this._iframeDisposer!.addDispose(
85
+ new DomListener(this.webview, 'ipc-message', (e) => {
86
+ if (!this.webview) {
87
+ return;
88
+ }
89
+ if (e.channel === channel) {
90
+ if (!this._isListening) {
91
+ return;
92
+ }
93
+ listener(e.args[0]);
94
+ }
95
+ }),
96
+ );
97
+ }
98
+
99
+ appendTo(container: HTMLElement) {
100
+ if (this.webview) {
101
+ if (container.style.position === 'static' || !container.style.position) {
102
+ container.style.position = 'relative';
103
+ }
104
+ container.innerHTML = '';
105
+ container.appendChild(this.webview);
106
+ if (this._needReload) {
107
+ this.init();
108
+ this.doUpdateContent();
109
+ }
110
+ }
111
+ }
112
+
113
+ remove() {
114
+ if (this.webview) {
115
+ this.webview.remove();
116
+ this._onRemove.fire();
117
+ // remove 只是视图被销毁,但是html,state等内容保留,因此这里之前是改坏了
118
+ // this.dispose();
119
+ this.clear();
120
+ this._needReload = true;
121
+ }
122
+ }
123
+
124
+ clear() {
125
+ if (this._iframeDisposer) {
126
+ this._iframeDisposer.dispose();
127
+ this._iframeDisposer = null;
128
+ }
129
+ this._isReady = false;
130
+ }
131
+
132
+ dispose() {
133
+ super.dispose();
134
+ if (this._iframeDisposer) {
135
+ this._iframeDisposer.dispose();
136
+ }
137
+ }
138
+ }
139
+ // tslint:disable-next-line: no-unused-variable
140
+ const WebviewHTMLStr = `<!DOCTYPE html>
141
+ <html lang="en" style="width: 100%; height: 100%;">
142
+
143
+ <head>
144
+ <meta charset="UTF-8">
145
+ <meta http-equiv="Content-Security-Policy"
146
+ content="default-src 'self'; script-src 'self'; frame-src 'self'; style-src 'unsafe-inline'; worker-src 'self'; img-src * data: ; " />
147
+
148
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
149
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
150
+ <title>Webview Panel Container</title>
151
+ </head>
152
+
153
+ <body style="margin: 0; overflow: hidden; width: 100%; height: 100%">
154
+ </body>
155
+
156
+ </html>`;
@@ -0,0 +1,140 @@
1
+ import { Injectable, Autowired } from '@opensumi/di';
2
+ import { Disposable, DomListener, getDebugLogger, IDisposable, AppConfig } from '@opensumi/ide-core-browser';
3
+
4
+ import { AbstractWebviewPanel } from './abstract-webview';
5
+ import { IWebview, IWebviewContentOptions } from './types';
6
+
7
+
8
+ @Injectable({ multiple: true })
9
+ export class IFrameWebviewPanel extends AbstractWebviewPanel implements IWebview {
10
+ private iframe: HTMLIFrameElement;
11
+
12
+ private _needReload = false;
13
+
14
+ private _iframeDisposer: Disposable | null = new Disposable();
15
+
16
+ private _isReady: boolean;
17
+
18
+ @Autowired(AppConfig)
19
+ config: AppConfig;
20
+
21
+ constructor(public readonly id: string, options: IWebviewContentOptions = {}) {
22
+ super(id, options);
23
+
24
+ this.iframe = document.createElement('iframe');
25
+ this.iframe.setAttribute('allow', 'autoplay');
26
+
27
+ const sandboxRules = new Set(['allow-same-origin', 'allow-scripts']);
28
+
29
+ if (options.allowForms) {
30
+ sandboxRules.add('allow-forms');
31
+ }
32
+ this.iframe.setAttribute('sandbox', Array.from(sandboxRules).join(' '));
33
+ this.iframe.setAttribute('src', `${this.config.webviewEndpoint}/index.html?id=${this.id}`);
34
+ this.iframe.style.border = 'none';
35
+ this.iframe.style.width = '100%';
36
+ this.iframe.style.position = 'absolute';
37
+ this.iframe.style.height = '100%';
38
+ this.iframe.style.zIndex = '2';
39
+
40
+ super.init();
41
+ }
42
+
43
+ prepareContainer() {
44
+ this.clear();
45
+ this._iframeDisposer = new Disposable();
46
+ this._ready = new Promise<void>((resolve) => {
47
+ // tslint:disable-next-line: no-unused-variable
48
+ const disposer = this._onWebviewMessage('webview-ready', () => {
49
+ if (this._isReady) {
50
+ // 这种情况一般是由于iframe在dom中的位置变动导致了重载。
51
+ // 此时我们需要重新初始化
52
+ this.initEvents();
53
+ this.doUpdateContent();
54
+ }
55
+ this._isReady = true;
56
+ resolve();
57
+ });
58
+ });
59
+ this._needReload = false;
60
+ }
61
+
62
+ getDomNode() {
63
+ return this.iframe;
64
+ }
65
+
66
+ protected _sendToWebview(channel: string, data: any) {
67
+ if (!this._isListening) {
68
+ return;
69
+ }
70
+ this._ready
71
+ .then(() => {
72
+ if (!this.iframe) {
73
+ return;
74
+ }
75
+ this.iframe.contentWindow!.postMessage(
76
+ {
77
+ channel,
78
+ data,
79
+ },
80
+ '*',
81
+ );
82
+ })
83
+ .catch((err) => {
84
+ getDebugLogger().error(err);
85
+ });
86
+ }
87
+
88
+ protected _onWebviewMessage(channel: string, listener: (data: any) => any): IDisposable {
89
+ return this._iframeDisposer!.addDispose(
90
+ new DomListener(window, 'message', (e) => {
91
+ if (e.data && e.data.target === this.id && e.data.channel === channel) {
92
+ if (!this._isListening) {
93
+ return;
94
+ }
95
+ listener(e.data.data);
96
+ }
97
+ }),
98
+ );
99
+ }
100
+
101
+ appendTo(container: HTMLElement) {
102
+ if (this.iframe) {
103
+ if (container.style.position === 'static' || !container.style.position) {
104
+ container.style.position = 'relative';
105
+ }
106
+ container.innerHTML = '';
107
+ container.appendChild(this.iframe);
108
+ if (this._needReload) {
109
+ this.init();
110
+ this.doUpdateContent();
111
+ }
112
+ }
113
+ }
114
+
115
+ remove() {
116
+ if (this.iframe) {
117
+ this.iframe.remove();
118
+ this._onRemove.fire();
119
+ // remove 只是视图被销毁,但是html,state等内容保留,因此这里之前是改坏了
120
+ // this.dispose();
121
+ this.clear();
122
+ this._needReload = true;
123
+ }
124
+ }
125
+
126
+ clear() {
127
+ if (this._iframeDisposer) {
128
+ this._iframeDisposer.dispose();
129
+ this._iframeDisposer = null;
130
+ }
131
+ this._isReady = false;
132
+ }
133
+
134
+ dispose() {
135
+ super.dispose();
136
+ if (this._iframeDisposer) {
137
+ this._iframeDisposer.dispose();
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,19 @@
1
+ import { Provider, Injectable } from '@opensumi/di';
2
+ import { BrowserModule } from '@opensumi/ide-core-browser';
3
+
4
+ import { WebviewModuleContribution } from './contribution';
5
+ import { IWebviewService } from './types';
6
+ import { WebviewServiceImpl } from './webview.service';
7
+ export * from './types';
8
+ export { PlainWebview } from './editor-webview';
9
+
10
+ @Injectable()
11
+ export class WebviewModule extends BrowserModule {
12
+ providers: Provider[] = [
13
+ {
14
+ token: IWebviewService,
15
+ useClass: WebviewServiceImpl,
16
+ },
17
+ WebviewModuleContribution,
18
+ ];
19
+ }
@@ -0,0 +1,284 @@
1
+ import { Disposable, DomListener, electronEnv, Emitter, Deferred, Event } from '@opensumi/ide-core-browser';
2
+
3
+ import { IPlainWebview } from './types';
4
+
5
+ export class IframePlainWebview extends Disposable implements IPlainWebview {
6
+ private _url: string | undefined;
7
+
8
+ private _iframe: HTMLIFrameElement | null;
9
+
10
+ private wrapper: HTMLIFrameElement | null;
11
+
12
+ _onMessage = new Emitter<any>();
13
+ onMessage = this._onMessage.event;
14
+
15
+ _onRemove: Emitter<void> = new Emitter<void>();
16
+ onRemove: Event<void> = this._onRemove.event;
17
+
18
+ _onLoadURL: Emitter<string> = new Emitter<string>();
19
+ onLoadURL: Event<string> = this._onLoadURL.event;
20
+
21
+ private _ready = new Deferred<void>();
22
+
23
+ get ready() {
24
+ return this._ready.promise;
25
+ }
26
+
27
+ constructor() {
28
+ super();
29
+ /**
30
+ * Here we create a `outer iframe`, and later we will create an `inner iframe` and append it into outer's child.
31
+ *
32
+ * So we can capture message in the `outer iframe` instead of in the `main renderer`.
33
+ * +---------------------------------------------------------+
34
+ * | Main renderer |
35
+ * | +---------------------------------------------------+ |
36
+ * | | Outer iframe ^ | |
37
+ * | | | capture message here | |
38
+ * | | +-----------------+---------------------------+ | |
39
+ * | | | Inner iframe | | | |
40
+ * | | | postMessage | | |
41
+ * | | | | | |
42
+ * | | +---------------------------------------------+ | |
43
+ * | +---------------------------------------------------+ |
44
+ * +---------------------------------------------------------+
45
+ */
46
+ this.wrapper = document.createElement('iframe');
47
+ this.wrapper.setAttribute('src', 'javascript:""');
48
+ this.wrapper.style.width = '100%';
49
+ this.wrapper.style.height = '100%';
50
+ this.wrapper.style.display = 'block';
51
+ this.wrapper.style.position = 'absolute';
52
+ this.wrapper.style.border = 'none';
53
+ this.wrapper.style.zIndex = '2';
54
+ const disposer = this.addDispose(
55
+ new DomListener(this.wrapper, 'load', () => {
56
+ this.addDispose(
57
+ new DomListener(this.wrapper!.contentWindow!, 'message', (e) => {
58
+ this._onMessage.fire(e.data);
59
+ }),
60
+ );
61
+ this.wrapper!.contentDocument!.body.style.margin = '0';
62
+ this._ready.resolve();
63
+ disposer.dispose();
64
+ }),
65
+ );
66
+
67
+ this.addDispose(this._onMessage);
68
+ }
69
+
70
+ get url() {
71
+ return this._url;
72
+ }
73
+
74
+ setPartition(value: string): void {
75
+ // noop
76
+ }
77
+
78
+ async loadURL(url: string): Promise<void> {
79
+ if (!this.wrapper) {
80
+ return;
81
+ }
82
+ await this.ready;
83
+ if (!this.wrapper) {
84
+ return;
85
+ }
86
+ this._url = url;
87
+ if (!this._iframe) {
88
+ this._iframe = document.createElement('iframe');
89
+ this._iframe.style.width = '100%';
90
+ this._iframe.style.height = '100%';
91
+ this._iframe.style.display = 'block';
92
+ this._iframe.style.border = 'none';
93
+ this.wrapper!.contentWindow!.document.body.appendChild(this._iframe);
94
+ }
95
+ this._iframe.setAttribute('src', url);
96
+ this._onLoadURL.fire(url);
97
+ return new Promise<void>((resolve) => {
98
+ this.addDispose(
99
+ new DomListener(this._iframe!, 'load', () => {
100
+ resolve();
101
+ }),
102
+ );
103
+ });
104
+ }
105
+
106
+ getDomNode() {
107
+ return this.wrapper;
108
+ }
109
+
110
+ appendTo(container: HTMLElement): void {
111
+ if (this.wrapper) {
112
+ if (this.wrapper.parentElement) {
113
+ this.wrapper.remove();
114
+ }
115
+ }
116
+ container.innerHTML = '';
117
+ container.appendChild(this.wrapper!);
118
+ if (this._url) {
119
+ this.loadURL(this._url);
120
+ }
121
+ }
122
+
123
+ dispose() {
124
+ super.dispose();
125
+ if (this.wrapper) {
126
+ this.wrapper!.remove();
127
+ this.wrapper = null;
128
+ }
129
+ if (this._iframe) {
130
+ this._iframe!.remove();
131
+ this._iframe = null;
132
+ }
133
+ }
134
+
135
+ postMessage(message: any) {
136
+ if (this._iframe) {
137
+ this._iframe!.contentWindow!.postMessage(message, '*');
138
+ }
139
+ }
140
+
141
+ remove() {
142
+ if (this.wrapper) {
143
+ this.wrapper.remove();
144
+ if (this._iframe) {
145
+ this._iframe.remove();
146
+ this._iframe = null;
147
+ }
148
+ this._onRemove.fire();
149
+ }
150
+ }
151
+ }
152
+
153
+ interface IAllowedWebviewAttributes {
154
+ partition?: string;
155
+ }
156
+
157
+ export class ElectronPlainWebview extends Disposable implements IPlainWebview {
158
+ private _url: string | undefined;
159
+
160
+ private webview: Electron.WebviewTag | null;
161
+
162
+ private wrapper: HTMLDivElement | null;
163
+
164
+ private webviewDomReady: Deferred<void> = new Deferred();
165
+
166
+ _onMessage = new Emitter<any>();
167
+ onMessage = this._onMessage.event;
168
+
169
+ _onRemove: Emitter<void> = new Emitter<void>();
170
+ onRemove: Event<void> = this._onRemove.event;
171
+
172
+ _onLoadURL: Emitter<string> = new Emitter<string>();
173
+ onLoadURL: Event<string> = this._onLoadURL.event;
174
+
175
+ constructor() {
176
+ super();
177
+ this.wrapper = document.createElement('div');
178
+ this.wrapper.style.width = '100%';
179
+ this.wrapper.style.height = '100%';
180
+ this.wrapper.style.display = 'block';
181
+ this.wrapper.style.position = 'absolute';
182
+ this.wrapper.style.border = 'none';
183
+ this.wrapper.style.zIndex = '2';
184
+ this.addDispose(this._onMessage);
185
+ }
186
+
187
+ get url() {
188
+ return this._url;
189
+ }
190
+
191
+ getDomNode() {
192
+ return this.wrapper;
193
+ }
194
+
195
+ getWebviewElement() {
196
+ return this.webview;
197
+ }
198
+
199
+ extendAttributes: IAllowedWebviewAttributes = {};
200
+
201
+ setPartition(value: string): void {
202
+ this.extendAttributes.partition = value;
203
+ }
204
+
205
+ async loadURL(url: string): Promise<void> {
206
+ if (!this.wrapper) {
207
+ return;
208
+ }
209
+ this._url = url;
210
+ if (!this.webview) {
211
+ this.webview = document.createElement('webview');
212
+ this.webview.style.width = '100%';
213
+ this.webview.style.height = '100%';
214
+ this.webview.style.border = 'none';
215
+ this.webview.style.zIndex = '2';
216
+ this.webview.src = url;
217
+ this.webview.preload = electronEnv.plainWebviewPreload;
218
+ if (this.extendAttributes.partition) {
219
+ this.webview.partition = this.extendAttributes.partition;
220
+ }
221
+
222
+ this.wrapper!.appendChild(this.webview);
223
+ this.webview.addEventListener('ipc-message', (event) => {
224
+ if (event.channel === 'webview-message') {
225
+ this._onMessage.fire(event.args[0]);
226
+ }
227
+ });
228
+ this.addDispose(
229
+ new DomListener(this.webview!, 'dom-ready', () => {
230
+ this.webviewDomReady.resolve();
231
+ }),
232
+ );
233
+ this.addDispose(
234
+ new DomListener(this.webview!, 'destroyed', () => {
235
+ this.webviewDomReady = new Deferred();
236
+ }),
237
+ );
238
+ }
239
+ this._url = url;
240
+ if (document.body.contains(this.wrapper)) {
241
+ return this.doLoadURL();
242
+ }
243
+ }
244
+
245
+ private async doLoadURL(): Promise<void> {
246
+ return new Promise<void>(async (resolve) => {
247
+ this.webview!.src = this.url!;
248
+ const disposer = this.addDispose(
249
+ new DomListener(this.webview!, 'did-finish-load', () => {
250
+ disposer.dispose();
251
+ this._onLoadURL.fire(this.url!);
252
+ resolve();
253
+ }),
254
+ );
255
+ });
256
+ }
257
+
258
+ appendTo(container: HTMLElement): void {
259
+ container.appendChild(this.wrapper!);
260
+ if (this._url) {
261
+ this.doLoadURL();
262
+ }
263
+ }
264
+
265
+ dispose() {
266
+ this.wrapper!.remove();
267
+ this.wrapper = null;
268
+ this.webview!.remove();
269
+ this.webview = null;
270
+ }
271
+
272
+ postMessage(message: any) {
273
+ if (this.webview) {
274
+ this.webview!.send('webview-message', message);
275
+ }
276
+ }
277
+
278
+ remove() {
279
+ if (this.wrapper) {
280
+ this.wrapper.remove();
281
+ this._onRemove.fire();
282
+ }
283
+ }
284
+ }