dom.ts 34.8 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

A
Alex Dima 已提交
6
import * as browser from 'vs/base/browser/browser';
A
Alex Dima 已提交
7
import { domEvent } from 'vs/base/browser/event';
J
Johannes Rieken 已提交
8 9
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
A
Alex Dima 已提交
10
import { TimeoutTimer } from 'vs/base/common/async';
J
Johannes Rieken 已提交
11
import { CharCode } from 'vs/base/common/charCode';
A
Alex Dima 已提交
12 13
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
14
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
15
import * as platform from 'vs/base/common/platform';
M
Matt Bierner 已提交
16
import { coalesce } from 'vs/base/common/arrays';
E
Erich Gamma 已提交
17

B
Benjamin Pasero 已提交
18
export function clearNode(node: HTMLElement): void {
E
Erich Gamma 已提交
19 20 21 22
	while (node.firstChild) {
		node.removeChild(node.firstChild);
	}
}
A
Alex Dima 已提交
23 24 25 26 27 28

export function removeNode(node: HTMLElement): void {
	if (node.parentNode) {
		node.parentNode.removeChild(node);
	}
}
E
Erich Gamma 已提交
29

A
Alex Dima 已提交
30
export function isInDOM(node: Node | null): boolean {
E
Erich Gamma 已提交
31 32 33 34 35 36 37 38 39
	while (node) {
		if (node === document.body) {
			return true;
		}
		node = node.parentNode;
	}
	return false;
}

A
Alex Dima 已提交
40 41 42
interface IDomClassList {
	hasClass(node: HTMLElement, className: string): boolean;
	addClass(node: HTMLElement, className: string): void;
43
	addClasses(node: HTMLElement, ...classNames: string[]): void;
A
Alex Dima 已提交
44
	removeClass(node: HTMLElement, className: string): void;
45
	removeClasses(node: HTMLElement, ...classNames: string[]): void;
A
Alex Dima 已提交
46 47 48 49
	toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void;
}

const _manualClassList = new class implements IDomClassList {
E
Erich Gamma 已提交
50

51 52
	private _lastStart: number;
	private _lastEnd: number;
E
Erich Gamma 已提交
53

54
	private _findClassName(node: HTMLElement, className: string): void {
E
Erich Gamma 已提交
55

56 57 58 59 60
		let classes = node.className;
		if (!classes) {
			this._lastStart = -1;
			return;
		}
E
Erich Gamma 已提交
61

62
		className = className.trim();
E
Erich Gamma 已提交
63

64 65
		let classesLen = classes.length,
			classLen = className.length;
E
Erich Gamma 已提交
66

67 68
		if (classLen === 0) {
			this._lastStart = -1;
E
Erich Gamma 已提交
69 70 71
			return;
		}

72 73
		if (classesLen < classLen) {
			this._lastStart = -1;
E
Erich Gamma 已提交
74 75 76
			return;
		}

77 78 79
		if (classes === className) {
			this._lastStart = 0;
			this._lastEnd = classesLen;
E
Erich Gamma 已提交
80 81
			return;
		}
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

		let idx = -1,
			idxEnd: number;

		while ((idx = classes.indexOf(className, idx + 1)) >= 0) {

			idxEnd = idx + classLen;

			// a class that is followed by another class
			if ((idx === 0 || classes.charCodeAt(idx - 1) === CharCode.Space) && classes.charCodeAt(idxEnd) === CharCode.Space) {
				this._lastStart = idx;
				this._lastEnd = idxEnd + 1;
				return;
			}

			// last class
			if (idx > 0 && classes.charCodeAt(idx - 1) === CharCode.Space && idxEnd === classesLen) {
				this._lastStart = idx - 1;
				this._lastEnd = idxEnd;
				return;
			}

			// equal - duplicate of cmp above
			if (idx === 0 && idxEnd === classesLen) {
				this._lastStart = 0;
				this._lastEnd = idxEnd;
				return;
			}
		}

		this._lastStart = -1;
E
Erich Gamma 已提交
113 114
	}

115 116 117 118
	hasClass(node: HTMLElement, className: string): boolean {
		this._findClassName(node, className);
		return this._lastStart !== -1;
	}
E
Erich Gamma 已提交
119

120 121 122 123
	addClasses(node: HTMLElement, ...classNames: string[]): void {
		classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.addClass(node, name)));
	}

124 125 126 127 128 129 130 131 132 133
	addClass(node: HTMLElement, className: string): void {
		if (!node.className) { // doesn't have it for sure
			node.className = className;
		} else {
			this._findClassName(node, className); // see if it's already there
			if (this._lastStart === -1) {
				node.className = node.className + ' ' + className;
			}
		}
	}
E
Erich Gamma 已提交
134

135 136 137 138 139 140
	removeClass(node: HTMLElement, className: string): void {
		this._findClassName(node, className);
		if (this._lastStart === -1) {
			return; // Prevent styles invalidation if not necessary
		} else {
			node.className = node.className.substring(0, this._lastStart) + node.className.substring(this._lastEnd);
E
Erich Gamma 已提交
141 142 143
		}
	}

144 145 146 147
	removeClasses(node: HTMLElement, ...classNames: string[]): void {
		classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.removeClass(node, name)));
	}

148 149
	toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void {
		this._findClassName(node, className);
R
Rob Lourens 已提交
150
		if (this._lastStart !== -1 && (shouldHaveIt === undefined || !shouldHaveIt)) {
151 152
			this.removeClass(node, className);
		}
R
Rob Lourens 已提交
153
		if (this._lastStart === -1 && (shouldHaveIt === undefined || shouldHaveIt)) {
154 155
			this.addClass(node, className);
		}
E
Erich Gamma 已提交
156
	}
157
};
E
Erich Gamma 已提交
158

A
Alex Dima 已提交
159
const _nativeClassList = new class implements IDomClassList {
160
	hasClass(node: HTMLElement, className: string): boolean {
A
Alex Dima 已提交
161
		return Boolean(className) && node.classList && node.classList.contains(className);
E
Erich Gamma 已提交
162
	}
163

164 165 166 167
	addClasses(node: HTMLElement, ...classNames: string[]): void {
		classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.addClass(node, name)));
	}

