diff --git a/npm-shrinkwrap.clean.json b/npm-shrinkwrap.clean.json index 3df94b57ac..7241649e63 100644 --- a/npm-shrinkwrap.clean.json +++ b/npm-shrinkwrap.clean.json @@ -2352,6 +2352,9 @@ "domhandler": { "version": "2.3.0" }, + "domino": { + "version": "1.0.29" + }, "domutils": { "version": "1.5.1" }, @@ -6162,14 +6165,6 @@ "parse-json": { "version": "2.2.0" }, - "parse5": { - "version": "3.0.1", - "dependencies": { - "@types/node": { - "version": "6.0.63" - } - } - }, "parsejson": { "version": "0.0.1" }, diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7490791dd0..eccd03819b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3720,6 +3720,11 @@ "from": "domhandler@>=2.3.0 <3.0.0", "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": { "version": "1.5.1", "from": "domutils@>=1.5.1 <2.0.0", @@ -9850,18 +9855,6 @@ "from": "parse-json@>=2.2.0 <3.0.0", "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": { "version": "0.0.1", "from": "parsejson@0.0.1", diff --git a/package.json b/package.json index e4177aebf4..4577e44ed3 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "cors": "^2.7.1", "dgeni": "^0.4.2", "dgeni-packages": "^0.16.5", + "domino": "^1.0.29", "entities": "^1.1.1", "firebase-tools": "^3.9.2", "firefox-profile": "^0.3.4", @@ -81,7 +82,6 @@ "minimist": "^1.2.0", "nan": "^2.4.0", "node-uuid": "1.4.x", - "parse5": "^3.0.1", "protractor": "^4.0.14", "react": "^0.14.0", "rewire": "^2.3.3", diff --git a/packages/compiler-cli/integrationtest/test/basic_spec.ts b/packages/compiler-cli/integrationtest/test/basic_spec.ts index e9e7f0ac46..188c138545 100644 --- a/packages/compiler-cli/integrationtest/test/basic_spec.ts +++ b/packages/compiler-cli/integrationtest/test/basic_spec.ts @@ -84,8 +84,8 @@ describe('template codegen output', () => { it('should support i18n for content tags', () => { const containerElement = createComponent(BasicComp).nativeElement; - const pElement = containerElement.children.find((c: any) => c.name == 'p'); - const pText = pElement.children.map((c: any) => c.data).join('').trim(); + const pElement = containerElement.querySelector('p'); + const pText = pElement.textContent; expect(pText).toBe('tervetuloa'); }); diff --git a/packages/compiler-cli/integrationtest/test/ng_module_spec.ts b/packages/compiler-cli/integrationtest/test/ng_module_spec.ts index 4e8a2706df..4e58a8f4ea 100644 --- a/packages/compiler-cli/integrationtest/test/ng_module_spec.ts +++ b/packages/compiler-cli/integrationtest/test/ng_module_spec.ts @@ -49,7 +49,7 @@ describe('NgModule', () => { // https://github.com/angular/angular/issues/15221 const fixture = createComponent(ComponentUsingFlatModule); 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', () => { const fixture = createComponent(ComponentUsingThirdParty); const thirdPComps = fixture.nativeElement.children; - expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component'); - expect(thirdPComps[1].children[0].children[0].data).toEqual(`other-3rdP-component + expect(thirdPComps[0].children[0].textContent).toEqual('3rdP-component'); + expect(thirdPComps[1].children[0].textContent).toEqual(`other-3rdP-component multi-lines`); }); diff --git a/packages/platform-browser/src/browser/browser_adapter.ts b/packages/platform-browser/src/browser/browser_adapter.ts index 89dafbd21d..e11f969822 100644 --- a/packages/platform-browser/src/browser/browser_adapter.ts +++ b/packages/platform-browser/src/browser/browser_adapter.ts @@ -129,12 +129,12 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { } dispatchEvent(el: Node, evt: any) { el.dispatchEvent(evt); } createMouseEvent(eventType: string): MouseEvent { - const evt: MouseEvent = document.createEvent('MouseEvent'); + const evt: MouseEvent = this.getDefaultDocument().createEvent('MouseEvent'); evt.initEvent(eventType, true, true); return evt; } createEvent(eventType: any): Event { - const evt: Event = document.createEvent('Event'); + const evt: Event = this.getDefaultDocument().createEvent('Event'); evt.initEvent(eventType, true, true); return evt; } @@ -147,7 +147,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { } getInnerHTML(el: HTMLElement): string { return el.innerHTML; } getTemplateContent(el: Node): Node|null { - return 'content' in el && el instanceof HTMLTemplateElement ? el.content : null; + return 'content' in el && this.isTemplateElement(el) ? (el).content : null; } getOuterHTML(el: HTMLElement): string { return el.outerHTML; } nodeName(node: Node): string { return node.nodeName; } @@ -198,25 +198,34 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { setValue(el: any, value: string) { el.value = value; } getChecked(el: any): boolean { return el.checked; } setChecked(el: any, value: boolean) { el.checked = value; } - createComment(text: string): Comment { return document.createComment(text); } + createComment(text: string): Comment { return this.getDefaultDocument().createComment(text); } createTemplate(html: any): HTMLElement { - const t = document.createElement('template'); + const t = this.getDefaultDocument().createElement('template'); t.innerHTML = html; return t; } - createElement(tagName: string, doc = document): HTMLElement { return doc.createElement(tagName); } - createElementNS(ns: string, tagName: string, doc = document): Element { + createElement(tagName: string, doc?: Document): HTMLElement { + 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); } - createTextNode(text: string, doc = document): Text { return doc.createTextNode(text); } - createScriptTag(attrName: string, attrValue: string, doc = document): HTMLScriptElement { + createTextNode(text: string, doc?: Document): Text { + doc = doc || this.getDefaultDocument(); + return doc.createTextNode(text); + } + createScriptTag(attrName: string, attrValue: string, doc?: Document): HTMLScriptElement { + doc = doc || this.getDefaultDocument(); const el = doc.createElement('SCRIPT'); el.setAttribute(attrName, attrValue); return el; } - createStyleElement(css: string, doc = document): HTMLStyleElement { + createStyleElement(css: string, doc?: Document): HTMLStyleElement { + doc = doc || this.getDefaultDocument(); const style = doc.createElement('style'); - this.appendChild(style, this.createTextNode(css)); + this.appendChild(style, this.createTextNode(css, doc)); return style; } createShadowRoot(el: HTMLElement): DocumentFragment { return (el).createShadowRoot(); } @@ -253,7 +262,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { const res = new Map(); const elAttrs = element.attributes; for (let i = 0; i < elAttrs.length; i++) { - const attrib = elAttrs[i]; + const attrib = elAttrs.item(i); res.set(attrib.name, attrib.value); } return res; @@ -282,6 +291,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { createHtmlDocument(): HTMLDocument { return document.implementation.createHTMLDocument('fakeTitle'); } + getDefaultDocument(): Document { return document; } getBoundingClientRect(el: Element): any { try { 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}; } } - getTitle(doc: Document): string { return document.title; } - setTitle(doc: Document, newTitle: string) { document.title = newTitle || ''; } + getTitle(doc: Document): string { return doc.title; } + setTitle(doc: Document, newTitle: string) { doc.title = newTitle || ''; } elementMatches(n: any, selector: string): boolean { - if (n instanceof HTMLElement) { + if (this.isElementNode(n)) { return n.matches && n.matches(selector) || n.msMatchesSelector && n.msMatchesSelector(selector) || n.webkitMatchesSelector && n.webkitMatchesSelector(selector); @@ -301,7 +311,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { return false; } 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; } 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; } importIntoDoc(node: Node): any { return document.importNode(this.templateAwareRoot(node), true); } adoptNode(node: Node): any { return document.adoptNode(node); } - getHref(el: Element): string { return (el).href; } + getHref(el: Element): string { return el.getAttribute('href') !; } getEventKey(event: any): string { let key = event.key; @@ -342,10 +352,10 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { return window; } if (target === 'document') { - return document; + return doc; } if (target === 'body') { - return document.body; + return doc.body; } return null; } diff --git a/packages/platform-browser/src/browser/meta.ts b/packages/platform-browser/src/browser/meta.ts index acc4308cc0..17edf2925d 100644 --- a/packages/platform-browser/src/browser/meta.ts +++ b/packages/platform-browser/src/browser/meta.ts @@ -56,7 +56,7 @@ export class Meta { getTag(attrSelector: string): HTMLMetaElement|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[] { diff --git a/packages/platform-browser/src/dom/dom_adapter.ts b/packages/platform-browser/src/dom/dom_adapter.ts index 922aca5f8c..cc81fab2b2 100644 --- a/packages/platform-browser/src/dom/dom_adapter.ts +++ b/packages/platform-browser/src/dom/dom_adapter.ts @@ -125,6 +125,7 @@ export abstract class DomAdapter { abstract removeAttributeNS(element: any, ns: string, attribute: string): any; abstract templateAwareRoot(el: any): any; abstract createHtmlDocument(): HTMLDocument; + abstract getDefaultDocument(): Document; abstract getBoundingClientRect(el: any): any; abstract getTitle(doc: Document): string; abstract setTitle(doc: Document, newTitle: string): any; diff --git a/packages/platform-browser/test/browser/meta_spec.ts b/packages/platform-browser/test/browser/meta_spec.ts index 2ef69df558..d435f06a01 100644 --- a/packages/platform-browser/test/browser/meta_spec.ts +++ b/packages/platform-browser/test/browser/meta_spec.ts @@ -14,11 +14,13 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; export function main() { describe('Meta service', () => { - const doc = getDOM().createHtmlDocument(); - const metaService = new Meta(doc); + let doc: Document; + let metaService: Meta; let defaultMeta: HTMLMetaElement; beforeEach(() => { + doc = getDOM().createHtmlDocument(); + metaService = new Meta(doc); defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement; getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id'); getDOM().setAttribute(defaultMeta, 'content', '123456789'); diff --git a/packages/platform-browser/test/browser/title_spec.ts b/packages/platform-browser/test/browser/title_spec.ts index 9c1b27e7ca..a23ef51fda 100644 --- a/packages/platform-browser/test/browser/title_spec.ts +++ b/packages/platform-browser/test/browser/title_spec.ts @@ -14,9 +14,15 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; export function main() { describe('title service', () => { - const doc = getDOM().createHtmlDocument(); - const initialTitle = getDOM().getTitle(doc); - const titleService = new Title(doc); + let doc: Document; + let initialTitle: string; + let titleService: Title; + + beforeEach(() => { + doc = getDOM().createHtmlDocument(); + initialTitle = getDOM().getTitle(doc); + titleService = new Title(doc); + }); afterEach(() => { getDOM().setTitle(doc, initialTitle); }); diff --git a/packages/platform-server/package.json b/packages/platform-server/package.json index eb364efc01..1e188a7ed1 100644 --- a/packages/platform-server/package.json +++ b/packages/platform-server/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "tslib": "^1.7.1", - "parse5": "^3.0.1", + "domino": "^1.0.29", "xhr2": "^0.1.4" }, "repository": { diff --git a/packages/platform-server/src/domino_adapter.ts b/packages/platform-server/src/domino_adapter.ts new file mode 100644 index 0000000000..0bbab8f1c7 --- /dev/null +++ b/packages/platform-server/src/domino_adapter.ts @@ -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('fakeTitle'); + } + + 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 (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); + } + (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'); } +} \ No newline at end of file diff --git a/packages/platform-server/src/parse5_adapter.ts b/packages/platform-server/src/parse5_adapter.ts deleted file mode 100644 index 590c9bbdd1..0000000000 --- a/packages/platform-server/src/parse5_adapter.ts +++ /dev/null @@ -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((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 = { - type: eventType, - defaultPrevented: false, - preventDefault: () => { (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 = 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 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 { - const res = new Map(); - 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 (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 = 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(list: T[], el: T): void { - const index = list.indexOf(el); - if (index > -1) { - list.splice(index, 1); - } -} diff --git a/packages/platform-server/src/platform_state.ts b/packages/platform-server/src/platform_state.ts index d61aeff9ec..d12898addb 100644 --- a/packages/platform-server/src/platform_state.ts +++ b/packages/platform-server/src/platform_state.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -const parse5 = require('parse5'); - -import {Injectable, Inject} from '@angular/core'; +import {Inject, Injectable} from '@angular/core'; import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/platform-browser'; +import {serializeDocument} from './domino_adapter'; + /** * Representation of the current platform state. * @@ -23,7 +23,7 @@ export class PlatformState { /** * 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. diff --git a/packages/platform-server/src/server.ts b/packages/platform-server/src/server.ts index 25a150bd56..46e447eb61 100644 --- a/packages/platform-server/src/server.ts +++ b/packages/platform-server/src/server.ts @@ -15,9 +15,9 @@ import {BrowserModule, DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANS import {ɵplatformCoreDynamic as platformCoreDynamic} from '@angular/platform-browser-dynamic'; import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations'; +import {DominoAdapter, parseDocument} from './domino_adapter'; import {SERVER_HTTP_PROVIDERS} from './http'; import {ServerPlatformLocation} from './location'; -import {Parse5DomAdapter, parseDocument} from './parse5_adapter'; import {PlatformState} from './platform_state'; import {ServerRendererFactory2} from './server_renderer'; import {ServerStylesHost} from './styles_host'; @@ -41,7 +41,7 @@ export const INTERNAL_SERVER_PLATFORM_PROVIDERS: StaticProvider[] = [ ]; function initParse5Adapter(injector: Injector) { - return () => { Parse5DomAdapter.makeCurrent(); }; + return () => { DominoAdapter.makeCurrent(); }; } export function instantiateServerRendererFactory( @@ -80,7 +80,7 @@ export class ServerModule { function _document(injector: Injector) { let config: PlatformConfig|null = injector.get(INITIAL_CONFIG, null); if (config && config.document) { - return parseDocument(config.document); + return parseDocument(config.document, config.url); } else { return getDOM().createHtmlDocument(); } diff --git a/packages/platform-server/src/styles_host.ts b/packages/platform-server/src/styles_host.ts index d85e763e70..fa19f0faeb 100644 --- a/packages/platform-server/src/styles_host.ts +++ b/packages/platform-server/src/styles_host.ts @@ -7,9 +7,7 @@ */ import {ApplicationRef, Inject, Injectable, Optional} from '@angular/core'; -import {DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser'; - -import {Parse5DomAdapter} from './parse5_adapter'; +import {DOCUMENT, ɵDomAdapter as DomAdapter, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser'; @Injectable() export class ServerStylesHost extends SharedStylesHost { @@ -23,7 +21,7 @@ export class ServerStylesHost extends SharedStylesHost { } private _addStyle(style: string): void { - let adapter: Parse5DomAdapter = getDOM() as Parse5DomAdapter; + let adapter = getDOM(); const el = adapter.createElement('style'); adapter.setText(el, style); if (!!this.transitionId) { diff --git a/packages/platform-server/src/utils.ts b/packages/platform-server/src/utils.ts index 3239cc82ba..b26593aa23 100644 --- a/packages/platform-server/src/utils.ts +++ b/packages/platform-server/src/utils.ts @@ -16,8 +16,6 @@ import {PlatformState} from './platform_state'; import {platformDynamicServer, platformServer} from './server'; import {INITIAL_CONFIG} from './tokens'; -const parse5 = require('parse5'); - interface PlatformOptions { document?: string; url?: string; diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index 3620dc532b..2e73010975 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -231,9 +231,8 @@ export function main() { expect(doc.head).toBe(getDOM().querySelector(doc, 'head')); expect(doc.body).toBe(getDOM().querySelector(doc, 'body')); - expect((doc)._window).toEqual({}); - expect(getDOM().getText(doc)).toEqual('Works!'); + expect(getDOM().getText(doc.documentElement)).toEqual('Works!'); platform.destroy(); }); @@ -249,13 +248,13 @@ export function main() { platform.bootstrapModule(ExampleModule).then((moduleRef) => { const doc = moduleRef.injector.get(DOCUMENT); - expect(getDOM().getText(doc)).toEqual('Works!'); + expect(getDOM().getText(doc.documentElement)).toEqual('Works!'); platform.destroy(); }); platform2.bootstrapModule(ExampleModule2).then((moduleRef) => { const doc = moduleRef.injector.get(DOCUMENT); - expect(getDOM().getText(doc)).toEqual('Works too!'); + expect(getDOM().getText(doc.documentElement)).toEqual('Works too!'); platform2.destroy(); }); })); @@ -310,7 +309,7 @@ export function main() { const appRef: ApplicationRef = ref.injector.get(ApplicationRef); const app = appRef.components[0].location.nativeElement; 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 called: boolean; let expectedOutput = - 'Works!

fine

'; + 'Works!

'; beforeEach(() => { // 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(() => { renderModule(AnimationServerModule, {document: doc}).then(output => { - expect(output).toBe( - '' + - '
Works!
'); + expect(output).toContain('Works!'); + expect(output).toContain('ng-trigger-myAnimation'); called = true; }); })); diff --git a/packages/platform-webworker/src/web_workers/worker/worker_adapter.ts b/packages/platform-webworker/src/web_workers/worker/worker_adapter.ts index 7c44a6434a..09229126f6 100644 --- a/packages/platform-webworker/src/web_workers/worker/worker_adapter.ts +++ b/packages/platform-webworker/src/web_workers/worker/worker_adapter.ts @@ -128,6 +128,7 @@ export class WorkerDomAdapter extends DomAdapter { removeAttributeNS(element: any, ns: string, attribute: string) { throw 'not implemented'; } templateAwareRoot(el: any) { throw 'not implemented'; } createHtmlDocument(): HTMLDocument { throw 'not implemented'; } + getDefaultDocument(): Document { throw 'not implemented'; } getBoundingClientRect(el: any) { throw 'not implemented'; } getTitle(doc: Document): string { throw 'not implemented'; } setTitle(doc: Document, newTitle: string) { throw 'not implemented'; } diff --git a/test-main.js b/test-main.js index d496715f52..6ce3887dab 100644 --- a/test-main.js +++ b/test-main.js @@ -22,10 +22,10 @@ System.config({ 'benchpress/*': 'dist/js/dev/es5/benchpress/*.js', '@angular': 'dist/all/@angular', 'rxjs': 'node_modules/rxjs', - 'parse5': 'dist/all/@angular/empty.js', + 'domino': 'dist/all/@angular/empty.js', 'url': '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/src/alt_router/router_testing_providers': 'dist/all/angular2/src/alt_router/router_testing_providers.js' diff --git a/tools/cjs-jasmine/index.ts b/tools/cjs-jasmine/index.ts index 2e0ad2d3d7..12621bf006 100644 --- a/tools/cjs-jasmine/index.ts +++ b/tools/cjs-jasmine/index.ts @@ -83,7 +83,7 @@ jrunner.onComplete(function(passed: boolean) { process.exit(passed ? 0 : 1); }); jrunner.projectBaseDir = path.resolve(__dirname, '../../'); jrunner.specDir = ''; 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) => { const r = distAllRequire(file); if (r.main) {