Skip to content

Commit c36d7bc

Browse files
author
winjo
committed
fix: clear side effect when destroy
1 parent 0c45a53 commit c36d7bc

5 files changed

Lines changed: 202 additions & 23 deletions

File tree

packages/alex/src/api/createApp.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { IEditorDocumentModelService } from '@ali/ide-editor/lib/browser';
1616
import { EditorDocumentModelServiceImpl } from '@ali/ide-editor/lib/browser/doc-model/editor-document-model-service';
1717
import { EditorDocumentModel } from '@ali/ide-editor/lib/browser/doc-model/editor-document-model';
1818
import { FileTreeModelService } from '@ali/ide-file-tree-next/lib/browser/services/file-tree-model.service';
19+
import { WorkerExtensionService } from '@ali/ide-kaitian-extension/lib/browser/extension.worker.service';
1920
import * as os from 'os';
2021

2122
import { modules } from '../core/modules';
@@ -101,6 +102,11 @@ export function createApp({ appConfig, runtimeConfig }: IConfig): IAppInstance {
101102
app.injector.get(FileTreeModelService).handleTreeBlur();
102103
};
103104

105+
/**
106+
* 目前整个应用有太多的副作用,尤其是注册到 monaco 的事件,如 DocumentSymbolProviderRegistry.onChange
107+
* 在 monaco 上的事件无法注销,除非重新全局实例化一个 monaco,目前 kaitian 并未暴露,暂时不可行
108+
* 因此这里的 destroy 仍然可能有不少副作用无法清除,暂时清理已知的,避免报错
109+
*/
104110
let destroyed = false;
105111
app.destroy = () => {
106112
if (destroyed) {
@@ -119,6 +125,12 @@ export function createApp({ appConfig, runtimeConfig }: IConfig): IAppInstance {
119125
if (codeEditorService) {
120126
codeEditorService._value = null;
121127
}
128+
(monaco as any).services.StaticServices.modeService._value = null;
129+
// @ts-ignore
130+
// common-di 通过参数实例化无法自动 dispose
131+
app.injector.get(WorkerExtensionService).protocol._locals.forEach((instance) => {
132+
instance.dispose?.();
133+
});
122134
app.injector.disposeAll();
123135
};
124136

packages/core/src/client/index.ts

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ import {
44
ClientApp as BasicClientApp,
55
IAppRenderer,
66
IClientAppOpts,
7+
NO_KEYBINDING_NAME,
78
} from '@ali/ide-core-browser';
8-
import { BasicModule } from '@ali/ide-core-common';
9+
import { BasicModule, isOSX, DisposableCollection } from '@ali/ide-core-common';
910
import { WSChannelHandler } from '@ali/ide-connection';
11+
import { WorkerExtensionService } from '@ali/ide-kaitian-extension/lib/browser/extension.worker.service';
12+
import { WorkerExtensionServicePatch } from './patch/extension.worker.service';
13+
import { TextmateService } from '@ali/ide-monaco/lib/browser/textmate.service';
14+
import { TextmateServicePatch } from './patch/textmate.service';
1015

1116
import { FCServiceCenter, ClientPort, initFCService } from '../connection';
1217
import { KaitianExtFsProvider, KtExtFsProviderContribution } from './extension';
@@ -33,7 +38,7 @@ export type ModuleConstructor = ConstructorOf<BrowserModule>;
3338

3439
@Injectable()
3540
export class ClientModule extends BrowserModule {
36-
providers = [
41+
providers: Provider[] = [
3742
KaitianExtFsProvider,
3843
KtExtFsProviderContribution,
3944
TextmateLanguageGrammarContribution,
@@ -46,6 +51,14 @@ export class ClientModule extends BrowserModule {
4651
EditorEmptyContribution,
4752
WelcomeContribution,
4853
MenuConfigContribution,
54+
{
55+
token: WorkerExtensionService,
56+
useClass: WorkerExtensionServicePatch,
57+
},
58+
{
59+
token: TextmateService,
60+
useClass: TextmateServicePatch,
61+
},
4962
];
5063
preferences = injectDebugPreferences;
5164
}
@@ -55,7 +68,7 @@ export interface IAppOpts extends IClientAppOpts, IServerAppOpts {}
5568
export { IClientAppOpts };
5669

5770
export class ClientApp extends BasicClientApp {
58-
private disposed = false;
71+
private disposeCollection = new DisposableCollection();
5972

6073
constructor(opts: IAppOpts) {
6174
super(opts);
@@ -91,25 +104,78 @@ export class ClientApp extends BasicClientApp {
91104
return super.start(container, type);
92105
}
93106

94-
stopContributions() {
95-
if (!this.disposed) {
96-
super.stopContributions();
97-
}
98-
}
107+
/**
108+
* 注册全局事件监听
109+
* TODO: kaitian 中无 removeEventLister 逻辑,这里 override 下,待提 mr
110+
*/
111+
protected registerEventListeners(): void {
112+
const addEventListener = (
113+
target: Window | HTMLElement,
114+
type: string,
115+
listener: (...args: any[]) => any,
116+
...extra: any
117+
) => {
118+
target.addEventListener(type, listener, ...extra);
119+
this.disposeCollection.push({
120+
dispose() {
121+
target.removeEventListener(type, listener, ...extra);
122+
},
123+
});
124+
};
125+
126+
addEventListener(window, 'beforeunload', (event) => {
127+
// 为了避免不必要的弹窗,如果页面并没有发生交互浏览器可能不会展示在 beforeunload 事件中引发的弹框,甚至可能即使发生交互了也直接不显示。
128+
if (this.preventStop()) {
129+
(event || window.event).returnValue = true;
130+
return true;
131+
}
132+
});
133+
addEventListener(window, 'unload', () => {
134+
// 浏览器关闭事件
135+
this.stateService.state = 'closing_window';
136+
this.stopContributions();
137+
});
99138

100-
preventStop() {
101-
if (!this.disposed) {
102-
return super.preventStop();
139+
addEventListener(window, 'resize', () => {
140+
// 浏览器resize事件
141+
});
142+
// 处理中文输入回退时可能出现多个光标问题
143+
// https://github.com/eclipse-theia/theia/pull/6673
144+
let inComposition = false;
145+
addEventListener(window, 'compositionstart', (event) => {
146+
inComposition = true;
147+
});
148+
addEventListener(window, 'compositionend', (event) => {
149+
inComposition = false;
150+
});
151+
addEventListener(
152+
window,
153+
'keydown',
154+
(event: any) => {
155+
if (event && event.target!.name !== NO_KEYBINDING_NAME && !inComposition) {
156+
this.keybindingService.run(event);
157+
}
158+
},
159+
true
160+
);
161+
162+
if (isOSX) {
163+
addEventListener(
164+
document.body,
165+
'wheel',
166+
(event) => {
167+
// 屏蔽在OSX系统浏览器中由于滚动导致的前进后退事件
168+
},
169+
{ passive: false }
170+
);
103171
}
104-
return false;
105172
}
106173

107174
/**
108-
* kaitian 中没有注销注册在 window 上上的事件,在把 IDE 销毁后,此时刷新页面依然会执行事件
109-
* 此时 injector.get 都会报错,因此这里加个状态判断
175+
* kaitian 中没有注销注册在 window 上的事件
110176
*/
111177
dispose() {
112-
this.disposed = true;
178+
this.disposeCollection.dispose();
113179
}
114180
}
115181

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// @ts-nocheck
2+
3+
// TODO:
4+
/**
5+
* kaitian 没有在 dispose terminal worker
6+
* 临时 override,待 PR
7+
*/
8+
9+
import { Injectable } from '@ali/common-di';
10+
import { Deferred, Emitter } from '@ali/ide-core-common';
11+
import { WorkerExtensionService } from '@ali/ide-kaitian-extension/lib/browser/extension.worker.service';
12+
import { IRPCProtocol } from '@ali/ide-connection/lib/common/rpcProtocol';
13+
14+
@Injectable()
15+
export class WorkerExtensionServicePatch extends WorkerExtensionService {
16+
private extendWorkerHost: Worker;
17+
18+
initWorkerProtocol(workerUrl: string): Promise<IRPCProtocol> {
19+
this.logger.log('[Worker Host] init web worker extension host');
20+
21+
return new Promise((resolve, reject) => {
22+
const ready = new Deferred<MessagePort>();
23+
const onMessageEmitter = new Emitter<any>();
24+
try {
25+
const extendWorkerHost = new Worker(workerUrl, { name: 'KaitianWorkerExtensionHost' });
26+
this.extendWorkerHost = extendWorkerHost;
27+
extendWorkerHost.onmessage = (e) => {
28+
if (e.data instanceof MessagePort) {
29+
ready.resolve(e.data);
30+
}
31+
};
32+
33+
ready.promise.then((port) => {
34+
resolve(this.createProtocol(port, onMessageEmitter));
35+
});
36+
} catch (err) {
37+
reject(err);
38+
}
39+
});
40+
}
41+
42+
dispose() {
43+
this.extendWorkerHost.terminate();
44+
}
45+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @ts-nocheck
2+
3+
import { TextmateService } from '@ali/ide-monaco/lib/browser/textmate.service';
4+
import { IOnigLib } from 'vscode-textmate';
5+
import { OnigScanner, OnigString } from 'vscode-oniguruma';
6+
7+
let onigLoaded = false;
8+
9+
class OnigasmLib implements IOnigLib {
10+
createOnigScanner(source: string[]) {
11+
return new OnigScanner(source);
12+
}
13+
createOnigString(source: string) {
14+
return new OnigString(source);
15+
}
16+
}
17+
18+
export class TextmateServicePatch extends TextmateService {
19+
getOnigLib() {
20+
if (onigLoaded) {
21+
return new OnigasmLib();
22+
}
23+
onigLoaded = true;
24+
return super.getOnigLib();
25+
}
26+
}

packages/core/src/client/textmate-language-grammar/language-grammar.service.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ import { Disposable, Deferred, Emitter, Event, URI } from '@ali/ide-core-common'
33
import { TextmateService } from '@ali/ide-monaco/lib/browser/textmate.service';
44
import { LanguagesContribution, GrammarsContribution } from '@ali/ide-monaco';
55

6+
const languageListCache: LanguagesContribution[] = [];
7+
const grammarListCache: GrammarsContribution[] = [];
8+
9+
class SingleEventEmitter<T> extends Emitter<T> {
10+
clear() {
11+
this._listeners?.clear();
12+
}
13+
}
14+
615
@Injectable()
716
export class LanguageGrammarRegistrationService extends Disposable {
8-
static languageEmitter = new Emitter<LanguagesContribution>();
9-
static grammarEmitter = new Emitter<GrammarsContribution>();
10-
11-
static languageEvent = Event.buffer(LanguageGrammarRegistrationService.languageEmitter.event);
12-
static grammarEvent = Event.buffer(LanguageGrammarRegistrationService.grammarEmitter.event);
17+
static languageEmitter = new SingleEventEmitter<LanguagesContribution>();
18+
static grammarEmitter = new SingleEventEmitter<GrammarsContribution>();
1319

1420
@Autowired(TextmateService)
1521
private readonly textMateService: TextmateService;
@@ -20,14 +26,38 @@ export class LanguageGrammarRegistrationService extends Disposable {
2026
}
2127

2228
async initRegisterLanguageAndGrammar() {
23-
// TODO: 框架侧参数类型改为可选
29+
// 没啥作用,只是确保传参类型正确
2430
const uri = new URI();
25-
LanguageGrammarRegistrationService.languageEvent((contrib) => {
31+
languageListCache.forEach((contrib) => {
2632
this.textMateService.registerLanguage(contrib, uri);
2733
});
28-
LanguageGrammarRegistrationService.grammarEvent((contrib) => {
34+
grammarListCache.forEach((contrib) => {
35+
this.textMateService.registerGrammar(contrib, uri);
36+
});
37+
this.clear();
38+
LanguageGrammarRegistrationService.languageEmitter.event((contrib) => {
39+
this.textMateService.registerLanguage(contrib, uri);
40+
});
41+
LanguageGrammarRegistrationService.grammarEmitter.event((contrib) => {
2942
this.textMateService.registerGrammar(contrib, uri);
3043
});
3144
this.languageDidRegisterDeferred.resolve();
3245
}
46+
47+
clear() {
48+
LanguageGrammarRegistrationService.languageEmitter.clear();
49+
LanguageGrammarRegistrationService.grammarEmitter.clear();
50+
}
51+
52+
dispose() {
53+
this.clear();
54+
}
3355
}
56+
57+
LanguageGrammarRegistrationService.languageEmitter.event((e) => {
58+
languageListCache.push(e);
59+
});
60+
61+
LanguageGrammarRegistrationService.grammarEmitter.event((e) => {
62+
grammarListCache.push(e);
63+
});

0 commit comments

Comments
 (0)