168
	addClass(node: HTMLElement, className: string): void {
J
Johannes Rieken 已提交
169 170 171
		if (className && node.classList) {
			node.classList.add(className);
		}
E
Erich Gamma 已提交
172
	}
173 174

	removeClass(node: HTMLElement, className: string): void {
J
Johannes Rieken 已提交
175 176 177
		if (className && node.classList) {
			node.classList.remove(className);
		}
178 179
	}

180 181 182 183
	removeClasses(node: HTMLElement, ...classNames: string[]): void {
		classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.removeClass(node, name)));
	}

184
	toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void {
J
Johannes Rieken 已提交
185 186 187
		if (node.classList) {
			node.classList.toggle(className, shouldHaveIt);
		}
188 189 190 191 192
	}
};

// In IE11 there is only partial support for `classList` which makes us keep our
// custom implementation. Otherwise use the native implementation, see: http://caniuse.com/#search=classlist
A
Alex Dima 已提交
193
const _classList: IDomClassList = browser.isIE ? _manualClassList : _nativeClassList;
194 195
export const hasClass: (node: HTMLElement, className: string) => boolean = _classList.hasClass.bind(_classList);
export const addClass: (node: HTMLElement, className: string) => void = _classList.addClass.bind(_classList);
196
export const addClasses: (node: HTMLElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList);
197
export const removeClass: (node: HTMLElement, className: string) => void = _classList.removeClass.bind(_classList);
198
export const removeClasses: (node: HTMLElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList);
199
export const toggleClass: (node: HTMLElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList);
E
Erich Gamma 已提交
200

A
Alex Dima 已提交
201
class DomListener implements IDisposable {
E
Erich Gamma 已提交
202

A
Alex Dima 已提交
203 204 205 206
	private _handler: (e: any) => void;
	private _node: Element | Window | Document;
	private readonly _type: string;
	private readonly _useCapture: boolean;
E
Erich Gamma 已提交
207

A
Alex Dima 已提交
208
	constructor(node: Element | Window | Document, type: string, handler: (e: any) => void, useCapture?: boolean) {
209 210
		this._node = node;
		this._type = type;
A
Alex Dima 已提交
211
		this._handler = handler;
212
		this._useCapture = (useCapture || false);
213
		this._node.addEventListener(this._type, this._handler, this._useCapture);
E
Erich Gamma 已提交
214 215
	}

216
	public dispose(): void {
A
Alex Dima 已提交
217
		if (!this._handler) {
218 219 220 221
			// Already disposed
			return;
		}

A
Alex Dima 已提交
222
		this._node.removeEventListener(this._type, this._handler, this._useCapture);
223 224

		// Prevent leakers from holding on to the dom or handler func
A
Alex Dima 已提交
225 226
		this._node = null!;
		this._handler = null!;
227
	}
E
Erich Gamma 已提交
228
}
M
Matt Bierner 已提交
229

