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
3296 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
const element = mainWindow.document.querySelector(selector);
46
47
if (!element) {
48
return Promise.reject(new Error(`Element not found: ${selector}`));
49
}
50
51
const inputElement = element as HTMLInputElement;
52
inputElement.value = text;
53
54
const event = new Event('input', { bubbles: true, cancelable: true });
55
inputElement.dispatchEvent(event);
56
}
57
58
async isActiveElement(selector: string): Promise<boolean> {
59
const element = mainWindow.document.querySelector(selector);
60
61
if (element !== mainWindow.document.activeElement) {
62
const chain: string[] = [];
63
let el = mainWindow.document.activeElement;
64
65
while (el) {
66
const tagName = el.tagName;
67
const id = el.id ? `#${el.id}` : '';
68
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
69
chain.unshift(`${tagName}${id}${classes}`);
70
71
el = el.parentElement;
72
}
73
74
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
75
}
76
77
return true;
78
}
79
80
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
81
const query = mainWindow.document.querySelectorAll(selector);
82
const result: IElement[] = [];
83
for (let i = 0; i < query.length; i++) {
84
const element = query.item(i);
85
result.push(this.serializeElement(element, recursive));
86
}
87
88
return result;
89
}
90
91
private serializeElement(element: Element, recursive: boolean): IElement {
92
const attributes = Object.create(null);
93
94
for (let j = 0; j < element.attributes.length; j++) {
95
const attr = element.attributes.item(j);
96
if (attr) {
97
attributes[attr.name] = attr.value;
98
}
99
}
100
101
const children: IElement[] = [];
102
103
if (recursive) {
104
for (let i = 0; i < element.children.length; i++) {
105
const child = element.children.item(i);
106
if (child) {
107
children.push(this.serializeElement(child, true));
108
}
109
}
110
}
111
112
const { left, top } = getTopLeftOffset(element as HTMLElement);
113
114
return {
115
tagName: element.tagName,
116
className: element.className,
117
textContent: element.textContent || '',
118
attributes,
119
children,
120
left,
121
top
122
};
123
}
124
125
async getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }> {
126
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
127
return this._getElementXY(selector, offset);
128
}
129
130
async typeInEditor(selector: string, text: string): Promise<void> {
131
const element = mainWindow.document.querySelector(selector);
132
133
if (!element) {
134
throw new Error(`Editor not found: ${selector}`);
135
}
136
if (isHTMLDivElement(element)) {
137
// Edit context is enabled
138
const editContext = element.editContext;
139
if (!editContext) {
140
throw new Error(`Edit context not found: ${selector}`);
141
}
142
const selectionStart = editContext.selectionStart;
143
const selectionEnd = editContext.selectionEnd;
144
const event = new TextUpdateEvent('textupdate', {
145
updateRangeStart: selectionStart,
146
updateRangeEnd: selectionEnd,
147
text,
148
selectionStart: selectionStart + text.length,
149
selectionEnd: selectionStart + text.length,
150
compositionStart: 0,
151
compositionEnd: 0
152
});
153
editContext.dispatchEvent(event);
154
} else if (isHTMLTextAreaElement(element)) {
155
const start = element.selectionStart;
156
const newStart = start + text.length;
157
const value = element.value;
158
const newValue = value.substr(0, start) + text + value.substr(start);
159
160
element.value = newValue;
161
element.setSelectionRange(newStart, newStart);
162
163
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
164
element.dispatchEvent(event);
165
}
166
}
167
168
async getEditorSelection(selector: string): Promise<{ selectionStart: number; selectionEnd: number }> {
169
const element = mainWindow.document.querySelector(selector);
170
if (!element) {
171
throw new Error(`Editor not found: ${selector}`);
172
}
173
if (isHTMLDivElement(element)) {
174
const editContext = element.editContext;
175
if (!editContext) {
176
throw new Error(`Edit context not found: ${selector}`);
177
}
178
return { selectionStart: editContext.selectionStart, selectionEnd: editContext.selectionEnd };
179
} else if (isHTMLTextAreaElement(element)) {
180
return { selectionStart: element.selectionStart, selectionEnd: element.selectionEnd };
181
} else {
182
throw new Error(`Unknown type of element: ${selector}`);
183
}
184
}
185
186
async getTerminalBuffer(selector: string): Promise<string[]> {
187
const element = mainWindow.document.querySelector(selector);
188
189
if (!element) {
190
throw new Error(`Terminal not found: ${selector}`);
191
}
192
193
const xterm = (element as any).xterm;
194
195
if (!xterm) {
196
throw new Error(`Xterm not found: ${selector}`);
197
}
198
199
const lines: string[] = [];
200
for (let i = 0; i < xterm.buffer.active.length; i++) {
201
lines.push(xterm.buffer.active.getLine(i)!.translateToString(true));
202
}
203
204
return lines;
205
}
206
207
async writeInTerminal(selector: string, text: string): Promise<void> {
208
const element = mainWindow.document.querySelector(selector);
209
210
if (!element) {
211
throw new Error(`Element not found: ${selector}`);
212
}
213
214
const xterm = (element as any).xterm as (XtermTerminal | undefined);
215
216
if (!xterm) {
217
throw new Error(`Xterm not found: ${selector}`);
218
}
219
220
xterm.input(text);
221
}
222
223
getLocaleInfo(): Promise<ILocaleInfo> {
224
return Promise.resolve({
225
language: language,
226
locale: locale
227
});
228
}
229
230
getLocalizedStrings(): Promise<ILocalizedStrings> {
231
return Promise.resolve({
232
open: localizedStrings.open,
233
close: localizedStrings.close,
234
find: localizedStrings.find
235
});
236
}
237
238
protected async _getElementXY(selector: string, offset?: { x: number; y: number }): Promise<{ x: number; y: number }> {
239
const element = mainWindow.document.querySelector(selector);
240
241
if (!element) {
242
return Promise.reject(new Error(`Element not found: ${selector}`));
243
}
244
245
const { left, top } = getTopLeftOffset(element as HTMLElement);
246
const { width, height } = getClientArea(element as HTMLElement);
247
let x: number, y: number;
248
249
if (offset) {
250
x = left + offset.x;
251
y = top + offset.y;
252
} else {
253
x = left + (width / 2);
254
y = top + (height / 2);
255
}
256
257
x = Math.round(x);
258
y = Math.round(y);
259
260
return { x, y };
261
}
262
}
263
264
export function registerWindowDriver(instantiationService: IInstantiationService): void {
265
Object.assign(mainWindow, { driver: instantiationService.createInstance(BrowserWindowDriver) });
266
}
267
268