refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
428
packages/platform-browser/src/browser/browser_adapter.ts
Normal file
428
packages/platform-browser/src/browser/browser_adapter.ts
Normal file
@ -0,0 +1,428 @@
|
||||
/**
|
||||
* @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 {ɵglobal as global} from '@angular/core';
|
||||
import {setRootDomAdapter} from '../dom/dom_adapter';
|
||||
|
||||
import {GenericBrowserDomAdapter} from './generic_browser_adapter';
|
||||
|
||||
const _attrToPropMap = {
|
||||
'class': 'className',
|
||||
'innerHtml': 'innerHTML',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex',
|
||||
};
|
||||
|
||||
const DOM_KEY_LOCATION_NUMPAD = 3;
|
||||
|
||||
// Map to convert some key or keyIdentifier values to what will be returned by getEventKey
|
||||
const _keyMap: {[k: string]: string} = {
|
||||
// The following values are here for cross-browser compatibility and to match the W3C standard
|
||||
// cf http://www.w3.org/TR/DOM-Level-3-Events-key/
|
||||
'\b': 'Backspace',
|
||||
'\t': 'Tab',
|
||||
'\x7F': 'Delete',
|
||||
'\x1B': 'Escape',
|
||||
'Del': 'Delete',
|
||||
'Esc': 'Escape',
|
||||
'Left': 'ArrowLeft',
|
||||
'Right': 'ArrowRight',
|
||||
'Up': 'ArrowUp',
|
||||
'Down': 'ArrowDown',
|
||||
'Menu': 'ContextMenu',
|
||||
'Scroll': 'ScrollLock',
|
||||
'Win': 'OS'
|
||||
};
|
||||
|
||||
// There is a bug in Chrome for numeric keypad keys:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=155654
|
||||
// 1, 2, 3 ... are reported as A, B, C ...
|
||||
const _chromeNumKeyPadMap = {
|
||||
'A': '1',
|
||||
'B': '2',
|
||||
'C': '3',
|
||||
'D': '4',
|
||||
'E': '5',
|
||||
'F': '6',
|
||||
'G': '7',
|
||||
'H': '8',
|
||||
'I': '9',
|
||||
'J': '*',
|
||||
'K': '+',
|
||||
'M': '-',
|
||||
'N': '.',
|
||||
'O': '/',
|
||||
'\x60': '0',
|
||||
'\x90': 'NumLock'
|
||||
};
|
||||
|
||||
/**
|
||||
* A `DomAdapter` powered by full browser DOM APIs.
|
||||
*
|
||||
* @security Tread carefully! Interacting with the DOM directly is dangerous and
|
||||
* can introduce XSS risks.
|
||||
*/
|
||||
/* tslint:disable:requireParameterType no-console */
|
||||
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
parse(templateHtml: string) { throw new Error('parse not implemented'); }
|
||||
static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); }
|
||||
hasProperty(element: Node, name: string): boolean { return name in element; }
|
||||
setProperty(el: Node, name: string, value: any) { (<any>el)[name] = value; }
|
||||
getProperty(el: Node, name: string): any { return (<any>el)[name]; }
|
||||
invoke(el: Node, methodName: string, args: any[]): any { (<any>el)[methodName](...args); }
|
||||
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
logError(error: string): void {
|
||||
if (window.console) {
|
||||
if (console.error) {
|
||||
console.error(error);
|
||||
} else {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(error: string): void {
|
||||
if (window.console) {
|
||||
window.console.log && window.console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
logGroup(error: string): void {
|
||||
if (window.console) {
|
||||
window.console.group && window.console.group(error);
|
||||
}
|
||||
}
|
||||
|
||||
logGroupEnd(): void {
|
||||
if (window.console) {
|
||||
window.console.groupEnd && window.console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
get attrToPropMap(): any { return _attrToPropMap; }
|
||||
|
||||
querySelector(el: Element, selector: string): any { return el.querySelector(selector); }
|
||||
querySelectorAll(el: any, selector: string): any[] { return el.querySelectorAll(selector); }
|
||||
on(el: Node, evt: any, listener: any) { el.addEventListener(evt, listener, false); }
|
||||
onAndCancel(el: Node, evt: any, listener: any): Function {
|
||||
el.addEventListener(evt, listener, false);
|
||||
// Needed to follow Dart's subscription semantic, until fix of
|
||||
// https://code.google.com/p/dart/issues/detail?id=17406
|
||||
return () => { el.removeEventListener(evt, listener, false); };
|
||||
}
|
||||
dispatchEvent(el: Node, evt: any) { el.dispatchEvent(evt); }
|
||||
createMouseEvent(eventType: string): MouseEvent {
|
||||
const evt: MouseEvent = document.createEvent('MouseEvent');
|
||||
evt.initEvent(eventType, true, true);
|
||||
return evt;
|
||||
}
|
||||
createEvent(eventType: any): Event {
|
||||
const evt: Event = document.createEvent('Event');
|
||||
evt.initEvent(eventType, true, true);
|
||||
return evt;
|
||||
}
|
||||
preventDefault(evt: Event) {
|
||||
evt.preventDefault();
|
||||
evt.returnValue = false;
|
||||
}
|
||||
isPrevented(evt: Event): boolean {
|
||||
return evt.defaultPrevented || evt.returnValue != null && !evt.returnValue;
|
||||
}
|
||||
getInnerHTML(el: HTMLElement): string { return el.innerHTML; }
|
||||
getTemplateContent(el: Node): Node {
|
||||
return 'content' in el && el instanceof HTMLTemplateElement ? el.content : null;
|
||||
}
|
||||
getOuterHTML(el: HTMLElement): string { return el.outerHTML; }
|
||||
nodeName(node: Node): string { return node.nodeName; }
|
||||
nodeValue(node: Node): string { return node.nodeValue; }
|
||||
type(node: HTMLInputElement): string { return node.type; }
|
||||
content(node: Node): Node {
|
||||
if (this.hasProperty(node, 'content')) {
|
||||
return (<any>node).content;
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
firstChild(el: Node): Node { return el.firstChild; }
|
||||
nextSibling(el: Node): Node { return el.nextSibling; }
|
||||
parentElement(el: Node): Node { return el.parentNode; }
|
||||
childNodes(el: any): Node[] { return el.childNodes; }
|
||||
childNodesAsList(el: Node): any[] {
|
||||
const childNodes = el.childNodes;
|
||||
const res = new Array(childNodes.length);
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
res[i] = childNodes[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
clearNodes(el: Node) {
|
||||
while (el.firstChild) {
|
||||
el.removeChild(el.firstChild);
|
||||
}
|
||||
}
|
||||
appendChild(el: Node, node: Node) { el.appendChild(node); }
|
||||
removeChild(el: Node, node: Node) { el.removeChild(node); }
|
||||
replaceChild(el: Node, newChild: Node, oldChild: Node) { el.replaceChild(newChild, oldChild); }
|
||||
remove(node: Node): Node {
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
insertBefore(parent: Node, ref: Node, node: Node) { parent.insertBefore(node, ref); }
|
||||
insertAllBefore(parent: Node, ref: Node, nodes: Node[]) {
|
||||
nodes.forEach((n: any) => parent.insertBefore(n, ref));
|
||||
}
|
||||
insertAfter(parent: Node, ref: Node, node: any) { parent.insertBefore(node, ref.nextSibling); }
|
||||
setInnerHTML(el: Element, value: string) { el.innerHTML = value; }
|
||||
getText(el: Node): string { return el.textContent; }
|
||||
setText(el: Node, value: string) { el.textContent = value; }
|
||||
getValue(el: any): string { return el.value; }
|
||||
setValue(el: any, value: string) { el.value = value; }
|
||||
getChecked(el: any): boolean { return el.checked; }
|
||||
setChecked(el: any, value: boolean) { el.checked = value; }
|
||||
createComment(text: string): Comment { return document.createComment(text); }
|
||||
createTemplate(html: any): HTMLElement {
|
||||
const t = document.createElement('template');
|
||||
t.innerHTML = html;
|
||||
return t;
|
||||
}
|
||||
createElement(tagName: string, doc = document): HTMLElement { return doc.createElement(tagName); }
|
||||
createElementNS(ns: string, tagName: string, doc = document): Element {
|
||||
return doc.createElementNS(ns, tagName);
|
||||
}
|
||||
createTextNode(text: string, doc = document): Text { return doc.createTextNode(text); }
|
||||
createScriptTag(attrName: string, attrValue: string, doc = document): HTMLScriptElement {
|
||||
const el = <HTMLScriptElement>doc.createElement('SCRIPT');
|
||||
el.setAttribute(attrName, attrValue);
|
||||
return el;
|
||||
}
|
||||
createStyleElement(css: string, doc = document): HTMLStyleElement {
|
||||
const style = <HTMLStyleElement>doc.createElement('style');
|
||||
this.appendChild(style, this.createTextNode(css));
|
||||
return style;
|
||||
}
|
||||
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
|
||||
getShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).shadowRoot; }
|
||||
getHost(el: HTMLElement): HTMLElement { return (<any>el).host; }
|
||||
clone(node: Node): Node { return node.cloneNode(true); }
|
||||
getElementsByClassName(element: any, name: string): HTMLElement[] {
|
||||
return element.getElementsByClassName(name);
|
||||
}
|
||||
getElementsByTagName(element: any, name: string): HTMLElement[] {
|
||||
return element.getElementsByTagName(name);
|
||||
}
|
||||
classList(element: any): any[] { return Array.prototype.slice.call(element.classList, 0); }
|
||||
addClass(element: any, className: string) { element.classList.add(className); }
|
||||
removeClass(element: any, className: string) { element.classList.remove(className); }
|
||||
hasClass(element: any, className: string): boolean {
|
||||
return element.classList.contains(className);
|
||||
}
|
||||
setStyle(element: any, styleName: string, styleValue: string) {
|
||||
element.style[styleName] = styleValue;
|
||||
}
|
||||
removeStyle(element: any, stylename: string) {
|
||||
// IE requires '' instead of null
|
||||
// see https://github.com/angular/angular/issues/7916
|
||||
element.style[stylename] = '';
|
||||
}
|
||||
getStyle(element: any, stylename: string): string { return element.style[stylename]; }
|
||||
hasStyle(element: any, styleName: string, styleValue: string = null): boolean {
|
||||
const value = this.getStyle(element, styleName) || '';
|
||||
return styleValue ? value == styleValue : value.length > 0;
|
||||
}
|
||||
tagName(element: any): string { return element.tagName; }
|
||||
attributeMap(element: any): Map<string, string> {
|
||||
const res = new Map<string, string>();
|
||||
const elAttrs = element.attributes;
|
||||
for (let i = 0; i < elAttrs.length; i++) {
|
||||
const attrib = elAttrs[i];
|
||||
res.set(attrib.name, attrib.value);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
hasAttribute(element: Element, attribute: string): boolean {
|
||||
return element.hasAttribute(attribute);
|
||||
}
|
||||
hasAttributeNS(element: Element, ns: string, attribute: string): boolean {
|
||||
return element.hasAttributeNS(ns, attribute);
|
||||
}
|
||||
getAttribute(element: Element, attribute: string): string {
|
||||
return element.getAttribute(attribute);
|
||||
}
|
||||
getAttributeNS(element: Element, ns: string, name: string): string {
|
||||
return element.getAttributeNS(ns, name);
|
||||
}
|
||||
setAttribute(element: Element, name: string, value: string) { element.setAttribute(name, value); }
|
||||
setAttributeNS(element: Element, ns: string, name: string, value: string) {
|
||||
element.setAttributeNS(ns, name, value);
|
||||
}
|
||||
removeAttribute(element: Element, attribute: string) { element.removeAttribute(attribute); }
|
||||
removeAttributeNS(element: Element, ns: string, name: string) {
|
||||
element.removeAttributeNS(ns, name);
|
||||
}
|
||||
templateAwareRoot(el: Node): any { return this.isTemplateElement(el) ? this.content(el) : el; }
|
||||
createHtmlDocument(): HTMLDocument {
|
||||
return document.implementation.createHTMLDocument('fakeTitle');
|
||||
}
|
||||
getBoundingClientRect(el: Element): any {
|
||||
try {
|
||||
return el.getBoundingClientRect();
|
||||
} catch (e) {
|
||||
return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0};
|
||||
}
|
||||
}
|
||||
getTitle(doc: Document): string { return document.title; }
|
||||
setTitle(doc: Document, newTitle: string) { document.title = newTitle || ''; }
|
||||
elementMatches(n: any, selector: string): boolean {
|
||||
if (n instanceof HTMLElement) {
|
||||
return n.matches && n.matches(selector) ||
|
||||
n.msMatchesSelector && n.msMatchesSelector(selector) ||
|
||||
n.webkitMatchesSelector && n.webkitMatchesSelector(selector);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
isTemplateElement(el: Node): boolean {
|
||||
return el instanceof HTMLElement && el.nodeName == 'TEMPLATE';
|
||||
}
|
||||
isTextNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; }
|
||||
isCommentNode(node: Node): boolean { return node.nodeType === Node.COMMENT_NODE; }
|
||||
isElementNode(node: Node): boolean { return node.nodeType === Node.ELEMENT_NODE; }
|
||||
hasShadowRoot(node: any): boolean {
|
||||
return node.shadowRoot != null && node instanceof HTMLElement;
|
||||
}
|
||||
isShadowRoot(node: any): boolean { return node instanceof DocumentFragment; }
|
||||
importIntoDoc(node: Node): any { return document.importNode(this.templateAwareRoot(node), true); }
|
||||
adoptNode(node: Node): any { return document.adoptNode(node); }
|
||||
getHref(el: Element): string { return (<any>el).href; }
|
||||
|
||||
getEventKey(event: any): string {
|
||||
let key = event.key;
|
||||
if (key == null) {
|
||||
key = event.keyIdentifier;
|
||||
// keyIdentifier is defined in the old draft of DOM Level 3 Events implemented by Chrome and
|
||||
// Safari cf
|
||||
// http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/events.html#Events-KeyboardEvents-Interfaces
|
||||
if (key == null) {
|
||||
return 'Unidentified';
|
||||
}
|
||||
if (key.startsWith('U+')) {
|
||||
key = String.fromCharCode(parseInt(key.substring(2), 16));
|
||||
if (event.location === DOM_KEY_LOCATION_NUMPAD && _chromeNumKeyPadMap.hasOwnProperty(key)) {
|
||||
// There is a bug in Chrome for numeric keypad keys:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=155654
|
||||
// 1, 2, 3 ... are reported as A, B, C ...
|
||||
key = (_chromeNumKeyPadMap as any)[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _keyMap[key] || key;
|
||||
}
|
||||
getGlobalEventTarget(doc: Document, target: string): EventTarget {
|
||||
if (target === 'window') {
|
||||
return window;
|
||||
}
|
||||
if (target === 'document') {
|
||||
return document;
|
||||
}
|
||||
if (target === 'body') {
|
||||
return document.body;
|
||||
}
|
||||
}
|
||||
getHistory(): History { return window.history; }
|
||||
getLocation(): Location { return window.location; }
|
||||
getBaseHref(doc: Document): string {
|
||||
const href = getBaseElementHref();
|
||||
return href == null ? null : relativePath(href);
|
||||
}
|
||||
resetBaseElement(): void { baseElement = null; }
|
||||
getUserAgent(): string { return window.navigator.userAgent; }
|
||||
setData(element: Element, name: string, value: string) {
|
||||
this.setAttribute(element, 'data-' + name, value);
|
||||
}
|
||||
getData(element: Element, name: string): string {
|
||||
return this.getAttribute(element, 'data-' + name);
|
||||
}
|
||||
getComputedStyle(element: any): any { return getComputedStyle(element); }
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
setGlobalVar(path: string, value: any) { setValueOnPath(global, path, value); }
|
||||
supportsWebAnimation(): boolean {
|
||||
return typeof(<any>Element).prototype['animate'] === 'function';
|
||||
}
|
||||
performanceNow(): number {
|
||||
// performance.now() is not available in all browsers, see
|
||||
// http://caniuse.com/#search=performance.now
|
||||
return window.performance && window.performance.now ? window.performance.now() :
|
||||
new Date().getTime();
|
||||
}
|
||||
|
||||
supportsCookies(): boolean { return true; }
|
||||
|
||||
getCookie(name: string): string { return parseCookieValue(document.cookie, name); }
|
||||
|
||||
setCookie(name: string, value: string) {
|
||||
// document.cookie is magical, assigning into it assigns/overrides one cookie value, but does
|
||||
// not clear other cookies.
|
||||
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
|
||||
let baseElement: HTMLElement = null;
|
||||
function getBaseElementHref(): string {
|
||||
if (!baseElement) {
|
||||
baseElement = document.querySelector('base');
|
||||
if (!baseElement) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return baseElement.getAttribute('href');
|
||||
}
|
||||
|
||||
// based on urlUtils.js in AngularJS 1
|
||||
let urlParsingNode: any;
|
||||
function relativePath(url: any): string {
|
||||
if (!urlParsingNode) {
|
||||
urlParsingNode = document.createElement('a');
|
||||
}
|
||||
urlParsingNode.setAttribute('href', url);
|
||||
return (urlParsingNode.pathname.charAt(0) === '/') ? urlParsingNode.pathname :
|
||||
'/' + urlParsingNode.pathname;
|
||||
}
|
||||
|
||||
export function parseCookieValue(cookieStr: string, name: string): string {
|
||||
name = encodeURIComponent(name);
|
||||
for (const cookie of cookieStr.split(';')) {
|
||||
const eqIndex = cookie.indexOf('=');
|
||||
const [cookieName, cookieValue]: string[] =
|
||||
eqIndex == -1 ? [cookie, ''] : [cookie.slice(0, eqIndex), cookie.slice(eqIndex + 1)];
|
||||
if (cookieName.trim() === name) {
|
||||
return decodeURIComponent(cookieValue);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setValueOnPath(global: any, path: string, value: any) {
|
||||
const parts = path.split('.');
|
||||
let obj: any = global;
|
||||
while (parts.length > 1) {
|
||||
const name = parts.shift();
|
||||
if (obj.hasOwnProperty(name) && obj[name] != null) {
|
||||
obj = obj[name];
|
||||
} else {
|
||||
obj = obj[name] = {};
|
||||
}
|
||||
}
|
||||
if (obj === undefined || obj === null) {
|
||||
obj = {};
|
||||
}
|
||||
obj[parts.shift()] = value;
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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 {DomAdapter} from '../dom/dom_adapter';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides DOM operations in any browser environment.
|
||||
*
|
||||
* @security Tread carefully! Interacting with the DOM directly is dangerous and
|
||||
* can introduce XSS risks.
|
||||
*/
|
||||
export abstract class GenericBrowserDomAdapter extends DomAdapter {
|
||||
private _animationPrefix: string = null;
|
||||
private _transitionEnd: string = null;
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
const element = this.createElement('div', document);
|
||||
if (this.getStyle(element, 'animationName') != null) {
|
||||
this._animationPrefix = '';
|
||||
} else {
|
||||
const domPrefixes = ['Webkit', 'Moz', 'O', 'ms'];
|
||||
|
||||
for (let i = 0; i < domPrefixes.length; i++) {
|
||||
if (this.getStyle(element, domPrefixes[i] + 'AnimationName') != null) {
|
||||
this._animationPrefix = '-' + domPrefixes[i].toLowerCase() + '-';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const transEndEventNames: {[key: string]: string} = {
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
transition: 'transitionend'
|
||||
};
|
||||
|
||||
Object.keys(transEndEventNames).forEach((key: string) => {
|
||||
if (this.getStyle(element, key) != null) {
|
||||
this._transitionEnd = transEndEventNames[key];
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
this._animationPrefix = null;
|
||||
this._transitionEnd = null;
|
||||
}
|
||||
}
|
||||
|
||||
getDistributedNodes(el: HTMLElement): Node[] { return (<any>el).getDistributedNodes(); }
|
||||
resolveAndSetHref(el: HTMLAnchorElement, baseUrl: string, href: string) {
|
||||
el.href = href == null ? baseUrl : baseUrl + '/../' + href;
|
||||
}
|
||||
supportsDOMEvents(): boolean { return true; }
|
||||
supportsNativeShadowDOM(): boolean {
|
||||
return typeof(<any>document.body).createShadowRoot === 'function';
|
||||
}
|
||||
getAnimationPrefix(): string { return this._animationPrefix ? this._animationPrefix : ''; }
|
||||
getTransitionEnd(): string { return this._transitionEnd ? this._transitionEnd : ''; }
|
||||
supportsAnimation(): boolean {
|
||||
return this._animationPrefix != null && this._transitionEnd != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @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 {LocationChangeListener, PlatformLocation} from '@angular/common';
|
||||
import {Inject, Injectable} from '@angular/core';
|
||||
|
||||
import {getDOM} from '../../dom/dom_adapter';
|
||||
import {DOCUMENT} from '../../dom/dom_tokens';
|
||||
|
||||
import {supportsState} from './history';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* `PlatformLocation` encapsulates all of the direct calls to platform APIs.
|
||||
* This class should not be used directly by an application developer. Instead, use
|
||||
* {@link Location}.
|
||||
*/
|
||||
@Injectable()
|
||||
export class BrowserPlatformLocation extends PlatformLocation {
|
||||
private _location: Location;
|
||||
private _history: History;
|
||||
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) {
|
||||
super();
|
||||
this._init();
|
||||
}
|
||||
|
||||
// This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it
|
||||
/** @internal */
|
||||
_init() {
|
||||
this._location = getDOM().getLocation();
|
||||
this._history = getDOM().getHistory();
|
||||
}
|
||||
|
||||
get location(): Location { return this._location; }
|
||||
|
||||
getBaseHrefFromDOM(): string { return getDOM().getBaseHref(this._doc); }
|
||||
|
||||
onPopState(fn: LocationChangeListener): void {
|
||||
getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('popstate', fn, false);
|
||||
}
|
||||
|
||||
onHashChange(fn: LocationChangeListener): void {
|
||||
getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('hashchange', fn, false);
|
||||
}
|
||||
|
||||
get pathname(): string { return this._location.pathname; }
|
||||
get search(): string { return this._location.search; }
|
||||
get hash(): string { return this._location.hash; }
|
||||
set pathname(newPath: string) { this._location.pathname = newPath; }
|
||||
|
||||
pushState(state: any, title: string, url: string): void {
|
||||
if (supportsState()) {
|
||||
this._history.pushState(state, title, url);
|
||||
} else {
|
||||
this._location.hash = url;
|
||||
}
|
||||
}
|
||||
|
||||
replaceState(state: any, title: string, url: string): void {
|
||||
if (supportsState()) {
|
||||
this._history.replaceState(state, title, url);
|
||||
} else {
|
||||
this._location.hash = url;
|
||||
}
|
||||
}
|
||||
|
||||
forward(): void { this._history.forward(); }
|
||||
|
||||
back(): void { this._history.back(); }
|
||||
}
|
11
packages/platform-browser/src/browser/location/history.ts
Normal file
11
packages/platform-browser/src/browser/location/history.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
export function supportsState(): boolean {
|
||||
return !!window.history.pushState;
|
||||
}
|
116
packages/platform-browser/src/browser/meta.ts
Normal file
116
packages/platform-browser/src/browser/meta.ts
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable} from '@angular/core';
|
||||
|
||||
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
||||
import {DOCUMENT} from '../dom/dom_tokens';
|
||||
|
||||
|
||||
/**
|
||||
* Represents a meta element.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export type MetaDefinition = {
|
||||
charset?: string; content?: string; httpEquiv?: string; id?: string; itemprop?: string;
|
||||
name?: string;
|
||||
property?: string;
|
||||
scheme?: string;
|
||||
url?: string;
|
||||
} &
|
||||
{
|
||||
// TODO(IgorMinar): this type looks wrong
|
||||
[prop: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A service that can be used to get and add meta tags.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class Meta {
|
||||
private _dom: DomAdapter;
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) { this._dom = getDOM(); }
|
||||
|
||||
addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement {
|
||||
if (!tag) return null;
|
||||
return this._getOrCreateElement(tag, forceCreation);
|
||||
}
|
||||
|
||||
addTags(tags: MetaDefinition[], forceCreation: boolean = false): HTMLMetaElement[] {
|
||||
if (!tags) return [];
|
||||
return tags.reduce((result: HTMLMetaElement[], tag: MetaDefinition) => {
|
||||
if (tag) {
|
||||
result.push(this._getOrCreateElement(tag, forceCreation));
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
getTag(attrSelector: string): HTMLMetaElement {
|
||||
if (!attrSelector) return null;
|
||||
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`);
|
||||
}
|
||||
|
||||
getTags(attrSelector: string): HTMLMetaElement[] {
|
||||
if (!attrSelector) return [];
|
||||
const list /*NodeList*/ = this._dom.querySelectorAll(this._doc, `meta[${attrSelector}]`);
|
||||
return list ? [].slice.call(list) : [];
|
||||
}
|
||||
|
||||
updateTag(tag: MetaDefinition, selector?: string): HTMLMetaElement {
|
||||
if (!tag) return null;
|
||||
selector = selector || this._parseSelector(tag);
|
||||
const meta: HTMLMetaElement = this.getTag(selector);
|
||||
if (meta) {
|
||||
return this._setMetaElementAttributes(tag, meta);
|
||||
}
|
||||
return this._getOrCreateElement(tag, true);
|
||||
}
|
||||
|
||||
removeTag(attrSelector: string): void { this.removeTagElement(this.getTag(attrSelector)); }
|
||||
|
||||
removeTagElement(meta: HTMLMetaElement): void {
|
||||
if (meta) {
|
||||
this._dom.remove(meta);
|
||||
}
|
||||
}
|
||||
|
||||
private _getOrCreateElement(meta: MetaDefinition, forceCreation: boolean = false):
|
||||
HTMLMetaElement {
|
||||
if (!forceCreation) {
|
||||
const selector: string = this._parseSelector(meta);
|
||||
const elem: HTMLMetaElement = this.getTag(selector);
|
||||
// It's allowed to have multiple elements with the same name so it's not enough to
|
||||
// just check that element with the same name already present on the page. We also need to
|
||||
// check if element has tag attributes
|
||||
if (elem && this._containsAttributes(meta, elem)) return elem;
|
||||
}
|
||||
const element: HTMLMetaElement = this._dom.createElement('meta') as HTMLMetaElement;
|
||||
this._setMetaElementAttributes(meta, element);
|
||||
const head = this._dom.getElementsByTagName(this._doc, 'head')[0];
|
||||
this._dom.appendChild(head, element);
|
||||
return element;
|
||||
}
|
||||
|
||||
private _setMetaElementAttributes(tag: MetaDefinition, el: HTMLMetaElement): HTMLMetaElement {
|
||||
Object.keys(tag).forEach((prop: string) => this._dom.setAttribute(el, prop, tag[prop]));
|
||||
return el;
|
||||
}
|
||||
|
||||
private _parseSelector(tag: MetaDefinition): string {
|
||||
const attr: string = tag.name ? 'name' : 'property';
|
||||
return `${attr}="${tag[attr]}"`;
|
||||
}
|
||||
|
||||
private _containsAttributes(tag: MetaDefinition, elem: HTMLMetaElement): boolean {
|
||||
return Object.keys(tag).every((key: string) => this._dom.getAttribute(elem, key) === tag[key]);
|
||||
}
|
||||
}
|
38
packages/platform-browser/src/browser/server-transition.ts
Normal file
38
packages/platform-browser/src/browser/server-transition.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @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 {APP_INITIALIZER, Inject, InjectionToken, Provider} from '@angular/core';
|
||||
|
||||
import {getDOM} from '../dom/dom_adapter';
|
||||
import {DOCUMENT} from '../dom/dom_tokens';
|
||||
|
||||
/**
|
||||
* An id that identifies a particular application being bootstrapped, that should
|
||||
* match across the client/server boundary.
|
||||
*/
|
||||
export const TRANSITION_ID = new InjectionToken('TRANSITION_ID');
|
||||
|
||||
export function bootstrapListenerFactory(transitionId: string, document: any) {
|
||||
const factory = () => {
|
||||
const dom = getDOM();
|
||||
const styles: any[] =
|
||||
Array.prototype.slice.apply(dom.querySelectorAll(document, `style[ng-transition]`));
|
||||
styles.filter(el => dom.getAttribute(el, 'ng-transition') === transitionId)
|
||||
.forEach(el => dom.remove(el));
|
||||
};
|
||||
return factory;
|
||||
}
|
||||
|
||||
export const SERVER_TRANSITION_PROVIDERS: Provider[] = [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: bootstrapListenerFactory,
|
||||
deps: [TRANSITION_ID, DOCUMENT],
|
||||
multi: true
|
||||
},
|
||||
];
|
67
packages/platform-browser/src/browser/testability.ts
Normal file
67
packages/platform-browser/src/browser/testability.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @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 {GetTestability, Testability, TestabilityRegistry, setTestabilityGetter, ɵglobal as global} from '@angular/core';
|
||||
|
||||
import {getDOM} from '../dom/dom_adapter';
|
||||
|
||||
export class BrowserGetTestability implements GetTestability {
|
||||
static init() { setTestabilityGetter(new BrowserGetTestability()); }
|
||||
|
||||
addToWindow(registry: TestabilityRegistry): void {
|
||||
global['getAngularTestability'] = (elem: any, findInAncestors: boolean = true) => {
|
||||
const testability = registry.findTestabilityInTree(elem, findInAncestors);
|
||||
if (testability == null) {
|
||||
throw new Error('Could not find testability for element.');
|
||||
}
|
||||
return testability;
|
||||
};
|
||||
|
||||
global['getAllAngularTestabilities'] = () => registry.getAllTestabilities();
|
||||
|
||||
global['getAllAngularRootElements'] = () => registry.getAllRootElements();
|
||||
|
||||
const whenAllStable = (callback: any /** TODO #9100 */) => {
|
||||
const testabilities = global['getAllAngularTestabilities']();
|
||||
let count = testabilities.length;
|
||||
let didWork = false;
|
||||
const decrement = function(didWork_: any /** TODO #9100 */) {
|
||||
didWork = didWork || didWork_;
|
||||
count--;
|
||||
if (count == 0) {
|
||||
callback(didWork);
|
||||
}
|
||||
};
|
||||
testabilities.forEach(function(testability: any /** TODO #9100 */) {
|
||||
testability.whenStable(decrement);
|
||||
});
|
||||
};
|
||||
|
||||
if (!global['frameworkStabilizers']) {
|
||||
global['frameworkStabilizers'] = [];
|
||||
}
|
||||
global['frameworkStabilizers'].push(whenAllStable);
|
||||
}
|
||||
|
||||
findTestabilityInTree(registry: TestabilityRegistry, elem: any, findInAncestors: boolean):
|
||||
Testability {
|
||||
if (elem == null) {
|
||||
return null;
|
||||
}
|
||||
const t = registry.getTestability(elem);
|
||||
if (t != null) {
|
||||
return t;
|
||||
} else if (!findInAncestors) {
|
||||
return null;
|
||||
}
|
||||
if (getDOM().isShadowRoot(elem)) {
|
||||
return this.findTestabilityInTree(registry, getDOM().getHost(elem), true);
|
||||
}
|
||||
return this.findTestabilityInTree(registry, getDOM().parentElement(elem), true);
|
||||
}
|
||||
}
|
39
packages/platform-browser/src/browser/title.ts
Normal file
39
packages/platform-browser/src/browser/title.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable} from '@angular/core';
|
||||
|
||||
import {getDOM} from '../dom/dom_adapter';
|
||||
import {DOCUMENT} from '../dom/dom_tokens';
|
||||
|
||||
|
||||
/**
|
||||
* A service that can be used to get and set the title of a current HTML document.
|
||||
*
|
||||
* Since an Angular application can't be bootstrapped on the entire HTML document (`<html>` tag)
|
||||
* it is not possible to bind to the `text` property of the `HTMLTitleElement` elements
|
||||
* (representing the `<title>` tag). Instead, this service can be used to set and get the current
|
||||
* title value.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class Title {
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) {}
|
||||
/**
|
||||
* Get the title of the current HTML document.
|
||||
* @returns {string}
|
||||
*/
|
||||
getTitle(): string { return getDOM().getTitle(this._doc); }
|
||||
|
||||
/**
|
||||
* Set the title of the current HTML document.
|
||||
* @param newTitle
|
||||
*/
|
||||
setTitle(newTitle: string) { getDOM().setTitle(this._doc, newTitle); }
|
||||
}
|
10
packages/platform-browser/src/browser/tools/browser.ts
Normal file
10
packages/platform-browser/src/browser/tools/browser.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
const win = typeof window !== 'undefined' && window || <any>{};
|
||||
export {win as window};
|
71
packages/platform-browser/src/browser/tools/common_tools.ts
Normal file
71
packages/platform-browser/src/browser/tools/common_tools.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @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 {ApplicationRef, ComponentRef} from '@angular/core';
|
||||
import {getDOM} from '../../dom/dom_adapter';
|
||||
import {window} from './browser';
|
||||
|
||||
export class ChangeDetectionPerfRecord {
|
||||
constructor(public msPerTick: number, public numTicks: number) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for all Angular profiling-related debug tools. This object
|
||||
* corresponds to the `ng.profiler` in the dev console.
|
||||
*/
|
||||
export class AngularProfiler {
|
||||
appRef: ApplicationRef;
|
||||
|
||||
constructor(ref: ComponentRef<any>) { this.appRef = ref.injector.get(ApplicationRef); }
|
||||
|
||||
// tslint:disable:no-console
|
||||
/**
|
||||
* Exercises change detection in a loop and then prints the average amount of
|
||||
* time in milliseconds how long a single round of change detection takes for
|
||||
* the current state of the UI. It runs a minimum of 5 rounds for a minimum
|
||||
* of 500 milliseconds.
|
||||
*
|
||||
* Optionally, a user may pass a `config` parameter containing a map of
|
||||
* options. Supported options are:
|
||||
*
|
||||
* `record` (boolean) - causes the profiler to record a CPU profile while
|
||||
* it exercises the change detector. Example:
|
||||
*
|
||||
* ```
|
||||
* ng.profiler.timeChangeDetection({record: true})
|
||||
* ```
|
||||
*/
|
||||
timeChangeDetection(config: any): ChangeDetectionPerfRecord {
|
||||
const record = config && config['record'];
|
||||
const profileName = 'Change Detection';
|
||||
// Profiler is not available in Android browsers, nor in IE 9 without dev tools opened
|
||||
const isProfilerAvailable = window.console.profile != null;
|
||||
if (record && isProfilerAvailable) {
|
||||
window.console.profile(profileName);
|
||||
}
|
||||
const start = getDOM().performanceNow();
|
||||
let numTicks = 0;
|
||||
while (numTicks < 5 || (getDOM().performanceNow() - start) < 500) {
|
||||
this.appRef.tick();
|
||||
numTicks++;
|
||||
}
|
||||
const end = getDOM().performanceNow();
|
||||
if (record && isProfilerAvailable) {
|
||||
// need to cast to <any> because type checker thinks there's no argument
|
||||
// while in fact there is:
|
||||
//
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Console/profileEnd
|
||||
(<any>window.console.profileEnd)(profileName);
|
||||
}
|
||||
const msPerTick = (end - start) / numTicks;
|
||||
window.console.log(`ran ${numTicks} change detection cycles`);
|
||||
window.console.log(`${msPerTick.toFixed(2)} ms per check`);
|
||||
|
||||
return new ChangeDetectionPerfRecord(msPerTick, numTicks);
|
||||
}
|
||||
}
|
41
packages/platform-browser/src/browser/tools/tools.ts
Normal file
41
packages/platform-browser/src/browser/tools/tools.ts
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @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 {ComponentRef} from '@angular/core';
|
||||
import {getDOM} from '../../dom/dom_adapter';
|
||||
|
||||
import {AngularProfiler} from './common_tools';
|
||||
|
||||
const PROFILER_GLOBAL_NAME = 'ng.profiler';
|
||||
|
||||
/**
|
||||
* Enabled Angular debug tools that are accessible via your browser's
|
||||
* developer console.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* 1. Open developer console (e.g. in Chrome Ctrl + Shift + j)
|
||||
* 1. Type `ng.` (usually the console will show auto-complete suggestion)
|
||||
* 1. Try the change detection profiler `ng.profiler.timeChangeDetection()`
|
||||
* then hit Enter.
|
||||
*
|
||||
* @experimental All debugging apis are currently experimental.
|
||||
*/
|
||||
export function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T> {
|
||||
getDOM().setGlobalVar(PROFILER_GLOBAL_NAME, new AngularProfiler(ref));
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables Angular tools.
|
||||
*
|
||||
* @experimental All debugging apis are currently experimental.
|
||||
*/
|
||||
export function disableDebugTools(): void {
|
||||
getDOM().setGlobalVar(PROFILER_GLOBAL_NAME, null);
|
||||
}
|
Reference in New Issue
Block a user