230 231
export function addDisposableListener<K extends keyof GlobalEventHandlersEventMap>(node: Element | Window | Document, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable;
export function addDisposableListener(node: Element | Window | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable;
232 233
export function addDisposableListener(node: Element | Window | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
	return new DomListener(node, type, handler, useCapture);
E
Erich Gamma 已提交
234 235 236
}

export interface IAddStandardDisposableListenerSignature {
B
Benjamin Pasero 已提交
237
	(node: HTMLElement, type: 'click', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable;
A
Alex Dima 已提交
238
	(node: HTMLElement, type: 'mousedown', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable;
B
Benjamin Pasero 已提交
239 240 241 242
	(node: HTMLElement, type: 'keydown', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
	(node: HTMLElement, type: 'keypress', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
	(node: HTMLElement, type: 'keyup', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
	(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable;
E
Erich Gamma 已提交
243
}
B
Benjamin Pasero 已提交
244
function _wrapAsStandardMouseEvent(handler: (e: IMouseEvent) => void): (e: MouseEvent) => void {
245
	return function (e: MouseEvent) {
A
Cleanup  
Alex Dima 已提交
246
		return handler(new StandardMouseEvent(e));
E
Erich Gamma 已提交
247 248
	};
}
B
Benjamin Pasero 已提交
249
function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e: KeyboardEvent) => void {
250
	return function (e: KeyboardEvent) {
A
Cleanup  
Alex Dima 已提交
251
		return handler(new StandardKeyboardEvent(e));
E
Erich Gamma 已提交
252 253
	};
}
B
Benjamin Pasero 已提交
254 255
export let addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
	let wrapHandler = handler;
E
Erich Gamma 已提交
256

A
Alex Dima 已提交
257
	if (type === 'click' || type === 'mousedown') {
E
Erich Gamma 已提交
258 259 260 261 262
		wrapHandler = _wrapAsStandardMouseEvent(handler);
	} else if (type === 'keydown' || type === 'keypress' || type === 'keyup') {
		wrapHandler = _wrapAsStandardKeyboardEvent(handler);
	}

263
	return addDisposableListener(node, type, wrapHandler, useCapture);
E
Erich Gamma 已提交
264 265
};

266 267
export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
	return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
E
Erich Gamma 已提交
268
		// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
R
Rob Lourens 已提交
269
		let toElement: Node | null = <Node>(e.relatedTarget || e.target);
E
Erich Gamma 已提交
270 271 272 273 274 275 276 277 278 279 280
		while (toElement && toElement !== node) {
			toElement = toElement.parentNode;
		}
		if (toElement === node) {
			return;
		}

		handler(e);
	});
}

A
Alex Dima 已提交
281 282 283
interface IRequestAnimationFrame {
	(callback: (time: number) => void): number;
}
284
let _animationFrame: IRequestAnimationFrame | null = null;
A
Alex Dima 已提交
285 286
function doRequestAnimationFrame(callback: (time: number) => void): number {
	if (!_animationFrame) {
287
		const emulatedRequestAnimationFrame = (callback: (time: number) => void): any => {
A
Alex Dima 已提交
288 289 290 291 292 293 294 295 296 297 298
			return setTimeout(() => callback(new Date().getTime()), 0);
		};
		_animationFrame = (
			self.requestAnimationFrame
			|| (<any>self).msRequestAnimationFrame
			|| (<any>self).webkitRequestAnimationFrame
			|| (<any>self).mozRequestAnimationFrame
			|| (<any>self).oRequestAnimationFrame
			|| emulatedRequestAnimationFrame
		);
	}
A
Alex Dima 已提交
299
	return _animationFrame.call(self, callback);
A
Alex Dima 已提交
300
}
E
Erich Gamma 已提交
301 302 303 304 305 306 307

/**
 * Schedule a callback to be run at the next animation frame.
 * This allows multiple parties to register callbacks that should run at the next animation frame.
 * If currently in an animation frame, `runner` will be executed immediately.
 * @return token that can be used to cancel the scheduled runner (only if `runner` was not executed immediately).
 */
B
Benjamin Pasero 已提交
308
export let runAtThisOrScheduleAtNextAnimationFrame: (runner: () => void, priority?: number) => IDisposable;
E
Erich Gamma 已提交
309 310 311 312 313 314
/**
 * Schedule a callback to be run at the next animation frame.
 * This allows multiple parties to register callbacks that should run at the next animation frame.
 * If currently in an animation frame, `runner` will be executed at the next animation frame.
 * @return token that can be used to cancel the scheduled runner.
 */
B
Benjamin Pasero 已提交
315
export let scheduleAtNextAnimationFrame: (runner: () => void, priority?: number) => IDisposable;
E
Erich Gamma 已提交
316

B
Benjamin Pasero 已提交
317
class AnimationFrameQueueItem implements IDisposable {
E
Erich Gamma 已提交
318 319 320 321 322

	private _runner: () => void;
	public priority: number;
	private _canceled: boolean;

A
Alex Dima 已提交
323
	constructor(runner: () => void, priority: number = 0) {
E
Erich Gamma 已提交
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
		this._runner = runner;
		this.priority = priority;
		this._canceled = false;
	}

	public dispose(): void {
		this._canceled = true;
	}

	public execute(): void {
		if (this._canceled) {
			return;
		}

		try {
			this._runner();
		} catch (e) {
B
Benjamin Pasero 已提交
341
			onUnexpectedError(e);
E
Erich Gamma 已提交
342 343 344 345 346 347 348 349 350
		}
	}

	// Sort by priority (largest to lowest)
	public static sort(a: AnimationFrameQueueItem, b: AnimationFrameQueueItem): number {
		return b.priority - a.priority;
	}
}

351
(function () {
E
Erich Gamma 已提交
352 353 354
	/**
	 * The runners scheduled at the next animation frame
	 */
B
Benjamin Pasero 已提交
355
	let NEXT_QUEUE: AnimationFrameQueueItem[] = [];
E
Erich Gamma 已提交
356 357 358
	/**
	 * The runners scheduled at the current animation frame
	 */
359
	let CURRENT_QUEUE: AnimationFrameQueueItem[] | null = null;
E
Erich Gamma 已提交
360 361 362
	/**
	 * A flag to keep track if the native requestAnimationFrame was already called
	 */
B
Benjamin Pasero 已提交
363
	let animFrameRequested = false;
E
Erich Gamma 已提交
364 365 366
	/**
	 * A flag to indicate if currently handling a native requestAnimationFrame callback
	 */
B
Benjamin Pasero 已提交
367
	let inAnimationFrameRunner = false;
E
Erich Gamma 已提交
368

B
Benjamin Pasero 已提交
369
	let animationFrameRunner = () => {
E
Erich Gamma 已提交
370 371 372 373 374 375 376 377
		animFrameRequested = false;

		CURRENT_QUEUE = NEXT_QUEUE;
		NEXT_QUEUE = [];

		inAnimationFrameRunner = true;
		while (CURRENT_QUEUE.length > 0) {
			CURRENT_QUEUE.sort(AnimationFrameQueueItem.sort);
A
Alex Dima 已提交
378
			let top = CURRENT_QUEUE.shift()!;
E
Erich Gamma 已提交
379 380 381 382 383
			top.execute();
		}
		inAnimationFrameRunner = false;
	};

B
Benjamin Pasero 已提交
384 385
	scheduleAtNextAnimationFrame = (runner: () => void, priority: number = 0) => {
		let item = new AnimationFrameQueueItem(runner, priority);
E
Erich Gamma 已提交
386 387 388 389
		NEXT_QUEUE.push(item);

		if (!animFrameRequested) {
			animFrameRequested = true;
A
Alex Dima 已提交
390
			doRequestAnimationFrame(animationFrameRunner);
E
Erich Gamma 已提交
391 392 393 394 395
		}

		return item;
	};

B
Benjamin Pasero 已提交
396
	runAtThisOrScheduleAtNextAnimationFrame = (runner: () => void, priority?: number) => {
E
Erich Gamma 已提交
397
		if (inAnimationFrameRunner) {
B
Benjamin Pasero 已提交
398
			let item = new AnimationFrameQueueItem(runner, priority);
A
Alex Dima 已提交
399
			CURRENT_QUEUE!.push(item);
E
Erich Gamma 已提交
400 401 402 403 404 405 406
			return item;
		} else {
			return scheduleAtNextAnimationFrame(runner, priority);
		}
	};
})();

407 408 409 410 411 412 413 414
export function measure(callback: () => void): IDisposable {
	return scheduleAtNextAnimationFrame(callback, 10000 /* must be early */);
}

export function modify(callback: () => void): IDisposable {
	return scheduleAtNextAnimationFrame(callback, -10000 /* must be late */);
}

A
Alex Dima 已提交
415 416 417
/**
 * Add a throttled listener. `handler` is fired at most every 16ms or with the next animation frame (if browser supports it).
 */
418
export interface IEventMerger<R, E> {
A
Alex Dima 已提交
419
	(lastEvent: R | null, currentEvent: E): R;
E
Erich Gamma 已提交
420 421
}

422 423 424 425 426
export interface DOMEvent {
	preventDefault(): void;
	stopPropagation(): void;
}

B
Benjamin Pasero 已提交
427
const MINIMUM_TIME_MS = 16;
428
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent, currentEvent: DOMEvent) {
E
Erich Gamma 已提交
429 430 431
	return currentEvent;
};

432
class TimeoutThrottledDomListener<R, E extends DOMEvent> extends Disposable {
E
Erich Gamma 已提交
433

434
	constructor(node: any, type: string, handler: (event: R) => void, eventMerger: IEventMerger<R, E> = <any>DEFAULT_EVENT_MERGER, minimumTimeMs: number = MINIMUM_TIME_MS) {
435
		super();
E
Erich Gamma 已提交
436

437
		let lastEvent: R | null = null;
438 439
		let lastHandlerTime = 0;
		let timeout = this._register(new TimeoutTimer());
E
Erich Gamma 已提交
440

441 442
		let invokeHandler = () => {
			lastHandlerTime = (new Date()).getTime();
A
Alex Dima 已提交
443
			handler(<R>lastEvent);
444 445
			lastEvent = null;
		};
E
Erich Gamma 已提交
446

447
		this._register(addDisposableListener(node, type, (e) => {
E
Erich Gamma 已提交
448

449 450 451 452 453 454 455 456 457 458 459
			lastEvent = eventMerger(lastEvent, e);
			let elapsedTime = (new Date()).getTime() - lastHandlerTime;

			if (elapsedTime >= minimumTimeMs) {
				timeout.cancel();
				invokeHandler();
			} else {
				timeout.setIfNotSet(invokeHandler, minimumTimeMs - elapsedTime);
			}
		}));
	}
E
Erich Gamma 已提交
460 461
}

462
export function addDisposableThrottledListener<R, E extends DOMEvent = DOMEvent>(node: any, type: string, handler: (event: R) => void, eventMerger?: IEventMerger<R, E>, minimumTimeMs?: number): IDisposable {
463
	return new TimeoutThrottledDomListener<R, E>(node, type, handler, eventMerger, minimumTimeMs);
E
Erich Gamma 已提交
464 465
}

B
Benjamin Pasero 已提交
466
export function getComputedStyle(el: HTMLElement): CSSStyleDeclaration {
A
Alex Dima 已提交
467
	return document.defaultView!.getComputedStyle(el, null);
E
Erich Gamma 已提交
468 469
}

470 471 472 473 474 475 476 477 478 479 480 481
export function getClientArea(element: HTMLElement): Dimension {

	// Try with DOM clientWidth / clientHeight
	if (element !== document.body) {
		return new Dimension(element.clientWidth, element.clientHeight);
	}

	// Try innerWidth / innerHeight
	if (window.innerWidth && window.innerHeight) {
		return new Dimension(window.innerWidth, window.innerHeight);
	}

482 483
	// Try with document.body.clientWidth / document.body.clientHeight
	if (document.body && document.body.clientWidth && document.body.clientHeight) {
484 485 486 487 488 489 490 491 492 493 494
		return new Dimension(document.body.clientWidth, document.body.clientHeight);
	}

	// Try with document.documentElement.clientWidth / document.documentElement.clientHeight
	if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientHeight) {
		return new Dimension(document.documentElement.clientWidth, document.documentElement.clientHeight);
	}

	throw new Error('Unable to figure out browser width and height');
}

495
class SizeUtils {
496 497 498 499 500 501
	// Adapted from WinJS
	// Converts a CSS positioning string for the specified element to pixels.
	private static convertToPixels(element: HTMLElement, value: string): number {
		return parseFloat(value) || 0;
	}

M
Matt Bierner 已提交
502 503 504 505 506 507 508 509 510 511 512
	private static getDimension(element: HTMLElement, cssPropertyName: string, jsPropertyName: string): number {
		let computedStyle: CSSStyleDeclaration = getComputedStyle(element);
		let value = '0';
		if (computedStyle) {
			if (computedStyle.getPropertyValue) {
				value = computedStyle.getPropertyValue(cssPropertyName);
			} else {
				// IE8
				value = (<any>computedStyle).getAttribute(jsPropertyName);
			}
		}
513
		return SizeUtils.convertToPixels(element, value);
M
Matt Bierner 已提交
514
	}
E
Erich Gamma 已提交
515

516
	static getBorderLeftWidth(element: HTMLElement): number {
M
Matt Bierner 已提交
517
		return SizeUtils.getDimension(element, 'border-left-width', 'borderLeftWidth');
518 519
	}
	static getBorderRightWidth(element: HTMLElement): number {
M
Matt Bierner 已提交
520
		return SizeUtils.getDimension(element, 'border-right-width', 'borderRightWidth');
521 522
	}
	static getBorderTopWidth(element: HTMLElement): number {
M
Matt Bierner 已提交
523
		return SizeUtils.getDimension(element, 'border-top-width', 'borderTopWidth');
524 525
	}
	static getBorderBottomWidth(element: HTMLElement): number {
M
Matt Bierner 已提交
526
		return SizeUtils.getDimension(element, 'border-bottom-width', 'borderBottomWidth');
527
	}
E
Erich Gamma 已提交
528

529
	static getPaddingLeft(element: HTMLElement): number {
M
Matt Bierner 已提交
530
		return SizeUtils.getDimension(element, 'padding-left', 'paddingLeft');
531 532
	}
	static getPaddingRight(element: HTMLElement): number {
M
Matt Bierner 已提交
533
		return SizeUtils.getDimension(element, 'padding-right', 'paddingRight');
534 535
	}
	static getPaddingTop(element: HTMLElement): number {
M
Matt Bierner 已提交
536
		return SizeUtils.getDimension(element, 'padding-top', 'paddingTop');
537 538
	}
	static getPaddingBottom(element: HTMLElement): number {
M
Matt Bierner 已提交
539
		return SizeUtils.getDimension(element, 'padding-bottom', 'paddingBottom');
540
	}
E
Erich Gamma 已提交
541

542
	static getMarginLeft(element: HTMLElement): number {
M
Matt Bierner 已提交
543
		return SizeUtils.getDimension(element, 'margin-left', 'marginLeft');
544 545
	}
	static getMarginTop(element: HTMLElement): number {
M
Matt Bierner 已提交
546
		return SizeUtils.getDimension(element, 'margin-top', 'marginTop');
547 548
	}
	static getMarginRight(element: HTMLElement): number {
M
Matt Bierner 已提交
549
		return SizeUtils.getDimension(element, 'margin-right', 'marginRight');
550 551
	}
	static getMarginBottom(element: HTMLElement): number {
M
Matt Bierner 已提交
552
		return SizeUtils.getDimension(element, 'margin-bottom', 'marginBottom');
553 554
	}
}
E
Erich Gamma 已提交
555 556 557 558

// ----------------------------------------------------------------------------------------
// Position & Dimension

559 560 561 562 563 564 565 566
export class Dimension {
	public width: number;
	public height: number;

	constructor(width: number, height: number) {
		this.width = width;
		this.height = height;
	}
567

M
Matt Bierner 已提交
568
	static equals(a: Dimension | undefined, b: Dimension | undefined): boolean {
569 570 571 572 573 574 575 576
		if (a === b) {
			return true;
		}
		if (!a || !b) {
			return false;
		}
		return a.width === b.width && a.height === b.height;
	}
577 578
}

B
Benjamin Pasero 已提交
579
export function getTopLeftOffset(element: HTMLElement): { left: number; top: number; } {
E
Erich Gamma 已提交
580 581 582
	// Adapted from WinJS.Utilities.getPosition
	// and added borders to the mix

B
Benjamin Pasero 已提交
583
	let offsetParent = element.offsetParent, top = element.offsetTop, left = element.offsetLeft;
E
Erich Gamma 已提交
584 585 586

	while ((element = <HTMLElement>element.parentNode) !== null && element !== document.body && element !== document.documentElement) {
		top -= element.scrollTop;
B
Benjamin Pasero 已提交
587
		let c = getComputedStyle(element);
E
Erich Gamma 已提交
588 589 590 591 592
		if (c) {
			left -= c.direction !== 'rtl' ? element.scrollLeft : -element.scrollLeft;
		}

		if (element === offsetParent) {
593 594
			left += SizeUtils.getBorderLeftWidth(element);
			top += SizeUtils.getBorderTopWidth(element);
E
Erich Gamma 已提交
595 596 597 598 599 600 601 602 603 604 605 606
			top += element.offsetTop;
			left += element.offsetLeft;
			offsetParent = element.offsetParent;
		}
	}

	return {
		left: left,
		top: top
	};
}

607
export interface IDomNodePagePosition {
B
Benjamin Pasero 已提交
608 609 610 611
	left: number;
	top: number;
	width: number;
	height: number;
E
Erich Gamma 已提交
612 613
}

614
export function size(element: HTMLElement, width: number | null, height: number | null): void {
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
	if (typeof width === 'number') {
		element.style.width = `${width}px`;
	}

	if (typeof height === 'number') {
		element.style.height = `${height}px`;
	}
}

export function position(element: HTMLElement, top: number, right?: number, bottom?: number, left?: number, position: string = 'absolute'): void {
	if (typeof top === 'number') {
		element.style.top = `${top}px`;
	}

	if (typeof right === 'number') {
		element.style.right = `${right}px`;
	}

	if (typeof bottom === 'number') {
		element.style.bottom = `${bottom}px`;
	}

	if (typeof left === 'number') {
		element.style.left = `${left}px`;
	}

	element.style.position = position;
}

644 645 646 647 648
/**
 * Returns the position of a dom node relative to the entire page.
 */
export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition {
	let bb = domNode.getBoundingClientRect();
E
Erich Gamma 已提交
649
	return {
650 651
		left: bb.left + StandardWindow.scrollX,
		top: bb.top + StandardWindow.scrollY,
652 653
		width: bb.width,
		height: bb.height
E
Erich Gamma 已提交
654 655 656
	};
}

657
export interface IStandardWindow {
658 659
	readonly scrollX: number;
	readonly scrollY: number;
660 661
}

662
export const StandardWindow: IStandardWindow = new class implements IStandardWindow {
663 664 665 666 667
	get scrollX(): number {
		if (typeof window.scrollX === 'number') {
			// modern browsers
			return window.scrollX;
		} else {
A
Alex Dima 已提交
668
			return document.body.scrollLeft + document.documentElement!.scrollLeft;
669 670 671 672 673 674 675 676
		}
	}

	get scrollY(): number {
		if (typeof window.scrollY === 'number') {
			// modern browsers
			return window.scrollY;
		} else {
A
Alex Dima 已提交
677
			return document.body.scrollTop + document.documentElement!.scrollTop;
678 679 680 681
		}
	}
};

E
Erich Gamma 已提交
682 683
// Adapted from WinJS
// Gets the width of the element, including margins.
B
Benjamin Pasero 已提交
684
export function getTotalWidth(element: HTMLElement): number {
685
	let margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
E
Erich Gamma 已提交
686 687 688
	return element.offsetWidth + margin;
}

J
Joao Moreno 已提交
689
export function getContentWidth(element: HTMLElement): number {
690 691
	let border = SizeUtils.getBorderLeftWidth(element) + SizeUtils.getBorderRightWidth(element);
	let padding = SizeUtils.getPaddingLeft(element) + SizeUtils.getPaddingRight(element);
J
Joao Moreno 已提交
692 693 694
	return element.offsetWidth - border - padding;
}

695
export function getTotalScrollWidth(element: HTMLElement): number {
696
	let margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
697 698 699
	return element.scrollWidth + margin;
}

E
Erich Gamma 已提交
700 701
// Adapted from WinJS
// Gets the height of the content of the specified element. The content height does not include borders or padding.
B
Benjamin Pasero 已提交
702
export function getContentHeight(element: HTMLElement): number {
703 704
	let border = SizeUtils.getBorderTopWidth(element) + SizeUtils.getBorderBottomWidth(element);
	let padding = SizeUtils.getPaddingTop(element) + SizeUtils.getPaddingBottom(element);
E
Erich Gamma 已提交
705 706 707 708 709
	return element.offsetHeight - border - padding;
}

// Adapted from WinJS
// Gets the height of the element, including its margins.
B
Benjamin Pasero 已提交
710
export function getTotalHeight(element: HTMLElement): number {
711
	let margin = SizeUtils.getMarginTop(element) + SizeUtils.getMarginBottom(element);
E
Erich Gamma 已提交
712 713 714 715
	return element.offsetHeight + margin;
}

// Gets the left coordinate of the specified element relative to the specified parent.
716
function getRelativeLeft(element: HTMLElement, parent: HTMLElement): number {
E
Erich Gamma 已提交
717 718 719 720
	if (element === null) {
		return 0;
	}

M
Maxime Quandalle 已提交
721 722 723
	let elementPosition = getTopLeftOffset(element);
	let parentPosition = getTopLeftOffset(parent);
	return elementPosition.left - parentPosition.left;
E
Erich Gamma 已提交
724 725
}

M
Maxime Quandalle 已提交
726 727
export function getLargestChildWidth(parent: HTMLElement, children: HTMLElement[]): number {
	let childWidths = children.map((child) => {
728
		return Math.max(getTotalScrollWidth(child), getTotalWidth(child)) + getRelativeLeft(child, parent) || 0;
M
Maxime Quandalle 已提交
729 730 731
	});
	let maxWidth = Math.max(...childWidths);
	return maxWidth;
E
Erich Gamma 已提交
732 733 734 735
}

// ----------------------------------------------------------------------------------------

A
Alex Dima 已提交
736
export function isAncestor(testChild: Node | null, testAncestor: Node | null): boolean {
B
Benjamin Pasero 已提交
737
	while (testChild) {
E
Erich Gamma 已提交
738 739 740 741 742 743 744 745 746
		if (testChild === testAncestor) {
			return true;
		}
		testChild = testChild.parentNode;
	}

	return false;
}

A
Alex Dima 已提交
747
export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClazzOrNode?: string | HTMLElement): HTMLElement | null {
E
Erich Gamma 已提交
748 749 750 751 752
	while (node) {
		if (hasClass(node, clazz)) {
			return node;
		}

B
Benjamin Pasero 已提交
753 754 755 756 757 758 759 760 761 762
		if (stopAtClazzOrNode) {
			if (typeof stopAtClazzOrNode === 'string') {
				if (hasClass(node, stopAtClazzOrNode)) {
					return null;
				}
			} else {
				if (node === stopAtClazzOrNode) {
					return null;
				}
			}
E
Erich Gamma 已提交
763 764 765 766 767 768 769 770
		}

		node = <HTMLElement>node.parentNode;
	}

	return null;
}

