Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts
5241 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 { getZoomLevel } from '../../../../base/browser/browser.js';
7
import { $, Dimension, EventHelper, EventType, ModifierKeyEmitter, addDisposableListener, copyAttributes, createLinkElement, createMetaElement, getActiveWindow, getClientArea, getWindowId, isHTMLElement, position, registerWindow, sharedMutationObserver, trackAttributes } from '../../../../base/browser/dom.js';
8
import { cloneGlobalStylesheets, isGlobalStylesheet } from '../../../../base/browser/domStylesheets.js';
9
import { CodeWindow, ensureCodeWindow, mainWindow } from '../../../../base/browser/window.js';
10
import { coalesce } from '../../../../base/common/arrays.js';
11
import { Barrier } from '../../../../base/common/async.js';
12
import { onUnexpectedError } from '../../../../base/common/errors.js';
13
import { Emitter, Event } from '../../../../base/common/event.js';
14
import { MarkdownString } from '../../../../base/common/htmlContent.js';
15
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
16
import { mark } from '../../../../base/common/performance.js';
17
import { isFirefox, isWeb } from '../../../../base/common/platform.js';
18
import Severity from '../../../../base/common/severity.js';
19
import { localize } from '../../../../nls.js';
20
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
21
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
22
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
23
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
24
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
25
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
26
import { DEFAULT_AUX_WINDOW_SIZE, IRectangle, WindowMinimumSize } from '../../../../platform/window/common/window.js';
27
import { BaseWindow } from '../../../browser/window.js';
28
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
29
import { IHostService } from '../../host/browser/host.js';
30
import { IWorkbenchLayoutService } from '../../layout/browser/layoutService.js';
31
32
export const IAuxiliaryWindowService = createDecorator<IAuxiliaryWindowService>('auxiliaryWindowService');
33
34
export interface IAuxiliaryWindowOpenEvent {
35
readonly window: IAuxiliaryWindow;
36
readonly disposables: DisposableStore;
37
}
38
39
export enum AuxiliaryWindowMode {
40
Maximized,
41
Normal,
42
Fullscreen
43
}
44
45
export interface IAuxiliaryWindowOpenOptions {
46
readonly bounds?: Partial<IRectangle>;
47
readonly compact?: boolean;
48
49
readonly mode?: AuxiliaryWindowMode;
50
readonly zoomLevel?: number;
51
readonly alwaysOnTop?: boolean;
52
53
readonly nativeTitlebar?: boolean;
54
readonly disableFullscreen?: boolean;
55
}
56
57
export interface IAuxiliaryWindowService {
58
59
readonly _serviceBrand: undefined;
60
61
readonly onDidOpenAuxiliaryWindow: Event<IAuxiliaryWindowOpenEvent>;
62
63
open(options?: IAuxiliaryWindowOpenOptions): Promise<IAuxiliaryWindow>;
64
65
getWindow(windowId: number): IAuxiliaryWindow | undefined;
66
}
67
68
export interface BeforeAuxiliaryWindowUnloadEvent {
69
veto(reason: string | undefined): void;
70
}
71
72
export interface IAuxiliaryWindow extends IDisposable {
73
74
readonly onWillLayout: Event<Dimension>;
75
readonly onDidLayout: Event<Dimension>;
76
77
readonly onBeforeUnload: Event<BeforeAuxiliaryWindowUnloadEvent>;
78
readonly onUnload: Event<void>;
79
80
readonly whenStylesHaveLoaded: Promise<void>;
81
82
readonly window: CodeWindow;
83
readonly container: HTMLElement;
84
85
updateOptions(options: { compact: boolean } | undefined): void;
86
87
layout(): void;
88
89
createState(): IAuxiliaryWindowOpenOptions;
90
}
91
92
const DEFAULT_AUX_WINDOW_DIMENSIONS = new Dimension(DEFAULT_AUX_WINDOW_SIZE.width, DEFAULT_AUX_WINDOW_SIZE.height);
93
94
export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
95
96
private readonly _onWillLayout = this._register(new Emitter<Dimension>());
97
readonly onWillLayout = this._onWillLayout.event;
98
99
private readonly _onDidLayout = this._register(new Emitter<Dimension>());
100
readonly onDidLayout = this._onDidLayout.event;
101
102
private readonly _onBeforeUnload = this._register(new Emitter<BeforeAuxiliaryWindowUnloadEvent>());
103
readonly onBeforeUnload = this._onBeforeUnload.event;
104
105
private readonly _onUnload = this._register(new Emitter<void>());
106
readonly onUnload = this._onUnload.event;
107
108
private readonly _onWillDispose = this._register(new Emitter<void>());
109
readonly onWillDispose = this._onWillDispose.event;
110
111
readonly whenStylesHaveLoaded: Promise<void>;
112
113
private compact = false;
114
115
constructor(
116
readonly window: CodeWindow,
117
readonly container: HTMLElement,
118
stylesHaveLoaded: Barrier,
119
@IConfigurationService private readonly configurationService: IConfigurationService,
120
@IHostService hostService: IHostService,
121
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
122
@IContextMenuService contextMenuService: IContextMenuService,
123
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
124
) {
125
super(window, undefined, hostService, environmentService, contextMenuService, layoutService);
126
127
this.whenStylesHaveLoaded = stylesHaveLoaded.wait().then(() => undefined);
128
129
this.registerListeners();
130
}
131
132
updateOptions(options: { compact: boolean }): void {
133
this.compact = options.compact;
134
}
135
136
private registerListeners(): void {
137
this._register(addDisposableListener(this.window, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.handleBeforeUnload(e)));
138
this._register(addDisposableListener(this.window, EventType.UNLOAD, () => this.handleUnload()));
139
140
this._register(addDisposableListener(this.window, 'unhandledrejection', e => {
141
onUnexpectedError(e.reason);
142
e.preventDefault();
143
}));
144
145
this._register(addDisposableListener(this.window, EventType.RESIZE, () => this.layout()));
146
147
this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); // Prevent container from scrolling (#55456)
148
149
if (isWeb) {
150
this._register(addDisposableListener(this.container, EventType.DROP, e => EventHelper.stop(e, true))); // Prevent default navigation on drop
151
this._register(addDisposableListener(this.container, EventType.WHEEL, e => e.preventDefault(), { passive: false })); // Prevent the back/forward gestures in macOS
152
this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, e => EventHelper.stop(e, true))); // Prevent native context menus in web
153
} else {
154
this._register(addDisposableListener(this.window.document.body, EventType.DRAG_OVER, (e: DragEvent) => EventHelper.stop(e))); // Prevent drag feedback on <body>
155
this._register(addDisposableListener(this.window.document.body, EventType.DROP, (e: DragEvent) => EventHelper.stop(e))); // Prevent default navigation on drop
156
}
157
}
158
159
private handleBeforeUnload(e: BeforeUnloadEvent): void {
160
161
// Check for veto from a listening component
162
let veto: string | undefined;
163
this._onBeforeUnload.fire({
164
veto(reason) {
165
if (reason) {
166
veto = reason;
167
}
168
}
169
});
170
if (veto) {
171
this.handleVetoBeforeClose(e, veto);
172
173
return;
174
}
175
176
// Check for confirm before close setting
177
const confirmBeforeCloseSetting = this.configurationService.getValue<'always' | 'never' | 'keyboardOnly'>('window.confirmBeforeClose');
178
const confirmBeforeClose = confirmBeforeCloseSetting === 'always' || (confirmBeforeCloseSetting === 'keyboardOnly' && ModifierKeyEmitter.getInstance().isModifierPressed);
179
if (confirmBeforeClose) {
180
this.confirmBeforeClose(e);
181
}
182
}
183
184
protected handleVetoBeforeClose(e: BeforeUnloadEvent, reason: string): void {
185
this.preventUnload(e);
186
}
187
188
protected preventUnload(e: BeforeUnloadEvent): void {
189
e.preventDefault();
190
e.returnValue = localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again.");
191
}
192
193
protected confirmBeforeClose(e: BeforeUnloadEvent): void {
194
this.preventUnload(e);
195
}
196
197
private handleUnload(): void {
198
199
// Event
200
this._onUnload.fire();
201
}
202
203
layout(): void {
204
205
// Split layout up into two events so that downstream components
206
// have a chance to participate in the beginning or end of the
207
// layout phase.
208
// This helps to build the auxiliary window in another component
209
// in the `onWillLayout` phase and then let other compoments
210
// react when the overall layout has finished in `onDidLayout`.
211
212
const dimension = getClientArea(this.window.document.body, DEFAULT_AUX_WINDOW_DIMENSIONS, this.container);
213
this._onWillLayout.fire(dimension);
214
this._onDidLayout.fire(dimension);
215
}
216
217
createState(): IAuxiliaryWindowOpenOptions {
218
return {
219
bounds: {
220
x: this.window.screenX,
221
y: this.window.screenY,
222
width: this.window.outerWidth,
223
height: this.window.outerHeight
224
},
225
zoomLevel: getZoomLevel(this.window),
226
compact: this.compact
227
};
228
}
229
230
override dispose(): void {
231
if (this._store.isDisposed) {
232
return;
233
}
234
235
this._onWillDispose.fire();
236
237
super.dispose();
238
}
239
}
240
241
export class BrowserAuxiliaryWindowService extends Disposable implements IAuxiliaryWindowService {
242
243
declare readonly _serviceBrand: undefined;
244
245
private static WINDOW_IDS = getWindowId(mainWindow) + 1; // start from the main window ID + 1
246
247
private readonly _onDidOpenAuxiliaryWindow = this._register(new Emitter<IAuxiliaryWindowOpenEvent>());
248
readonly onDidOpenAuxiliaryWindow = this._onDidOpenAuxiliaryWindow.event;
249
250
private readonly windows = new Map<number, IAuxiliaryWindow>();
251
252
constructor(
253
@IWorkbenchLayoutService protected readonly layoutService: IWorkbenchLayoutService,
254
@IDialogService protected readonly dialogService: IDialogService,
255
@IConfigurationService protected readonly configurationService: IConfigurationService,
256
@ITelemetryService private readonly telemetryService: ITelemetryService,
257
@IHostService protected readonly hostService: IHostService,
258
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
259
@IContextMenuService protected readonly contextMenuService: IContextMenuService,
260
) {
261
super();
262
}
263
264
async open(options?: IAuxiliaryWindowOpenOptions): Promise<IAuxiliaryWindow> {
265
mark('code/auxiliaryWindow/willOpen');
266
267
const targetWindow = await this.openWindow(options);
268
if (!targetWindow) {
269
throw new Error(localize('unableToOpenWindowError', "Unable to open a new window."));
270
}
271
272
// Add a `vscodeWindowId` property to identify auxiliary windows
273
const resolvedWindowId = await this.resolveWindowId(targetWindow);
274
ensureCodeWindow(targetWindow, resolvedWindowId);
275
276
const containerDisposables = new DisposableStore();
277
const { container, stylesLoaded } = this.createContainer(targetWindow, containerDisposables, options);
278
279
const auxiliaryWindow = this.createAuxiliaryWindow(targetWindow, container, stylesLoaded);
280
auxiliaryWindow.updateOptions({ compact: options?.compact ?? false });
281
282
const registryDisposables = new DisposableStore();
283
this.windows.set(targetWindow.vscodeWindowId, auxiliaryWindow);
284
registryDisposables.add(toDisposable(() => this.windows.delete(targetWindow.vscodeWindowId)));
285
286
const eventDisposables = new DisposableStore();
287
288
Event.once(auxiliaryWindow.onWillDispose)(() => {
289
targetWindow.close();
290
291
containerDisposables.dispose();
292
registryDisposables.dispose();
293
eventDisposables.dispose();
294
});
295
296
registryDisposables.add(registerWindow(targetWindow));
297
this._onDidOpenAuxiliaryWindow.fire({ window: auxiliaryWindow, disposables: eventDisposables });
298
299
mark('code/auxiliaryWindow/didOpen');
300
301
type AuxiliaryWindowClassification = {
302
owner: 'bpasero';
303
comment: 'An event that fires when an auxiliary window is opened';
304
bounds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Has window bounds provided.' };
305
};
306
type AuxiliaryWindowOpenEvent = {
307
bounds: boolean;
308
};
309
this.telemetryService.publicLog2<AuxiliaryWindowOpenEvent, AuxiliaryWindowClassification>('auxiliaryWindowOpen', { bounds: !!options?.bounds });
310
311
return auxiliaryWindow;
312
}
313
314
protected createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesLoaded: Barrier): AuxiliaryWindow {
315
return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService, this.hostService, this.environmentService, this.contextMenuService, this.layoutService);
316
}
317
318
private async openWindow(options?: IAuxiliaryWindowOpenOptions): Promise<Window | undefined> {
319
const activeWindow = getActiveWindow();
320
const activeWindowBounds = {
321
x: activeWindow.screenX,
322
y: activeWindow.screenY,
323
width: activeWindow.outerWidth,
324
height: activeWindow.outerHeight
325
};
326
327
const defaultSize = DEFAULT_AUX_WINDOW_SIZE;
328
329
const width = Math.max(options?.bounds?.width ?? defaultSize.width, WindowMinimumSize.WIDTH);
330
const height = Math.max(options?.bounds?.height ?? defaultSize.height, WindowMinimumSize.HEIGHT);
331
332
let newWindowBounds: IRectangle = {
333
x: options?.bounds?.x ?? Math.max(activeWindowBounds.x + activeWindowBounds.width / 2 - width / 2, 0),
334
y: options?.bounds?.y ?? Math.max(activeWindowBounds.y + activeWindowBounds.height / 2 - height / 2, 0),
335
width,
336
height
337
};
338
339
if (!options?.bounds && newWindowBounds.x === activeWindowBounds.x && newWindowBounds.y === activeWindowBounds.y) {
340
// Offset the new window a bit so that it does not overlap
341
// with the active window, unless bounds are provided
342
newWindowBounds = {
343
...newWindowBounds,
344
x: newWindowBounds.x + 30,
345
y: newWindowBounds.y + 30
346
};
347
}
348
349
const features = coalesce([
350
'popup=yes',
351
`left=${newWindowBounds.x}`,
352
`top=${newWindowBounds.y}`,
353
`width=${newWindowBounds.width}`,
354
`height=${newWindowBounds.height}`,
355
356
// non-standard properties
357
options?.nativeTitlebar ? 'window-native-titlebar=yes' : undefined,
358
options?.disableFullscreen ? 'window-disable-fullscreen=yes' : undefined,
359
options?.alwaysOnTop ? 'window-always-on-top=yes' : undefined,
360
options?.mode === AuxiliaryWindowMode.Maximized ? 'window-maximized=yes' : undefined,
361
options?.mode === AuxiliaryWindowMode.Fullscreen ? 'window-fullscreen=yes' : undefined
362
]);
363
364
const auxiliaryWindow = mainWindow.open(isFirefox ? '' /* FF immediately fires an unload event if using about:blank */ : 'about:blank', undefined, features.join(','));
365
if (!auxiliaryWindow && isWeb) {
366
return (await this.dialogService.prompt({
367
type: Severity.Warning,
368
message: localize('unableToOpenWindow', "The browser blocked opening a new window. Press 'Retry' to try again."),
369
custom: {
370
markdownDetails: [{ markdown: new MarkdownString(localize('unableToOpenWindowDetail', "Please allow pop-ups for this website in your [browser settings]({0}).", 'https://aka.ms/allow-vscode-popup'), true) }]
371
},
372
buttons: [
373
{
374
label: localize({ key: 'retry', comment: ['&& denotes a mnemonic'] }, "&&Retry"),
375
run: () => this.openWindow(options)
376
}
377
],
378
cancelButton: true
379
})).result;
380
}
381
382
return auxiliaryWindow?.window;
383
}
384
385
protected async resolveWindowId(auxiliaryWindow: Window): Promise<number> {
386
return BrowserAuxiliaryWindowService.WINDOW_IDS++;
387
}
388
389
protected createContainer(auxiliaryWindow: CodeWindow, disposables: DisposableStore, options?: IAuxiliaryWindowOpenOptions): { stylesLoaded: Barrier; container: HTMLElement } {
390
auxiliaryWindow.document.createElement = function () {
391
// Disallow `createElement` because it would create
392
// HTML Elements in the "wrong" context and break
393
// code that does "instanceof HTMLElement" etc.
394
throw new Error('Not allowed to create elements in child window JavaScript context. Always use the main window so that "xyz instanceof HTMLElement" continues to work.');
395
};
396
397
this.applyMeta(auxiliaryWindow);
398
const { stylesLoaded } = this.applyCSS(auxiliaryWindow, disposables);
399
const container = this.applyHTML(auxiliaryWindow, disposables);
400
401
return { stylesLoaded, container };
402
}
403
404
private applyMeta(auxiliaryWindow: CodeWindow): void {
405
for (const metaTag of ['meta[charset="utf-8"]', 'meta[http-equiv="Content-Security-Policy"]', 'meta[name="viewport"]', 'meta[name="theme-color"]']) {
406
// eslint-disable-next-line no-restricted-syntax
407
const metaElement = mainWindow.document.querySelector(metaTag);
408
if (metaElement) {
409
const clonedMetaElement = createMetaElement(auxiliaryWindow.document.head);
410
copyAttributes(metaElement, clonedMetaElement);
411
412
if (metaTag === 'meta[http-equiv="Content-Security-Policy"]') {
413
const content = clonedMetaElement.getAttribute('content');
414
if (content) {
415
clonedMetaElement.setAttribute('content', content.replace(/(script-src[^\;]*)/, `script-src 'none'`));
416
}
417
}
418
}
419
}
420
421
// eslint-disable-next-line no-restricted-syntax
422
const originalIconLinkTag = mainWindow.document.querySelector('link[rel="icon"]');
423
if (originalIconLinkTag) {
424
const icon = createLinkElement(auxiliaryWindow.document.head);
425
copyAttributes(originalIconLinkTag, icon);
426
}
427
}
428
429
private applyCSS(auxiliaryWindow: CodeWindow, disposables: DisposableStore) {
430
mark('code/auxiliaryWindow/willApplyCSS');
431
432
const mapOriginalToClone = new Map<Node /* original */, Node /* clone */>();
433
434
const stylesLoaded = new Barrier();
435
stylesLoaded.wait().then(() => mark('code/auxiliaryWindow/didLoadCSSStyles'));
436
437
const pendingLinksDisposables = disposables.add(new DisposableStore());
438
439
let pendingLinksToSettle = 0;
440
function onLinkSettled() {
441
if (--pendingLinksToSettle === 0) {
442
pendingLinksDisposables.dispose();
443
stylesLoaded.open();
444
}
445
}
446
447
function cloneNode(originalNode: Element): void {
448
if (isGlobalStylesheet(originalNode)) {
449
return; // global stylesheets are handled by `cloneGlobalStylesheets` below
450
}
451
452
const clonedNode = auxiliaryWindow.document.head.appendChild(originalNode.cloneNode(true));
453
if (originalNode.tagName.toLowerCase() === 'link') {
454
pendingLinksToSettle++;
455
456
pendingLinksDisposables.add(addDisposableListener(clonedNode, 'load', onLinkSettled));
457
pendingLinksDisposables.add(addDisposableListener(clonedNode, 'error', onLinkSettled));
458
}
459
460
mapOriginalToClone.set(originalNode, clonedNode);
461
}
462
463
// Clone all style elements and stylesheet links from the window to the child window
464
// and keep track of <link> elements to settle to signal that styles have loaded
465
// Increment pending links right from the beginning to ensure we only settle when
466
// all style related nodes have been cloned.
467
pendingLinksToSettle++;
468
try {
469
// eslint-disable-next-line no-restricted-syntax
470
for (const originalNode of mainWindow.document.head.querySelectorAll('link[rel="stylesheet"], style')) {
471
cloneNode(originalNode);
472
}
473
} finally {
474
onLinkSettled();
475
}
476
477
// Global stylesheets in <head> are cloned in a special way because the mutation
478
// observer is not firing for changes done via `style.sheet` API. Only text changes
479
// can be observed.
480
disposables.add(cloneGlobalStylesheets(auxiliaryWindow));
481
482
// Listen to new stylesheets as they are being added or removed in the main window
483
// and apply to child window (including changes to existing stylesheets elements)
484
disposables.add(sharedMutationObserver.observe(mainWindow.document.head, disposables, { childList: true, subtree: true })(mutations => {
485
for (const mutation of mutations) {
486
if (
487
mutation.type !== 'childList' || // only interested in added/removed nodes
488
mutation.target.nodeName.toLowerCase() === 'title' || // skip over title changes that happen frequently
489
mutation.target.nodeName.toLowerCase() === 'script' || // block <script> changes that are unsupported anyway
490
mutation.target.nodeName.toLowerCase() === 'meta' // do not observe <meta> elements for now
491
) {
492
continue;
493
}
494
495
for (const node of mutation.addedNodes) {
496
497
// <style>/<link> element was added
498
if (isHTMLElement(node) && (node.tagName.toLowerCase() === 'style' || node.tagName.toLowerCase() === 'link')) {
499
cloneNode(node);
500
}
501
502
// text-node was changed, try to apply to our clones
503
else if (node.nodeType === Node.TEXT_NODE && node.parentNode) {
504
const clonedNode = mapOriginalToClone.get(node.parentNode);
505
if (clonedNode) {
506
clonedNode.textContent = node.textContent;
507
}
508
}
509
}
510
511
for (const node of mutation.removedNodes) {
512
const clonedNode = mapOriginalToClone.get(node);
513
if (clonedNode) {
514
clonedNode.parentNode?.removeChild(clonedNode);
515
mapOriginalToClone.delete(node);
516
}
517
}
518
}
519
}));
520
521
mark('code/auxiliaryWindow/didApplyCSS');
522
523
return { stylesLoaded };
524
}
525
526
private applyHTML(auxiliaryWindow: CodeWindow, disposables: DisposableStore): HTMLElement {
527
mark('code/auxiliaryWindow/willApplyHTML');
528
529
// Create workbench container and apply classes
530
const container = $('div', { role: 'application' });
531
position(container, 0, 0, 0, 0, 'relative');
532
container.style.display = 'flex';
533
container.style.height = '100%';
534
container.style.flexDirection = 'column';
535
auxiliaryWindow.document.body.append(container);
536
537
// Track attributes
538
disposables.add(trackAttributes(mainWindow.document.documentElement, auxiliaryWindow.document.documentElement));
539
disposables.add(trackAttributes(mainWindow.document.body, auxiliaryWindow.document.body));
540
disposables.add(trackAttributes(this.layoutService.mainContainer, container, ['class'])); // only class attribute
541
542
mark('code/auxiliaryWindow/didApplyHTML');
543
544
return container;
545
}
546
547
getWindow(windowId: number): IAuxiliaryWindow | undefined {
548
return this.windows.get(windowId);
549
}
550
}
551
552
registerSingleton(IAuxiliaryWindowService, BrowserAuxiliaryWindowService, InstantiationType.Delayed);
553
554