Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/code/electron-browser/workbench/workbench.ts
5237 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
/* eslint-disable no-restricted-globals */
7
8
(async function () {
9
10
// Add a perf entry right from the top
11
performance.mark('code/didStartRenderer');
12
13
type ISandboxConfiguration = import('../../../base/parts/sandbox/common/sandboxTypes.js').ISandboxConfiguration;
14
type ILoadResult<M, T extends ISandboxConfiguration> = import('../../../platform/window/electron-browser/window.js').ILoadResult<M, T>;
15
type ILoadOptions<T extends ISandboxConfiguration> = import('../../../platform/window/electron-browser/window.js').ILoadOptions<T>;
16
type INativeWindowConfiguration = import('../../../platform/window/common/window.ts').INativeWindowConfiguration;
17
type IMainWindowSandboxGlobals = import('../../../base/parts/sandbox/electron-browser/globals.js').IMainWindowSandboxGlobals;
18
type IDesktopMain = import('../../../workbench/electron-browser/desktop.main.js').IDesktopMain;
19
20
const preloadGlobals = (window as unknown as { vscode: IMainWindowSandboxGlobals }).vscode; // defined by preload.ts
21
const safeProcess = preloadGlobals.process;
22
23
//#region Splash Screen Helpers
24
25
function showSplash(configuration: INativeWindowConfiguration) {
26
performance.mark('code/willShowPartsSplash');
27
showDefaultSplash(configuration);
28
performance.mark('code/didShowPartsSplash');
29
}
30
31
function showDefaultSplash(configuration: INativeWindowConfiguration) {
32
let data = configuration.partsSplash;
33
if (data) {
34
if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) {
35
if ((configuration.colorScheme.dark && data.baseTheme !== 'hc-black') || (!configuration.colorScheme.dark && data.baseTheme !== 'hc-light')) {
36
data = undefined; // high contrast mode has been turned by the OS -> ignore stored colors and layouts
37
}
38
} else if (configuration.autoDetectColorScheme) {
39
if ((configuration.colorScheme.dark && data.baseTheme !== 'vs-dark') || (!configuration.colorScheme.dark && data.baseTheme !== 'vs')) {
40
data = undefined; // OS color scheme is tracked and has changed
41
}
42
}
43
}
44
45
// developing an extension -> ignore stored layouts
46
if (data && configuration.extensionDevelopmentPath) {
47
data.layoutInfo = undefined;
48
}
49
50
// minimal color configuration (works with or without persisted data)
51
let baseTheme;
52
let shellBackground;
53
let shellForeground;
54
if (data) {
55
baseTheme = data.baseTheme;
56
shellBackground = data.colorInfo.editorBackground;
57
shellForeground = data.colorInfo.foreground;
58
} else if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) {
59
if (configuration.colorScheme.dark) {
60
baseTheme = 'hc-black';
61
shellBackground = '#000000';
62
shellForeground = '#FFFFFF';
63
} else {
64
baseTheme = 'hc-light';
65
shellBackground = '#FFFFFF';
66
shellForeground = '#000000';
67
}
68
} else if (configuration.autoDetectColorScheme) {
69
if (configuration.colorScheme.dark) {
70
baseTheme = 'vs-dark';
71
shellBackground = '#1E1E1E';
72
shellForeground = '#CCCCCC';
73
} else {
74
baseTheme = 'vs';
75
shellBackground = '#FFFFFF';
76
shellForeground = '#000000';
77
}
78
}
79
80
const style = document.createElement('style');
81
style.className = 'initialShellColors';
82
window.document.head.appendChild(style);
83
style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`;
84
85
// set zoom level as soon as possible
86
if (typeof data?.zoomLevel === 'number' && typeof preloadGlobals?.webFrame?.setZoomLevel === 'function') {
87
preloadGlobals.webFrame.setZoomLevel(data.zoomLevel);
88
}
89
90
// restore parts if possible (we might not always store layout info)
91
if (data?.layoutInfo) {
92
const { layoutInfo, colorInfo } = data;
93
94
const splash = document.createElement('div');
95
splash.id = 'monaco-parts-splash';
96
splash.className = baseTheme ?? 'vs-dark';
97
98
if (layoutInfo.windowBorder && colorInfo.windowBorder) {
99
const borderElement = document.createElement('div');
100
borderElement.style.position = 'absolute';
101
borderElement.style.width = 'calc(100vw - 2px)';
102
borderElement.style.height = 'calc(100vh - 2px)';
103
borderElement.style.zIndex = '1'; // allow border above other elements
104
borderElement.style.border = `1px solid var(--window-border-color)`;
105
borderElement.style.setProperty('--window-border-color', colorInfo.windowBorder);
106
107
if (layoutInfo.windowBorderRadius) {
108
borderElement.style.borderRadius = layoutInfo.windowBorderRadius;
109
}
110
111
splash.appendChild(borderElement);
112
}
113
114
if (layoutInfo.auxiliaryBarWidth === Number.MAX_SAFE_INTEGER) {
115
// if auxiliary bar is maximized, it goes as wide as the
116
// window width but leaving room for activity bar
117
layoutInfo.auxiliaryBarWidth = window.innerWidth - layoutInfo.activityBarWidth;
118
} else {
119
// otherwise adjust for other parts sizes if not maximized
120
layoutInfo.auxiliaryBarWidth = Math.min(layoutInfo.auxiliaryBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth + layoutInfo.sideBarWidth));
121
}
122
layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth + layoutInfo.auxiliaryBarWidth));
123
124
// part: title
125
if (layoutInfo.titleBarHeight > 0) {
126
const titleDiv = document.createElement('div');
127
titleDiv.style.position = 'absolute';
128
titleDiv.style.width = '100%';
129
titleDiv.style.height = `${layoutInfo.titleBarHeight}px`;
130
titleDiv.style.left = '0';
131
titleDiv.style.top = '0';
132
titleDiv.style.backgroundColor = `${colorInfo.titleBarBackground}`;
133
(titleDiv.style as CSSStyleDeclaration & { '-webkit-app-region': string })['-webkit-app-region'] = 'drag';
134
splash.appendChild(titleDiv);
135
136
if (colorInfo.titleBarBorder) {
137
const titleBorder = document.createElement('div');
138
titleBorder.style.position = 'absolute';
139
titleBorder.style.width = '100%';
140
titleBorder.style.height = '1px';
141
titleBorder.style.left = '0';
142
titleBorder.style.bottom = '0';
143
titleBorder.style.borderBottom = `1px solid ${colorInfo.titleBarBorder}`;
144
titleDiv.appendChild(titleBorder);
145
}
146
}
147
148
// part: activity bar
149
if (layoutInfo.activityBarWidth > 0) {
150
const activityDiv = document.createElement('div');
151
activityDiv.style.position = 'absolute';
152
activityDiv.style.width = `${layoutInfo.activityBarWidth}px`;
153
activityDiv.style.height = `calc(100% - ${layoutInfo.titleBarHeight + layoutInfo.statusBarHeight}px)`;
154
activityDiv.style.top = `${layoutInfo.titleBarHeight}px`;
155
if (layoutInfo.sideBarSide === 'left') {
156
activityDiv.style.left = '0';
157
} else {
158
activityDiv.style.right = '0';
159
}
160
activityDiv.style.backgroundColor = `${colorInfo.activityBarBackground}`;
161
splash.appendChild(activityDiv);
162
163
if (colorInfo.activityBarBorder) {
164
const activityBorderDiv = document.createElement('div');
165
activityBorderDiv.style.position = 'absolute';
166
activityBorderDiv.style.width = '1px';
167
activityBorderDiv.style.height = '100%';
168
activityBorderDiv.style.top = '0';
169
if (layoutInfo.sideBarSide === 'left') {
170
activityBorderDiv.style.right = '0';
171
activityBorderDiv.style.borderRight = `1px solid ${colorInfo.activityBarBorder}`;
172
} else {
173
activityBorderDiv.style.left = '0';
174
activityBorderDiv.style.borderLeft = `1px solid ${colorInfo.activityBarBorder}`;
175
}
176
activityDiv.appendChild(activityBorderDiv);
177
}
178
}
179
180
// part: side bar
181
if (layoutInfo.sideBarWidth > 0) {
182
const sideDiv = document.createElement('div');
183
sideDiv.style.position = 'absolute';
184
sideDiv.style.width = `${layoutInfo.sideBarWidth}px`;
185
sideDiv.style.height = `calc(100% - ${layoutInfo.titleBarHeight + layoutInfo.statusBarHeight}px)`;
186
sideDiv.style.top = `${layoutInfo.titleBarHeight}px`;
187
if (layoutInfo.sideBarSide === 'left') {
188
sideDiv.style.left = `${layoutInfo.activityBarWidth}px`;
189
} else {
190
sideDiv.style.right = `${layoutInfo.activityBarWidth}px`;
191
}
192
sideDiv.style.backgroundColor = `${colorInfo.sideBarBackground}`;
193
splash.appendChild(sideDiv);
194
195
if (colorInfo.sideBarBorder) {
196
const sideBorderDiv = document.createElement('div');
197
sideBorderDiv.style.position = 'absolute';
198
sideBorderDiv.style.width = '1px';
199
sideBorderDiv.style.height = '100%';
200
sideBorderDiv.style.top = '0';
201
sideBorderDiv.style.right = '0';
202
if (layoutInfo.sideBarSide === 'left') {
203
sideBorderDiv.style.borderRight = `1px solid ${colorInfo.sideBarBorder}`;
204
} else {
205
sideBorderDiv.style.left = '0';
206
sideBorderDiv.style.borderLeft = `1px solid ${colorInfo.sideBarBorder}`;
207
}
208
sideDiv.appendChild(sideBorderDiv);
209
}
210
}
211
212
// part: auxiliary sidebar
213
if (layoutInfo.auxiliaryBarWidth > 0) {
214
const auxSideDiv = document.createElement('div');
215
auxSideDiv.style.position = 'absolute';
216
auxSideDiv.style.width = `${layoutInfo.auxiliaryBarWidth}px`;
217
auxSideDiv.style.height = `calc(100% - ${layoutInfo.titleBarHeight + layoutInfo.statusBarHeight}px)`;
218
auxSideDiv.style.top = `${layoutInfo.titleBarHeight}px`;
219
if (layoutInfo.sideBarSide === 'left') {
220
auxSideDiv.style.right = '0';
221
} else {
222
auxSideDiv.style.left = '0';
223
}
224
auxSideDiv.style.backgroundColor = `${colorInfo.sideBarBackground}`;
225
splash.appendChild(auxSideDiv);
226
227
if (colorInfo.sideBarBorder) {
228
const auxSideBorderDiv = document.createElement('div');
229
auxSideBorderDiv.style.position = 'absolute';
230
auxSideBorderDiv.style.width = '1px';
231
auxSideBorderDiv.style.height = '100%';
232
auxSideBorderDiv.style.top = '0';
233
if (layoutInfo.sideBarSide === 'left') {
234
auxSideBorderDiv.style.left = '0';
235
auxSideBorderDiv.style.borderLeft = `1px solid ${colorInfo.sideBarBorder}`;
236
} else {
237
auxSideBorderDiv.style.right = '0';
238
auxSideBorderDiv.style.borderRight = `1px solid ${colorInfo.sideBarBorder}`;
239
}
240
auxSideDiv.appendChild(auxSideBorderDiv);
241
}
242
}
243
244
// part: statusbar
245
if (layoutInfo.statusBarHeight > 0) {
246
const statusDiv = document.createElement('div');
247
statusDiv.style.position = 'absolute';
248
statusDiv.style.width = '100%';
249
statusDiv.style.height = `${layoutInfo.statusBarHeight}px`;
250
statusDiv.style.bottom = '0';
251
statusDiv.style.left = '0';
252
if (configuration.workspace && colorInfo.statusBarBackground) {
253
statusDiv.style.backgroundColor = colorInfo.statusBarBackground;
254
} else if (!configuration.workspace && colorInfo.statusBarNoFolderBackground) {
255
statusDiv.style.backgroundColor = colorInfo.statusBarNoFolderBackground;
256
}
257
splash.appendChild(statusDiv);
258
259
if (colorInfo.statusBarBorder) {
260
const statusBorderDiv = document.createElement('div');
261
statusBorderDiv.style.position = 'absolute';
262
statusBorderDiv.style.width = '100%';
263
statusBorderDiv.style.height = '1px';
264
statusBorderDiv.style.top = '0';
265
statusBorderDiv.style.borderTop = `1px solid ${colorInfo.statusBarBorder}`;
266
statusDiv.appendChild(statusBorderDiv);
267
}
268
}
269
270
window.document.body.appendChild(splash);
271
}
272
}
273
274
//#endregion
275
276
//#region Window Helpers
277
278
async function load<M, T extends ISandboxConfiguration>(options: ILoadOptions<T>): Promise<ILoadResult<M, T>> {
279
280
// Window Configuration from Preload Script
281
const configuration = await resolveWindowConfiguration<T>();
282
283
// Signal before import()
284
options?.beforeImport?.(configuration);
285
286
// Developer settings
287
const { enableDeveloperKeybindings, removeDeveloperKeybindingsAfterLoad, developerDeveloperKeybindingsDisposable, forceDisableShowDevtoolsOnError } = setupDeveloperKeybindings(configuration, options);
288
289
// NLS
290
setupNLS<T>(configuration);
291
292
// Compute base URL and set as global
293
const baseUrl = new URL(`${fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out/`);
294
globalThis._VSCODE_FILE_ROOT = baseUrl.toString();
295
296
// Dev only: CSS import map tricks
297
setupCSSImportMaps<T>(configuration, baseUrl);
298
299
// ESM Import
300
try {
301
let workbenchUrl: string;
302
if (!!safeProcess.env['VSCODE_DEV'] && globalThis._VSCODE_USE_RELATIVE_IMPORTS) {
303
workbenchUrl = '../../../workbench/workbench.desktop.main.js'; // for dev purposes only
304
} else {
305
workbenchUrl = new URL(`vs/workbench/workbench.desktop.main.js`, baseUrl).href;
306
}
307
308
const result = await import(workbenchUrl);
309
if (developerDeveloperKeybindingsDisposable && removeDeveloperKeybindingsAfterLoad) {
310
developerDeveloperKeybindingsDisposable();
311
}
312
313
return { result, configuration };
314
} catch (error) {
315
onUnexpectedError(error, enableDeveloperKeybindings && !forceDisableShowDevtoolsOnError);
316
317
throw error;
318
}
319
}
320
321
async function resolveWindowConfiguration<T extends ISandboxConfiguration>() {
322
const timeout = setTimeout(() => { console.error(`[resolve window config] Could not resolve window configuration within 10 seconds, but will continue to wait...`); }, 10000);
323
performance.mark('code/willWaitForWindowConfig');
324
325
const configuration = await preloadGlobals.context.resolveConfiguration() as T;
326
performance.mark('code/didWaitForWindowConfig');
327
328
clearTimeout(timeout);
329
330
return configuration;
331
}
332
333
function setupDeveloperKeybindings<T extends ISandboxConfiguration>(configuration: T, options: ILoadOptions<T>) {
334
const {
335
forceEnableDeveloperKeybindings,
336
disallowReloadKeybinding,
337
removeDeveloperKeybindingsAfterLoad,
338
forceDisableShowDevtoolsOnError
339
} = typeof options?.configureDeveloperSettings === 'function' ? options.configureDeveloperSettings(configuration) : {
340
forceEnableDeveloperKeybindings: false,
341
disallowReloadKeybinding: false,
342
removeDeveloperKeybindingsAfterLoad: false,
343
forceDisableShowDevtoolsOnError: false
344
};
345
346
const isDev = !!safeProcess.env['VSCODE_DEV'];
347
const enableDeveloperKeybindings = Boolean(isDev || forceEnableDeveloperKeybindings);
348
let developerDeveloperKeybindingsDisposable: Function | undefined = undefined;
349
if (enableDeveloperKeybindings) {
350
developerDeveloperKeybindingsDisposable = registerDeveloperKeybindings(disallowReloadKeybinding);
351
}
352
353
return {
354
enableDeveloperKeybindings,
355
removeDeveloperKeybindingsAfterLoad,
356
developerDeveloperKeybindingsDisposable,
357
forceDisableShowDevtoolsOnError
358
};
359
}
360
361
function registerDeveloperKeybindings(disallowReloadKeybinding: boolean | undefined): Function {
362
const ipcRenderer = preloadGlobals.ipcRenderer;
363
364
const extractKey =
365
function (e: KeyboardEvent) {
366
return [
367
e.ctrlKey ? 'ctrl-' : '',
368
e.metaKey ? 'meta-' : '',
369
e.altKey ? 'alt-' : '',
370
e.shiftKey ? 'shift-' : '',
371
e.keyCode
372
].join('');
373
};
374
375
// Devtools & reload support
376
const TOGGLE_DEV_TOOLS_KB = (safeProcess.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
377
const TOGGLE_DEV_TOOLS_KB_ALT = '123'; // F12
378
const RELOAD_KB = (safeProcess.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
379
380
let listener: ((e: KeyboardEvent) => void) | undefined = function (e) {
381
const key = extractKey(e);
382
if (key === TOGGLE_DEV_TOOLS_KB || key === TOGGLE_DEV_TOOLS_KB_ALT) {
383
ipcRenderer.send('vscode:toggleDevTools');
384
} else if (key === RELOAD_KB && !disallowReloadKeybinding) {
385
ipcRenderer.send('vscode:reloadWindow');
386
}
387
};
388
389
window.addEventListener('keydown', listener);
390
391
return function () {
392
if (listener) {
393
window.removeEventListener('keydown', listener);
394
listener = undefined;
395
}
396
};
397
}
398
399
function setupNLS<T extends ISandboxConfiguration>(configuration: T): void {
400
globalThis._VSCODE_NLS_MESSAGES = configuration.nls.messages;
401
globalThis._VSCODE_NLS_LANGUAGE = configuration.nls.language;
402
403
let language = configuration.nls.language || 'en';
404
if (language === 'zh-tw') {
405
language = 'zh-Hant';
406
} else if (language === 'zh-cn') {
407
language = 'zh-Hans';
408
}
409
410
window.document.documentElement.setAttribute('lang', language);
411
}
412
413
function onUnexpectedError(error: string | Error, showDevtoolsOnError: boolean): void {
414
if (showDevtoolsOnError) {
415
const ipcRenderer = preloadGlobals.ipcRenderer;
416
ipcRenderer.send('vscode:openDevTools');
417
}
418
419
console.error(`[uncaught exception]: ${error}`);
420
421
if (error && typeof error !== 'string' && error.stack) {
422
console.error(error.stack);
423
}
424
}
425
426
function fileUriFromPath(path: string, config: { isWindows?: boolean; scheme?: string; fallbackAuthority?: string }): string {
427
428
// Since we are building a URI, we normalize any backslash
429
// to slashes and we ensure that the path begins with a '/'.
430
let pathName = path.replace(/\\/g, '/');
431
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
432
pathName = `/${pathName}`;
433
}
434
435
let uri: string;
436
437
// Windows: in order to support UNC paths (which start with '//')
438
// that have their own authority, we do not use the provided authority
439
// but rather preserve it.
440
if (config.isWindows && pathName.startsWith('//')) {
441
uri = encodeURI(`${config.scheme || 'file'}:${pathName}`);
442
}
443
444
// Otherwise we optionally add the provided authority if specified
445
else {
446
uri = encodeURI(`${config.scheme || 'file'}://${config.fallbackAuthority || ''}${pathName}`);
447
}
448
449
return uri.replace(/#/g, '%23');
450
}
451
452
function setupCSSImportMaps<T extends ISandboxConfiguration>(configuration: T, baseUrl: URL) {
453
454
// DEV ---------------------------------------------------------------------------------------
455
// DEV: This is for development and enables loading CSS via import-statements via import-maps.
456
// DEV: For each CSS modules that we have we defined an entry in the import map that maps to
457
// DEV: a blob URL that loads the CSS via a dynamic @import-rule.
458
// DEV ---------------------------------------------------------------------------------------
459
460
if (globalThis._VSCODE_DISABLE_CSS_IMPORT_MAP) {
461
return; // disabled in certain development setups
462
}
463
464
if (Array.isArray(configuration.cssModules) && configuration.cssModules.length > 0) {
465
performance.mark('code/willAddCssLoader');
466
467
globalThis._VSCODE_CSS_LOAD = function (url) {
468
const link = document.createElement('link');
469
link.setAttribute('rel', 'stylesheet');
470
link.setAttribute('type', 'text/css');
471
link.setAttribute('href', url);
472
473
window.document.head.appendChild(link);
474
};
475
476
const importMap: { imports: Record<string, string> } = { imports: {} };
477
for (const cssModule of configuration.cssModules) {
478
const cssUrl = new URL(cssModule, baseUrl).href;
479
const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n`;
480
const blob = new Blob([jsSrc], { type: 'application/javascript' });
481
importMap.imports[cssUrl] = URL.createObjectURL(blob);
482
}
483
484
const ttp = window.trustedTypes?.createPolicy('vscode-bootstrapImportMap', { createScript(value) { return value; }, });
485
const importMapSrc = JSON.stringify(importMap, undefined, 2);
486
const importMapScript = document.createElement('script');
487
importMapScript.type = 'importmap';
488
importMapScript.setAttribute('nonce', '0c6a828f1297');
489
// @ts-expect-error
490
importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc;
491
window.document.head.appendChild(importMapScript);
492
493
performance.mark('code/didAddCssLoader');
494
}
495
}
496
497
//#endregion
498
499
const { result, configuration } = await load<IDesktopMain, INativeWindowConfiguration>(
500
{
501
configureDeveloperSettings: function (windowConfig) {
502
return {
503
// disable automated devtools opening on error when running extension tests
504
// as this can lead to nondeterministic test execution (devtools steals focus)
505
forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string' || windowConfig['enable-smoke-test-driver'] === true,
506
// enable devtools keybindings in extension development window
507
forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0,
508
removeDeveloperKeybindingsAfterLoad: true
509
};
510
},
511
beforeImport: function (windowConfig) {
512
513
// Show our splash as early as possible
514
showSplash(windowConfig);
515
516
// Code windows have a `vscodeWindowId` property to identify them
517
Object.defineProperty(window, 'vscodeWindowId', {
518
get: () => windowConfig.windowId
519
});
520
521
// It looks like browsers only lazily enable
522
// the <canvas> element when needed. Since we
523
// leverage canvas elements in our code in many
524
// locations, we try to help the browser to
525
// initialize canvas when it is idle, right
526
// before we wait for the scripts to be loaded.
527
window.requestIdleCallback(() => {
528
const canvas = document.createElement('canvas');
529
const context = canvas.getContext('2d');
530
context?.clearRect(0, 0, canvas.width, canvas.height);
531
canvas.remove();
532
}, { timeout: 50 });
533
534
// Track import() perf
535
performance.mark('code/willLoadWorkbenchMain');
536
}
537
}
538
);
539
540
// Mark start of workbench
541
performance.mark('code/didLoadWorkbenchMain');
542
543
// Load workbench
544
result.main(configuration);
545
}());
546
547