771
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement {
B
Benjamin Pasero 已提交
772
	let style = document.createElement('style');
E
Erich Gamma 已提交
773 774
	style.type = 'text/css';
	style.media = 'screen';
775
	container.appendChild(style);
E
Erich Gamma 已提交
776 777 778
	return style;
}

779
let _sharedStyleSheet: HTMLStyleElement | null = null;
A
Alex Dima 已提交
780 781 782 783 784 785
function getSharedStyleSheet(): HTMLStyleElement {
	if (!_sharedStyleSheet) {
		_sharedStyleSheet = createStyleSheet();
	}
	return _sharedStyleSheet;
}
E
Erich Gamma 已提交
786

B
Benjamin Pasero 已提交
787
function getDynamicStyleSheetRules(style: any) {
E
Erich Gamma 已提交
788 789 790 791 792 793 794 795 796 797 798
	if (style && style.sheet && style.sheet.rules) {
		// Chrome, IE
		return style.sheet.rules;
	}
	if (style && style.sheet && style.sheet.cssRules) {
		// FF
		return style.sheet.cssRules;
	}
	return [];
}

A
Alex Dima 已提交
799
export function createCSSRule(selector: string, cssText: string, style: HTMLStyleElement = getSharedStyleSheet()): void {
E
Erich Gamma 已提交
800 801 802 803
	if (!style || !cssText) {
		return;
	}

804
	(<CSSStyleSheet>style.sheet).insertRule(selector + '{' + cssText + '}', 0);
E
Erich Gamma 已提交
805 806
}

