feat(platform-server): provide a DOM implementation on the server
Fixes #14638 Uses Domino - https://github.com/fgnass/domino and removes dependency on Parse5. The DOCUMENT and nativeElement were never typed earlier and were different on the browser(DOM nodes) and the server(Parse5 nodes). With this change, platform-server also exposes a DOCUMENT and nativeElement that is closer to the client. If you were relying on nativeElement on the server, you would have to change your code to use the DOM API now instead of Parse5 AST API. Removes the need to add services for each and every Document manipulation like Title/Meta etc. This does *not* provide a global variable 'document' or 'window' on the server. You still have to inject DOCUMENT to get the document backing the current platform server instance.
This commit is contained in:
parent
30d53a8942
commit
2f2d5f35bd
|
@ -2352,6 +2352,9 @@
|
||||||
"domhandler": {
|
"domhandler": {
|
||||||
"version": "2.3.0"
|
"version": "2.3.0"
|
||||||
},
|
},
|
||||||
|
"domino": {
|
||||||
|
"version": "1.0.29"
|
||||||
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "1.5.1"
|
"version": "1.5.1"
|
||||||
},
|
},
|
||||||
|
@ -6162,14 +6165,6 @@
|
||||||
"parse-json": {
|
"parse-json": {
|
||||||
"version": "2.2.0"
|
"version": "2.2.0"
|
||||||
},
|
},
|
||||||
"parse5": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "6.0.63"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parsejson": {
|
"parsejson": {
|
||||||
"version": "0.0.1"
|
"version": "0.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -3720,6 +3720,11 @@
|
||||||
"from": "domhandler@>=2.3.0 <3.0.0",
|
"from": "domhandler@>=2.3.0 <3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
|
||||||
},
|
},
|
||||||
|
"domino": {
|
||||||
|
"version": "1.0.29",
|
||||||
|
"from": "domino@latest",
|
||||||
|
"resolved": "https://registry.npmjs.org/domino/-/domino-1.0.29.tgz"
|
||||||
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"from": "domutils@>=1.5.1 <2.0.0",
|
"from": "domutils@>=1.5.1 <2.0.0",
|
||||||
|
@ -9850,18 +9855,6 @@
|
||||||
"from": "parse-json@>=2.2.0 <3.0.0",
|
"from": "parse-json@>=2.2.0 <3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz"
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz"
|
||||||
},
|
},
|
||||||
"parse5": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"from": "parse5@3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.1.tgz",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "6.0.63",
|
|
||||||
"from": "@types/node@>=6.0.46 <7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.63.tgz"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parsejson": {
|
"parsejson": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"from": "parsejson@0.0.1",
|
"from": "parsejson@0.0.1",
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
"cors": "^2.7.1",
|
"cors": "^2.7.1",
|
||||||
"dgeni": "^0.4.2",
|
"dgeni": "^0.4.2",
|
||||||
"dgeni-packages": "^0.16.5",
|
"dgeni-packages": "^0.16.5",
|
||||||
|
"domino": "^1.0.29",
|
||||||
"entities": "^1.1.1",
|
"entities": "^1.1.1",
|
||||||
"firebase-tools": "^3.9.2",
|
"firebase-tools": "^3.9.2",
|
||||||
"firefox-profile": "^0.3.4",
|
"firefox-profile": "^0.3.4",
|
||||||
|
@ -81,7 +82,6 @@
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"nan": "^2.4.0",
|
"nan": "^2.4.0",
|
||||||
"node-uuid": "1.4.x",
|
"node-uuid": "1.4.x",
|
||||||
"parse5": "^3.0.1",
|
|
||||||
"protractor": "^4.0.14",
|
"protractor": "^4.0.14",
|
||||||
"react": "^0.14.0",
|
"react": "^0.14.0",
|
||||||
"rewire": "^2.3.3",
|
"rewire": "^2.3.3",
|
||||||
|
|
|
@ -84,8 +84,8 @@ describe('template codegen output', () => {
|
||||||
|
|
||||||
it('should support i18n for content tags', () => {
|
it('should support i18n for content tags', () => {
|
||||||
const containerElement = createComponent(BasicComp).nativeElement;
|
const containerElement = createComponent(BasicComp).nativeElement;
|
||||||
const pElement = containerElement.children.find((c: any) => c.name == 'p');
|
const pElement = containerElement.querySelector('p');
|
||||||
const pText = pElement.children.map((c: any) => c.data).join('').trim();
|
const pText = pElement.textContent;
|
||||||
expect(pText).toBe('tervetuloa');
|
expect(pText).toBe('tervetuloa');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ describe('NgModule', () => {
|
||||||
// https://github.com/angular/angular/issues/15221
|
// https://github.com/angular/angular/issues/15221
|
||||||
const fixture = createComponent(ComponentUsingFlatModule);
|
const fixture = createComponent(ComponentUsingFlatModule);
|
||||||
const bundleComp = fixture.nativeElement.children;
|
const bundleComp = fixture.nativeElement.children;
|
||||||
expect(bundleComp[0].children[0].children[0].data).toEqual('flat module component');
|
expect(bundleComp[0].children[0].textContent).toEqual('flat module component');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ describe('NgModule', () => {
|
||||||
it('should support third party entryComponents components', () => {
|
it('should support third party entryComponents components', () => {
|
||||||
const fixture = createComponent(ComponentUsingThirdParty);
|
const fixture = createComponent(ComponentUsingThirdParty);
|
||||||
const thirdPComps = fixture.nativeElement.children;
|
const thirdPComps = fixture.nativeElement.children;
|
||||||
expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component');
|
expect(thirdPComps[0].children[0].textContent).toEqual('3rdP-component');
|
||||||
expect(thirdPComps[1].children[0].children[0].data).toEqual(`other-3rdP-component
|
expect(thirdPComps[1].children[0].textContent).toEqual(`other-3rdP-component
|
||||||
multi-lines`);
|
multi-lines`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -129,12 +129,12 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
}
|
}
|
||||||
dispatchEvent(el: Node, evt: any) { el.dispatchEvent(evt); }
|
dispatchEvent(el: Node, evt: any) { el.dispatchEvent(evt); }
|
||||||
createMouseEvent(eventType: string): MouseEvent {
|
createMouseEvent(eventType: string): MouseEvent {
|
||||||
const evt: MouseEvent = document.createEvent('MouseEvent');
|
const evt: MouseEvent = this.getDefaultDocument().createEvent('MouseEvent');
|
||||||
evt.initEvent(eventType, true, true);
|
evt.initEvent(eventType, true, true);
|
||||||
return evt;
|
return evt;
|
||||||
}
|
}
|
||||||
createEvent(eventType: any): Event {
|
createEvent(eventType: any): Event {
|
||||||
const evt: Event = document.createEvent('Event');
|
const evt: Event = this.getDefaultDocument().createEvent('Event');
|
||||||
evt.initEvent(eventType, true, true);
|
evt.initEvent(eventType, true, true);
|
||||||
return evt;
|
return evt;
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
}
|
}
|
||||||
getInnerHTML(el: HTMLElement): string { return el.innerHTML; }
|
getInnerHTML(el: HTMLElement): string { return el.innerHTML; }
|
||||||
getTemplateContent(el: Node): Node|null {
|
getTemplateContent(el: Node): Node|null {
|
||||||
return 'content' in el && el instanceof HTMLTemplateElement ? el.content : null;
|
return 'content' in el && this.isTemplateElement(el) ? (<any>el).content : null;
|
||||||
}
|
}
|
||||||
getOuterHTML(el: HTMLElement): string { return el.outerHTML; }
|
getOuterHTML(el: HTMLElement): string { return el.outerHTML; }
|
||||||
nodeName(node: Node): string { return node.nodeName; }
|
nodeName(node: Node): string { return node.nodeName; }
|
||||||
|
@ -198,25 +198,34 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
setValue(el: any, value: string) { el.value = value; }
|
setValue(el: any, value: string) { el.value = value; }
|
||||||
getChecked(el: any): boolean { return el.checked; }
|
getChecked(el: any): boolean { return el.checked; }
|
||||||
setChecked(el: any, value: boolean) { el.checked = value; }
|
setChecked(el: any, value: boolean) { el.checked = value; }
|
||||||
createComment(text: string): Comment { return document.createComment(text); }
|
createComment(text: string): Comment { return this.getDefaultDocument().createComment(text); }
|
||||||
createTemplate(html: any): HTMLElement {
|
createTemplate(html: any): HTMLElement {
|
||||||
const t = document.createElement('template');
|
const t = this.getDefaultDocument().createElement('template');
|
||||||
t.innerHTML = html;
|
t.innerHTML = html;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
createElement(tagName: string, doc = document): HTMLElement { return doc.createElement(tagName); }
|
createElement(tagName: string, doc?: Document): HTMLElement {
|
||||||
createElementNS(ns: string, tagName: string, doc = document): Element {
|
doc = doc || this.getDefaultDocument();
|
||||||
|
return doc.createElement(tagName);
|
||||||
|
}
|
||||||
|
createElementNS(ns: string, tagName: string, doc?: Document): Element {
|
||||||
|
doc = doc || this.getDefaultDocument();
|
||||||
return doc.createElementNS(ns, tagName);
|
return doc.createElementNS(ns, tagName);
|
||||||
}
|
}
|
||||||
createTextNode(text: string, doc = document): Text { return doc.createTextNode(text); }
|
createTextNode(text: string, doc?: Document): Text {
|
||||||
createScriptTag(attrName: string, attrValue: string, doc = document): HTMLScriptElement {
|
doc = doc || this.getDefaultDocument();
|
||||||
|
return doc.createTextNode(text);
|
||||||
|
}
|
||||||
|
createScriptTag(attrName: string, attrValue: string, doc?: Document): HTMLScriptElement {
|
||||||
|
doc = doc || this.getDefaultDocument();
|
||||||
const el = <HTMLScriptElement>doc.createElement('SCRIPT');
|
const el = <HTMLScriptElement>doc.createElement('SCRIPT');
|
||||||
el.setAttribute(attrName, attrValue);
|
el.setAttribute(attrName, attrValue);
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
createStyleElement(css: string, doc = document): HTMLStyleElement {
|
createStyleElement(css: string, doc?: Document): HTMLStyleElement {
|
||||||
|
doc = doc || this.getDefaultDocument();
|
||||||
const style = <HTMLStyleElement>doc.createElement('style');
|
const style = <HTMLStyleElement>doc.createElement('style');
|
||||||
this.appendChild(style, this.createTextNode(css));
|
this.appendChild(style, this.createTextNode(css, doc));
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
|
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
|
||||||
|
@ -253,7 +262,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
const res = new Map<string, string>();
|
const res = new Map<string, string>();
|
||||||
const elAttrs = element.attributes;
|
const elAttrs = element.attributes;
|
||||||
for (let i = 0; i < elAttrs.length; i++) {
|
for (let i = 0; i < elAttrs.length; i++) {
|
||||||
const attrib = elAttrs[i];
|
const attrib = elAttrs.item(i);
|
||||||
res.set(attrib.name, attrib.value);
|
res.set(attrib.name, attrib.value);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@ -282,6 +291,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
createHtmlDocument(): HTMLDocument {
|
createHtmlDocument(): HTMLDocument {
|
||||||
return document.implementation.createHTMLDocument('fakeTitle');
|
return document.implementation.createHTMLDocument('fakeTitle');
|
||||||
}
|
}
|
||||||
|
getDefaultDocument(): Document { return document; }
|
||||||
getBoundingClientRect(el: Element): any {
|
getBoundingClientRect(el: Element): any {
|
||||||
try {
|
try {
|
||||||
return el.getBoundingClientRect();
|
return el.getBoundingClientRect();
|
||||||
|
@ -289,10 +299,10 @@ 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(doc: Document): string { return document.title; }
|
getTitle(doc: Document): string { return doc.title; }
|
||||||
setTitle(doc: Document, newTitle: string) { document.title = newTitle || ''; }
|
setTitle(doc: Document, newTitle: string) { doc.title = newTitle || ''; }
|
||||||
elementMatches(n: any, selector: string): boolean {
|
elementMatches(n: any, selector: string): boolean {
|
||||||
if (n instanceof HTMLElement) {
|
if (this.isElementNode(n)) {
|
||||||
return n.matches && n.matches(selector) ||
|
return n.matches && n.matches(selector) ||
|
||||||
n.msMatchesSelector && n.msMatchesSelector(selector) ||
|
n.msMatchesSelector && n.msMatchesSelector(selector) ||
|
||||||
n.webkitMatchesSelector && n.webkitMatchesSelector(selector);
|
n.webkitMatchesSelector && n.webkitMatchesSelector(selector);
|
||||||
|
@ -301,7 +311,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
isTemplateElement(el: Node): boolean {
|
isTemplateElement(el: Node): boolean {
|
||||||
return el instanceof HTMLElement && el.nodeName == 'TEMPLATE';
|
return this.isElementNode(el) && el.nodeName === 'TEMPLATE';
|
||||||
}
|
}
|
||||||
isTextNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; }
|
isTextNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; }
|
||||||
isCommentNode(node: Node): boolean { return node.nodeType === Node.COMMENT_NODE; }
|
isCommentNode(node: Node): boolean { return node.nodeType === Node.COMMENT_NODE; }
|
||||||
|
@ -312,7 +322,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
isShadowRoot(node: any): boolean { return node instanceof DocumentFragment; }
|
isShadowRoot(node: any): boolean { return node instanceof DocumentFragment; }
|
||||||
importIntoDoc(node: Node): any { return document.importNode(this.templateAwareRoot(node), true); }
|
importIntoDoc(node: Node): any { return document.importNode(this.templateAwareRoot(node), true); }
|
||||||
adoptNode(node: Node): any { return document.adoptNode(node); }
|
adoptNode(node: Node): any { return document.adoptNode(node); }
|
||||||
getHref(el: Element): string { return (<any>el).href; }
|
getHref(el: Element): string { return el.getAttribute('href') !; }
|
||||||
|
|
||||||
getEventKey(event: any): string {
|
getEventKey(event: any): string {
|
||||||
let key = event.key;
|
let key = event.key;
|
||||||
|
@ -342,10 +352,10 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
if (target === 'document') {
|
if (target === 'document') {
|
||||||
return document;
|
return doc;
|
||||||
}
|
}
|
||||||
if (target === 'body') {
|
if (target === 'body') {
|
||||||
return document.body;
|
return doc.body;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class Meta {
|
||||||
|
|
||||||
getTag(attrSelector: string): HTMLMetaElement|null {
|
getTag(attrSelector: string): HTMLMetaElement|null {
|
||||||
if (!attrSelector) return null;
|
if (!attrSelector) return null;
|
||||||
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`);
|
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTags(attrSelector: string): HTMLMetaElement[] {
|
getTags(attrSelector: string): HTMLMetaElement[] {
|
||||||
|
|
|
@ -125,6 +125,7 @@ export abstract class DomAdapter {
|
||||||
abstract removeAttributeNS(element: any, ns: string, attribute: string): any;
|
abstract removeAttributeNS(element: any, ns: string, attribute: string): any;
|
||||||
abstract templateAwareRoot(el: any): any;
|
abstract templateAwareRoot(el: any): any;
|
||||||
abstract createHtmlDocument(): HTMLDocument;
|
abstract createHtmlDocument(): HTMLDocument;
|
||||||
|
abstract getDefaultDocument(): Document;
|
||||||
abstract getBoundingClientRect(el: any): any;
|
abstract getBoundingClientRect(el: any): any;
|
||||||
abstract getTitle(doc: Document): string;
|
abstract getTitle(doc: Document): string;
|
||||||
abstract setTitle(doc: Document, newTitle: string): any;
|
abstract setTitle(doc: Document, newTitle: string): any;
|
||||||
|
|
|
@ -14,11 +14,13 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('Meta service', () => {
|
describe('Meta service', () => {
|
||||||
const doc = getDOM().createHtmlDocument();
|
let doc: Document;
|
||||||
const metaService = new Meta(doc);
|
let metaService: Meta;
|
||||||
let defaultMeta: HTMLMetaElement;
|
let defaultMeta: HTMLMetaElement;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
doc = getDOM().createHtmlDocument();
|
||||||
|
metaService = new Meta(doc);
|
||||||
defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement;
|
defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement;
|
||||||
getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id');
|
getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id');
|
||||||
getDOM().setAttribute(defaultMeta, 'content', '123456789');
|
getDOM().setAttribute(defaultMeta, 'content', '123456789');
|
||||||
|
|
|
@ -14,9 +14,15 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('title service', () => {
|
describe('title service', () => {
|
||||||
const doc = getDOM().createHtmlDocument();
|
let doc: Document;
|
||||||
const initialTitle = getDOM().getTitle(doc);
|
let initialTitle: string;
|
||||||
const titleService = new Title(doc);
|
let titleService: Title;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
doc = getDOM().createHtmlDocument();
|
||||||
|
initialTitle = getDOM().getTitle(doc);
|
||||||
|
titleService = new Title(doc);
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => { getDOM().setTitle(doc, initialTitle); });
|
afterEach(() => { getDOM().setTitle(doc, initialTitle); });
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^1.7.1",
|
"tslib": "^1.7.1",
|
||||||
"parse5": "^3.0.1",
|
"domino": "^1.0.29",
|
||||||
"xhr2": "^0.1.4"
|
"xhr2": "^0.1.4"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
/**
|
||||||
|
* @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 domino = require('domino');
|
||||||
|
|
||||||
|
import {ɵBrowserDomAdapter as BrowserDomAdapter, ɵsetRootDomAdapter as setRootDomAdapter} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
function _notImplemented(methodName: string) {
|
||||||
|
return new Error('This method is not implemented in DominoAdapter: ' + methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a document string to a Document object.
|
||||||
|
*/
|
||||||
|
export function parseDocument(html: string, url = '/') {
|
||||||
|
let window = domino.createWindow(html, url);
|
||||||
|
let doc = window.document;
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a document to string.
|
||||||
|
*/
|
||||||
|
export function serializeDocument(doc: Document): string {
|
||||||
|
return (doc as any).serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOM Adapter for the server platform based on https://github.com/fgnass/domino.
|
||||||
|
*/
|
||||||
|
export class DominoAdapter extends BrowserDomAdapter {
|
||||||
|
static makeCurrent() { setRootDomAdapter(new DominoAdapter()); }
|
||||||
|
|
||||||
|
private static defaultDoc: Document;
|
||||||
|
|
||||||
|
logError(error: string) { console.error(error); }
|
||||||
|
|
||||||
|
// tslint:disable-next-line:no-console
|
||||||
|
log(error: string) { console.log(error); }
|
||||||
|
|
||||||
|
logGroup(error: string) { console.error(error); }
|
||||||
|
|
||||||
|
logGroupEnd() {}
|
||||||
|
|
||||||
|
supportsDOMEvents(): boolean { return false; }
|
||||||
|
supportsNativeShadowDOM(): boolean { return false; }
|
||||||
|
|
||||||
|
contains(nodeA: any, nodeB: any): boolean {
|
||||||
|
let inner = nodeB;
|
||||||
|
while (inner) {
|
||||||
|
if (inner === nodeA) return true;
|
||||||
|
inner = inner.parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
createHtmlDocument(): HTMLDocument {
|
||||||
|
return parseDocument('<html><head><title>fakeTitle</title></head><body></body></html>');
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultDocument(): Document {
|
||||||
|
if (!DominoAdapter.defaultDoc) {
|
||||||
|
DominoAdapter.defaultDoc = domino.createDocument();
|
||||||
|
}
|
||||||
|
return DominoAdapter.defaultDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
createShadowRoot(el: any, doc: Document = document): DocumentFragment {
|
||||||
|
el.shadowRoot = doc.createDocumentFragment();
|
||||||
|
el.shadowRoot.parent = el;
|
||||||
|
return el.shadowRoot;
|
||||||
|
}
|
||||||
|
getShadowRoot(el: any): DocumentFragment { return el.shadowRoot; }
|
||||||
|
|
||||||
|
isTextNode(node: any): boolean { return node.nodeType === DominoAdapter.defaultDoc.TEXT_NODE; }
|
||||||
|
isCommentNode(node: any): boolean {
|
||||||
|
return node.nodeType === DominoAdapter.defaultDoc.COMMENT_NODE;
|
||||||
|
}
|
||||||
|
isElementNode(node: any): boolean {
|
||||||
|
return node ? node.nodeType === DominoAdapter.defaultDoc.ELEMENT_NODE : false;
|
||||||
|
}
|
||||||
|
hasShadowRoot(node: any): boolean { return node.shadowRoot != null; }
|
||||||
|
isShadowRoot(node: any): boolean { return this.getShadowRoot(node) == node; }
|
||||||
|
|
||||||
|
getProperty(el: Element, name: string): any {
|
||||||
|
// Domino tries tp resolve href-s which we do not want. Just return the
|
||||||
|
// atribute value.
|
||||||
|
if (name === 'href') {
|
||||||
|
return this.getAttribute(el, 'href');
|
||||||
|
}
|
||||||
|
return (<any>el)[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
setProperty(el: Element, name: string, value: any) {
|
||||||
|
// Eventhough the server renderer reflects any properties to attributes
|
||||||
|
// map 'href' to atribute just to handle when setProperty is directly called.
|
||||||
|
if (name === 'href') {
|
||||||
|
this.setAttribute(el, 'href', value);
|
||||||
|
}
|
||||||
|
(<any>el)[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGlobalEventTarget(doc: Document, target: string): EventTarget|null {
|
||||||
|
if (target === 'window') {
|
||||||
|
return doc.defaultView;
|
||||||
|
}
|
||||||
|
if (target === 'document') {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
if (target === 'body') {
|
||||||
|
return doc.body;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseHref(doc: Document): string {
|
||||||
|
const base = this.querySelector(doc.documentElement, 'base');
|
||||||
|
let href = '';
|
||||||
|
if (base) {
|
||||||
|
href = this.getHref(base);
|
||||||
|
}
|
||||||
|
// TODO(alxhub): Need relative path logic from BrowserDomAdapter here?
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_readStyleAttribute(element: any) {
|
||||||
|
const styleMap = {};
|
||||||
|
const styleAttribute = element.getAttribute('style');
|
||||||
|
if (styleAttribute) {
|
||||||
|
const styleList = styleAttribute.split(/;+/g);
|
||||||
|
for (let i = 0; i < styleList.length; i++) {
|
||||||
|
if (styleList[i].length > 0) {
|
||||||
|
const style = styleList[i] as string;
|
||||||
|
const colon = style.indexOf(':');
|
||||||
|
if (colon === -1) {
|
||||||
|
throw new Error(`Invalid CSS style: ${style}`);
|
||||||
|
}
|
||||||
|
(styleMap as any)[style.substr(0, colon).trim()] = style.substr(colon + 1).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return styleMap;
|
||||||
|
}
|
||||||
|
/** @internal */
|
||||||
|
_writeStyleAttribute(element: any, styleMap: any) {
|
||||||
|
let styleAttrValue = '';
|
||||||
|
for (const key in styleMap) {
|
||||||
|
const newValue = styleMap[key];
|
||||||
|
if (newValue) {
|
||||||
|
styleAttrValue += key + ':' + styleMap[key] + ';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.setAttribute('style', styleAttrValue);
|
||||||
|
}
|
||||||
|
setStyle(element: any, styleName: string, styleValue?: string|null) {
|
||||||
|
const styleMap = this._readStyleAttribute(element);
|
||||||
|
(styleMap as any)[styleName] = styleValue;
|
||||||
|
this._writeStyleAttribute(element, styleMap);
|
||||||
|
}
|
||||||
|
removeStyle(element: any, styleName: string) { this.setStyle(element, styleName, null); }
|
||||||
|
getStyle(element: any, styleName: string): string {
|
||||||
|
const styleMap = this._readStyleAttribute(element);
|
||||||
|
return styleMap.hasOwnProperty(styleName) ? (styleMap as any)[styleName] : '';
|
||||||
|
}
|
||||||
|
hasStyle(element: any, styleName: string, styleValue?: string): boolean {
|
||||||
|
const value = this.getStyle(element, styleName) || '';
|
||||||
|
return styleValue ? value == styleValue : value.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(el: Node, evt: any) {
|
||||||
|
el.dispatchEvent(evt);
|
||||||
|
|
||||||
|
// Dispatch the event to the window also.
|
||||||
|
const doc = el.ownerDocument || el;
|
||||||
|
const win = (doc as any).defaultView;
|
||||||
|
if (win) {
|
||||||
|
win.dispatchEvent(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory(): History { throw _notImplemented('getHistory'); }
|
||||||
|
getLocation(): Location { throw _notImplemented('getLocation'); }
|
||||||
|
getUserAgent(): string { return 'Fake user agent'; }
|
||||||
|
|
||||||
|
supportsWebAnimation(): boolean { return false; }
|
||||||
|
performanceNow(): number { return Date.now(); }
|
||||||
|
getAnimationPrefix(): string { return ''; }
|
||||||
|
getTransitionEnd(): string { return 'transitionend'; }
|
||||||
|
supportsAnimation(): boolean { return true; }
|
||||||
|
|
||||||
|
getDistributedNodes(el: any): Node[] { throw _notImplemented('getDistributedNodes'); }
|
||||||
|
|
||||||
|
supportsCookies(): boolean { return false; }
|
||||||
|
getCookie(name: string): string { throw _notImplemented('getCookie'); }
|
||||||
|
setCookie(name: string, value: string) { throw _notImplemented('setCookie'); }
|
||||||
|
}
|
|
@ -1,837 +0,0 @@
|
||||||
/**
|
|
||||||
* @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 {ɵglobal as global} from '@angular/core';
|
|
||||||
import {ɵDomAdapter as DomAdapter, ɵsetRootDomAdapter as setRootDomAdapter} from '@angular/platform-browser';
|
|
||||||
import {SelectorMatcher, CssSelector} from '@angular/compiler';
|
|
||||||
|
|
||||||
let treeAdapter: any;
|
|
||||||
|
|
||||||
const _attrToPropMap: {[key: string]: string} = {
|
|
||||||
'class': 'className',
|
|
||||||
'innerHtml': 'innerHTML',
|
|
||||||
'readonly': 'readOnly',
|
|
||||||
'tabindex': 'tabIndex',
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapProps = ['attribs', 'x-attribsNamespace', 'x-attribsPrefix'];
|
|
||||||
|
|
||||||
function _notImplemented(methodName: string) {
|
|
||||||
return new Error('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getElement(el: any, name: string) {
|
|
||||||
for (let i = 0; i < el.childNodes.length; i++) {
|
|
||||||
let node = el.childNodes[i];
|
|
||||||
if (node.name === name) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a document string to a Document object.
|
|
||||||
*/
|
|
||||||
export function parseDocument(html: string) {
|
|
||||||
let doc = parse5.parse(html, {treeAdapter: parse5.treeAdapters.htmlparser2});
|
|
||||||
let docElement = _getElement(doc, 'html');
|
|
||||||
doc['head'] = _getElement(docElement, 'head');
|
|
||||||
doc['body'] = _getElement(docElement, 'body');
|
|
||||||
doc['_window'] = {};
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* tslint:disable:requireParameterType */
|
|
||||||
/**
|
|
||||||
* A `DomAdapter` powered by the `parse5` NodeJS module.
|
|
||||||
*
|
|
||||||
* @security Tread carefully! Interacting with the DOM directly is dangerous and
|
|
||||||
* can introduce XSS risks.
|
|
||||||
*/
|
|
||||||
export class Parse5DomAdapter extends DomAdapter {
|
|
||||||
static makeCurrent() {
|
|
||||||
treeAdapter = parse5.treeAdapters.htmlparser2;
|
|
||||||
setRootDomAdapter(new Parse5DomAdapter());
|
|
||||||
}
|
|
||||||
|
|
||||||
contains(nodeA: any, nodeB: any): boolean {
|
|
||||||
let inner = nodeB;
|
|
||||||
while (inner) {
|
|
||||||
if (inner === nodeA) return true;
|
|
||||||
inner = inner.parent;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasProperty(element: any, name: string): boolean {
|
|
||||||
return _HTMLElementPropertyList.indexOf(name) > -1;
|
|
||||||
}
|
|
||||||
// TODO(tbosch): don't even call this method when we run the tests on server side
|
|
||||||
// by not using the DomRenderer in tests. Keeping this for now to make tests happy...
|
|
||||||
setProperty(el: any, name: string, value: any) {
|
|
||||||
if (name === 'innerHTML') {
|
|
||||||
this.setInnerHTML(el, value);
|
|
||||||
} else if (name === 'innerText') {
|
|
||||||
this.setText(el, value);
|
|
||||||
} else if (name === 'className') {
|
|
||||||
el.attribs['class'] = el.className = value;
|
|
||||||
} else {
|
|
||||||
// Store the property in a separate property bag so that it doesn't clobber
|
|
||||||
// actual parse5 properties on the Element.
|
|
||||||
el.properties = el.properties || {};
|
|
||||||
el.properties[name] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO(tbosch): don't even call this method when we run the tests on server side
|
|
||||||
// by not using the DomRenderer in tests. Keeping this for now to make tests happy...
|
|
||||||
getProperty(el: any, name: string): any {
|
|
||||||
return el.properties ? el.properties[name] : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
logError(error: string) { console.error(error); }
|
|
||||||
|
|
||||||
// tslint:disable-next-line:no-console
|
|
||||||
log(error: string) { console.log(error); }
|
|
||||||
|
|
||||||
logGroup(error: string) { console.error(error); }
|
|
||||||
|
|
||||||
logGroupEnd() {}
|
|
||||||
|
|
||||||
get attrToPropMap() { return _attrToPropMap; }
|
|
||||||
|
|
||||||
querySelector(el: any, selector: string): any {
|
|
||||||
return this.querySelectorAll(el, selector)[0] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
querySelectorAll(el: any, selector: string): any[] {
|
|
||||||
const res: any[] = [];
|
|
||||||
const _recursive = (result: any, node: any, selector: any, matcher: any) => {
|
|
||||||
const cNodes = node.childNodes;
|
|
||||||
if (cNodes && cNodes.length > 0) {
|
|
||||||
for (let i = 0; i < cNodes.length; i++) {
|
|
||||||
const childNode = cNodes[i];
|
|
||||||
if (this.elementMatches(childNode, selector, matcher)) {
|
|
||||||
result.push(childNode);
|
|
||||||
}
|
|
||||||
_recursive(result, childNode, selector, matcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const matcher = new SelectorMatcher();
|
|
||||||
matcher.addSelectables(CssSelector.parse(selector));
|
|
||||||
_recursive(res, el, selector, matcher);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
elementMatches(node: any, selector: string, matcher: any = null): boolean {
|
|
||||||
if (this.isElementNode(node) && selector === '*') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
let result = false;
|
|
||||||
if (selector && selector.charAt(0) == '#') {
|
|
||||||
result = this.getAttribute(node, 'id') == selector.substring(1);
|
|
||||||
} else if (selector) {
|
|
||||||
if (!matcher) {
|
|
||||||
matcher = new SelectorMatcher();
|
|
||||||
matcher.addSelectables(CssSelector.parse(selector));
|
|
||||||
}
|
|
||||||
|
|
||||||
const cssSelector = new CssSelector();
|
|
||||||
cssSelector.setElement(this.tagName(node));
|
|
||||||
if (node.attribs) {
|
|
||||||
for (const attrName in node.attribs) {
|
|
||||||
cssSelector.addAttribute(attrName, node.attribs[attrName]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const classList = this.classList(node);
|
|
||||||
for (let i = 0; i < classList.length; i++) {
|
|
||||||
cssSelector.addClassName(classList[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher.match(cssSelector, function(selector: any, cb: any) { result = true; });
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
on(el: any, evt: any, listener: any) {
|
|
||||||
let listenersMap: {[k: string]: any} = el._eventListenersMap;
|
|
||||||
if (!listenersMap) {
|
|
||||||
listenersMap = {};
|
|
||||||
el._eventListenersMap = listenersMap;
|
|
||||||
}
|
|
||||||
const listeners = listenersMap[evt] || [];
|
|
||||||
listenersMap[evt] = [...listeners, listener];
|
|
||||||
}
|
|
||||||
onAndCancel(el: any, evt: any, listener: any): Function {
|
|
||||||
this.on(el, evt, listener);
|
|
||||||
return () => { remove(<any[]>(el._eventListenersMap[evt]), listener); };
|
|
||||||
}
|
|
||||||
dispatchEvent(el: any, evt: any) {
|
|
||||||
if (!evt.target) {
|
|
||||||
evt.target = el;
|
|
||||||
}
|
|
||||||
if (el._eventListenersMap) {
|
|
||||||
const listeners: any = el._eventListenersMap[evt.type];
|
|
||||||
if (listeners) {
|
|
||||||
for (let i = 0; i < listeners.length; i++) {
|
|
||||||
listeners[i](evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (el.parent) {
|
|
||||||
this.dispatchEvent(el.parent, evt);
|
|
||||||
}
|
|
||||||
if (el._window) {
|
|
||||||
this.dispatchEvent(el._window, evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createMouseEvent(eventType: any): Event { return this.createEvent(eventType); }
|
|
||||||
createEvent(eventType: string): Event {
|
|
||||||
const event = <Event>{
|
|
||||||
type: eventType,
|
|
||||||
defaultPrevented: false,
|
|
||||||
preventDefault: () => { (<any>event).defaultPrevented = true; }
|
|
||||||
};
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
preventDefault(event: any) { event.returnValue = false; }
|
|
||||||
isPrevented(event: any): boolean { return event.returnValue != null && !event.returnValue; }
|
|
||||||
getInnerHTML(el: any): string {
|
|
||||||
return parse5.serialize(this.templateAwareRoot(el), {treeAdapter});
|
|
||||||
}
|
|
||||||
getTemplateContent(el: any): Node|null { return null; }
|
|
||||||
getOuterHTML(el: any): string {
|
|
||||||
const fragment = treeAdapter.createDocumentFragment();
|
|
||||||
this.appendChild(fragment, el);
|
|
||||||
return parse5.serialize(fragment, {treeAdapter});
|
|
||||||
}
|
|
||||||
nodeName(node: any): string { return node.tagName; }
|
|
||||||
nodeValue(node: any): string { return node.nodeValue; }
|
|
||||||
type(node: any): string { throw _notImplemented('type'); }
|
|
||||||
content(node: any): string { return node.childNodes[0]; }
|
|
||||||
firstChild(el: any): Node { return el.firstChild; }
|
|
||||||
nextSibling(el: any): Node { return el.nextSibling; }
|
|
||||||
parentElement(el: any): Node { return el.parent; }
|
|
||||||
childNodes(el: any): Node[] { return el.childNodes; }
|
|
||||||
childNodesAsList(el: any): 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: any) {
|
|
||||||
while (el.childNodes.length > 0) {
|
|
||||||
this.remove(el.childNodes[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appendChild(el: any, node: any) {
|
|
||||||
this.remove(node);
|
|
||||||
treeAdapter.appendChild(this.templateAwareRoot(el), node);
|
|
||||||
}
|
|
||||||
removeChild(el: any, node: any) {
|
|
||||||
if (el.childNodes.indexOf(node) > -1) {
|
|
||||||
this.remove(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remove(el: any): HTMLElement {
|
|
||||||
const parent = el.parent;
|
|
||||||
if (parent) {
|
|
||||||
const index = parent.childNodes.indexOf(el);
|
|
||||||
parent.childNodes.splice(index, 1);
|
|
||||||
}
|
|
||||||
const prev = el.previousSibling;
|
|
||||||
const next = el.nextSibling;
|
|
||||||
if (prev) {
|
|
||||||
prev.next = next;
|
|
||||||
}
|
|
||||||
if (next) {
|
|
||||||
next.prev = prev;
|
|
||||||
}
|
|
||||||
el.prev = null;
|
|
||||||
el.next = null;
|
|
||||||
el.parent = null;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
insertBefore(parent: any, ref: any, newNode: any) {
|
|
||||||
this.remove(newNode);
|
|
||||||
if (ref) {
|
|
||||||
treeAdapter.insertBefore(parent, newNode, ref);
|
|
||||||
} else {
|
|
||||||
this.appendChild(parent, newNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
insertAllBefore(parent: any, ref: any, nodes: any) {
|
|
||||||
nodes.forEach((n: any) => this.insertBefore(parent, ref, n));
|
|
||||||
}
|
|
||||||
insertAfter(parent: any, ref: any, node: any) {
|
|
||||||
if (ref.nextSibling) {
|
|
||||||
this.insertBefore(parent, ref.nextSibling, node);
|
|
||||||
} else {
|
|
||||||
this.appendChild(parent, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setInnerHTML(el: any, value: any) {
|
|
||||||
this.clearNodes(el);
|
|
||||||
const content = parse5.parseFragment(value, {treeAdapter});
|
|
||||||
for (let i = 0; i < content.childNodes.length; i++) {
|
|
||||||
treeAdapter.appendChild(el, content.childNodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getText(el: any, isRecursive?: boolean): string {
|
|
||||||
if (this.isTextNode(el)) {
|
|
||||||
return el.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isCommentNode(el)) {
|
|
||||||
// In the DOM, comments within an element return an empty string for textContent
|
|
||||||
// However, comment node instances return the comment content for textContent getter
|
|
||||||
return isRecursive ? '' : el.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!el.childNodes || el.childNodes.length == 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
let textContent = '';
|
|
||||||
for (let i = 0; i < el.childNodes.length; i++) {
|
|
||||||
textContent += this.getText(el.childNodes[i], true);
|
|
||||||
}
|
|
||||||
return textContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
setText(el: any, value: string) {
|
|
||||||
if (this.isTextNode(el) || this.isCommentNode(el)) {
|
|
||||||
el.data = value;
|
|
||||||
} else {
|
|
||||||
this.clearNodes(el);
|
|
||||||
if (value !== '') treeAdapter.insertText(el, 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 treeAdapter.createCommentNode(text); }
|
|
||||||
createTemplate(html: any): HTMLElement {
|
|
||||||
const template = treeAdapter.createElement('template', 'http://www.w3.org/1999/xhtml', []);
|
|
||||||
const content = parse5.parseFragment(html, {treeAdapter});
|
|
||||||
treeAdapter.setTemplateContent(template, content);
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
createElement(tagName: any): HTMLElement {
|
|
||||||
return treeAdapter.createElement(tagName, 'http://www.w3.org/1999/xhtml', []);
|
|
||||||
}
|
|
||||||
createElementNS(ns: any, tagName: any): HTMLElement {
|
|
||||||
return treeAdapter.createElement(tagName, ns, []);
|
|
||||||
}
|
|
||||||
createTextNode(text: string): Text {
|
|
||||||
const t = <any>this.createComment(text);
|
|
||||||
t.type = 'text';
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
createScriptTag(attrName: string, attrValue: string): HTMLElement {
|
|
||||||
return treeAdapter.createElement(
|
|
||||||
'script', 'http://www.w3.org/1999/xhtml', [{name: attrName, value: attrValue}]);
|
|
||||||
}
|
|
||||||
createStyleElement(css: string): HTMLStyleElement {
|
|
||||||
const style = this.createElement('style');
|
|
||||||
this.setText(style, css);
|
|
||||||
return <HTMLStyleElement>style;
|
|
||||||
}
|
|
||||||
createShadowRoot(el: any): HTMLElement {
|
|
||||||
el.shadowRoot = treeAdapter.createDocumentFragment();
|
|
||||||
el.shadowRoot.parent = el;
|
|
||||||
return el.shadowRoot;
|
|
||||||
}
|
|
||||||
getShadowRoot(el: any): Element { return el.shadowRoot; }
|
|
||||||
getHost(el: any): string { return el.host; }
|
|
||||||
getDistributedNodes(el: any): Node[] { throw _notImplemented('getDistributedNodes'); }
|
|
||||||
clone(node: Node): Node {
|
|
||||||
const _recursive = (node: any) => {
|
|
||||||
const nodeClone = Object.create(Object.getPrototypeOf(node));
|
|
||||||
for (const prop in node) {
|
|
||||||
const desc = Object.getOwnPropertyDescriptor(node, prop);
|
|
||||||
if (desc && 'value' in desc && typeof desc.value !== 'object') {
|
|
||||||
nodeClone[prop] = node[prop];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodeClone.parent = null;
|
|
||||||
nodeClone.prev = null;
|
|
||||||
nodeClone.next = null;
|
|
||||||
nodeClone.children = null;
|
|
||||||
|
|
||||||
mapProps.forEach(mapName => {
|
|
||||||
if (node[mapName] != null) {
|
|
||||||
nodeClone[mapName] = {};
|
|
||||||
for (const prop in node[mapName]) {
|
|
||||||
nodeClone[mapName][prop] = node[mapName][prop];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const cNodes = node.children;
|
|
||||||
if (cNodes) {
|
|
||||||
const cNodesClone = new Array(cNodes.length);
|
|
||||||
for (let i = 0; i < cNodes.length; i++) {
|
|
||||||
const childNode = cNodes[i];
|
|
||||||
const childNodeClone = _recursive(childNode);
|
|
||||||
cNodesClone[i] = childNodeClone;
|
|
||||||
if (i > 0) {
|
|
||||||
childNodeClone.prev = cNodesClone[i - 1];
|
|
||||||
cNodesClone[i - 1].next = childNodeClone;
|
|
||||||
}
|
|
||||||
childNodeClone.parent = nodeClone;
|
|
||||||
}
|
|
||||||
nodeClone.children = cNodesClone;
|
|
||||||
}
|
|
||||||
return nodeClone;
|
|
||||||
};
|
|
||||||
return _recursive(node);
|
|
||||||
}
|
|
||||||
getElementsByClassName(element: any, name: string): HTMLElement[] {
|
|
||||||
return this.querySelectorAll(element, '.' + name);
|
|
||||||
}
|
|
||||||
getElementsByTagName(element: any, name: string): HTMLElement[] {
|
|
||||||
return this.querySelectorAll(element, name);
|
|
||||||
}
|
|
||||||
classList(element: any): string[] {
|
|
||||||
let classAttrValue: any = null;
|
|
||||||
const attributes = element.attribs;
|
|
||||||
|
|
||||||
if (attributes && attributes['class'] != null) {
|
|
||||||
classAttrValue = attributes['class'];
|
|
||||||
}
|
|
||||||
return classAttrValue ? classAttrValue.trim().split(/\s+/g) : [];
|
|
||||||
}
|
|
||||||
addClass(element: any, className: string) {
|
|
||||||
const classList = this.classList(element);
|
|
||||||
const index = classList.indexOf(className);
|
|
||||||
if (index == -1) {
|
|
||||||
classList.push(className);
|
|
||||||
element.attribs['class'] = element.className = classList.join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
removeClass(element: any, className: string) {
|
|
||||||
const classList = this.classList(element);
|
|
||||||
const index = classList.indexOf(className);
|
|
||||||
if (index > -1) {
|
|
||||||
classList.splice(index, 1);
|
|
||||||
element.attribs['class'] = element.className = classList.join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hasClass(element: any, className: string): boolean {
|
|
||||||
return this.classList(element).indexOf(className) > -1;
|
|
||||||
}
|
|
||||||
hasStyle(element: any, styleName: string, styleValue?: string): boolean {
|
|
||||||
const value = this.getStyle(element, styleName) || '';
|
|
||||||
return styleValue ? value == styleValue : value.length > 0;
|
|
||||||
}
|
|
||||||
/** @internal */
|
|
||||||
_readStyleAttribute(element: any) {
|
|
||||||
const styleMap = {};
|
|
||||||
const attributes = element.attribs;
|
|
||||||
if (attributes && attributes['style'] != null) {
|
|
||||||
const styleAttrValue = attributes['style'];
|
|
||||||
const styleList = styleAttrValue.split(/;+/g);
|
|
||||||
for (let i = 0; i < styleList.length; i++) {
|
|
||||||
if (styleList[i].length > 0) {
|
|
||||||
const style = styleList[i] as string;
|
|
||||||
const colon = style.indexOf(':');
|
|
||||||
if (colon === -1) {
|
|
||||||
throw new Error(`Invalid CSS style: ${style}`);
|
|
||||||
}
|
|
||||||
(styleMap as any)[style.substr(0, colon).trim()] = style.substr(colon + 1).trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return styleMap;
|
|
||||||
}
|
|
||||||
/** @internal */
|
|
||||||
_writeStyleAttribute(element: any, styleMap: any) {
|
|
||||||
let styleAttrValue = '';
|
|
||||||
for (const key in styleMap) {
|
|
||||||
const newValue = styleMap[key];
|
|
||||||
if (newValue) {
|
|
||||||
styleAttrValue += key + ':' + styleMap[key] + ';';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element.attribs['style'] = styleAttrValue;
|
|
||||||
}
|
|
||||||
setStyle(element: any, styleName: string, styleValue?: string|null) {
|
|
||||||
const styleMap = this._readStyleAttribute(element);
|
|
||||||
(styleMap as any)[styleName] = styleValue;
|
|
||||||
this._writeStyleAttribute(element, styleMap);
|
|
||||||
}
|
|
||||||
removeStyle(element: any, styleName: string) { this.setStyle(element, styleName, null); }
|
|
||||||
getStyle(element: any, styleName: string): string {
|
|
||||||
const styleMap = this._readStyleAttribute(element);
|
|
||||||
return styleMap.hasOwnProperty(styleName) ? (styleMap as any)[styleName] : '';
|
|
||||||
}
|
|
||||||
tagName(element: any): string { return element.tagName == 'style' ? 'STYLE' : element.tagName; }
|
|
||||||
attributeMap(element: any): Map<string, string> {
|
|
||||||
const res = new Map<string, string>();
|
|
||||||
const elAttrs = treeAdapter.getAttrList(element);
|
|
||||||
for (let i = 0; i < elAttrs.length; i++) {
|
|
||||||
const attrib = elAttrs[i];
|
|
||||||
res.set(attrib.name, attrib.value);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
hasAttribute(element: any, attribute: string): boolean {
|
|
||||||
return element.attribs && element.attribs[attribute] != null;
|
|
||||||
}
|
|
||||||
hasAttributeNS(element: any, ns: string, attribute: string): boolean {
|
|
||||||
return this.hasAttribute(element, attribute);
|
|
||||||
}
|
|
||||||
getAttribute(element: any, attribute: string): string {
|
|
||||||
return this.hasAttribute(element, attribute) ? element.attribs[attribute] : null;
|
|
||||||
}
|
|
||||||
getAttributeNS(element: any, ns: string, attribute: string): string {
|
|
||||||
return this.getAttribute(element, attribute);
|
|
||||||
}
|
|
||||||
setAttribute(element: any, attribute: string, value: string) {
|
|
||||||
if (attribute) {
|
|
||||||
element.attribs[attribute] = value;
|
|
||||||
if (attribute === 'class') {
|
|
||||||
element.className = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setAttributeNS(element: any, ns: string, attribute: string, value: string) {
|
|
||||||
this.setAttribute(element, attribute, value);
|
|
||||||
}
|
|
||||||
removeAttribute(element: any, attribute: string) {
|
|
||||||
if (attribute) {
|
|
||||||
delete element.attribs[attribute];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
removeAttributeNS(element: any, ns: string, name: string) { throw 'not implemented'; }
|
|
||||||
templateAwareRoot(el: any): any {
|
|
||||||
return this.isTemplateElement(el) ? treeAdapter.getTemplateContent(el) : el;
|
|
||||||
}
|
|
||||||
createHtmlDocument(): Document {
|
|
||||||
const newDoc = treeAdapter.createDocument();
|
|
||||||
newDoc.title = 'fakeTitle';
|
|
||||||
const head = treeAdapter.createElement('head', null, []);
|
|
||||||
const body = treeAdapter.createElement('body', 'http://www.w3.org/1999/xhtml', []);
|
|
||||||
this.appendChild(newDoc, head);
|
|
||||||
this.appendChild(newDoc, body);
|
|
||||||
newDoc['head'] = head;
|
|
||||||
newDoc['body'] = body;
|
|
||||||
newDoc['_window'] = {};
|
|
||||||
return newDoc;
|
|
||||||
}
|
|
||||||
getBoundingClientRect(el: any): any { return {left: 0, top: 0, width: 0, height: 0}; }
|
|
||||||
getTitle(doc: Document): string { return this.getText(this.getTitleNode(doc)) || ''; }
|
|
||||||
setTitle(doc: Document, newTitle: string) {
|
|
||||||
this.setText(this.getTitleNode(doc), newTitle || '');
|
|
||||||
}
|
|
||||||
isTemplateElement(el: any): boolean {
|
|
||||||
return this.isElementNode(el) && this.tagName(el) === 'template';
|
|
||||||
}
|
|
||||||
isTextNode(node: any): boolean { return treeAdapter.isTextNode(node); }
|
|
||||||
isCommentNode(node: any): boolean { return treeAdapter.isCommentNode(node); }
|
|
||||||
isElementNode(node: any): boolean { return node ? treeAdapter.isElementNode(node) : false; }
|
|
||||||
hasShadowRoot(node: any): boolean { return node.shadowRoot != null; }
|
|
||||||
isShadowRoot(node: any): boolean { return this.getShadowRoot(node) == node; }
|
|
||||||
importIntoDoc(node: any): any { return this.clone(node); }
|
|
||||||
adoptNode(node: any): any { return node; }
|
|
||||||
getHref(el: any): string { return this.getAttribute(el, 'href'); }
|
|
||||||
resolveAndSetHref(el: any, baseUrl: string, href: string) {
|
|
||||||
if (href == null) {
|
|
||||||
el.href = baseUrl;
|
|
||||||
} else {
|
|
||||||
el.href = baseUrl + '/../' + href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** @internal */
|
|
||||||
_buildRules(parsedRules: any, css?: any) {
|
|
||||||
const rules: any[] = [];
|
|
||||||
for (let i = 0; i < parsedRules.length; i++) {
|
|
||||||
const parsedRule = parsedRules[i];
|
|
||||||
const rule: {[key: string]: any} = {};
|
|
||||||
rule['cssText'] = css;
|
|
||||||
rule['style'] = {content: '', cssText: ''};
|
|
||||||
if (parsedRule.type == 'rule') {
|
|
||||||
rule['type'] = 1;
|
|
||||||
|
|
||||||
rule['selectorText'] =
|
|
||||||
parsedRule.selectors.join(', '.replace(/\s{2,}/g, ' ')
|
|
||||||
.replace(/\s*~\s*/g, ' ~ ')
|
|
||||||
.replace(/\s*\+\s*/g, ' + ')
|
|
||||||
.replace(/\s*>\s*/g, ' > ')
|
|
||||||
.replace(/\[(\w+)=(\w+)\]/g, '[$1="$2"]'));
|
|
||||||
if (parsedRule.declarations == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (let j = 0; j < parsedRule.declarations.length; j++) {
|
|
||||||
const declaration = parsedRule.declarations[j];
|
|
||||||
rule['style'] = declaration.property[declaration.value];
|
|
||||||
rule['style'].cssText += declaration.property + ': ' + declaration.value + ';';
|
|
||||||
}
|
|
||||||
} else if (parsedRule.type == 'media') {
|
|
||||||
rule['type'] = 4;
|
|
||||||
rule['media'] = {mediaText: parsedRule.media};
|
|
||||||
if (parsedRule.rules) {
|
|
||||||
rule['cssRules'] = this._buildRules(parsedRule.rules);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rules.push(rule);
|
|
||||||
}
|
|
||||||
return rules;
|
|
||||||
}
|
|
||||||
supportsDOMEvents(): boolean { return false; }
|
|
||||||
supportsNativeShadowDOM(): boolean { return false; }
|
|
||||||
getGlobalEventTarget(doc: Document, target: string): any {
|
|
||||||
if (target == 'window') {
|
|
||||||
return (<any>doc)._window;
|
|
||||||
} else if (target == 'document') {
|
|
||||||
return doc;
|
|
||||||
} else if (target == 'body') {
|
|
||||||
return doc.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getBaseHref(doc: Document): string|null {
|
|
||||||
const base = this.querySelector(doc, 'base');
|
|
||||||
let href = '';
|
|
||||||
if (base) {
|
|
||||||
href = this.getHref(base);
|
|
||||||
}
|
|
||||||
// TODO(alxhub): Need relative path logic from BrowserDomAdapter here?
|
|
||||||
return href == null ? null : href;
|
|
||||||
}
|
|
||||||
resetBaseElement(): void { throw 'not implemented'; }
|
|
||||||
getHistory(): History { throw 'not implemented'; }
|
|
||||||
getLocation(): Location { throw 'not implemented'; }
|
|
||||||
getUserAgent(): string { return 'Fake user agent'; }
|
|
||||||
getData(el: any, name: string): string { return this.getAttribute(el, 'data-' + name); }
|
|
||||||
getComputedStyle(el: any): any { throw 'not implemented'; }
|
|
||||||
setData(el: any, name: string, value: string) { this.setAttribute(el, 'data-' + name, value); }
|
|
||||||
// TODO(tbosch): move this into a separate environment class once we have it
|
|
||||||
supportsWebAnimation(): boolean { return false; }
|
|
||||||
performanceNow(): number { return Date.now(); }
|
|
||||||
getAnimationPrefix(): string { return ''; }
|
|
||||||
getTransitionEnd(): string { return 'transitionend'; }
|
|
||||||
supportsAnimation(): boolean { return true; }
|
|
||||||
|
|
||||||
replaceChild(el: any, newNode: any, oldNode: any) { throw new Error('not implemented'); }
|
|
||||||
parse(templateHtml: string) { throw new Error('not implemented'); }
|
|
||||||
invoke(el: Element, methodName: string, args: any[]): any { throw new Error('not implemented'); }
|
|
||||||
getEventKey(event: any): string { throw new Error('not implemented'); }
|
|
||||||
|
|
||||||
supportsCookies(): boolean { return false; }
|
|
||||||
getCookie(name: string): string { throw new Error('not implemented'); }
|
|
||||||
setCookie(name: string, value: string) { throw new Error('not implemented'); }
|
|
||||||
animate(element: any, keyframes: any[], options: any): any { throw new Error('not implemented'); }
|
|
||||||
private getTitleNode(doc: Document) {
|
|
||||||
let title = this.querySelector(doc, 'title');
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
title = <HTMLTitleElement>this.createElement('title');
|
|
||||||
this.appendChild(this.querySelector(doc, 'head'), title);
|
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: build a proper list, this one is all the keys of a HTMLInputElement
|
|
||||||
const _HTMLElementPropertyList = [
|
|
||||||
'webkitEntries',
|
|
||||||
'incremental',
|
|
||||||
'webkitdirectory',
|
|
||||||
'selectionDirection',
|
|
||||||
'selectionEnd',
|
|
||||||
'selectionStart',
|
|
||||||
'labels',
|
|
||||||
'validationMessage',
|
|
||||||
'validity',
|
|
||||||
'willValidate',
|
|
||||||
'width',
|
|
||||||
'valueAsNumber',
|
|
||||||
'valueAsDate',
|
|
||||||
'value',
|
|
||||||
'useMap',
|
|
||||||
'defaultValue',
|
|
||||||
'type',
|
|
||||||
'step',
|
|
||||||
'src',
|
|
||||||
'size',
|
|
||||||
'required',
|
|
||||||
'readOnly',
|
|
||||||
'placeholder',
|
|
||||||
'pattern',
|
|
||||||
'name',
|
|
||||||
'multiple',
|
|
||||||
'min',
|
|
||||||
'minLength',
|
|
||||||
'maxLength',
|
|
||||||
'max',
|
|
||||||
'list',
|
|
||||||
'indeterminate',
|
|
||||||
'height',
|
|
||||||
'formTarget',
|
|
||||||
'formNoValidate',
|
|
||||||
'formMethod',
|
|
||||||
'formEnctype',
|
|
||||||
'formAction',
|
|
||||||
'files',
|
|
||||||
'form',
|
|
||||||
'disabled',
|
|
||||||
'dirName',
|
|
||||||
'checked',
|
|
||||||
'defaultChecked',
|
|
||||||
'autofocus',
|
|
||||||
'autocomplete',
|
|
||||||
'alt',
|
|
||||||
'align',
|
|
||||||
'accept',
|
|
||||||
'onautocompleteerror',
|
|
||||||
'onautocomplete',
|
|
||||||
'onwaiting',
|
|
||||||
'onvolumechange',
|
|
||||||
'ontoggle',
|
|
||||||
'ontimeupdate',
|
|
||||||
'onsuspend',
|
|
||||||
'onsubmit',
|
|
||||||
'onstalled',
|
|
||||||
'onshow',
|
|
||||||
'onselect',
|
|
||||||
'onseeking',
|
|
||||||
'onseeked',
|
|
||||||
'onscroll',
|
|
||||||
'onresize',
|
|
||||||
'onreset',
|
|
||||||
'onratechange',
|
|
||||||
'onprogress',
|
|
||||||
'onplaying',
|
|
||||||
'onplay',
|
|
||||||
'onpause',
|
|
||||||
'onmousewheel',
|
|
||||||
'onmouseup',
|
|
||||||
'onmouseover',
|
|
||||||
'onmouseout',
|
|
||||||
'onmousemove',
|
|
||||||
'onmouseleave',
|
|
||||||
'onmouseenter',
|
|
||||||
'onmousedown',
|
|
||||||
'onloadstart',
|
|
||||||
'onloadedmetadata',
|
|
||||||
'onloadeddata',
|
|
||||||
'onload',
|
|
||||||
'onkeyup',
|
|
||||||
'onkeypress',
|
|
||||||
'onkeydown',
|
|
||||||
'oninvalid',
|
|
||||||
'oninput',
|
|
||||||
'onfocus',
|
|
||||||
'onerror',
|
|
||||||
'onended',
|
|
||||||
'onemptied',
|
|
||||||
'ondurationchange',
|
|
||||||
'ondrop',
|
|
||||||
'ondragstart',
|
|
||||||
'ondragover',
|
|
||||||
'ondragleave',
|
|
||||||
'ondragenter',
|
|
||||||
'ondragend',
|
|
||||||
'ondrag',
|
|
||||||
'ondblclick',
|
|
||||||
'oncuechange',
|
|
||||||
'oncontextmenu',
|
|
||||||
'onclose',
|
|
||||||
'onclick',
|
|
||||||
'onchange',
|
|
||||||
'oncanplaythrough',
|
|
||||||
'oncanplay',
|
|
||||||
'oncancel',
|
|
||||||
'onblur',
|
|
||||||
'onabort',
|
|
||||||
'spellcheck',
|
|
||||||
'isContentEditable',
|
|
||||||
'contentEditable',
|
|
||||||
'outerText',
|
|
||||||
'innerText',
|
|
||||||
'accessKey',
|
|
||||||
'hidden',
|
|
||||||
'webkitdropzone',
|
|
||||||
'draggable',
|
|
||||||
'tabIndex',
|
|
||||||
'dir',
|
|
||||||
'translate',
|
|
||||||
'lang',
|
|
||||||
'title',
|
|
||||||
'childElementCount',
|
|
||||||
'lastElementChild',
|
|
||||||
'firstElementChild',
|
|
||||||
'children',
|
|
||||||
'onwebkitfullscreenerror',
|
|
||||||
'onwebkitfullscreenchange',
|
|
||||||
'nextElementSibling',
|
|
||||||
'previousElementSibling',
|
|
||||||
'onwheel',
|
|
||||||
'onselectstart',
|
|
||||||
'onsearch',
|
|
||||||
'onpaste',
|
|
||||||
'oncut',
|
|
||||||
'oncopy',
|
|
||||||
'onbeforepaste',
|
|
||||||
'onbeforecut',
|
|
||||||
'onbeforecopy',
|
|
||||||
'shadowRoot',
|
|
||||||
'dataset',
|
|
||||||
'classList',
|
|
||||||
'className',
|
|
||||||
'outerHTML',
|
|
||||||
'innerHTML',
|
|
||||||
'scrollHeight',
|
|
||||||
'scrollWidth',
|
|
||||||
'scrollTop',
|
|
||||||
'scrollLeft',
|
|
||||||
'clientHeight',
|
|
||||||
'clientWidth',
|
|
||||||
'clientTop',
|
|
||||||
'clientLeft',
|
|
||||||
'offsetParent',
|
|
||||||
'offsetHeight',
|
|
||||||
'offsetWidth',
|
|
||||||
'offsetTop',
|
|
||||||
'offsetLeft',
|
|
||||||
'localName',
|
|
||||||
'prefix',
|
|
||||||
'namespaceURI',
|
|
||||||
'id',
|
|
||||||
'style',
|
|
||||||
'attributes',
|
|
||||||
'tagName',
|
|
||||||
'parentElement',
|
|
||||||
'textContent',
|
|
||||||
'baseURI',
|
|
||||||
'ownerDocument',
|
|
||||||
'nextSibling',
|
|
||||||
'previousSibling',
|
|
||||||
'lastChild',
|
|
||||||
'firstChild',
|
|
||||||
'childNodes',
|
|
||||||
'parentNode',
|
|
||||||
'nodeType',
|
|
||||||
'nodeValue',
|
|
||||||
'nodeName',
|
|
||||||
'closure_lm_714617',
|
|
||||||
'__jsaction',
|
|
||||||
];
|
|
||||||
|
|
||||||
function remove<T>(list: T[], el: T): void {
|
|
||||||
const index = list.indexOf(el);
|
|
||||||
if (index > -1) {
|
|
||||||
list.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,11 +6,11 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const parse5 = require('parse5');
|
import {Inject, Injectable} from '@angular/core';
|
||||||
|
|
||||||
import {Injectable, Inject} from '@angular/core';
|
|
||||||
import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/platform-browser';
|
import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import {serializeDocument} from './domino_adapter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of the current platform state.
|
* Representation of the current platform state.
|
||||||
*
|
*
|
||||||
|
@ -23,7 +23,7 @@ export class PlatformState {
|
||||||
/**
|
/**
|
||||||
* Renders the current state of the platform to string.
|
* Renders the current state of the platform to string.
|
||||||
*/
|
*/
|
||||||
renderToString(): string { return getDOM().getInnerHTML(this._doc); }
|
renderToString(): string { return serializeDocument(this._doc); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current DOM state.
|
* Returns the current DOM state.
|
||||||
|
|
|
@ -15,9 +15,9 @@ import {BrowserModule, DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANS
|
||||||
import {ɵplatformCoreDynamic as platformCoreDynamic} from '@angular/platform-browser-dynamic';
|
import {ɵplatformCoreDynamic as platformCoreDynamic} from '@angular/platform-browser-dynamic';
|
||||||
import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
|
import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
import {DominoAdapter, parseDocument} from './domino_adapter';
|
||||||
import {SERVER_HTTP_PROVIDERS} from './http';
|
import {SERVER_HTTP_PROVIDERS} from './http';
|
||||||
import {ServerPlatformLocation} from './location';
|
import {ServerPlatformLocation} from './location';
|
||||||
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
|
|
||||||
import {PlatformState} from './platform_state';
|
import {PlatformState} from './platform_state';
|
||||||
import {ServerRendererFactory2} from './server_renderer';
|
import {ServerRendererFactory2} from './server_renderer';
|
||||||
import {ServerStylesHost} from './styles_host';
|
import {ServerStylesHost} from './styles_host';
|
||||||
|
@ -41,7 +41,7 @@ export const INTERNAL_SERVER_PLATFORM_PROVIDERS: StaticProvider[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
function initParse5Adapter(injector: Injector) {
|
function initParse5Adapter(injector: Injector) {
|
||||||
return () => { Parse5DomAdapter.makeCurrent(); };
|
return () => { DominoAdapter.makeCurrent(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function instantiateServerRendererFactory(
|
export function instantiateServerRendererFactory(
|
||||||
|
@ -80,7 +80,7 @@ export class ServerModule {
|
||||||
function _document(injector: Injector) {
|
function _document(injector: Injector) {
|
||||||
let config: PlatformConfig|null = injector.get(INITIAL_CONFIG, null);
|
let config: PlatformConfig|null = injector.get(INITIAL_CONFIG, null);
|
||||||
if (config && config.document) {
|
if (config && config.document) {
|
||||||
return parseDocument(config.document);
|
return parseDocument(config.document, config.url);
|
||||||
} else {
|
} else {
|
||||||
return getDOM().createHtmlDocument();
|
return getDOM().createHtmlDocument();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ApplicationRef, Inject, Injectable, Optional} from '@angular/core';
|
import {ApplicationRef, Inject, Injectable, Optional} from '@angular/core';
|
||||||
import {DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser';
|
import {DOCUMENT, ɵDomAdapter as DomAdapter, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {Parse5DomAdapter} from './parse5_adapter';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerStylesHost extends SharedStylesHost {
|
export class ServerStylesHost extends SharedStylesHost {
|
||||||
|
@ -23,7 +21,7 @@ export class ServerStylesHost extends SharedStylesHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addStyle(style: string): void {
|
private _addStyle(style: string): void {
|
||||||
let adapter: Parse5DomAdapter = getDOM() as Parse5DomAdapter;
|
let adapter = getDOM();
|
||||||
const el = adapter.createElement('style');
|
const el = adapter.createElement('style');
|
||||||
adapter.setText(el, style);
|
adapter.setText(el, style);
|
||||||
if (!!this.transitionId) {
|
if (!!this.transitionId) {
|
||||||
|
|
|
@ -16,8 +16,6 @@ import {PlatformState} from './platform_state';
|
||||||
import {platformDynamicServer, platformServer} from './server';
|
import {platformDynamicServer, platformServer} from './server';
|
||||||
import {INITIAL_CONFIG} from './tokens';
|
import {INITIAL_CONFIG} from './tokens';
|
||||||
|
|
||||||
const parse5 = require('parse5');
|
|
||||||
|
|
||||||
interface PlatformOptions {
|
interface PlatformOptions {
|
||||||
document?: string;
|
document?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|
|
@ -231,9 +231,8 @@ export function main() {
|
||||||
|
|
||||||
expect(doc.head).toBe(getDOM().querySelector(doc, 'head'));
|
expect(doc.head).toBe(getDOM().querySelector(doc, 'head'));
|
||||||
expect(doc.body).toBe(getDOM().querySelector(doc, 'body'));
|
expect(doc.body).toBe(getDOM().querySelector(doc, 'body'));
|
||||||
expect((<any>doc)._window).toEqual({});
|
|
||||||
|
|
||||||
expect(getDOM().getText(doc)).toEqual('Works!');
|
expect(getDOM().getText(doc.documentElement)).toEqual('Works!');
|
||||||
|
|
||||||
platform.destroy();
|
platform.destroy();
|
||||||
});
|
});
|
||||||
|
@ -249,13 +248,13 @@ export function main() {
|
||||||
|
|
||||||
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
|
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
|
||||||
const doc = moduleRef.injector.get(DOCUMENT);
|
const doc = moduleRef.injector.get(DOCUMENT);
|
||||||
expect(getDOM().getText(doc)).toEqual('Works!');
|
expect(getDOM().getText(doc.documentElement)).toEqual('Works!');
|
||||||
platform.destroy();
|
platform.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
platform2.bootstrapModule(ExampleModule2).then((moduleRef) => {
|
platform2.bootstrapModule(ExampleModule2).then((moduleRef) => {
|
||||||
const doc = moduleRef.injector.get(DOCUMENT);
|
const doc = moduleRef.injector.get(DOCUMENT);
|
||||||
expect(getDOM().getText(doc)).toEqual('Works too!');
|
expect(getDOM().getText(doc.documentElement)).toEqual('Works too!');
|
||||||
platform2.destroy();
|
platform2.destroy();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -310,7 +309,7 @@ export function main() {
|
||||||
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
|
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
|
||||||
const app = appRef.components[0].location.nativeElement;
|
const app = appRef.components[0].location.nativeElement;
|
||||||
const img = getDOM().getElementsByTagName(app, 'img')[0] as any;
|
const img = getDOM().getElementsByTagName(app, 'img')[0] as any;
|
||||||
expect(img.attribs['src']).toEqual('link');
|
expect(img.attributes['src'].value).toEqual('link');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -383,7 +382,7 @@ export function main() {
|
||||||
let doc: string;
|
let doc: string;
|
||||||
let called: boolean;
|
let called: boolean;
|
||||||
let expectedOutput =
|
let expectedOutput =
|
||||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 innerText="fine">fine</h1></app></body></html>';
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 innertext="fine"></h1></app></body></html>';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// PlatformConfig takes in a parsed document so that it can be cached across requests.
|
// PlatformConfig takes in a parsed document so that it can be cached across requests.
|
||||||
|
@ -439,9 +438,8 @@ export function main() {
|
||||||
|
|
||||||
it('works with animation', async(() => {
|
it('works with animation', async(() => {
|
||||||
renderModule(AnimationServerModule, {document: doc}).then(output => {
|
renderModule(AnimationServerModule, {document: doc}).then(output => {
|
||||||
expect(output).toBe(
|
expect(output).toContain('Works!');
|
||||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
expect(output).toContain('ng-trigger-myAnimation');
|
||||||
'<div>Works!</div></app></body></html>');
|
|
||||||
called = true;
|
called = true;
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -128,6 +128,7 @@ export class WorkerDomAdapter extends DomAdapter {
|
||||||
removeAttributeNS(element: any, ns: string, attribute: string) { throw 'not implemented'; }
|
removeAttributeNS(element: any, ns: string, attribute: string) { throw 'not implemented'; }
|
||||||
templateAwareRoot(el: any) { throw 'not implemented'; }
|
templateAwareRoot(el: any) { throw 'not implemented'; }
|
||||||
createHtmlDocument(): HTMLDocument { throw 'not implemented'; }
|
createHtmlDocument(): HTMLDocument { throw 'not implemented'; }
|
||||||
|
getDefaultDocument(): Document { throw 'not implemented'; }
|
||||||
getBoundingClientRect(el: any) { throw 'not implemented'; }
|
getBoundingClientRect(el: any) { throw 'not implemented'; }
|
||||||
getTitle(doc: Document): string { throw 'not implemented'; }
|
getTitle(doc: Document): string { throw 'not implemented'; }
|
||||||
setTitle(doc: Document, newTitle: string) { throw 'not implemented'; }
|
setTitle(doc: Document, newTitle: string) { throw 'not implemented'; }
|
||||||
|
|
|
@ -22,10 +22,10 @@ System.config({
|
||||||
'benchpress/*': 'dist/js/dev/es5/benchpress/*.js',
|
'benchpress/*': 'dist/js/dev/es5/benchpress/*.js',
|
||||||
'@angular': 'dist/all/@angular',
|
'@angular': 'dist/all/@angular',
|
||||||
'rxjs': 'node_modules/rxjs',
|
'rxjs': 'node_modules/rxjs',
|
||||||
'parse5': 'dist/all/@angular/empty.js',
|
'domino': 'dist/all/@angular/empty.js',
|
||||||
'url': 'dist/all/@angular/empty.js',
|
'url': 'dist/all/@angular/empty.js',
|
||||||
'xhr2': 'dist/all/@angular/empty.js',
|
'xhr2': 'dist/all/@angular/empty.js',
|
||||||
'@angular/platform-server/src/parse5_adapter': 'dist/all/empty.js',
|
'@angular/platform-server/src/domino_adapter': 'dist/all/empty.js',
|
||||||
'angular2/*': 'dist/all/angular2/*.js',
|
'angular2/*': 'dist/all/angular2/*.js',
|
||||||
'angular2/src/alt_router/router_testing_providers':
|
'angular2/src/alt_router/router_testing_providers':
|
||||||
'dist/all/angular2/src/alt_router/router_testing_providers.js'
|
'dist/all/angular2/src/alt_router/router_testing_providers.js'
|
||||||
|
|
|
@ -83,7 +83,7 @@ jrunner.onComplete(function(passed: boolean) { process.exit(passed ? 0 : 1); });
|
||||||
jrunner.projectBaseDir = path.resolve(__dirname, '../../');
|
jrunner.projectBaseDir = path.resolve(__dirname, '../../');
|
||||||
jrunner.specDir = '';
|
jrunner.specDir = '';
|
||||||
require('./test-cjs-main.js');
|
require('./test-cjs-main.js');
|
||||||
distAllRequire('@angular/platform-server/src/parse5_adapter.js').Parse5DomAdapter.makeCurrent();
|
distAllRequire('@angular/platform-server/src/domino_adapter.js').DominoAdapter.makeCurrent();
|
||||||
specFiles.forEach((file: string) => {
|
specFiles.forEach((file: string) => {
|
||||||
const r = distAllRequire(file);
|
const r = distAllRequire(file);
|
||||||
if (r.main) {
|
if (r.main) {
|
||||||
|
|
Loading…
Reference in New Issue