Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/driver/browser/driver.ts
5251 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { getClientArea, getTopLeftOffset, isHTMLDivElement, isHTMLTextAreaElement } from '../../../../base/browser/dom.js';
7
import { mainWindow } from '../../../../base/browser/window.js';
8
import { coalesce } from '../../../../base/common/arrays.js';
9
import { language, locale } from '../../../../base/common/platform.js';
10
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
11
import { IFileService } from '../../../../platform/files/common/files.js';
12
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
13
import localizedStrings from '../../../../platform/languagePacks/common/localizedStrings.js';
14
import { ILogFile, getLogs } from '../../../../platform/log/browser/log.js';
15
import { ILogService } from '../../../../platform/log/common/log.js';
16
import { Registry } from '../../../../platform/registry/common/platform.js';
17
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js';
18
import { IWindowDriver, IElement, ILocaleInfo, ILocalizedStrings } from '../common/driver.js';
19
import { ILifecycleService, LifecyclePhase } from '../../lifecycle/common/lifecycle.js';
20
import type { Terminal as XtermTerminal } from '@xterm/xterm';
21
22
export class BrowserWindowDriver implements IWindowDriver {
23
24
constructor(
25
@IFileService private readonly fileService: IFileService,
26
@IEnvironmentService private readonly environmentService: IEnvironmentService,
27
@ILifecycleService private readonly lifecycleService: ILifecycleService,
28
@ILogService private readonly logService: ILogService
29
) {
30
}
31
32
async getLogs(): Promise<ILogFile[]> {
33
return getLogs(this.fileService, this.environmentService);
34
}
35
36
async whenWorkbenchRestored(): Promise<void> {
37
this.logService.info('[driver] Waiting for restored lifecycle phase...');
38
await this.lifecycleService.when(LifecyclePhase.Restored);
39
this.logService.info('[driver] Restored lifecycle phase reached. Waiting for contributions...');
40
await Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).whenRestored;
41
this.logService.info('[driver] Workbench contributions created.');
42
}
43
44
async setValue(selector: string, text: string): Promise<void> {
45
// eslint-disable-next-line no-restricted-syntax
46
const element = mainWindow.document.querySelector(selector);
47
48
if (!element) {
49
return Promise.reject(new Error(`Element not found: ${selector}`));
50
}
51
52
const inputElement = element as HTMLInputElement;
53
inputElement.value = text;
54
55
const event = new Event('input', { bubbles: true, cancelable: true });
56
inputElement.dispatchEvent(event);
57
}
58
59
async isActiveElement(selector: string): Promise<boolean> {
60
// eslint-disable-next-line no-restricted-syntax
61
const element = mainWindow.document.querySelector(selector);
62
63
if (element !== mainWindow.document.activeElement) {
64
const chain: string[] = [];
65
let el = mainWindow.document.activeElement;
66
67
while (el) {
68
const tagName = el.tagName;
69
const id = el.id ? `#${el.id}` : '';
70
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
71
chain.unshift(`${tagName}${id}${classes}`);
72
73
el = el.parentElement;
74
}
75
76
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
77
}
78
79
return true;
80
}
81
82
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
83
// eslint-disable-next-line no-restricted-syntax
84
const query = mainWindow.document.querySelectorAll(selector);
85
const result: IElement[] = [];
86
for (let i = 0; i < query.length; i++) {
87
const element = query.item(i);
88
result.push(this.serializeElement(element, recursive));
89
}
90
91
return result;
92
}
93
94
private serializeElement(element: Element, recursive: boolean): IElement {
95
const attributes = Object.create(null);
96
97
for (let j = 0; j < element.attributes.length; j++) {
98
const attr = element.attributes.item(j);
99
if (attr) {
100
attributes[attr.name] = attr.value;
101
}
102
}
103
104
const children: IElement[] = [];
105
106
if (recursive) {
107
for (let i = 0; i < element.children.length; i++) {
108
const child = element.children.item(i);
109
if (child) {
110
children.push(this.serializeElement(child, true));
111
}
112
}
113
}
114
115
const { left, top } = getTopLeftOffset(element as HTMLElement);
116
117
return {
118
tagName: element.tagName,
119
className: element.className,
120
textContent: element.textContent || '',
121
attributes,
122
children,
123
left,
124
top
125
};
126
}
127
128
async getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }> {
129
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
130
return this._getElementXY(selector, offset);
131
}
132
133
async typeInEditor(selector: string, text: string): Promise<void> {
134
// eslint-disable-next-line no-restricted-syntax
135
const element = mainWindow.document.querySelector(selector);
136
137
if (!element) {
138
throw new Error(`Editor not found: ${selector}`);
139
}
140
if (isHTMLDivElement(element)) {
141
// Edit context is enabled
142
const editContext = element.editContext;
143
if (!editContext) {
144
throw new Error(`Edit context not found: ${selector}`);
145
}
146
const selectionStart = editContext.selectionStart;
147
const selectionEnd = editContext.selectionEnd;
148
const event = new TextUpdateEvent('textupdate', {
149
updateRangeStart: selectionStart,
150
updateRangeEnd: selectionEnd,
151
text,
152
selectionStart: selectionStart + text.length,
153
selectionEnd: selectionStart + text.length,
154
compositionStart: 0,
155
compositionEnd: 0
156
});
157
editContext.dispatchEvent(event);
158
} else if (isHTMLTextAreaElement(element)) {
159
const start = element.selectionStart;
160
const newStart = start + text.length;
161
const value = element.value;
162
const newValue = value.substr(0, start) + text + value.substr(start);
163
164
element.value = newValue;
165
element.setSelectionRange(newStart, newStart);
166
167
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
168
element.dispatchEvent(event);
169
}
170
}
171
172
async getEditorSelection(selector: string): Promise<{ selectionStart: number; selectionEnd: number }> {
173
// eslint-disable-next-line no-restricted-syntax
174
const element = mainWindow.document.querySelector(selector);
175
if (!element) {
176
throw new Error(`Editor not found: ${selector}`);
177
}
178
if (isHTMLDivElement(element)) {
179
const editContext = element.editContext;
180
if (!editContext) {
181
throw new Error(`Edit context not found: ${selector}`);
182
}
183
return { selectionStart: editContext.selectionStart, selectionEnd: editContext.selectionEnd };
184
} else if (isHTMLTextAreaElement(element)) {
185
return { selectionStart: element.selectionStart, selectionEnd: element.selectionEnd };
186
} else {
187
throw new Error(`Unknown type of element: ${selector}`);
188
}
189
}
190
191
async getTerminalBuffer(selector: string): Promise<string[]> {
192
// eslint-disable-next-line no-restricted-syntax
193
const element = mainWindow.document.querySelector(selector);
194
195
if (!element) {
196
throw new Error(`Terminal not found: ${selector}`);
197
}
198
199
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
200
const xterm = (element as any).xterm;
201
202
if (!xterm) {
203
throw new Error(`Xterm not found: ${selector}`);
204
}
205
206
const lines: string[] = [];
207
for (let i = 0; i < xterm.buffer.active.length; i++) {
208
lines.push(xterm.buffer.active.getLine(i)!.translateToString(true));
209
}
210
211
return lines;
212
}
213
214
async writeInTerminal(selector: string, text: string): Promise<void> {
215
// eslint-disable-next-line no-restricted-syntax
216
const element = mainWindow.document.querySelector(selector);
217
218
if (!element) {
219
throw new Error(`Element not found: ${selector}`);
220
}
221
222
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
223
const xterm = (element as any).xterm as (XtermTerminal | undefined);
224
225
if (!xterm) {
226
throw new Error(`Xterm not found: ${selector}`);
227
}
228
229
xterm.input(text);
230
}
231
232
getLocaleInfo(): Promise<ILocaleInfo> {
233
return Promise.resolve({
234
language: language,
235
locale: locale
236
});
237
}
238
239
getLocalizedStrings(): Promise<ILocalizedStrings> {
240
return Promise.resolve({
241
open: localizedStrings.open,
242
close: localizedStrings.close,
243
find: localizedStrings.find
244
});
245
}
246
247
protected async _getElementXY(selector: string, offset?: { x: number; y: number }): Promise<{ x: number; y: number }> {
248
// eslint-disable-next-line no-restricted-syntax
249
const element = mainWindow.document.querySelector(selector);
250
251
if (!element) {
252
return Promise.reject(new Error(`Element not found: ${selector}`));
253
}
254
255
const { left, top } = getTopLeftOffset(element as HTMLElement);
256
const { width, height } = getClientArea(element as HTMLElement);
257
let x: number, y: number;
258
259
if (offset) {
260
x = left + offset.x;
261
y = top + offset.y;
262
} else {
263
x = left + (width / 2);
264
y = top + (height / 2);
265
}
266
267
x = Math.round(x);
268
y = Math.round(y);
269
270
return { x, y };
271
}
272
}
273
274
export function registerWindowDriver(instantiationService: IInstantiationService): void {
275
Object.assign(mainWindow, { driver: instantiationService.createInstance(BrowserWindowDriver) });
276
}
277
278