A
Alex Dima 已提交
807
export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLStyleElement = getSharedStyleSheet()): void {
E
Erich Gamma 已提交
808 809 810 811
	if (!style) {
		return;
	}

B
Benjamin Pasero 已提交
812 813 814 815
	let rules = getDynamicStyleSheetRules(style);
	let toDelete: number[] = [];
	for (let i = 0; i < rules.length; i++) {
		let rule = rules[i];
816
		if (rule.selectorText.indexOf(ruleName) !== -1) {
E
Erich Gamma 已提交
817 818 819 820
			toDelete.push(i);
		}
	}

B
Benjamin Pasero 已提交
821
	for (let i = toDelete.length - 1; i >= 0; i--) {
A
Alex Dima 已提交
822
		(<any>style.sheet).deleteRule(toDelete[i]);
E
Erich Gamma 已提交
823 824 825
	}
}

B
Benjamin Pasero 已提交
826
export function isHTMLElement(o: any): o is HTMLElement {
827 828 829 830
	if (typeof HTMLElement === 'object') {
		return o instanceof HTMLElement;
	}
	return o && typeof o === 'object' && o.nodeType === 1 && typeof o.nodeName === 'string';
E
Erich Gamma 已提交
831 832
}

B
Benjamin Pasero 已提交
833
export const EventType = {
E
Erich Gamma 已提交
834
	// Mouse
835 836 837 838 839 840 841 842 843 844 845
	CLICK: 'click',
	DBLCLICK: 'dblclick',
	MOUSE_UP: 'mouseup',
	MOUSE_DOWN: 'mousedown',
	MOUSE_OVER: 'mouseover',
	MOUSE_MOVE: 'mousemove',
	MOUSE_OUT: 'mouseout',
	MOUSE_ENTER: 'mouseenter',
	MOUSE_LEAVE: 'mouseleave',
	CONTEXT_MENU: 'contextmenu',
	WHEEL: 'wheel',
E
Erich Gamma 已提交
846
	// Keyboard
847 848 849
	KEY_DOWN: 'keydown',
	KEY_PRESS: 'keypress',
	KEY_UP: 'keyup',
E
Erich Gamma 已提交
850
	// HTML Document
851 852 853 854 855 856
	LOAD: 'load',
	UNLOAD: 'unload',
	ABORT: 'abort',
	ERROR: 'error',
	RESIZE: 'resize',
	SCROLL: 'scroll',
E
Erich Gamma 已提交
857
	// Form
858 859 860 861 862 863 864 865 866
	SELECT: 'select',
	CHANGE: 'change',
	SUBMIT: 'submit',
	RESET: 'reset',
	FOCUS: 'focus',
	FOCUS_IN: 'focusin',
	FOCUS_OUT: 'focusout',
	BLUR: 'blur',
	INPUT: 'input',
E
Erich Gamma 已提交
867
	// Local Storage
868
	STORAGE: 'storage',
E
Erich Gamma 已提交
869
	// Drag
870 871 872 873 874 875 876
	DRAG_START: 'dragstart',
	DRAG: 'drag',
	DRAG_ENTER: 'dragenter',
	DRAG_LEAVE: 'dragleave',
	DRAG_OVER: 'dragover',
	DROP: 'drop',
	DRAG_END: 'dragend',
E
Erich Gamma 已提交
877
	// Animation
A
Alex Dima 已提交
878 879 880
	ANIMATION_START: browser.isWebKit ? 'webkitAnimationStart' : 'animationstart',
	ANIMATION_END: browser.isWebKit ? 'webkitAnimationEnd' : 'animationend',
	ANIMATION_ITERATION: browser.isWebKit ? 'webkitAnimationIteration' : 'animationiteration'
881
} as const;
E
Erich Gamma 已提交
882

