feat(platform-server): add API to render Module and ModuleFactory to string (#14381)
- PlatformState provides an interface to serialize the current Platform State as a string or Document. - renderModule and renderModuleFactory are convenience methods to wait for Angular Application to stabilize and then render the state to a string. - refactor code to remove defaultDoc from DomAdapter and inject DOCUMENT where it's needed.
This commit is contained in:
parent
2f2b65bd38
commit
b4d444a0a7
|
@ -223,7 +223,7 @@ function _extractToken(
|
||||||
|
|
||||||
if (!Array.isArray(metadata)) {
|
if (!Array.isArray(metadata)) {
|
||||||
if (metadata instanceof Inject) {
|
if (metadata instanceof Inject) {
|
||||||
return _createDependency(metadata.token, optional, null);
|
return _createDependency(metadata['token'], optional, null);
|
||||||
} else {
|
} else {
|
||||||
return _createDependency(metadata, optional, null);
|
return _createDependency(metadata, optional, null);
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ function _extractToken(
|
||||||
token = paramMetadata;
|
token = paramMetadata;
|
||||||
|
|
||||||
} else if (paramMetadata instanceof Inject) {
|
} else if (paramMetadata instanceof Inject) {
|
||||||
token = paramMetadata.token;
|
token = paramMetadata['token'];
|
||||||
|
|
||||||
} else if (paramMetadata instanceof Optional) {
|
} else if (paramMetadata instanceof Optional) {
|
||||||
optional = true;
|
optional = true;
|
||||||
|
|
|
@ -12,6 +12,11 @@ import {el, stringifyElement} from '@angular/platform-browser/testing/browser_ut
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('dom adapter', () => {
|
describe('dom adapter', () => {
|
||||||
|
let defaultDoc: any;
|
||||||
|
beforeEach(() => {
|
||||||
|
defaultDoc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not coalesque text nodes', () => {
|
it('should not coalesque text nodes', () => {
|
||||||
const el1 = el('<div>a</div>');
|
const el1 = el('<div>a</div>');
|
||||||
const el2 = el('<div>b</div>');
|
const el2 = el('<div>b</div>');
|
||||||
|
@ -68,15 +73,15 @@ export function main() {
|
||||||
beforeEach(() => getDOM().resetBaseElement());
|
beforeEach(() => getDOM().resetBaseElement());
|
||||||
|
|
||||||
it('should return null if base element is absent',
|
it('should return null if base element is absent',
|
||||||
() => { expect(getDOM().getBaseHref()).toBeNull(); });
|
() => { expect(getDOM().getBaseHref(defaultDoc)).toBeNull(); });
|
||||||
|
|
||||||
it('should return the value of the base element', () => {
|
it('should return the value of the base element', () => {
|
||||||
const baseEl = getDOM().createElement('base');
|
const baseEl = getDOM().createElement('base');
|
||||||
getDOM().setAttribute(baseEl, 'href', '/drop/bass/connon/');
|
getDOM().setAttribute(baseEl, 'href', '/drop/bass/connon/');
|
||||||
const headEl = getDOM().defaultDoc().head;
|
const headEl = defaultDoc.head;
|
||||||
getDOM().appendChild(headEl, baseEl);
|
getDOM().appendChild(headEl, baseEl);
|
||||||
|
|
||||||
const baseHref = getDOM().getBaseHref();
|
const baseHref = getDOM().getBaseHref(defaultDoc);
|
||||||
getDOM().removeChild(headEl, baseEl);
|
getDOM().removeChild(headEl, baseEl);
|
||||||
getDOM().resetBaseElement();
|
getDOM().resetBaseElement();
|
||||||
|
|
||||||
|
@ -86,10 +91,10 @@ export function main() {
|
||||||
it('should return a relative url', () => {
|
it('should return a relative url', () => {
|
||||||
const baseEl = getDOM().createElement('base');
|
const baseEl = getDOM().createElement('base');
|
||||||
getDOM().setAttribute(baseEl, 'href', 'base');
|
getDOM().setAttribute(baseEl, 'href', 'base');
|
||||||
const headEl = getDOM().defaultDoc().head;
|
const headEl = defaultDoc.head;
|
||||||
getDOM().appendChild(headEl, baseEl);
|
getDOM().appendChild(headEl, baseEl);
|
||||||
|
|
||||||
const baseHref = getDOM().getBaseHref();
|
const baseHref = getDOM().getBaseHref(defaultDoc);
|
||||||
getDOM().removeChild(headEl, baseEl);
|
getDOM().removeChild(headEl, baseEl);
|
||||||
getDOM().resetBaseElement();
|
getDOM().resetBaseElement();
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {Attribute, Component, ContentChildren, Directive, HostBinding, HostListe
|
||||||
import {Renderer} from '@angular/core/src/render';
|
import {Renderer} from '@angular/core/src/render';
|
||||||
import {TestBed, async, fakeAsync, getTestBed, tick} from '@angular/core/testing';
|
import {TestBed, async, fakeAsync, getTestBed, tick} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||||
import {dispatchEvent, el} from '@angular/platform-browser/testing/browser_util';
|
import {dispatchEvent, el} from '@angular/platform-browser/testing/browser_util';
|
||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
|
@ -838,19 +839,20 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
const template = '<div listener></div>';
|
const template = '<div listener></div>';
|
||||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||||
const fixture = TestBed.createComponent(MyComp);
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
const doc = TestBed.get(DOCUMENT);
|
||||||
|
|
||||||
const tc = fixture.debugElement.children[0];
|
const tc = fixture.debugElement.children[0];
|
||||||
const listener = tc.injector.get(DirectiveListeningDomEvent);
|
const listener = tc.injector.get(DirectiveListeningDomEvent);
|
||||||
dispatchEvent(getDOM().getGlobalEventTarget('window'), 'domEvent');
|
dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent');
|
||||||
expect(listener.eventTypes).toEqual(['window_domEvent']);
|
expect(listener.eventTypes).toEqual(['window_domEvent']);
|
||||||
|
|
||||||
listener.eventTypes = [];
|
listener.eventTypes = [];
|
||||||
dispatchEvent(getDOM().getGlobalEventTarget('document'), 'domEvent');
|
dispatchEvent(getDOM().getGlobalEventTarget(doc, 'document'), 'domEvent');
|
||||||
expect(listener.eventTypes).toEqual(['document_domEvent', 'window_domEvent']);
|
expect(listener.eventTypes).toEqual(['document_domEvent', 'window_domEvent']);
|
||||||
|
|
||||||
fixture.destroy();
|
fixture.destroy();
|
||||||
listener.eventTypes = [];
|
listener.eventTypes = [];
|
||||||
dispatchEvent(getDOM().getGlobalEventTarget('body'), 'domEvent');
|
dispatchEvent(getDOM().getGlobalEventTarget(doc, 'body'), 'domEvent');
|
||||||
expect(listener.eventTypes).toEqual([]);
|
expect(listener.eventTypes).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -990,6 +992,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
const template = '<div *ngIf="ctxBoolProp" listener listenerother></div>';
|
const template = '<div *ngIf="ctxBoolProp" listener listenerother></div>';
|
||||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||||
const fixture = TestBed.createComponent(MyComp);
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
const doc = TestBed.get(DOCUMENT);
|
||||||
|
|
||||||
globalCounter = 0;
|
globalCounter = 0;
|
||||||
fixture.componentInstance.ctxBoolProp = true;
|
fixture.componentInstance.ctxBoolProp = true;
|
||||||
|
@ -999,7 +1002,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
|
|
||||||
const listener = tc.injector.get(DirectiveListeningDomEvent);
|
const listener = tc.injector.get(DirectiveListeningDomEvent);
|
||||||
const listenerother = tc.injector.get(DirectiveListeningDomEventOther);
|
const listenerother = tc.injector.get(DirectiveListeningDomEventOther);
|
||||||
dispatchEvent(getDOM().getGlobalEventTarget('window'), 'domEvent');
|
dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent');
|
||||||
expect(listener.eventTypes).toEqual(['window_domEvent']);
|
expect(listener.eventTypes).toEqual(['window_domEvent']);
|
||||||
expect(listenerother.eventType).toEqual('other_domEvent');
|
expect(listenerother.eventType).toEqual('other_domEvent');
|
||||||
expect(globalCounter).toEqual(1);
|
expect(globalCounter).toEqual(1);
|
||||||
|
@ -1007,12 +1010,12 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
|
||||||
|
|
||||||
fixture.componentInstance.ctxBoolProp = false;
|
fixture.componentInstance.ctxBoolProp = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
dispatchEvent(getDOM().getGlobalEventTarget('window'), 'domEvent');
|
dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent');
|
||||||
expect(globalCounter).toEqual(1);
|
expect(globalCounter).toEqual(1);
|
||||||
|
|
||||||
fixture.componentInstance.ctxBoolProp = true;
|
fixture.componentInstance.ctxBoolProp = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
dispatchEvent(getDOM().getGlobalEventTarget('window'), 'domEvent');
|
dispatchEvent(getDOM().getGlobalEventTarget(doc, 'window'), 'domEvent');
|
||||||
expect(globalCounter).toEqual(2);
|
expect(globalCounter).toEqual(2);
|
||||||
|
|
||||||
// need to destroy to release all remaining global event listeners
|
// need to destroy to release all remaining global event listeners
|
||||||
|
|
|
@ -30,7 +30,8 @@ import {DomSanitizer, DomSanitizerImpl} from './security/dom_sanitization_servic
|
||||||
|
|
||||||
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [
|
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [
|
||||||
{provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true},
|
{provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true},
|
||||||
{provide: PlatformLocation, useClass: BrowserPlatformLocation}
|
{provide: PlatformLocation, useClass: BrowserPlatformLocation},
|
||||||
|
{provide: DOCUMENT, useFactory: _document, deps: []},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,12 +60,8 @@ export function errorHandler(): ErrorHandler {
|
||||||
return new ErrorHandler();
|
return new ErrorHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function meta(): Meta {
|
|
||||||
return new Meta(getDOM());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function _document(): any {
|
export function _document(): any {
|
||||||
return getDOM().defaultDoc();
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _resolveDefaultAnimationDriver(): AnimationDriver {
|
export function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||||
|
@ -83,7 +80,6 @@ export function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||||
providers: [
|
providers: [
|
||||||
BROWSER_SANITIZATION_PROVIDERS,
|
BROWSER_SANITIZATION_PROVIDERS,
|
||||||
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
|
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
|
||||||
{provide: DOCUMENT, useFactory: _document, deps: []},
|
|
||||||
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
|
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
|
||||||
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
|
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
|
||||||
{provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true},
|
{provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true},
|
||||||
|
@ -92,11 +88,11 @@ export function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||||
{provide: RootRenderer, useExisting: DomRootRenderer},
|
{provide: RootRenderer, useExisting: DomRootRenderer},
|
||||||
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
||||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
||||||
{provide: Meta, useFactory: meta},
|
|
||||||
DomSharedStylesHost,
|
DomSharedStylesHost,
|
||||||
Testability,
|
Testability,
|
||||||
EventManager,
|
EventManager,
|
||||||
ELEMENT_PROBE_PROVIDERS,
|
ELEMENT_PROBE_PROVIDERS,
|
||||||
|
Meta,
|
||||||
Title,
|
Title,
|
||||||
],
|
],
|
||||||
exports: [CommonModule, ApplicationModule]
|
exports: [CommonModule, ApplicationModule]
|
||||||
|
|
|
@ -107,10 +107,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
|
|
||||||
get attrToPropMap(): any { return _attrToPropMap; }
|
get attrToPropMap(): any { return _attrToPropMap; }
|
||||||
|
|
||||||
query(selector: string): any { return document.querySelector(selector); }
|
querySelector(el: Element, selector: string): any { return el.querySelector(selector); }
|
||||||
querySelector(el: Element, selector: string): HTMLElement {
|
|
||||||
return el.querySelector(selector) as HTMLElement;
|
|
||||||
}
|
|
||||||
querySelectorAll(el: any, selector: string): any[] { return el.querySelectorAll(selector); }
|
querySelectorAll(el: any, selector: string): any[] { return el.querySelectorAll(selector); }
|
||||||
on(el: Node, evt: any, listener: any) { el.addEventListener(evt, listener, false); }
|
on(el: Node, evt: any, listener: any) { el.addEventListener(evt, listener, false); }
|
||||||
onAndCancel(el: Node, evt: any, listener: any): Function {
|
onAndCancel(el: Node, evt: any, listener: any): Function {
|
||||||
|
@ -274,7 +271,6 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
createHtmlDocument(): HTMLDocument {
|
createHtmlDocument(): HTMLDocument {
|
||||||
return document.implementation.createHTMLDocument('fakeTitle');
|
return document.implementation.createHTMLDocument('fakeTitle');
|
||||||
}
|
}
|
||||||
defaultDoc(): HTMLDocument { return document; }
|
|
||||||
getBoundingClientRect(el: Element): any {
|
getBoundingClientRect(el: Element): any {
|
||||||
try {
|
try {
|
||||||
return el.getBoundingClientRect();
|
return el.getBoundingClientRect();
|
||||||
|
@ -282,8 +278,8 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0};
|
return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getTitle(): string { return document.title; }
|
getTitle(doc: Document): string { return document.title; }
|
||||||
setTitle(newTitle: string) { document.title = newTitle || ''; }
|
setTitle(doc: Document, newTitle: string) { document.title = newTitle || ''; }
|
||||||
elementMatches(n: any, selector: string): boolean {
|
elementMatches(n: any, selector: string): boolean {
|
||||||
if (n instanceof HTMLElement) {
|
if (n instanceof HTMLElement) {
|
||||||
return n.matches && n.matches(selector) ||
|
return n.matches && n.matches(selector) ||
|
||||||
|
@ -330,7 +326,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
|
|
||||||
return _keyMap[key] || key;
|
return _keyMap[key] || key;
|
||||||
}
|
}
|
||||||
getGlobalEventTarget(target: string): EventTarget {
|
getGlobalEventTarget(doc: Document, target: string): EventTarget {
|
||||||
if (target === 'window') {
|
if (target === 'window') {
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
@ -343,7 +339,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
}
|
}
|
||||||
getHistory(): History { return window.history; }
|
getHistory(): History { return window.history; }
|
||||||
getLocation(): Location { return window.location; }
|
getLocation(): Location { return window.location; }
|
||||||
getBaseHref(): string {
|
getBaseHref(doc: Document): string {
|
||||||
const href = getBaseElementHref();
|
const href = getBaseElementHref();
|
||||||
return isBlank(href) ? null : relativePath(href);
|
return isBlank(href) ? null : relativePath(href);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ export abstract class GenericBrowserDomAdapter extends DomAdapter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
try {
|
try {
|
||||||
const element = this.createElement('div', this.defaultDoc());
|
const element = this.createElement('div', document);
|
||||||
if (isPresent(this.getStyle(element, 'animationName'))) {
|
if (isPresent(this.getStyle(element, 'animationName'))) {
|
||||||
this._animationPrefix = '';
|
this._animationPrefix = '';
|
||||||
} else {
|
} else {
|
||||||
|
@ -61,7 +61,7 @@ export abstract class GenericBrowserDomAdapter extends DomAdapter {
|
||||||
}
|
}
|
||||||
supportsDOMEvents(): boolean { return true; }
|
supportsDOMEvents(): boolean { return true; }
|
||||||
supportsNativeShadowDOM(): boolean {
|
supportsNativeShadowDOM(): boolean {
|
||||||
return typeof(<any>this.defaultDoc().body).createShadowRoot === 'function';
|
return typeof(<any>document.body).createShadowRoot === 'function';
|
||||||
}
|
}
|
||||||
getAnimationPrefix(): string { return this._animationPrefix ? this._animationPrefix : ''; }
|
getAnimationPrefix(): string { return this._animationPrefix ? this._animationPrefix : ''; }
|
||||||
getTransitionEnd(): string { return this._transitionEnd ? this._transitionEnd : ''; }
|
getTransitionEnd(): string { return this._transitionEnd ? this._transitionEnd : ''; }
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {LocationChangeListener, PlatformLocation} from '@angular/common';
|
import {LocationChangeListener, PlatformLocation} from '@angular/common';
|
||||||
import {Injectable} from '@angular/core';
|
import {Inject, Injectable} from '@angular/core';
|
||||||
|
|
||||||
import {getDOM} from '../../dom/dom_adapter';
|
import {getDOM} from '../../dom/dom_adapter';
|
||||||
|
import {DOCUMENT} from '../../dom/dom_tokens';
|
||||||
|
|
||||||
import {supportsState} from './history';
|
import {supportsState} from './history';
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ export class BrowserPlatformLocation extends PlatformLocation {
|
||||||
private _location: Location;
|
private _location: Location;
|
||||||
private _history: History;
|
private _history: History;
|
||||||
|
|
||||||
constructor() {
|
constructor(@Inject(DOCUMENT) private _doc: any) {
|
||||||
super();
|
super();
|
||||||
this._init();
|
this._init();
|
||||||
}
|
}
|
||||||
|
@ -39,14 +40,14 @@ export class BrowserPlatformLocation extends PlatformLocation {
|
||||||
|
|
||||||
get location(): Location { return this._location; }
|
get location(): Location { return this._location; }
|
||||||
|
|
||||||
getBaseHrefFromDOM(): string { return getDOM().getBaseHref(); }
|
getBaseHrefFromDOM(): string { return getDOM().getBaseHref(this._doc); }
|
||||||
|
|
||||||
onPopState(fn: LocationChangeListener): void {
|
onPopState(fn: LocationChangeListener): void {
|
||||||
getDOM().getGlobalEventTarget('window').addEventListener('popstate', fn, false);
|
getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('popstate', fn, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
onHashChange(fn: LocationChangeListener): void {
|
onHashChange(fn: LocationChangeListener): void {
|
||||||
getDOM().getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
|
getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('hashchange', fn, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
get pathname(): string { return this._location.pathname; }
|
get pathname(): string { return this._location.pathname; }
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable} from '@angular/core';
|
import {Inject, Injectable} from '@angular/core';
|
||||||
import {DomAdapter} from '../dom/dom_adapter';
|
|
||||||
|
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
||||||
|
import {DOCUMENT} from '../dom/dom_tokens';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a meta element.
|
* Represents a meta element.
|
||||||
|
@ -33,7 +36,8 @@ export type MetaDefinition = {
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Meta {
|
export class Meta {
|
||||||
constructor(private _dom: DomAdapter) {}
|
private _dom: DomAdapter;
|
||||||
|
constructor(@Inject(DOCUMENT) private _doc: any) { this._dom = getDOM(); }
|
||||||
|
|
||||||
addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement {
|
addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement {
|
||||||
if (!tag) return null;
|
if (!tag) return null;
|
||||||
|
@ -52,13 +56,12 @@ export class Meta {
|
||||||
|
|
||||||
getTag(attrSelector: string): HTMLMetaElement {
|
getTag(attrSelector: string): HTMLMetaElement {
|
||||||
if (!attrSelector) return null;
|
if (!attrSelector) return null;
|
||||||
return this._dom.query(`meta[${attrSelector}]`);
|
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTags(attrSelector: string): HTMLMetaElement[] {
|
getTags(attrSelector: string): HTMLMetaElement[] {
|
||||||
if (!attrSelector) return [];
|
if (!attrSelector) return [];
|
||||||
const list /*NodeList*/ =
|
const list /*NodeList*/ = this._dom.querySelectorAll(this._doc, `meta[${attrSelector}]`);
|
||||||
this._dom.querySelectorAll(this._dom.defaultDoc(), `meta[${attrSelector}]`);
|
|
||||||
return list ? [].slice.call(list) : [];
|
return list ? [].slice.call(list) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +95,7 @@ export class Meta {
|
||||||
}
|
}
|
||||||
const element: HTMLMetaElement = this._dom.createElement('meta') as HTMLMetaElement;
|
const element: HTMLMetaElement = this._dom.createElement('meta') as HTMLMetaElement;
|
||||||
this._setMetaElementAttributes(meta, element);
|
this._setMetaElementAttributes(meta, element);
|
||||||
const head = this._dom.getElementsByTagName(this._dom.defaultDoc(), 'head')[0];
|
const head = this._dom.getElementsByTagName(this._doc, 'head')[0];
|
||||||
this._dom.appendChild(head, element);
|
this._dom.appendChild(head, element);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,12 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Inject, Injectable} from '@angular/core';
|
||||||
|
|
||||||
import {getDOM} from '../dom/dom_adapter';
|
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.
|
* A service that can be used to get and set the title of a current HTML document.
|
||||||
*
|
*
|
||||||
|
@ -17,16 +22,18 @@ import {getDOM} from '../dom/dom_adapter';
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
|
@Injectable()
|
||||||
export class Title {
|
export class Title {
|
||||||
|
constructor(@Inject(DOCUMENT) private _doc: any) {}
|
||||||
/**
|
/**
|
||||||
* Get the title of the current HTML document.
|
* Get the title of the current HTML document.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getTitle(): string { return getDOM().getTitle(); }
|
getTitle(): string { return getDOM().getTitle(this._doc); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the title of the current HTML document.
|
* Set the title of the current HTML document.
|
||||||
* @param newTitle
|
* @param newTitle
|
||||||
*/
|
*/
|
||||||
setTitle(newTitle: string) { getDOM().setTitle(newTitle); }
|
setTitle(newTitle: string) { getDOM().setTitle(this._doc, newTitle); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,7 @@ export abstract class DomAdapter {
|
||||||
_attrToPropMap: {[key: string]: string};
|
_attrToPropMap: {[key: string]: string};
|
||||||
|
|
||||||
abstract parse(templateHtml: string): any /** TODO #9100 */;
|
abstract parse(templateHtml: string): any /** TODO #9100 */;
|
||||||
abstract query(selector: string): any;
|
abstract querySelector(el: any /** TODO #9100 */, selector: string): any;
|
||||||
abstract querySelector(el: any /** TODO #9100 */, selector: string): HTMLElement;
|
|
||||||
abstract querySelectorAll(el: any /** TODO #9100 */, selector: string): any[];
|
abstract querySelectorAll(el: any /** TODO #9100 */, selector: string): any[];
|
||||||
abstract on(
|
abstract on(
|
||||||
el: any /** TODO #9100 */, evt: any /** TODO #9100 */, listener: any /** TODO #9100 */): any
|
el: any /** TODO #9100 */, evt: any /** TODO #9100 */, listener: any /** TODO #9100 */): any
|
||||||
|
@ -145,10 +144,9 @@ export abstract class DomAdapter {
|
||||||
/** TODO #9100 */;
|
/** TODO #9100 */;
|
||||||
abstract templateAwareRoot(el: any /** TODO #9100 */): any /** TODO #9100 */;
|
abstract templateAwareRoot(el: any /** TODO #9100 */): any /** TODO #9100 */;
|
||||||
abstract createHtmlDocument(): HTMLDocument;
|
abstract createHtmlDocument(): HTMLDocument;
|
||||||
abstract defaultDoc(): HTMLDocument;
|
|
||||||
abstract getBoundingClientRect(el: any /** TODO #9100 */): any /** TODO #9100 */;
|
abstract getBoundingClientRect(el: any /** TODO #9100 */): any /** TODO #9100 */;
|
||||||
abstract getTitle(): string;
|
abstract getTitle(doc: Document): string;
|
||||||
abstract setTitle(newTitle: string): any /** TODO #9100 */;
|
abstract setTitle(doc: Document, newTitle: string): any /** TODO #9100 */;
|
||||||
abstract elementMatches(n: any /** TODO #9100 */, selector: string): boolean;
|
abstract elementMatches(n: any /** TODO #9100 */, selector: string): boolean;
|
||||||
abstract isTemplateElement(el: any): boolean;
|
abstract isTemplateElement(el: any): boolean;
|
||||||
abstract isTextNode(node: any /** TODO #9100 */): boolean;
|
abstract isTextNode(node: any /** TODO #9100 */): boolean;
|
||||||
|
@ -164,10 +162,10 @@ export abstract class DomAdapter {
|
||||||
/** TODO #9100 */;
|
/** TODO #9100 */;
|
||||||
abstract supportsDOMEvents(): boolean;
|
abstract supportsDOMEvents(): boolean;
|
||||||
abstract supportsNativeShadowDOM(): boolean;
|
abstract supportsNativeShadowDOM(): boolean;
|
||||||
abstract getGlobalEventTarget(target: string): any;
|
abstract getGlobalEventTarget(doc: Document, target: string): any;
|
||||||
abstract getHistory(): History;
|
abstract getHistory(): History;
|
||||||
abstract getLocation(): Location;
|
abstract getLocation(): Location;
|
||||||
abstract getBaseHref(): string;
|
abstract getBaseHref(doc: Document): string;
|
||||||
abstract resetBaseElement(): void;
|
abstract resetBaseElement(): void;
|
||||||
abstract getUserAgent(): string;
|
abstract getUserAgent(): string;
|
||||||
abstract setData(element: any /** TODO #9100 */, name: string, value: string): any
|
abstract setData(element: any /** TODO #9100 */, name: string, value: string): any
|
||||||
|
|
|
@ -6,11 +6,16 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable} from '@angular/core';
|
import {Inject, Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
import {DOCUMENT} from '../dom_tokens';
|
||||||
|
|
||||||
import {EventManagerPlugin} from './event_manager';
|
import {EventManagerPlugin} from './event_manager';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DomEventsPlugin extends EventManagerPlugin {
|
export class DomEventsPlugin extends EventManagerPlugin {
|
||||||
|
constructor(@Inject(DOCUMENT) doc: any) { super(doc); }
|
||||||
|
|
||||||
// This plugin should come last in the list of plugins, because it accepts all
|
// This plugin should come last in the list of plugins, because it accepts all
|
||||||
// events.
|
// events.
|
||||||
supports(eventName: string): boolean { return true; }
|
supports(eventName: string): boolean { return true; }
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {Inject, Injectable, InjectionToken, NgZone} from '@angular/core';
|
||||||
|
|
||||||
import {getDOM} from '../dom_adapter';
|
import {getDOM} from '../dom_adapter';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
|
@ -62,6 +61,8 @@ export class EventManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class EventManagerPlugin {
|
export abstract class EventManagerPlugin {
|
||||||
|
constructor(private _doc: any) {}
|
||||||
|
|
||||||
manager: EventManager;
|
manager: EventManager;
|
||||||
|
|
||||||
abstract supports(eventName: string): boolean;
|
abstract supports(eventName: string): boolean;
|
||||||
|
@ -69,7 +70,7 @@ export abstract class EventManagerPlugin {
|
||||||
abstract addEventListener(element: HTMLElement, eventName: string, handler: Function): Function;
|
abstract addEventListener(element: HTMLElement, eventName: string, handler: Function): Function;
|
||||||
|
|
||||||
addGlobalEventListener(element: string, eventName: string, handler: Function): Function {
|
addGlobalEventListener(element: string, eventName: string, handler: Function): Function {
|
||||||
const target: HTMLElement = getDOM().getGlobalEventTarget(element);
|
const target: HTMLElement = getDOM().getGlobalEventTarget(this._doc, element);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new Error(`Unsupported event target ${target} for event ${eventName}`);
|
throw new Error(`Unsupported event target ${target} for event ${eventName}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, Injectable, InjectionToken} from '@angular/core';
|
import {Inject, Injectable, InjectionToken} from '@angular/core';
|
||||||
|
|
||||||
|
import {DOCUMENT} from '../dom_tokens';
|
||||||
|
|
||||||
import {EventManagerPlugin} from './event_manager';
|
import {EventManagerPlugin} from './event_manager';
|
||||||
|
|
||||||
const EVENT_NAMES = {
|
const EVENT_NAMES = {
|
||||||
|
@ -85,7 +88,11 @@ export class HammerGestureConfig {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HammerGesturesPlugin extends EventManagerPlugin {
|
export class HammerGesturesPlugin extends EventManagerPlugin {
|
||||||
constructor(@Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig) { super(); }
|
constructor(
|
||||||
|
@Inject(DOCUMENT) doc: any,
|
||||||
|
@Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig) {
|
||||||
|
super(doc);
|
||||||
|
}
|
||||||
|
|
||||||
supports(eventName: string): boolean {
|
supports(eventName: string): boolean {
|
||||||
if (!EVENT_NAMES.hasOwnProperty(eventName.toLowerCase()) && !this.isCustomEvent(eventName)) {
|
if (!EVENT_NAMES.hasOwnProperty(eventName.toLowerCase()) && !this.isCustomEvent(eventName)) {
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, NgZone} from '@angular/core';
|
import {Inject, Injectable, NgZone} from '@angular/core';
|
||||||
|
|
||||||
import {getDOM} from '../dom_adapter';
|
import {getDOM} from '../dom_adapter';
|
||||||
|
import {DOCUMENT} from '../dom_tokens';
|
||||||
|
|
||||||
import {EventManagerPlugin} from './event_manager';
|
import {EventManagerPlugin} from './event_manager';
|
||||||
|
|
||||||
const MODIFIER_KEYS = ['alt', 'control', 'meta', 'shift'];
|
const MODIFIER_KEYS = ['alt', 'control', 'meta', 'shift'];
|
||||||
|
@ -23,7 +26,7 @@ const MODIFIER_KEY_GETTERS: {[key: string]: (event: KeyboardEvent) => boolean} =
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KeyEventsPlugin extends EventManagerPlugin {
|
export class KeyEventsPlugin extends EventManagerPlugin {
|
||||||
constructor() { super(); }
|
constructor(@Inject(DOCUMENT) doc: any) { super(doc); }
|
||||||
|
|
||||||
supports(eventName: string): boolean { return KeyEventsPlugin.parseEventName(eventName) != null; }
|
supports(eventName: string): boolean { return KeyEventsPlugin.parseEventName(eventName) != null; }
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, Sanitizer, SecurityContext} from '@angular/core';
|
import {Inject, Injectable, Sanitizer, SecurityContext} from '@angular/core';
|
||||||
|
|
||||||
|
import {DOCUMENT} from '../dom/dom_tokens';
|
||||||
|
|
||||||
import {sanitizeHtml} from './html_sanitizer';
|
import {sanitizeHtml} from './html_sanitizer';
|
||||||
import {sanitizeStyle} from './style_sanitizer';
|
import {sanitizeStyle} from './style_sanitizer';
|
||||||
|
@ -15,6 +17,7 @@ import {sanitizeUrl} from './url_sanitizer';
|
||||||
export {SecurityContext};
|
export {SecurityContext};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marker interface for a value that's safe to use in a particular context.
|
* Marker interface for a value that's safe to use in a particular context.
|
||||||
*
|
*
|
||||||
|
@ -147,6 +150,8 @@ export abstract class DomSanitizer implements Sanitizer {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DomSanitizerImpl extends DomSanitizer {
|
export class DomSanitizerImpl extends DomSanitizer {
|
||||||
|
constructor(@Inject(DOCUMENT) private _doc: any) { super(); }
|
||||||
|
|
||||||
sanitize(ctx: SecurityContext, value: any): string {
|
sanitize(ctx: SecurityContext, value: any): string {
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
switch (ctx) {
|
switch (ctx) {
|
||||||
|
@ -155,7 +160,7 @@ export class DomSanitizerImpl extends DomSanitizer {
|
||||||
case SecurityContext.HTML:
|
case SecurityContext.HTML:
|
||||||
if (value instanceof SafeHtmlImpl) return value.changingThisBreaksApplicationSecurity;
|
if (value instanceof SafeHtmlImpl) return value.changingThisBreaksApplicationSecurity;
|
||||||
this.checkNotSafeValue(value, 'HTML');
|
this.checkNotSafeValue(value, 'HTML');
|
||||||
return sanitizeHtml(String(value));
|
return sanitizeHtml(this._doc, String(value));
|
||||||
case SecurityContext.STYLE:
|
case SecurityContext.STYLE:
|
||||||
if (value instanceof SafeStyleImpl) return value.changingThisBreaksApplicationSecurity;
|
if (value instanceof SafeStyleImpl) return value.changingThisBreaksApplicationSecurity;
|
||||||
this.checkNotSafeValue(value, 'Style');
|
this.checkNotSafeValue(value, 'Style');
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {isDevMode} from '@angular/core';
|
import {isDevMode} from '@angular/core';
|
||||||
|
|
||||||
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
||||||
|
import {DOCUMENT} from '../dom/dom_tokens';
|
||||||
|
|
||||||
import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer';
|
import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer';
|
||||||
|
|
||||||
|
@ -243,7 +244,7 @@ function stripCustomNsAttrs(el: Element) {
|
||||||
* Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to
|
* Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to
|
||||||
* the DOM in a browser environment.
|
* the DOM in a browser environment.
|
||||||
*/
|
*/
|
||||||
export function sanitizeHtml(unsafeHtmlInput: string): string {
|
export function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
|
||||||
try {
|
try {
|
||||||
const containerEl = getInertElement();
|
const containerEl = getInertElement();
|
||||||
// Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime).
|
// Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime).
|
||||||
|
@ -262,7 +263,7 @@ export function sanitizeHtml(unsafeHtmlInput: string): string {
|
||||||
|
|
||||||
unsafeHtml = parsedHtml;
|
unsafeHtml = parsedHtml;
|
||||||
DOM.setInnerHTML(containerEl, unsafeHtml);
|
DOM.setInnerHTML(containerEl, unsafeHtml);
|
||||||
if ((DOM.defaultDoc() as any).documentMode) {
|
if (defaultDoc.documentMode) {
|
||||||
// strip custom-namespaced attributes on IE<=11
|
// strip custom-namespaced attributes on IE<=11
|
||||||
stripCustomNsAttrs(containerEl);
|
stripCustomNsAttrs(containerEl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,8 @@ import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('Meta service', () => {
|
describe('Meta service', () => {
|
||||||
|
const doc: HTMLDocument = getDOM().createHtmlDocument();
|
||||||
const metaService: Meta = new Meta(getDOM());
|
const metaService: Meta = new Meta(doc);
|
||||||
const doc: HTMLDocument = getDOM().defaultDoc();
|
|
||||||
let defaultMeta: HTMLMetaElement;
|
let defaultMeta: HTMLMetaElement;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -14,23 +14,24 @@ import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('title service', () => {
|
describe('title service', () => {
|
||||||
const initialTitle = getDOM().getTitle();
|
const doc = getDOM().createHtmlDocument();
|
||||||
const titleService = new Title();
|
const initialTitle = getDOM().getTitle(doc);
|
||||||
|
const titleService = new Title(doc);
|
||||||
|
|
||||||
afterEach(() => { getDOM().setTitle(initialTitle); });
|
afterEach(() => { getDOM().setTitle(doc, initialTitle); });
|
||||||
|
|
||||||
it('should allow reading initial title',
|
it('should allow reading initial title',
|
||||||
() => { expect(titleService.getTitle()).toEqual(initialTitle); });
|
() => { expect(titleService.getTitle()).toEqual(initialTitle); });
|
||||||
|
|
||||||
it('should set a title on the injected document', () => {
|
it('should set a title on the injected document', () => {
|
||||||
titleService.setTitle('test title');
|
titleService.setTitle('test title');
|
||||||
expect(getDOM().getTitle()).toEqual('test title');
|
expect(getDOM().getTitle(doc)).toEqual('test title');
|
||||||
expect(titleService.getTitle()).toEqual('test title');
|
expect(titleService.getTitle()).toEqual('test title');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset title to empty string if title not provided', () => {
|
it('should reset title to empty string if title not provided', () => {
|
||||||
titleService.setTitle(null);
|
titleService.setTitle(null);
|
||||||
expect(getDOM().getTitle()).toEqual('');
|
expect(getDOM().getTitle(doc)).toEqual('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,16 +15,20 @@ import {el} from '../../../testing/browser_util';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
let domEventPlugin: DomEventsPlugin;
|
let domEventPlugin: DomEventsPlugin;
|
||||||
|
let doc: any;
|
||||||
|
|
||||||
describe('EventManager', () => {
|
describe('EventManager', () => {
|
||||||
|
|
||||||
beforeEach(() => { domEventPlugin = new DomEventsPlugin(); });
|
beforeEach(() => {
|
||||||
|
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||||
|
domEventPlugin = new DomEventsPlugin(doc);
|
||||||
|
});
|
||||||
|
|
||||||
it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
|
it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
|
||||||
() => {
|
() => {
|
||||||
const element = el('<div></div>');
|
const element = el('<div></div>');
|
||||||
const handler = (e: any /** TODO #9100 */) => e;
|
const handler = (e: any /** TODO #9100 */) => e;
|
||||||
const plugin = new FakeEventManagerPlugin(['click']);
|
const plugin = new FakeEventManagerPlugin(doc, ['click']);
|
||||||
const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
|
const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
|
||||||
manager.addEventListener(element, 'click', handler);
|
manager.addEventListener(element, 'click', handler);
|
||||||
expect(plugin.eventHandler['click']).toBe(handler);
|
expect(plugin.eventHandler['click']).toBe(handler);
|
||||||
|
@ -34,8 +38,8 @@ export function main() {
|
||||||
const element = el('<div></div>');
|
const element = el('<div></div>');
|
||||||
const clickHandler = (e: any /** TODO #9100 */) => e;
|
const clickHandler = (e: any /** TODO #9100 */) => e;
|
||||||
const dblClickHandler = (e: any /** TODO #9100 */) => e;
|
const dblClickHandler = (e: any /** TODO #9100 */) => e;
|
||||||
const plugin1 = new FakeEventManagerPlugin(['dblclick']);
|
const plugin1 = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||||
const plugin2 = new FakeEventManagerPlugin(['click', 'dblclick']);
|
const plugin2 = new FakeEventManagerPlugin(doc, ['click', 'dblclick']);
|
||||||
const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
|
const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
|
||||||
manager.addEventListener(element, 'click', clickHandler);
|
manager.addEventListener(element, 'click', clickHandler);
|
||||||
manager.addEventListener(element, 'dblclick', dblClickHandler);
|
manager.addEventListener(element, 'dblclick', dblClickHandler);
|
||||||
|
@ -45,7 +49,7 @@ export function main() {
|
||||||
|
|
||||||
it('should throw when no plugin can handle the event', () => {
|
it('should throw when no plugin can handle the event', () => {
|
||||||
const element = el('<div></div>');
|
const element = el('<div></div>');
|
||||||
const plugin = new FakeEventManagerPlugin(['dblclick']);
|
const plugin = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||||
const manager = new EventManager([plugin], new FakeNgZone());
|
const manager = new EventManager([plugin], new FakeNgZone());
|
||||||
expect(() => manager.addEventListener(element, 'click', null))
|
expect(() => manager.addEventListener(element, 'click', null))
|
||||||
.toThrowError('No event manager plugin found for event click');
|
.toThrowError('No event manager plugin found for event click');
|
||||||
|
@ -54,7 +58,7 @@ export function main() {
|
||||||
it('events are caught when fired from a child', () => {
|
it('events are caught when fired from a child', () => {
|
||||||
const element = el('<div><div></div></div>');
|
const element = el('<div><div></div></div>');
|
||||||
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
|
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
|
||||||
getDOM().appendChild(getDOM().defaultDoc().body, element);
|
getDOM().appendChild(doc.body, element);
|
||||||
|
|
||||||
const child = getDOM().firstChild(element);
|
const child = getDOM().firstChild(element);
|
||||||
const dispatchedEvent = getDOM().createMouseEvent('click');
|
const dispatchedEvent = getDOM().createMouseEvent('click');
|
||||||
|
@ -69,7 +73,7 @@ export function main() {
|
||||||
|
|
||||||
it('should add and remove global event listeners', () => {
|
it('should add and remove global event listeners', () => {
|
||||||
const element = el('<div><div></div></div>');
|
const element = el('<div><div></div></div>');
|
||||||
getDOM().appendChild(getDOM().defaultDoc().body, element);
|
getDOM().appendChild(doc.body, element);
|
||||||
const dispatchedEvent = getDOM().createMouseEvent('click');
|
const dispatchedEvent = getDOM().createMouseEvent('click');
|
||||||
let receivedEvent: any /** TODO #9100 */ = null;
|
let receivedEvent: any /** TODO #9100 */ = null;
|
||||||
const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
|
const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
|
||||||
|
@ -91,7 +95,7 @@ export function main() {
|
||||||
class FakeEventManagerPlugin extends EventManagerPlugin {
|
class FakeEventManagerPlugin extends EventManagerPlugin {
|
||||||
eventHandler: {[event: string]: Function} = {};
|
eventHandler: {[event: string]: Function} = {};
|
||||||
|
|
||||||
constructor(public supportedEvents: string[]) { super(); }
|
constructor(doc: any, public supportedEvents: string[]) { super(doc); }
|
||||||
|
|
||||||
supports(eventName: string): boolean { return this.supportedEvents.indexOf(eventName) > -1; }
|
supports(eventName: string): boolean { return this.supportedEvents.indexOf(eventName) > -1; }
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function main() {
|
||||||
describe('HammerGesturesPlugin', () => {
|
describe('HammerGesturesPlugin', () => {
|
||||||
|
|
||||||
it('should implement addGlobalEventListener', () => {
|
it('should implement addGlobalEventListener', () => {
|
||||||
const plugin = new HammerGesturesPlugin(new HammerGestureConfig());
|
const plugin = new HammerGesturesPlugin(document, new HammerGestureConfig());
|
||||||
|
|
||||||
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should implement addGlobalEventListener', () => {
|
it('should implement addGlobalEventListener', () => {
|
||||||
const plugin = new KeyEventsPlugin();
|
const plugin = new KeyEventsPlugin(document);
|
||||||
|
|
||||||
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {DomSanitizerImpl} from '../../src/security/dom_sanitization_service';
|
||||||
export function main() {
|
export function main() {
|
||||||
t.describe('DOM Sanitization Service', () => {
|
t.describe('DOM Sanitization Service', () => {
|
||||||
t.it('accepts resource URL values for resource contexts', () => {
|
t.it('accepts resource URL values for resource contexts', () => {
|
||||||
const svc = new DomSanitizerImpl();
|
const svc = new DomSanitizerImpl(null);
|
||||||
const resourceUrl = svc.bypassSecurityTrustResourceUrl('http://hello/world');
|
const resourceUrl = svc.bypassSecurityTrustResourceUrl('http://hello/world');
|
||||||
t.expect(svc.sanitize(SecurityContext.URL, resourceUrl)).toBe('http://hello/world');
|
t.expect(svc.sanitize(SecurityContext.URL, resourceUrl)).toBe('http://hello/world');
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,10 +14,12 @@ import {sanitizeHtml} from '../../src/security/html_sanitizer';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
t.describe('HTML sanitizer', () => {
|
t.describe('HTML sanitizer', () => {
|
||||||
|
let defaultDoc: any;
|
||||||
let originalLog: (msg: any) => any = null;
|
let originalLog: (msg: any) => any = null;
|
||||||
let logMsgs: string[];
|
let logMsgs: string[];
|
||||||
|
|
||||||
t.beforeEach(() => {
|
t.beforeEach(() => {
|
||||||
|
defaultDoc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||||
logMsgs = [];
|
logMsgs = [];
|
||||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||||
getDOM().log = (msg) => logMsgs.push(msg);
|
getDOM().log = (msg) => logMsgs.push(msg);
|
||||||
|
@ -25,52 +27,55 @@ export function main() {
|
||||||
t.afterEach(() => { getDOM().log = originalLog; });
|
t.afterEach(() => { getDOM().log = originalLog; });
|
||||||
|
|
||||||
t.it('serializes nested structures', () => {
|
t.it('serializes nested structures', () => {
|
||||||
t.expect(sanitizeHtml('<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>'))
|
t.expect(sanitizeHtml(defaultDoc, '<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>'))
|
||||||
.toEqual('<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>');
|
.toEqual('<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>');
|
||||||
t.expect(logMsgs).toEqual([]);
|
t.expect(logMsgs).toEqual([]);
|
||||||
});
|
});
|
||||||
t.it('serializes self closing elements', () => {
|
t.it('serializes self closing elements', () => {
|
||||||
t.expect(sanitizeHtml('<p>Hello <br> World</p>')).toEqual('<p>Hello <br> World</p>');
|
t.expect(sanitizeHtml(defaultDoc, '<p>Hello <br> World</p>'))
|
||||||
|
.toEqual('<p>Hello <br> World</p>');
|
||||||
});
|
});
|
||||||
t.it('supports namespaced elements', () => {
|
t.it('supports namespaced elements', () => {
|
||||||
t.expect(sanitizeHtml('a<my:hr/><my:div>b</my:div>c')).toEqual('abc');
|
t.expect(sanitizeHtml(defaultDoc, 'a<my:hr/><my:div>b</my:div>c')).toEqual('abc');
|
||||||
});
|
});
|
||||||
t.it('supports namespaced attributes', () => {
|
t.it('supports namespaced attributes', () => {
|
||||||
t.expect(sanitizeHtml('<a xlink:href="something">t</a>'))
|
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="something">t</a>'))
|
||||||
.toEqual('<a xlink:href="something">t</a>');
|
.toEqual('<a xlink:href="something">t</a>');
|
||||||
t.expect(sanitizeHtml('<a xlink:evil="something">t</a>')).toEqual('<a>t</a>');
|
t.expect(sanitizeHtml(defaultDoc, '<a xlink:evil="something">t</a>')).toEqual('<a>t</a>');
|
||||||
t.expect(sanitizeHtml('<a xlink:href="javascript:foo()">t</a>'))
|
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="javascript:foo()">t</a>'))
|
||||||
.toEqual('<a xlink:href="unsafe:javascript:foo()">t</a>');
|
.toEqual('<a xlink:href="unsafe:javascript:foo()">t</a>');
|
||||||
});
|
});
|
||||||
t.it('supports HTML5 elements', () => {
|
t.it('supports HTML5 elements', () => {
|
||||||
t.expect(sanitizeHtml('<main><summary>Works</summary></main>'))
|
t.expect(sanitizeHtml(defaultDoc, '<main><summary>Works</summary></main>'))
|
||||||
.toEqual('<main><summary>Works</summary></main>');
|
.toEqual('<main><summary>Works</summary></main>');
|
||||||
});
|
});
|
||||||
t.it('sanitizes srcset attributes', () => {
|
t.it('sanitizes srcset attributes', () => {
|
||||||
t.expect(sanitizeHtml('<img srcset="/foo.png 400px, javascript:evil() 23px">'))
|
t.expect(sanitizeHtml(defaultDoc, '<img srcset="/foo.png 400px, javascript:evil() 23px">'))
|
||||||
.toEqual('<img srcset="/foo.png 400px, unsafe:javascript:evil() 23px">');
|
.toEqual('<img srcset="/foo.png 400px, unsafe:javascript:evil() 23px">');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.it('supports sanitizing plain text', () => {
|
t.it('supports sanitizing plain text', () => {
|
||||||
t.expect(sanitizeHtml('Hello, World')).toEqual('Hello, World');
|
t.expect(sanitizeHtml(defaultDoc, 'Hello, World')).toEqual('Hello, World');
|
||||||
});
|
});
|
||||||
t.it('ignores non-element, non-attribute nodes', () => {
|
t.it('ignores non-element, non-attribute nodes', () => {
|
||||||
t.expect(sanitizeHtml('<!-- comments? -->no.')).toEqual('no.');
|
t.expect(sanitizeHtml(defaultDoc, '<!-- comments? -->no.')).toEqual('no.');
|
||||||
t.expect(sanitizeHtml('<?pi nodes?>no.')).toEqual('no.');
|
t.expect(sanitizeHtml(defaultDoc, '<?pi nodes?>no.')).toEqual('no.');
|
||||||
t.expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/);
|
t.expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/);
|
||||||
});
|
});
|
||||||
t.it('supports sanitizing escaped entities', () => {
|
t.it('supports sanitizing escaped entities', () => {
|
||||||
t.expect(sanitizeHtml('🚀')).toEqual('🚀');
|
t.expect(sanitizeHtml(defaultDoc, '🚀')).toEqual('🚀');
|
||||||
t.expect(logMsgs).toEqual([]);
|
t.expect(logMsgs).toEqual([]);
|
||||||
});
|
});
|
||||||
t.it('does not warn when just re-encoding text', () => {
|
t.it('does not warn when just re-encoding text', () => {
|
||||||
t.expect(sanitizeHtml('<p>Hellö Wörld</p>')).toEqual('<p>Hellö Wörld</p>');
|
t.expect(sanitizeHtml(defaultDoc, '<p>Hellö Wörld</p>'))
|
||||||
|
.toEqual('<p>Hellö Wörld</p>');
|
||||||
t.expect(logMsgs).toEqual([]);
|
t.expect(logMsgs).toEqual([]);
|
||||||
});
|
});
|
||||||
t.it('escapes entities', () => {
|
t.it('escapes entities', () => {
|
||||||
t.expect(sanitizeHtml('<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>'))
|
||||||
t.expect(sanitizeHtml('<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
.toEqual('<p>Hello < World</p>');
|
||||||
t.expect(sanitizeHtml('<p alt="% & " !">Hello</p>'))
|
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
||||||
|
t.expect(sanitizeHtml(defaultDoc, '<p alt="% & " !">Hello</p>'))
|
||||||
.toEqual('<p alt="% & " !">Hello</p>'); // NB: quote encoded as ASCII ".
|
.toEqual('<p alt="% & " !">Hello</p>'); // NB: quote encoded as ASCII ".
|
||||||
});
|
});
|
||||||
t.describe('should strip dangerous elements', () => {
|
t.describe('should strip dangerous elements', () => {
|
||||||
|
@ -80,11 +85,12 @@ export function main() {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const tag of dangerousTags) {
|
for (const tag of dangerousTags) {
|
||||||
t.it(
|
t.it(`${tag}`, () => {
|
||||||
`${tag}`, () => { t.expect(sanitizeHtml(`<${tag}>evil!</${tag}>`)).toEqual('evil!'); });
|
t.expect(sanitizeHtml(defaultDoc, `<${tag}>evil!</${tag}>`)).toEqual('evil!');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
t.it(`swallows frame entirely`, () => {
|
t.it(`swallows frame entirely`, () => {
|
||||||
t.expect(sanitizeHtml(`<frame>evil!</frame>`)).not.toContain('<frame>');
|
t.expect(sanitizeHtml(defaultDoc, `<frame>evil!</frame>`)).not.toContain('<frame>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
t.describe('should strip dangerous attributes', () => {
|
t.describe('should strip dangerous attributes', () => {
|
||||||
|
@ -92,14 +98,14 @@ export function main() {
|
||||||
|
|
||||||
for (const attr of dangerousAttrs) {
|
for (const attr of dangerousAttrs) {
|
||||||
t.it(`${attr}`, () => {
|
t.it(`${attr}`, () => {
|
||||||
t.expect(sanitizeHtml(`<a ${attr}="x">evil!</a>`)).toEqual('<a>evil!</a>');
|
t.expect(sanitizeHtml(defaultDoc, `<a ${attr}="x">evil!</a>`)).toEqual('<a>evil!</a>');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (browserDetection.isWebkit) {
|
if (browserDetection.isWebkit) {
|
||||||
t.it('should prevent mXSS attacks', function() {
|
t.it('should prevent mXSS attacks', function() {
|
||||||
t.expect(sanitizeHtml('<a href=" javascript:alert(1)">CLICKME</a>'))
|
t.expect(sanitizeHtml(defaultDoc, '<a href=" javascript:alert(1)">CLICKME</a>'))
|
||||||
.toEqual('<a href="unsafe:javascript:alert(1)">CLICKME</a>');
|
.toEqual('<a href="unsafe:javascript:alert(1)">CLICKME</a>');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {LocationChangeEvent, LocationChangeListener, PlatformLocation} from '@angular/common';
|
import {LocationChangeEvent, LocationChangeListener, PlatformLocation} from '@angular/common';
|
||||||
import {Injectable} from '@angular/core';
|
import {Inject, Injectable} from '@angular/core';
|
||||||
|
import {DOCUMENT} from '@angular/platform-browser';
|
||||||
import {Subject} from 'rxjs/Subject';
|
import {Subject} from 'rxjs/Subject';
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
|
|
||||||
|
@ -27,7 +28,9 @@ export class ServerPlatformLocation implements PlatformLocation {
|
||||||
private _hash: string = '';
|
private _hash: string = '';
|
||||||
private _hashUpdate = new Subject<LocationChangeEvent>();
|
private _hashUpdate = new Subject<LocationChangeEvent>();
|
||||||
|
|
||||||
getBaseHrefFromDOM(): string { return getDOM().getBaseHref(); }
|
constructor(@Inject(DOCUMENT) private _doc: any) {}
|
||||||
|
|
||||||
|
getBaseHrefFromDOM(): string { return getDOM().getBaseHref(this._doc); }
|
||||||
|
|
||||||
onPopState(fn: LocationChangeListener): void {
|
onPopState(fn: LocationChangeListener): void {
|
||||||
// No-op: a state stack is not implemented, so
|
// No-op: a state stack is not implemented, so
|
||||||
|
|
|
@ -22,14 +22,20 @@ const _attrToPropMap: {[key: string]: string} = {
|
||||||
'tabindex': 'tabIndex',
|
'tabindex': 'tabIndex',
|
||||||
};
|
};
|
||||||
|
|
||||||
let defDoc: any = null;
|
|
||||||
|
|
||||||
const mapProps = ['attribs', 'x-attribsNamespace', 'x-attribsPrefix'];
|
const mapProps = ['attribs', 'x-attribsNamespace', 'x-attribsPrefix'];
|
||||||
|
|
||||||
function _notImplemented(methodName: string) {
|
function _notImplemented(methodName: string) {
|
||||||
return new Error('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
return new Error('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a document string to a Document object.
|
||||||
|
*/
|
||||||
|
export function parseDocument(html: string) {
|
||||||
|
return parse5.parse(html, {treeAdapter: parse5.treeAdapters.htmlparser2});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* tslint:disable:requireParameterType */
|
/* tslint:disable:requireParameterType */
|
||||||
/**
|
/**
|
||||||
* A `DomAdapter` powered by the `parse5` NodeJS module.
|
* A `DomAdapter` powered by the `parse5` NodeJS module.
|
||||||
|
@ -72,7 +78,6 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
|
|
||||||
get attrToPropMap() { return _attrToPropMap; }
|
get attrToPropMap() { return _attrToPropMap; }
|
||||||
|
|
||||||
query(selector: any) { throw _notImplemented('query'); }
|
|
||||||
querySelector(el: any, selector: string): any { return this.querySelectorAll(el, selector)[0]; }
|
querySelector(el: any, selector: string): any { return this.querySelectorAll(el, selector)[0]; }
|
||||||
querySelectorAll(el: any, selector: string): any[] {
|
querySelectorAll(el: any, selector: string): any[] {
|
||||||
const res: any[] = [];
|
const res: any[] = [];
|
||||||
|
@ -468,7 +473,7 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
}
|
}
|
||||||
createHtmlDocument(): Document {
|
createHtmlDocument(): Document {
|
||||||
const newDoc = treeAdapter.createDocument();
|
const newDoc = treeAdapter.createDocument();
|
||||||
newDoc.title = 'fake title';
|
newDoc.title = 'fakeTitle';
|
||||||
const head = treeAdapter.createElement('head', null, []);
|
const head = treeAdapter.createElement('head', null, []);
|
||||||
const body = treeAdapter.createElement('body', 'http://www.w3.org/1999/xhtml', []);
|
const body = treeAdapter.createElement('body', 'http://www.w3.org/1999/xhtml', []);
|
||||||
this.appendChild(newDoc, head);
|
this.appendChild(newDoc, head);
|
||||||
|
@ -478,10 +483,9 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
newDoc['_window'] = {};
|
newDoc['_window'] = {};
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
defaultDoc(): Document { return defDoc = defDoc || this.createHtmlDocument(); }
|
|
||||||
getBoundingClientRect(el: any): any { return {left: 0, top: 0, width: 0, height: 0}; }
|
getBoundingClientRect(el: any): any { return {left: 0, top: 0, width: 0, height: 0}; }
|
||||||
getTitle(): string { return this.defaultDoc().title || ''; }
|
getTitle(doc: Document): string { return doc.title || ''; }
|
||||||
setTitle(newTitle: string) { this.defaultDoc().title = newTitle; }
|
setTitle(doc: Document, newTitle: string) { doc.title = newTitle; }
|
||||||
isTemplateElement(el: any): boolean {
|
isTemplateElement(el: any): boolean {
|
||||||
return this.isElementNode(el) && this.tagName(el) === 'template';
|
return this.isElementNode(el) && this.tagName(el) === 'template';
|
||||||
}
|
}
|
||||||
|
@ -538,17 +542,17 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
}
|
}
|
||||||
supportsDOMEvents(): boolean { return false; }
|
supportsDOMEvents(): boolean { return false; }
|
||||||
supportsNativeShadowDOM(): boolean { return false; }
|
supportsNativeShadowDOM(): boolean { return false; }
|
||||||
getGlobalEventTarget(target: string): any {
|
getGlobalEventTarget(doc: Document, target: string): any {
|
||||||
if (target == 'window') {
|
if (target == 'window') {
|
||||||
return (<any>this.defaultDoc())._window;
|
return (<any>doc)._window;
|
||||||
} else if (target == 'document') {
|
} else if (target == 'document') {
|
||||||
return this.defaultDoc();
|
return doc;
|
||||||
} else if (target == 'body') {
|
} else if (target == 'body') {
|
||||||
return this.defaultDoc().body;
|
return doc.body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getBaseHref(): string {
|
getBaseHref(doc: Document): string {
|
||||||
const base = this.querySelector(this.defaultDoc(), 'base');
|
const base = this.querySelector(doc, 'base');
|
||||||
let href = '';
|
let href = '';
|
||||||
if (base) {
|
if (base) {
|
||||||
href = this.getHref(base);
|
href = this.getHref(base);
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {ServerModule, platformDynamicServer, platformServer} from './server';
|
export {PlatformState} from './platform_state';
|
||||||
|
export {INITIAL_CONFIG, ServerModule, platformDynamicServer, platformServer} from './server';
|
||||||
|
export {renderModule, renderModuleFactory} from './utils';
|
||||||
|
|
||||||
export * from './private_export';
|
export * from './private_export';
|
||||||
export {VERSION} from './version';
|
export {VERSION} from './version';
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* @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 parse5 = require('parse5');
|
||||||
|
|
||||||
|
import {Injectable, Inject} from '@angular/core';
|
||||||
|
import {DOCUMENT} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import {getDOM} from './private_import_platform-browser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of the current platform state.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class PlatformState {
|
||||||
|
constructor(@Inject(DOCUMENT) private _doc: any) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the current state of the platform to string.
|
||||||
|
*/
|
||||||
|
renderToString(): string { return getDOM().getInnerHTML(this._doc); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current DOM state.
|
||||||
|
*/
|
||||||
|
getDocument(): any { return this._doc; }
|
||||||
|
}
|
|
@ -8,13 +8,14 @@
|
||||||
|
|
||||||
import {PlatformLocation} from '@angular/common';
|
import {PlatformLocation} from '@angular/common';
|
||||||
import {platformCoreDynamic} from '@angular/compiler';
|
import {platformCoreDynamic} from '@angular/compiler';
|
||||||
import {Injectable, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
import {Injectable, InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule, DOCUMENT} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {ServerPlatformLocation} from './location';
|
import {ServerPlatformLocation} from './location';
|
||||||
import {Parse5DomAdapter} from './parse5_adapter';
|
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
|
||||||
|
import {PlatformState} from './platform_state';
|
||||||
import {DebugDomRootRenderer} from './private_import_core';
|
import {DebugDomRootRenderer} from './private_import_core';
|
||||||
import {DomAdapter, SharedStylesHost} from './private_import_platform-browser';
|
import {SharedStylesHost, getDOM} from './private_import_platform-browser';
|
||||||
import {ServerRootRenderer} from './server_renderer';
|
import {ServerRootRenderer} from './server_renderer';
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,15 +24,16 @@ function notSupported(feature: string): Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INTERNAL_SERVER_PLATFORM_PROVIDERS: Array<any /*Type | Provider | any[]*/> = [
|
export const INTERNAL_SERVER_PLATFORM_PROVIDERS: Array<any /*Type | Provider | any[]*/> = [
|
||||||
{provide: PLATFORM_INITIALIZER, useValue: initParse5Adapter, multi: true},
|
{provide: DOCUMENT, useFactory: _document, deps: [Injector]},
|
||||||
|
{provide: PLATFORM_INITIALIZER, useFactory: initParse5Adapter, multi: true, deps: [Injector]},
|
||||||
{provide: PlatformLocation, useClass: ServerPlatformLocation},
|
{provide: PlatformLocation, useClass: ServerPlatformLocation},
|
||||||
|
PlatformState,
|
||||||
];
|
];
|
||||||
|
|
||||||
function initParse5Adapter() {
|
function initParse5Adapter(injector: Injector) {
|
||||||
Parse5DomAdapter.makeCurrent();
|
return () => { Parse5DomAdapter.makeCurrent(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function _createConditionalRootRenderer(rootRenderer: any) {
|
export function _createConditionalRootRenderer(rootRenderer: any) {
|
||||||
if (isDevMode()) {
|
if (isDevMode()) {
|
||||||
return new DebugDomRootRenderer(rootRenderer);
|
return new DebugDomRootRenderer(rootRenderer);
|
||||||
|
@ -46,15 +48,46 @@ export const SERVER_RENDER_PROVIDERS: Provider[] = [
|
||||||
SharedStylesHost
|
SharedStylesHost
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config object passed to initialize the platform.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export interface PlatformConfig {
|
||||||
|
document?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DI token for setting the initial config for the platform.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export const INITIAL_CONFIG = new InjectionToken<PlatformConfig>('Server.INITIAL_CONFIG');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ng module for the server.
|
* The ng module for the server.
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
@NgModule({exports: [BrowserModule], providers: SERVER_RENDER_PROVIDERS})
|
@NgModule({
|
||||||
|
exports: [BrowserModule],
|
||||||
|
providers: [
|
||||||
|
SERVER_RENDER_PROVIDERS,
|
||||||
|
]
|
||||||
|
})
|
||||||
export class ServerModule {
|
export class ServerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _document(injector: Injector) {
|
||||||
|
let config: PlatformConfig|null = injector.get(INITIAL_CONFIG, null);
|
||||||
|
if (config && config.document) {
|
||||||
|
return parseDocument(config.document);
|
||||||
|
} else {
|
||||||
|
return getDOM().createHtmlDocument();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -136,7 +136,7 @@ export class ServerRenderer implements Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
listenGlobal(target: string, name: string, callback: Function): Function {
|
listenGlobal(target: string, name: string, callback: Function): Function {
|
||||||
const renderElement = getDOM().getGlobalEventTarget(target);
|
const renderElement = getDOM().getGlobalEventTarget(this._rootRenderer.document, target);
|
||||||
return this.listen(renderElement, name, callback);
|
return this.listen(renderElement, name, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, NgModuleFactory, NgModuleRef, PlatformRef, Provider, Type, destroyPlatform} from '@angular/core';
|
||||||
|
import {filter} from 'rxjs/operator/filter';
|
||||||
|
import {first} from 'rxjs/operator/first';
|
||||||
|
import {toPromise} from 'rxjs/operator/toPromise';
|
||||||
|
|
||||||
|
import {PlatformState} from './platform_state';
|
||||||
|
import {INITIAL_CONFIG, platformDynamicServer, platformServer} from './server';
|
||||||
|
|
||||||
|
const parse5 = require('parse5');
|
||||||
|
|
||||||
|
export interface PlatformOptions {
|
||||||
|
document?: string;
|
||||||
|
url?: string;
|
||||||
|
extraProviders?: Provider[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getPlatform(
|
||||||
|
platformFactory: (extraProviders: Provider[]) => PlatformRef,
|
||||||
|
options: PlatformOptions): PlatformRef {
|
||||||
|
const extraProviders = options.extraProviders ? options.extraProviders : [];
|
||||||
|
return platformFactory([
|
||||||
|
{provide: INITIAL_CONFIG, useValue: {document: options.document, url: options.url}},
|
||||||
|
extraProviders
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _render<T>(
|
||||||
|
platform: PlatformRef, moduleRefPromise: Promise<NgModuleRef<T>>): Promise<string> {
|
||||||
|
return moduleRefPromise.then((moduleRef) => {
|
||||||
|
const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
||||||
|
return toPromise
|
||||||
|
.call(first.call(filter.call(applicationRef.isStable, (isStable: boolean) => isStable)))
|
||||||
|
.then(() => {
|
||||||
|
const output = platform.injector.get(PlatformState).renderToString();
|
||||||
|
destroyPlatform();
|
||||||
|
return output;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a Module to string.
|
||||||
|
*
|
||||||
|
* Do not use this in a production server environment. Use pre-compiled {@link NgModuleFactory} with
|
||||||
|
* {link renderModuleFactory} instead.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export function renderModule<T>(module: Type<T>, options: PlatformOptions): Promise<string> {
|
||||||
|
const platform = _getPlatform(platformDynamicServer, options);
|
||||||
|
return _render(platform, platform.bootstrapModule(module));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a {@link NgModuleFactory} to string.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export function renderModuleFactory<T>(
|
||||||
|
moduleFactory: NgModuleFactory<T>, options: PlatformOptions): Promise<string> {
|
||||||
|
const platform = _getPlatform(platformServer, options);
|
||||||
|
return _render(platform, platform.bootstrapModuleFactory(moduleFactory));
|
||||||
|
}
|
|
@ -7,19 +7,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {PlatformLocation} from '@angular/common';
|
import {PlatformLocation} from '@angular/common';
|
||||||
import {Component, NgModule, destroyPlatform} from '@angular/core';
|
import {ApplicationRef, CompilerFactory, Component, NgModule, NgModuleRef, PlatformRef, destroyPlatform, getPlatform} from '@angular/core';
|
||||||
import {async} from '@angular/core/testing';
|
import {async, inject} from '@angular/core/testing';
|
||||||
|
import {DOCUMENT} from '@angular/platform-browser';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
import {ServerModule, platformDynamicServer} from '@angular/platform-server';
|
import {INITIAL_CONFIG, PlatformState, ServerModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
|
||||||
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
function writeBody(html: string): any {
|
import {filter} from 'rxjs/operator/filter';
|
||||||
const dom = getDOM();
|
import {first} from 'rxjs/operator/first';
|
||||||
const doc = dom.defaultDoc();
|
import {toPromise} from 'rxjs/operator/toPromise';
|
||||||
const body = dom.querySelector(doc, 'body');
|
|
||||||
dom.setInnerHTML(body, html);
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'app', template: `Works!`})
|
@Component({selector: 'app', template: `Works!`})
|
||||||
class MyServerApp {
|
class MyServerApp {
|
||||||
|
@ -38,23 +34,27 @@ export function main() {
|
||||||
afterEach(() => destroyPlatform());
|
afterEach(() => destroyPlatform());
|
||||||
|
|
||||||
it('should bootstrap', async(() => {
|
it('should bootstrap', async(() => {
|
||||||
const body = writeBody('<app></app>');
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}])
|
||||||
platformDynamicServer().bootstrapModule(ExampleModule).then(() => {
|
.bootstrapModule(ExampleModule)
|
||||||
expect(getDOM().getText(body)).toEqual('Works!');
|
.then((moduleRef) => {
|
||||||
|
const doc = moduleRef.injector.get(DOCUMENT);
|
||||||
|
expect(getDOM().getText(doc)).toEqual('Works!');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('PlatformLocation', () => {
|
describe('PlatformLocation', () => {
|
||||||
it('is injectable', () => {
|
it('is injectable', () => {
|
||||||
const body = writeBody('<app></app>');
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}])
|
||||||
platformDynamicServer().bootstrapModule(ExampleModule).then(appRef => {
|
.bootstrapModule(ExampleModule)
|
||||||
|
.then(appRef => {
|
||||||
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
||||||
expect(location.pathname).toBe('/');
|
expect(location.pathname).toBe('/');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('pushState causes the URL to update', () => {
|
it('pushState causes the URL to update', () => {
|
||||||
const body = writeBody('<app></app>');
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}])
|
||||||
platformDynamicServer().bootstrapModule(ExampleModule).then(appRef => {
|
.bootstrapModule(ExampleModule)
|
||||||
|
.then(appRef => {
|
||||||
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
||||||
location.pushState(null, 'Test', '/foo#bar');
|
location.pushState(null, 'Test', '/foo#bar');
|
||||||
expect(location.pathname).toBe('/foo');
|
expect(location.pathname).toBe('/foo');
|
||||||
|
@ -62,8 +62,9 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('allows subscription to the hash state', done => {
|
it('allows subscription to the hash state', done => {
|
||||||
const body = writeBody('<app></app>');
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}])
|
||||||
platformDynamicServer().bootstrapModule(ExampleModule).then(appRef => {
|
.bootstrapModule(ExampleModule)
|
||||||
|
.then(appRef => {
|
||||||
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
||||||
expect(location.pathname).toBe('/');
|
expect(location.pathname).toBe('/');
|
||||||
location.onHashChange((e: any) => {
|
location.onHashChange((e: any) => {
|
||||||
|
@ -77,4 +78,73 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Platform Server', () => {
|
||||||
|
@Component({selector: 'app', template: '{{text}}'})
|
||||||
|
class MyAsyncServerApp {
|
||||||
|
text = '';
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
Promise.resolve(null).then(() => setTimeout(() => { this.text = 'Works!'; }, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule(
|
||||||
|
{declarations: [MyAsyncServerApp], imports: [ServerModule], bootstrap: [MyAsyncServerApp]})
|
||||||
|
class AsyncServerModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc: string;
|
||||||
|
let called: boolean;
|
||||||
|
let expectedOutput =
|
||||||
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
destroyPlatform();
|
||||||
|
// PlatformConfig takes in a parsed document so that it can be cached across requests.
|
||||||
|
doc = '<html><head></head><body><app></app></body></html>';
|
||||||
|
called = false;
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
expect(called).toBe(true);
|
||||||
|
// Platform should have been destroyed at the end of rendering.
|
||||||
|
expect(getPlatform()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PlatformState should render to string (Long form rendering)', async(() => {
|
||||||
|
const platform =
|
||||||
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
|
||||||
|
|
||||||
|
platform.bootstrapModule(AsyncServerModule)
|
||||||
|
.then((moduleRef) => {
|
||||||
|
const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
||||||
|
return toPromise.call(first.call(
|
||||||
|
filter.call(applicationRef.isStable, (isStable: boolean) => isStable)));
|
||||||
|
})
|
||||||
|
.then((b) => {
|
||||||
|
expect(platform.injector.get(PlatformState).renderToString()).toBe(expectedOutput);
|
||||||
|
destroyPlatform();
|
||||||
|
called = true;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('renderModule should render to string (short form rendering)', async(() => {
|
||||||
|
renderModule(AsyncServerModule, {document: doc}).then(output => {
|
||||||
|
expect(output).toBe(expectedOutput);
|
||||||
|
called = true;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('renderModuleFactory should render to string (short form rendering)',
|
||||||
|
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
|
||||||
|
const compilerFactory: CompilerFactory =
|
||||||
|
defaultPlatform.injector.get(CompilerFactory, null);
|
||||||
|
const moduleFactory =
|
||||||
|
compilerFactory.createCompiler().compileModuleSync(AsyncServerModule);
|
||||||
|
renderModuleFactory(moduleFactory, {document: doc}).then(output => {
|
||||||
|
expect(output).toBe(expectedOutput);
|
||||||
|
called = true;
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,6 @@ export class WorkerDomAdapter extends DomAdapter {
|
||||||
set attrToPropMap(value: {[key: string]: string}) { throw 'not implemented'; }
|
set attrToPropMap(value: {[key: string]: string}) { throw 'not implemented'; }
|
||||||
|
|
||||||
parse(templateHtml: string) { throw 'not implemented'; }
|
parse(templateHtml: string) { throw 'not implemented'; }
|
||||||
query(selector: string): any { throw 'not implemented'; }
|
|
||||||
querySelector(el: any /** TODO #9100 */, selector: string): HTMLElement {
|
querySelector(el: any /** TODO #9100 */, selector: string): HTMLElement {
|
||||||
throw 'not implemented';
|
throw 'not implemented';
|
||||||
}
|
}
|
||||||
|
@ -169,10 +168,9 @@ export class WorkerDomAdapter extends DomAdapter {
|
||||||
}
|
}
|
||||||
templateAwareRoot(el: any /** TODO #9100 */) { throw 'not implemented'; }
|
templateAwareRoot(el: any /** TODO #9100 */) { throw 'not implemented'; }
|
||||||
createHtmlDocument(): HTMLDocument { throw 'not implemented'; }
|
createHtmlDocument(): HTMLDocument { throw 'not implemented'; }
|
||||||
defaultDoc(): HTMLDocument { throw 'not implemented'; }
|
|
||||||
getBoundingClientRect(el: any /** TODO #9100 */) { throw 'not implemented'; }
|
getBoundingClientRect(el: any /** TODO #9100 */) { throw 'not implemented'; }
|
||||||
getTitle(): string { throw 'not implemented'; }
|
getTitle(doc: Document): string { throw 'not implemented'; }
|
||||||
setTitle(newTitle: string) { throw 'not implemented'; }
|
setTitle(doc: Document, newTitle: string) { throw 'not implemented'; }
|
||||||
elementMatches(n: any /** TODO #9100 */, selector: string): boolean { throw 'not implemented'; }
|
elementMatches(n: any /** TODO #9100 */, selector: string): boolean { throw 'not implemented'; }
|
||||||
isTemplateElement(el: any): boolean { throw 'not implemented'; }
|
isTemplateElement(el: any): boolean { throw 'not implemented'; }
|
||||||
isTextNode(node: any /** TODO #9100 */): boolean { throw 'not implemented'; }
|
isTextNode(node: any /** TODO #9100 */): boolean { throw 'not implemented'; }
|
||||||
|
@ -189,10 +187,10 @@ export class WorkerDomAdapter extends DomAdapter {
|
||||||
}
|
}
|
||||||
supportsDOMEvents(): boolean { throw 'not implemented'; }
|
supportsDOMEvents(): boolean { throw 'not implemented'; }
|
||||||
supportsNativeShadowDOM(): boolean { throw 'not implemented'; }
|
supportsNativeShadowDOM(): boolean { throw 'not implemented'; }
|
||||||
getGlobalEventTarget(target: string): any { throw 'not implemented'; }
|
getGlobalEventTarget(doc: Document, target: string): any { throw 'not implemented'; }
|
||||||
getHistory(): History { throw 'not implemented'; }
|
getHistory(): History { throw 'not implemented'; }
|
||||||
getLocation(): Location { throw 'not implemented'; }
|
getLocation(): Location { throw 'not implemented'; }
|
||||||
getBaseHref(): string { throw 'not implemented'; }
|
getBaseHref(doc: Document): string { throw 'not implemented'; }
|
||||||
resetBaseElement(): void { throw 'not implemented'; }
|
resetBaseElement(): void { throw 'not implemented'; }
|
||||||
getUserAgent(): string { throw 'not implemented'; }
|
getUserAgent(): string { throw 'not implemented'; }
|
||||||
setData(element: any /** TODO #9100 */, name: string, value: string) { throw 'not implemented'; }
|
setData(element: any /** TODO #9100 */, name: string, value: string) { throw 'not implemented'; }
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PlatformRef, Provider, RootRenderer, createPlatformFactory, platformCore} from '@angular/core';
|
import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PlatformRef, Provider, RootRenderer, createPlatformFactory, platformCore} from '@angular/core';
|
||||||
|
import {DOCUMENT} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {BROWSER_SANITIZATION_PROVIDERS} from './private_import_platform-browser';
|
import {BROWSER_SANITIZATION_PROVIDERS} from './private_import_platform-browser';
|
||||||
import {ON_WEB_WORKER} from './web_workers/shared/api';
|
import {ON_WEB_WORKER} from './web_workers/shared/api';
|
||||||
|
@ -60,7 +61,7 @@ export function setupWebWorker(): void {
|
||||||
*/
|
*/
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [
|
providers: [
|
||||||
BROWSER_SANITIZATION_PROVIDERS, Serializer,
|
BROWSER_SANITIZATION_PROVIDERS, Serializer, {provide: DOCUMENT, useValue: null},
|
||||||
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
|
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
|
||||||
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
|
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
|
||||||
WebWorkerRootRenderer, {provide: RootRenderer, useExisting: WebWorkerRootRenderer},
|
WebWorkerRootRenderer, {provide: RootRenderer, useExisting: WebWorkerRootRenderer},
|
||||||
|
|
|
@ -135,7 +135,7 @@ function _exceptionHandler(): ErrorHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _document(): any {
|
function _document(): any {
|
||||||
return getDOM().defaultDoc();
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNgZone(): NgZone {
|
function createNgZone(): NgZone {
|
||||||
|
|
|
@ -120,8 +120,8 @@ class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||||
get _EVENT_MANAGER_PLUGINS_15(): any[] {
|
get _EVENT_MANAGER_PLUGINS_15(): any[] {
|
||||||
if ((this.__EVENT_MANAGER_PLUGINS_15 == (null as any))) {
|
if ((this.__EVENT_MANAGER_PLUGINS_15 == (null as any))) {
|
||||||
(this.__EVENT_MANAGER_PLUGINS_15 = [
|
(this.__EVENT_MANAGER_PLUGINS_15 = [
|
||||||
new import20.DomEventsPlugin(), new import21.KeyEventsPlugin(),
|
new import20.DomEventsPlugin(document), new import21.KeyEventsPlugin(document),
|
||||||
new import10.HammerGesturesPlugin(this._HAMMER_GESTURE_CONFIG_14)
|
new import10.HammerGesturesPlugin(document, this._HAMMER_GESTURE_CONFIG_14)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return this.__EVENT_MANAGER_PLUGINS_15;
|
return this.__EVENT_MANAGER_PLUGINS_15;
|
||||||
|
@ -163,7 +163,7 @@ class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||||
}
|
}
|
||||||
get _DomSanitizer_21(): import14.DomSanitizerImpl {
|
get _DomSanitizer_21(): import14.DomSanitizerImpl {
|
||||||
if ((this.__DomSanitizer_21 == (null as any))) {
|
if ((this.__DomSanitizer_21 == (null as any))) {
|
||||||
(this.__DomSanitizer_21 = new import14.DomSanitizerImpl());
|
(this.__DomSanitizer_21 = new import14.DomSanitizerImpl(document));
|
||||||
}
|
}
|
||||||
return this.__DomSanitizer_21;
|
return this.__DomSanitizer_21;
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||||
}
|
}
|
||||||
get _Title_27(): import16.Title {
|
get _Title_27(): import16.Title {
|
||||||
if ((this.__Title_27 == (null as any))) {
|
if ((this.__Title_27 == (null as any))) {
|
||||||
(this.__Title_27 = new import16.Title());
|
(this.__Title_27 = new import16.Title(document));
|
||||||
}
|
}
|
||||||
return this.__Title_27;
|
return this.__Title_27;
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ export class AppModule implements Injector {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
initServicesIfNeeded();
|
initServicesIfNeeded();
|
||||||
this.sanitizer = new DomSanitizerImpl();
|
this.sanitizer = new DomSanitizerImpl(document);
|
||||||
trustedEmptyColor = this.sanitizer.bypassSecurityTrustStyle('');
|
trustedEmptyColor = this.sanitizer.bypassSecurityTrustStyle('');
|
||||||
trustedGreyColor = this.sanitizer.bypassSecurityTrustStyle('grey');
|
trustedGreyColor = this.sanitizer.bypassSecurityTrustStyle('grey');
|
||||||
this.componentFactory = createComponentFactory('#root', TreeComponent, TreeComponent_Host);
|
this.componentFactory = createComponentFactory('#root', TreeComponent, TreeComponent_Host);
|
||||||
|
|
|
@ -120,8 +120,8 @@ class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||||
get _EVENT_MANAGER_PLUGINS_15(): any[] {
|
get _EVENT_MANAGER_PLUGINS_15(): any[] {
|
||||||
if ((this.__EVENT_MANAGER_PLUGINS_15 == (null as any))) {
|
if ((this.__EVENT_MANAGER_PLUGINS_15 == (null as any))) {
|
||||||
(this.__EVENT_MANAGER_PLUGINS_15 = [
|
(this.__EVENT_MANAGER_PLUGINS_15 = [
|
||||||
new import20.DomEventsPlugin(), new import21.KeyEventsPlugin(),
|
new import20.DomEventsPlugin(document), new import21.KeyEventsPlugin(document),
|
||||||
new import10.HammerGesturesPlugin(this._HAMMER_GESTURE_CONFIG_14)
|
new import10.HammerGesturesPlugin(document, this._HAMMER_GESTURE_CONFIG_14)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return this.__EVENT_MANAGER_PLUGINS_15;
|
return this.__EVENT_MANAGER_PLUGINS_15;
|
||||||
|
@ -163,7 +163,7 @@ class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||||
}
|
}
|
||||||
get _DomSanitizer_21(): import14.DomSanitizerImpl {
|
get _DomSanitizer_21(): import14.DomSanitizerImpl {
|
||||||
if ((this.__DomSanitizer_21 == (null as any))) {
|
if ((this.__DomSanitizer_21 == (null as any))) {
|
||||||
(this.__DomSanitizer_21 = new import14.DomSanitizerImpl());
|
(this.__DomSanitizer_21 = new import14.DomSanitizerImpl(document));
|
||||||
}
|
}
|
||||||
return this.__DomSanitizer_21;
|
return this.__DomSanitizer_21;
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||||
}
|
}
|
||||||
get _Title_27(): import16.Title {
|
get _Title_27(): import16.Title {
|
||||||
if ((this.__Title_27 == (null as any))) {
|
if ((this.__Title_27 == (null as any))) {
|
||||||
(this.__Title_27 = new import16.Title());
|
(this.__Title_27 = new import16.Title(document));
|
||||||
}
|
}
|
||||||
return this.__Title_27;
|
return this.__Title_27;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ export declare class HammerGestureConfig {
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class Meta {
|
export declare class Meta {
|
||||||
constructor(_dom: DomAdapter);
|
constructor(_doc: any);
|
||||||
addTag(tag: MetaDefinition, forceCreation?: boolean): HTMLMetaElement;
|
addTag(tag: MetaDefinition, forceCreation?: boolean): HTMLMetaElement;
|
||||||
addTags(tags: MetaDefinition[], forceCreation?: boolean): HTMLMetaElement[];
|
addTags(tags: MetaDefinition[], forceCreation?: boolean): HTMLMetaElement[];
|
||||||
getTag(attrSelector: string): HTMLMetaElement;
|
getTag(attrSelector: string): HTMLMetaElement;
|
||||||
|
@ -117,6 +117,7 @@ export interface SafeUrl extends SafeValue {
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class Title {
|
export declare class Title {
|
||||||
|
constructor(_doc: any);
|
||||||
getTitle(): string;
|
getTitle(): string;
|
||||||
setTitle(newTitle: string): void;
|
setTitle(newTitle: string): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,25 @@
|
||||||
|
/** @experimental */
|
||||||
|
export declare const INITIAL_CONFIG: InjectionToken<PlatformConfig>;
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare const platformDynamicServer: (extraProviders?: Provider[]) => PlatformRef;
|
export declare const platformDynamicServer: (extraProviders?: Provider[]) => PlatformRef;
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare const platformServer: (extraProviders?: Provider[]) => PlatformRef;
|
export declare const platformServer: (extraProviders?: Provider[]) => PlatformRef;
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare class PlatformState {
|
||||||
|
constructor(_doc: any);
|
||||||
|
getDocument(): any;
|
||||||
|
renderToString(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare function renderModule<T>(module: Type<T>, options: PlatformOptions): Promise<string>;
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare function renderModuleFactory<T>(moduleFactory: NgModuleFactory<T>, options: PlatformOptions): Promise<string>;
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class ServerModule {
|
export declare class ServerModule {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue