@opensumi/ide-webview 2.21.13 → 2.22.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/lib/browser/abstract-webview.js +3 -3
- package/lib/browser/abstract-webview.js.map +1 -1
- package/lib/browser/contribution.js.map +1 -1
- package/lib/browser/electron-webview-webview.js.map +1 -1
- package/lib/browser/iframe-webview.js.map +1 -1
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/plain-webview.js +3 -3
- package/lib/browser/plain-webview.js.map +1 -1
- package/lib/browser/webview-window.js.map +1 -1
- package/lib/browser/webview.service.d.ts +1 -1
- package/lib/browser/webview.service.d.ts.map +1 -1
- package/lib/browser/webview.service.js +38 -38
- package/lib/browser/webview.service.js.map +1 -1
- package/lib/electron-main/index.js.map +1 -1
- package/package.json +13 -12
- package/src/browser/abstract-webview.ts +231 -0
- package/src/browser/contribution.ts +80 -0
- package/src/browser/editor-webview.tsx +207 -0
- package/src/browser/electron-webview-webview.ts +156 -0
- package/src/browser/iframe-webview.ts +140 -0
- package/src/browser/index.ts +19 -0
- package/src/browser/plain-webview.ts +284 -0
- package/src/browser/types.ts +274 -0
- package/src/browser/webview-window.ts +130 -0
- package/src/browser/webview.service.ts +484 -0
- package/src/common/index.ts +1 -0
- package/src/electron-main/index.ts +31 -0
- package/src/electron-webview/host-channel.ts +64 -0
- package/src/electron-webview/host-preload.js +2 -0
- package/src/electron-webview/plain-preload.js +55 -0
- package/src/index.ts +1 -0
- package/src/webview-host/common.ts +108 -0
- package/src/webview-host/web-preload.ts +78 -0
- package/src/webview-host/webview-manager.ts +368 -0
- package/src/webview-host/webview.html +11 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Autowired, Injectable } from '@opensumi/di';
|
|
2
|
+
import {
|
|
3
|
+
Event,
|
|
4
|
+
URI,
|
|
5
|
+
Disposable,
|
|
6
|
+
IDisposable,
|
|
7
|
+
Emitter,
|
|
8
|
+
IEventBus,
|
|
9
|
+
MaybeNull,
|
|
10
|
+
AppConfig,
|
|
11
|
+
} from '@opensumi/ide-core-browser';
|
|
12
|
+
import { StaticResourceService } from '@opensumi/ide-static-resource/lib/browser';
|
|
13
|
+
import { ITheme, IThemeService } from '@opensumi/ide-theme';
|
|
14
|
+
import { ThemeChangedEvent } from '@opensumi/ide-theme/lib/common/event';
|
|
15
|
+
|
|
16
|
+
import { IWebview, IWebviewContentOptions, IWebviewContentScrollPosition, IWebviewService } from './types';
|
|
17
|
+
|
|
18
|
+
@Injectable({ multiple: true })
|
|
19
|
+
export abstract class AbstractWebviewPanel extends Disposable implements IWebview {
|
|
20
|
+
protected _html = '';
|
|
21
|
+
|
|
22
|
+
protected _options: IWebviewContentOptions;
|
|
23
|
+
|
|
24
|
+
initialScrollProgress: number;
|
|
25
|
+
|
|
26
|
+
state: any;
|
|
27
|
+
|
|
28
|
+
_onDidFocus: Emitter<void> = new Emitter<void>();
|
|
29
|
+
onDidFocus: Event<void> = this._onDidFocus.event;
|
|
30
|
+
|
|
31
|
+
_onDidBlur: Emitter<void> = new Emitter<void>();
|
|
32
|
+
onDidBlur: Event<void> = this._onDidFocus.event;
|
|
33
|
+
|
|
34
|
+
_onDidClickLink: Emitter<URI> = new Emitter<URI>();
|
|
35
|
+
onDidClickLink: Event<URI> = this._onDidClickLink.event;
|
|
36
|
+
|
|
37
|
+
_onDidScroll: Emitter<IWebviewContentScrollPosition> = new Emitter<IWebviewContentScrollPosition>();
|
|
38
|
+
onDidScroll: Event<IWebviewContentScrollPosition> = this._onDidScroll.event;
|
|
39
|
+
|
|
40
|
+
_onDidUpdateState: Emitter<any> = new Emitter<any>();
|
|
41
|
+
onDidUpdateState: Event<any> = this._onDidUpdateState.event;
|
|
42
|
+
|
|
43
|
+
_onRemove: Emitter<void> = new Emitter<void>();
|
|
44
|
+
onRemove: Event<void> = this._onRemove.event;
|
|
45
|
+
|
|
46
|
+
protected _isListening = true;
|
|
47
|
+
|
|
48
|
+
private _focused = false;
|
|
49
|
+
|
|
50
|
+
protected _ready: Promise<void>;
|
|
51
|
+
|
|
52
|
+
@Autowired(IWebviewService)
|
|
53
|
+
webviewService: IWebviewService;
|
|
54
|
+
|
|
55
|
+
@Autowired(IThemeService)
|
|
56
|
+
themeService: IThemeService;
|
|
57
|
+
|
|
58
|
+
@Autowired(IEventBus)
|
|
59
|
+
eventBus: IEventBus;
|
|
60
|
+
|
|
61
|
+
@Autowired(AppConfig)
|
|
62
|
+
private readonly appConfig: AppConfig;
|
|
63
|
+
|
|
64
|
+
@Autowired(StaticResourceService)
|
|
65
|
+
staticResourceService: StaticResourceService;
|
|
66
|
+
|
|
67
|
+
protected _keybindingDomTarget: HTMLElement | undefined = undefined;
|
|
68
|
+
|
|
69
|
+
public setKeybindingDomTarget(target) {
|
|
70
|
+
this._keybindingDomTarget = target;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
constructor(public readonly id: string, options: IWebviewContentOptions = {}) {
|
|
74
|
+
super();
|
|
75
|
+
this._options = options;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
init() {
|
|
79
|
+
this.prepareContainer();
|
|
80
|
+
|
|
81
|
+
this.initEvents();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async postMessage(message: any): Promise<void> {
|
|
85
|
+
return this._sendToWebview('message', message);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public get options() {
|
|
89
|
+
return this._options;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onMessage(listener: (message: any) => any): IDisposable {
|
|
93
|
+
return this._onWebviewMessage('onmessage', listener);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
protected initEvents() {
|
|
97
|
+
this._onWebviewMessage('did-click-link', (data) => {
|
|
98
|
+
this._onDidClickLink.fire(new URI(data));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this._onWebviewMessage('did-scroll', (data) => {
|
|
102
|
+
this._onDidScroll.fire(data);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
this._onWebviewMessage('do-reload', () => {
|
|
106
|
+
this.doUpdateContent();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this._onWebviewMessage('load-resource', () => {
|
|
110
|
+
// TODO: 资源相关
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this._onWebviewMessage('load-localhost', () => {
|
|
114
|
+
// TODO: 好像是消息转发
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this._onWebviewMessage('did-focus', () => {
|
|
118
|
+
this.handleFocusChange(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this._onWebviewMessage('did-blur', () => {
|
|
122
|
+
this.handleFocusChange(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this._onWebviewMessage('do-update-state', (state) => {
|
|
126
|
+
this.state = state;
|
|
127
|
+
this._onDidUpdateState.fire(state);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this._onWebviewMessage('did-keydown', (event) => {
|
|
131
|
+
// Create a fake KeyboardEvent from the data provided
|
|
132
|
+
const emulatedKeyboardEvent = new KeyboardEvent('keydown', event);
|
|
133
|
+
// Force override the target
|
|
134
|
+
Object.defineProperty(emulatedKeyboardEvent, 'target', {
|
|
135
|
+
get: () => this._keybindingDomTarget || this.getDomNode(),
|
|
136
|
+
});
|
|
137
|
+
// And re-dispatch
|
|
138
|
+
window.dispatchEvent(emulatedKeyboardEvent);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
this.updateStyle();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected updateStyle() {
|
|
145
|
+
this.style(this.themeService.getCurrentThemeSync());
|
|
146
|
+
this.addDispose(
|
|
147
|
+
this.eventBus.on(ThemeChangedEvent, (e) => {
|
|
148
|
+
this.style(e.payload.theme);
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getContent(): string {
|
|
154
|
+
return this._html;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async setContent(html: string): Promise<void> {
|
|
158
|
+
this._html = html;
|
|
159
|
+
await this.doUpdateContent();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected preprocessHtml(html: string): string {
|
|
163
|
+
if (this.appConfig.isElectronRenderer) {
|
|
164
|
+
// 将vscode-resource:/User/xxx 转换为 vscode-resource:///User/xxx
|
|
165
|
+
return html.replace(
|
|
166
|
+
/(["'])vscode-resource:(\/\/|)([^\s'"]+?)(["'])/gi,
|
|
167
|
+
(_, startQuote, slash, path, endQuote) => `${startQuote}vscode-resource://${path}${endQuote}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return html.replace(
|
|
171
|
+
/(["'])vscode-resource:([^\s'"]+?)(["'])/gi,
|
|
172
|
+
(_, startQuote, path, endQuote) =>
|
|
173
|
+
`${startQuote}${this.staticResourceService.resolveStaticResource(URI.file(path))}${endQuote}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
protected doUpdateContent() {
|
|
178
|
+
return this._sendToWebview('content', {
|
|
179
|
+
contents: this.preprocessHtml(this._html),
|
|
180
|
+
options: this._options,
|
|
181
|
+
state: this.state,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public abstract appendTo(container: HTMLElement);
|
|
186
|
+
|
|
187
|
+
protected abstract _sendToWebview(channel: string, data: any);
|
|
188
|
+
|
|
189
|
+
protected abstract _onWebviewMessage(channel: string, listener: (data: any) => any): IDisposable;
|
|
190
|
+
|
|
191
|
+
updateOptions(options: IWebviewContentOptions): void {
|
|
192
|
+
this._options = Object.assign(this._options, options);
|
|
193
|
+
this.doUpdateContent();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
layout(): void {
|
|
197
|
+
throw new Error('Method not implemented.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
focus(): void {
|
|
201
|
+
throw new Error('Method not implemented.');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
reload(): void {
|
|
205
|
+
throw new Error('Method not implemented.');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected handleFocusChange(isFocused: boolean): void {
|
|
209
|
+
this._focused = isFocused;
|
|
210
|
+
if (this._focused) {
|
|
211
|
+
this._onDidFocus.fire();
|
|
212
|
+
} else {
|
|
213
|
+
this._onDidBlur.fire();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private style(theme: ITheme): void {
|
|
218
|
+
const { styles, activeTheme } = this.webviewService.getWebviewThemeData(theme);
|
|
219
|
+
this._sendToWebview('styles', { styles, activeTheme });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
setListenMessages(listening: boolean): void {
|
|
223
|
+
this._isListening = listening;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
abstract prepareContainer(): any;
|
|
227
|
+
|
|
228
|
+
abstract getDomNode(): MaybeNull<HTMLElement>;
|
|
229
|
+
|
|
230
|
+
abstract remove(): void;
|
|
231
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* istanbul ignore file */
|
|
2
|
+
import { Autowired } from '@opensumi/di';
|
|
3
|
+
import { Domain, URI, CommandContribution, CommandRegistry, AppConfig } from '@opensumi/ide-core-browser';
|
|
4
|
+
import { localize } from '@opensumi/ide-core-common';
|
|
5
|
+
import { ResourceService, IResource } from '@opensumi/ide-editor';
|
|
6
|
+
import { BrowserEditorContribution, EditorComponentRegistry } from '@opensumi/ide-editor/lib/browser';
|
|
7
|
+
|
|
8
|
+
import { EDITOR_WEBVIEW_SCHEME, IWebviewService, IEditorWebviewMetaData, isWebview } from './types';
|
|
9
|
+
import { WebviewServiceImpl } from './webview.service';
|
|
10
|
+
|
|
11
|
+
const WEBVIEW_DEVTOOLS_COMMAND = {
|
|
12
|
+
id: 'workbench.action.webview.openDeveloperTools',
|
|
13
|
+
label: localize('openToolsLabel', 'Open Webview Developer Tools'),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
@Domain(BrowserEditorContribution, CommandContribution)
|
|
17
|
+
export class WebviewModuleContribution implements BrowserEditorContribution, CommandContribution {
|
|
18
|
+
@Autowired(IWebviewService)
|
|
19
|
+
webviewService: WebviewServiceImpl;
|
|
20
|
+
|
|
21
|
+
@Autowired(EditorComponentRegistry)
|
|
22
|
+
editorComponentRegistry: EditorComponentRegistry;
|
|
23
|
+
|
|
24
|
+
@Autowired(AppConfig)
|
|
25
|
+
private readonly appConfig: AppConfig;
|
|
26
|
+
|
|
27
|
+
registerResource(resourceService: ResourceService) {
|
|
28
|
+
resourceService.registerResourceProvider({
|
|
29
|
+
scheme: EDITOR_WEBVIEW_SCHEME,
|
|
30
|
+
provideResource: async (uri: URI): Promise<IResource<IEditorWebviewMetaData>> => {
|
|
31
|
+
const existingComponent = this.webviewService.editorWebviewComponents.get(uri.path.toString());
|
|
32
|
+
if (existingComponent) {
|
|
33
|
+
return existingComponent.resource;
|
|
34
|
+
} else {
|
|
35
|
+
// try revive, 如果无法恢复,会抛错
|
|
36
|
+
await this.webviewService.tryRestoredWebviewComponent(uri.path.toString());
|
|
37
|
+
return this.webviewService.editorWebviewComponents.get(uri.path.toString())!.resource;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
shouldCloseResource: (resource: IResource<IEditorWebviewMetaData>, openedResources: IResource[][]) => {
|
|
41
|
+
let count = 0;
|
|
42
|
+
for (const resources of openedResources) {
|
|
43
|
+
for (const r of resources) {
|
|
44
|
+
if (r.uri.scheme === EDITOR_WEBVIEW_SCHEME && r.uri.toString() === resource.uri.toString()) {
|
|
45
|
+
count++;
|
|
46
|
+
}
|
|
47
|
+
if (count > 1) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const component = this.webviewService.editorWebviewComponents.get(resource.uri.path.toString());
|
|
53
|
+
if (component?.webview && isWebview(component.webview)) {
|
|
54
|
+
// 只对类 vscode webview 进行 dispose,
|
|
55
|
+
// loadUrl 的 plainWebview 必须手动 dispose
|
|
56
|
+
this.webviewService.editorWebviewComponents.get(resource.uri.path.toString())!.clear();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
registerCommands(commandRegistry: CommandRegistry) {
|
|
65
|
+
commandRegistry.registerCommand(WEBVIEW_DEVTOOLS_COMMAND, {
|
|
66
|
+
execute: () => {
|
|
67
|
+
const elements = document.querySelectorAll<Electron.WebviewTag>('webview');
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
69
|
+
for (let i = 0; i < elements.length; i += 1) {
|
|
70
|
+
try {
|
|
71
|
+
elements[i].openDevTools();
|
|
72
|
+
} catch (e) {
|
|
73
|
+
// noop
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
isEnabled: () => this.appConfig.isElectronRenderer,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Disposable, DomListener, useInjectable } from '@opensumi/ide-core-browser';
|
|
4
|
+
import { ReactEditorComponent } from '@opensumi/ide-editor/lib/browser';
|
|
5
|
+
|
|
6
|
+
import { IWebview, IPlainWebview, IEditorWebviewMetaData, IWebviewService, isWebview } from './types';
|
|
7
|
+
import { WebviewServiceImpl } from './webview.service';
|
|
8
|
+
|
|
9
|
+
declare const ResizeObserver: any;
|
|
10
|
+
declare const MutationObserver: any;
|
|
11
|
+
|
|
12
|
+
export const EditorWebviewComponentView: ReactEditorComponent<IEditorWebviewMetaData> = ({ resource }) => {
|
|
13
|
+
const webviewService = useInjectable(IWebviewService) as WebviewServiceImpl;
|
|
14
|
+
const webview = webviewService.editorWebviewComponents.get(resource.metadata!.id)?.webview;
|
|
15
|
+
let container: HTMLDivElement | null = null;
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (webview && container) {
|
|
19
|
+
const mounter = new WebviewMounter(
|
|
20
|
+
webview,
|
|
21
|
+
container,
|
|
22
|
+
document.getElementById('workbench-editor')!,
|
|
23
|
+
document.getElementById('workbench-editor')!,
|
|
24
|
+
);
|
|
25
|
+
webview.onRemove(() => {
|
|
26
|
+
mounter.dispose();
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
webview.remove();
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
style={{ height: '100%', width: '100%', position: 'relative' }}
|
|
37
|
+
className='editor-webview-webview-component'
|
|
38
|
+
ref={(el) => (container = el)}
|
|
39
|
+
></div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 同一个ID创建的webview会保存在内存以便重复使用,不要使用这个组件进行大量不同webview的创建
|
|
45
|
+
*/
|
|
46
|
+
export const PlainWebview: React.ComponentType<{ id: string; renderRoot?: HTMLElement; appendToChild?: boolean }> = ({
|
|
47
|
+
id,
|
|
48
|
+
renderRoot = document.body,
|
|
49
|
+
appendToChild,
|
|
50
|
+
}) => {
|
|
51
|
+
let container: HTMLDivElement | null = null;
|
|
52
|
+
const webviewService = useInjectable(IWebviewService) as IWebviewService;
|
|
53
|
+
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const component = webviewService.getOrCreatePlainWebviewComponent(id);
|
|
56
|
+
if (component && container) {
|
|
57
|
+
if (appendToChild) {
|
|
58
|
+
component.webview.appendTo(container);
|
|
59
|
+
} else {
|
|
60
|
+
const mounter = new WebviewMounter(
|
|
61
|
+
component.webview,
|
|
62
|
+
container,
|
|
63
|
+
document.getElementById('workbench-editor')!,
|
|
64
|
+
renderRoot,
|
|
65
|
+
);
|
|
66
|
+
component.webview.onRemove(() => {
|
|
67
|
+
mounter.dispose();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
component.webview.remove();
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
return <div style={{ height: '100%', width: '100%', position: 'relative' }} ref={(el) => (container = el)}></div>;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// 将iframe挂载在一个固定的位置,以overlay的形式覆盖在container中,
|
|
81
|
+
// 防止它在DOM树改变时被重载
|
|
82
|
+
export class WebviewMounter extends Disposable {
|
|
83
|
+
private mounting: number;
|
|
84
|
+
|
|
85
|
+
private _container: HTMLElement | null;
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
private webview: IWebview | IPlainWebview,
|
|
89
|
+
private container: HTMLElement,
|
|
90
|
+
mutationRoot: HTMLElement,
|
|
91
|
+
private renderRoot: HTMLElement = document.body,
|
|
92
|
+
) {
|
|
93
|
+
super();
|
|
94
|
+
if (!this.webview.getDomNode()) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.webview.appendTo(this.getWebviewRealContainer());
|
|
98
|
+
if (isWebview(this.webview)) {
|
|
99
|
+
this.webview.setKeybindingDomTarget(container);
|
|
100
|
+
}
|
|
101
|
+
const resizeObserver = new ResizeObserver(this.doMount.bind(this));
|
|
102
|
+
const mutationObserver = new MutationObserver((mutations) => {
|
|
103
|
+
const ancestors: Set<HTMLElement> = new Set();
|
|
104
|
+
let ancestor: HTMLElement | null = this.container;
|
|
105
|
+
while (ancestor && ancestor !== mutationRoot) {
|
|
106
|
+
ancestors.add(ancestor);
|
|
107
|
+
ancestor = ancestor.parentElement;
|
|
108
|
+
}
|
|
109
|
+
for (const { addedNodes, removedNodes } of mutations) {
|
|
110
|
+
for (const node of addedNodes) {
|
|
111
|
+
if (ancestors.has(node)) {
|
|
112
|
+
this.doMount();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const node of removedNodes) {
|
|
117
|
+
if (ancestors.has(node)) {
|
|
118
|
+
this.doMount();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
resizeObserver.observe(container);
|
|
125
|
+
mutationObserver.observe(mutationRoot, { childList: true, subtree: true });
|
|
126
|
+
|
|
127
|
+
this.doMount();
|
|
128
|
+
|
|
129
|
+
this.addDispose({
|
|
130
|
+
dispose: () => {
|
|
131
|
+
if (this._container) {
|
|
132
|
+
this._container.remove();
|
|
133
|
+
this._container = null;
|
|
134
|
+
this.webview = null as any;
|
|
135
|
+
this.container = null as any;
|
|
136
|
+
}
|
|
137
|
+
resizeObserver.disconnect();
|
|
138
|
+
mutationObserver.disconnect();
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.addDispose(
|
|
143
|
+
new DomListener(window, 'resize', () => {
|
|
144
|
+
this.doMount();
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// 监听滚动
|
|
149
|
+
let parent = container.parentElement;
|
|
150
|
+
while (parent) {
|
|
151
|
+
this.addDispose(
|
|
152
|
+
new DomListener(parent, 'scroll', () => {
|
|
153
|
+
this.doMount();
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
parent = parent.parentElement;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
doMount() {
|
|
161
|
+
if (this.mounting) {
|
|
162
|
+
window.cancelAnimationFrame(this.mounting);
|
|
163
|
+
}
|
|
164
|
+
this.mounting = window.requestAnimationFrame(() => {
|
|
165
|
+
if (!this.webview.getDomNode()) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const rect = this.container.getBoundingClientRect();
|
|
169
|
+
if (rect.height === 0 || rect.width === 0) {
|
|
170
|
+
this.webview.getDomNode()!.style.display = 'none';
|
|
171
|
+
if (isWebview(this.webview) && !this.webview.options.longLive) {
|
|
172
|
+
this.webview.setListenMessages(false);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
this.webview.getDomNode()!.style.display = '';
|
|
176
|
+
if (isWebview(this.webview)) {
|
|
177
|
+
this.webview.setListenMessages(true);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const renderRootRects = this.renderRoot.getBoundingClientRect();
|
|
181
|
+
this.webview.getDomNode()!.style.top = rect.top - renderRootRects.top + 'px';
|
|
182
|
+
this.webview.getDomNode()!.style.left = rect.left - renderRootRects.left + 'px';
|
|
183
|
+
this.webview.getDomNode()!.style.height = rect.height + 'px';
|
|
184
|
+
this.webview.getDomNode()!.style.width = rect.width + 'px';
|
|
185
|
+
this.mounting = 0;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getWebviewRealContainer() {
|
|
190
|
+
if (this._container) {
|
|
191
|
+
return this._container;
|
|
192
|
+
}
|
|
193
|
+
let mountContainer = this.renderRoot.querySelector(':scope > div[data-webview-container=true]');
|
|
194
|
+
if (!mountContainer) {
|
|
195
|
+
const container = document.createElement('div');
|
|
196
|
+
container.style.zIndex = '2';
|
|
197
|
+
container.style.position = 'absolute';
|
|
198
|
+
container.setAttribute('data-webview-container', 'true');
|
|
199
|
+
container.style.top = '0';
|
|
200
|
+
this.renderRoot.appendChild(container);
|
|
201
|
+
mountContainer = container;
|
|
202
|
+
}
|
|
203
|
+
this._container = document.createElement('div');
|
|
204
|
+
mountContainer!.appendChild(this._container);
|
|
205
|
+
return this._container!;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -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>`;
|