A
Alex Dima 已提交
883 884 885 886 887
export interface EventLike {
	preventDefault(): void;
	stopPropagation(): void;
}

B
Benjamin Pasero 已提交
888
export const EventHelper = {
889
	stop: function (e: EventLike, cancelBubble?: boolean) {
E
Erich Gamma 已提交
890 891 892 893 894 895 896 897 898 899 900 901
		if (e.preventDefault) {
			e.preventDefault();
		} else {
			// IE8
			(<any>e).returnValue = false;
		}

		if (cancelBubble) {
			if (e.stopPropagation) {
				e.stopPropagation();
			} else {
				// IE8
A
Alex Dima 已提交
902
				(<any>e).cancelBubble = true;
E
Erich Gamma 已提交
903 904 905 906 907 908
			}
		}
	}
};

export interface IFocusTracker {
909 910
	onDidFocus: Event<void>;
	onDidBlur: Event<void>;
B
Benjamin Pasero 已提交
911
	dispose(): void;
E
Erich Gamma 已提交
912 913
}

B
Benjamin Pasero 已提交
914 915 916
export function saveParentsScrollTop(node: Element): number[] {
	let r: number[] = [];
	for (let i = 0; node && node.nodeType === node.ELEMENT_NODE; i++) {
E
Erich Gamma 已提交
917 918 919 920 921 922
		r[i] = node.scrollTop;
		node = <Element>node.parentNode;
	}
	return r;
}

