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:

committed by
Jason Aden

parent
30d53a8942
commit
2f2d5f35bd
@ -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) ? (<any>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 = <HTMLScriptElement>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 = <HTMLStyleElement>doc.createElement('style');
|
||||
this.appendChild(style, this.createTextNode(css));
|
||||
this.appendChild(style, this.createTextNode(css, doc));
|
||||
return style;
|
||||
}
|
||||
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
|
||||
@ -253,7 +262,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
const res = new Map<string, string>();
|
||||
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 (<any>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;
|
||||
}
|
||||
|
@ -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[] {
|
||||
|
Reference in New Issue
Block a user