/
browser_util.ts
161 lines (127 loc) · 5.33 KB
/
browser_util.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgZone, ɵglobal as global} from '@angular/core';
import {ɵgetDOM as getDOM} from '@angular/platform-browser';
export let browserDetection: BrowserDetection;
export class BrowserDetection {
private _overrideUa: string|null;
private get _ua(): string {
if (typeof this._overrideUa === 'string') {
return this._overrideUa;
}
return getDOM() ? getDOM().getUserAgent() : '';
}
static setup() { browserDetection = new BrowserDetection(null); }
constructor(ua: string|null) { this._overrideUa = ua; }
get isFirefox(): boolean { return this._ua.indexOf('Firefox') > -1; }
get isAndroid(): boolean {
return this._ua.indexOf('Mozilla/5.0') > -1 && this._ua.indexOf('Android') > -1 &&
this._ua.indexOf('AppleWebKit') > -1 && this._ua.indexOf('Chrome') == -1 &&
this._ua.indexOf('IEMobile') == -1;
}
get isEdge(): boolean { return this._ua.indexOf('Edge') > -1; }
get isIE(): boolean { return this._ua.indexOf('Trident') > -1; }
get isWebkit(): boolean {
return this._ua.indexOf('AppleWebKit') > -1 && this._ua.indexOf('Edge') == -1 &&
this._ua.indexOf('IEMobile') == -1;
}
get isIOS7(): boolean {
return (this._ua.indexOf('iPhone OS 7') > -1 || this._ua.indexOf('iPad OS 7') > -1) &&
this._ua.indexOf('IEMobile') == -1;
}
get isSlow(): boolean { return this.isAndroid || this.isIE || this.isIOS7; }
// The Intl API is only natively supported in Chrome, Firefox, IE11 and Edge.
// This detector is needed in tests to make the difference between:
// 1) IE11/Edge: they have a native Intl API, but with some discrepancies
// 2) IE9/IE10: they use the polyfill, and so no discrepancies
get supportsNativeIntlApi(): boolean {
return !!(<any>global).Intl && (<any>global).Intl !== (<any>global).IntlPolyfill;
}
get isChromeDesktop(): boolean {
return this._ua.indexOf('Chrome') > -1 && this._ua.indexOf('Mobile Safari') == -1 &&
this._ua.indexOf('Edge') == -1;
}
// "Old Chrome" means Chrome 3X, where there are some discrepancies in the Intl API.
// Android 4.4 and 5.X have such browsers by default (respectively 30 and 39).
get isOldChrome(): boolean {
return this._ua.indexOf('Chrome') > -1 && this._ua.indexOf('Chrome/3') > -1 &&
this._ua.indexOf('Edge') == -1;
}
get supportsCustomElements() { return (typeof(<any>global).customElements !== 'undefined'); }
get supportsDeprecatedCustomCustomElementsV0() {
return (typeof(document as any).registerElement !== 'undefined');
}
get supportsRegExUnicodeFlag(): boolean { return RegExp.prototype.hasOwnProperty('unicode'); }
get supportsShadowDom() {
const testEl = document.createElement('div');
return (typeof testEl.attachShadow !== 'undefined');
}
get supportsDeprecatedShadowDomV0() {
const testEl = document.createElement('div') as any;
return (typeof testEl.createShadowRoot !== 'undefined');
}
}
BrowserDetection.setup();
export function dispatchEvent(element: any, eventType: any): void {
getDOM().dispatchEvent(element, getDOM().createEvent(eventType));
}
export function el(html: string): HTMLElement {
return <HTMLElement>getDOM().firstChild(getDOM().content(getDOM().createTemplate(html)));
}
export function normalizeCSS(css: string): string {
return css.replace(/\s+/g, ' ')
.replace(/:\s/g, ':')
.replace(/'/g, '"')
.replace(/ }/g, '}')
.replace(/url\((\"|\s)(.+)(\"|\s)\)(\s*)/g, (...match: string[]) => `url("${match[2]}")`)
.replace(/\[(.+)=([^"\]]+)\]/g, (...match: string[]) => `[${match[1]}="${match[2]}"]`);
}
const _selfClosingTags = ['br', 'hr', 'input'];
export function stringifyElement(el: any /** TODO #9100 */): string {
let result = '';
if (getDOM().isElementNode(el)) {
const tagName = getDOM().tagName(el).toLowerCase();
// Opening tag
result += `<${tagName}`;
// Attributes in an ordered way
const attributeMap = getDOM().attributeMap(el);
const sortedKeys = Array.from(attributeMap.keys()).sort();
for (const key of sortedKeys) {
const lowerCaseKey = key.toLowerCase();
let attValue = attributeMap.get(key);
if (typeof attValue !== 'string') {
result += ` ${lowerCaseKey}`;
} else {
// Browsers order style rules differently. Order them alphabetically for consistency.
if (lowerCaseKey === 'style') {
attValue = attValue.split(/; ?/).filter(s => !!s).sort().map(s => `${s};`).join(' ');
}
result += ` ${lowerCaseKey}="${attValue}"`;
}
}
result += '>';
// Children
const childrenRoot = getDOM().templateAwareRoot(el);
const children = childrenRoot ? getDOM().childNodes(childrenRoot) : [];
for (let j = 0; j < children.length; j++) {
result += stringifyElement(children[j]);
}
// Closing tag
if (_selfClosingTags.indexOf(tagName) == -1) {
result += `</${tagName}>`;
}
} else if (getDOM().isCommentNode(el)) {
result += `<!--${getDOM().nodeValue(el)}-->`;
} else {
result += getDOM().getText(el);
}
return result;
}
export function createNgZone(): NgZone {
return new NgZone({enableLongStackTrace: true, shouldCoalesceEventChangeDetection: false});
}