B
Benjamin Pasero 已提交
923 924
export function restoreParentsScrollTop(node: Element, state: number[]): void {
	for (let i = 0; node && node.nodeType === node.ELEMENT_NODE; i++) {
E
Erich Gamma 已提交
925 926 927 928 929 930 931
		if (node.scrollTop !== state[i]) {
			node.scrollTop = state[i];
		}
		node = <Element>node.parentNode;
	}
}

932
class FocusTracker extends Disposable implements IFocusTracker {
E
Erich Gamma 已提交
933

934 935
	private readonly _onDidFocus = this._register(new Emitter<void>());
	public readonly onDidFocus: Event<void> = this._onDidFocus.event;
E
Erich Gamma 已提交
936

937 938
	private readonly _onDidBlur = this._register(new Emitter<void>());
	public readonly onDidBlur: Event<void> = this._onDidBlur.event;
939 940

	constructor(element: HTMLElement | Window) {
941
		super();
942
		let hasFocus = isAncestor(document.activeElement, <HTMLElement>element);
943 944
		let loosingFocus = false;

945
		const onFocus = () => {
946 947 948
			loosingFocus = false;
			if (!hasFocus) {
				hasFocus = true;
949
				this._onDidFocus.fire();
E
Erich Gamma 已提交
950
			}
951
		};
E
Erich Gamma 已提交
952

953
		const onBlur = () => {
954 955 956 957 958 959
			if (hasFocus) {
				loosingFocus = true;
				window.setTimeout(() => {
					if (loosingFocus) {
						loosingFocus = false;
						hasFocus = false;
960
						this._onDidBlur.fire();
961 962 963 964
					}
				}, 0);
			}
		};
E
Erich Gamma 已提交
965

966 967
		this._register(domEvent(element, EventType.FOCUS, true)(onFocus));
		this._register(domEvent(element, EventType.BLUR, true)(onBlur));
968 969 970
	}
}

971
export function trackFocus(element: HTMLElement | Window): IFocusTracker {
972
	return new FocusTracker(element);
E
Erich Gamma 已提交
973 974
}

J
Joao Moreno 已提交
975 976 977
export function append<T extends Node>(parent: HTMLElement, ...children: T[]): T {
	children.forEach(child => parent.appendChild(child));
	return children[children.length - 1];
E
Erich Gamma 已提交
978 979
}

980 981 982 983 984
export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
	parent.insertBefore(child, parent.firstChild);
	return child;
}

B
Benjamin Pasero 已提交
985
const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((.([\w\-]+))*)/;
E
Erich Gamma 已提交
986

987
export function $<T extends HTMLElement>(description: string, attrs?: { [key: string]: any; }, ...children: Array<Node | string>): T {
B
Benjamin Pasero 已提交
988
	let match = SELECTOR_REGEX.exec(description);
E
Erich Gamma 已提交
989 990 991 992 993

	if (!match) {
		throw new Error('Bad use of emmet');
	}

B
Benjamin Pasero 已提交
994
	let result = document.createElement(match[1] || 'div');
J
Joao Moreno 已提交
995

A
Alex Dima 已提交
996 997 998 999 1000 1001
	if (match[3]) {
		result.id = match[3];
	}
	if (match[4]) {
		result.className = match[4].replace(/\./g, ' ').trim();
	}
E
Erich Gamma 已提交
1002

A
Alex Dima 已提交
1003 1004 1005
	attrs = attrs || {};
	Object.keys(attrs).forEach(name => {
		const value = attrs![name];
J
Joao Moreno 已提交
1006
		if (/^on\w+$/.test(name)) {
A
Alex Dima 已提交
1007
			(<any>result)[name] = value;
J
Joao Moreno 已提交
1008 1009 1010 1011 1012
		} else if (name === 'selected') {
			if (value) {
				result.setAttribute(name, 'true');
			}

J
Joao Moreno 已提交
1013
		} else {
A
Alex Dima 已提交
1014
			result.setAttribute(name, value);
J
Joao Moreno 已提交
1015 1016
		}
	});
J
Joao Moreno 已提交
1017

M
Matt Bierner 已提交
1018
	coalesce(children)
J
Joao Moreno 已提交
1019 1020 1021 1022 1023 1024 1025
		.forEach(child => {
			if (child instanceof Node) {
				result.appendChild(child);
			} else {
				result.appendChild(document.createTextNode(child as string));
			}
		});
J
Joao Moreno 已提交
1026

J
Joao Moreno 已提交
1027
	return result as T;
A
Alex Dima 已提交
1028
}
1029

1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
export function join(nodes: Node[], separator: Node | string): Node[] {
	const result: Node[] = [];

	nodes.forEach((node, index) => {
		if (index > 0) {
			if (separator instanceof Node) {
				result.push(separator.cloneNode());
			} else {
				result.push(document.createTextNode(separator));
			}
		}

		result.push(node);
	});

	return result;
}

J
Joao Moreno 已提交
1048
export function show(...elements: HTMLElement[]): void {
J
Johannes Rieken 已提交
1049
	for (let element of elements) {
S
Sandy Armstrong 已提交
1050
		element.style.display = '';
1051
		element.removeAttribute('aria-hidden');
J
Joao Moreno 已提交
1052
	}
1053 1054
}

J
Joao Moreno 已提交
1055
export function hide(...elements: HTMLElement[]): void {
J
Johannes Rieken 已提交
1056
	for (let element of elements) {
J
Joao Moreno 已提交
1057
		element.style.display = 'none';
1058
		element.setAttribute('aria-hidden', 'true');
J
Joao Moreno 已提交
1059
	}
1060
}
1061

A
Alex Dima 已提交
1062
function findParentWithAttribute(node: Node | null, attribute: string): HTMLElement | null {
1063
	while (node) {
B
Benjamin Pasero 已提交
1064
		if (node instanceof HTMLElement && node.hasAttribute(attribute)) {
1065 1066 1067
			return node;
		}

B
Benjamin Pasero 已提交
1068
		node = node.parentNode;
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
	}

	return null;
}

export function removeTabIndexAndUpdateFocus(node: HTMLElement): void {
	if (!node || !node.hasAttribute('tabIndex')) {
		return;
	}

	// If we are the currently focused element and tabIndex is removed,
	// standard DOM behavior is to move focus to the <body> element. We
	// typically never want that, rather put focus to the closest element
	// in the hierarchy of the parent DOM nodes.
	if (document.activeElement === node) {
		let parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex');
		if (parentFocusable) {
			parentFocusable.focus();
		}
	}

B
fix npe  
Benjamin Pasero 已提交
1090
	node.removeAttribute('tabindex');
A
Alex Dima 已提交
1091
}
1092 1093 1094

export function getElementsByTagName(tag: string): HTMLElement[] {
	return Array.prototype.slice.call(document.getElementsByTagName(tag), 0);
J
Joao Moreno 已提交
1095 1096
}

1097
export function finalHandler<T extends DOMEvent>(fn: (event: T) => any): (event: T) => any {
J
Joao Moreno 已提交
1098 1099 1100 1101 1102
	return e => {
		e.preventDefault();
		e.stopPropagation();
		fn(e);
	};
1103 1104
}

J
Johannes Rieken 已提交
1105 1106
export function domContentLoaded(): Promise<any> {
	return new Promise<any>(resolve => {
1107 1108
		const readyState = document.readyState;
		if (readyState === 'complete' || (document && document.body !== null)) {
J
Johannes Rieken 已提交
1109
			platform.setImmediate(resolve);
1110
		} else {
J
Johannes Rieken 已提交
1111
			window.addEventListener('DOMContentLoaded', resolve, false);
1112 1113
		}
	});
1114
}
1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127

/**
 * Find a value usable for a dom node size such that the likelihood that it would be
 * displayed with constant screen pixels size is as high as possible.
 *
 * e.g. We would desire for the cursors to be 2px (CSS px) wide. Under a devicePixelRatio
 * of 1.25, the cursor will be 2.5 screen pixels wide. Depending on how the dom node aligns/"snaps"
 * with the screen pixels, it will sometimes be rendered with 2 screen pixels, and sometimes with 3 screen pixels.
 */
export function computeScreenAwareSize(cssPx: number): number {
	const screenPx = window.devicePixelRatio * cssPx;
	return Math.max(1, Math.floor(screenPx)) / window.devicePixelRatio;
}
1128 1129 1130 1131 1132 1133 1134 1135 1136

/**
 * See https://github.com/Microsoft/monaco-editor/issues/601
 * To protect against malicious code in the linked site, particularly phishing attempts,
 * the window.opener should be set to null to prevent the linked site from having access
 * to change the location of the current page.
 * See https://mathiasbynens.github.io/rel-noopener/
 */
export function windowOpenNoOpener(url: string): void {
A
Alex Dima 已提交
1137
	if (platform.isNative || browser.isEdgeWebView) {
1138
		// In VSCode, window.open() always returns null...
A
Alex Dima 已提交
1139
		// The same is true for a WebView (see https://github.com/Microsoft/monaco-editor/issues/628)
1140 1141 1142 1143
		window.open(url);
	} else {
		let newTab = window.open();
		if (newTab) {
M
Matt Bierner 已提交
1144
			(newTab as any).opener = null;
1145 1146 1147
			newTab.location.href = url;
		}
	}
1148
}
J
Joao Moreno 已提交
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158

export function animate(fn: () => void): IDisposable {
	const step = () => {
		fn();
		stepDisposable = scheduleAtNextAnimationFrame(step);
	};

	let stepDisposable = scheduleAtNextAnimationFrame(step);
	return toDisposable(() => stepDisposable.dispose());
}