2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2016-05-26 19:17:35 -04:00
|
|
|
const SVG_PREFIX = ':svg:';
|
2016-09-01 20:32:47 -04:00
|
|
|
|
|
|
|
// Element | Node interfaces
|
|
|
|
// see https://developer.mozilla.org/en-US/docs/Web/API/Element
|
|
|
|
// see https://developer.mozilla.org/en-US/docs/Web/API/Node
|
|
|
|
const ELEMENT_IF = '[Element]';
|
|
|
|
// HTMLElement interface
|
|
|
|
// see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
|
|
|
const HTMLELEMENT_IF = '[HTMLElement]';
|
|
|
|
|
|
|
|
const HTMLELEMENT_TAGS =
|
|
|
|
'abbr,address,article,aside,b,bdi,bdo,cite,code,dd,dfn,dt,em,figcaption,figure,footer,header,i,kbd,main,mark,nav,noscript,rb,rp,rt,rtc,ruby,s,samp,section,small,strong,sub,sup,u,var,wbr';
|
2016-04-28 20:50:03 -04:00
|
|
|
|
2016-08-23 13:52:40 -04:00
|
|
|
const _G: any = global;
|
|
|
|
const document: any = typeof _G['document'] == 'object' ? _G['document'] : null;
|
2016-04-28 20:50:03 -04:00
|
|
|
|
2016-06-02 13:27:33 -04:00
|
|
|
export function extractSchema(): Map<string, string[]> {
|
2016-08-23 13:52:40 -04:00
|
|
|
if (!document) return null;
|
|
|
|
const SVGGraphicsElement = _G['SVGGraphicsElement'];
|
|
|
|
if (!SVGGraphicsElement) return null;
|
|
|
|
|
|
|
|
const SVGAnimationElement = _G['SVGAnimationElement'];
|
|
|
|
const SVGGeometryElement = _G['SVGGeometryElement'];
|
|
|
|
const SVGComponentTransferFunctionElement = _G['SVGComponentTransferFunctionElement'];
|
|
|
|
const SVGGradientElement = _G['SVGGradientElement'];
|
|
|
|
const SVGTextContentElement = _G['SVGTextContentElement'];
|
|
|
|
const SVGTextPositioningElement = _G['SVGTextPositioningElement'];
|
|
|
|
const element = document.createElement('video');
|
|
|
|
const svgAnimation = document.createElementNS('http://www.w3.org/2000/svg', 'set');
|
|
|
|
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
|
|
const svgFeFuncA = document.createElementNS('http://www.w3.org/2000/svg', 'feFuncA');
|
|
|
|
const svgGradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
|
|
|
const svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
2016-04-28 20:50:03 -04:00
|
|
|
|
2016-08-23 13:52:40 -04:00
|
|
|
const descMap: Map<string, string[]> = new Map();
|
|
|
|
let visited: {[name: string]: boolean} = {};
|
|
|
|
|
|
|
|
// HTML top level
|
2016-09-01 20:32:47 -04:00
|
|
|
extractProperties(Node, element, visited, descMap, ELEMENT_IF, '');
|
|
|
|
extractProperties(Element, element, visited, descMap, ELEMENT_IF, '');
|
|
|
|
extractProperties(HTMLElement, element, visited, descMap, HTMLELEMENT_IF, ELEMENT_IF);
|
|
|
|
extractProperties(HTMLElement, element, visited, descMap, HTMLELEMENT_TAGS, HTMLELEMENT_IF);
|
|
|
|
extractProperties(HTMLMediaElement, element, visited, descMap, 'media', HTMLELEMENT_IF);
|
2016-08-23 13:52:40 -04:00
|
|
|
|
|
|
|
// SVG top level
|
2016-09-01 20:32:47 -04:00
|
|
|
extractProperties(SVGElement, svgText, visited, descMap, SVG_PREFIX, HTMLELEMENT_IF);
|
2016-06-08 19:38:52 -04:00
|
|
|
extractProperties(
|
|
|
|
SVGGraphicsElement, svgText, visited, descMap, SVG_PREFIX + 'graphics', SVG_PREFIX);
|
|
|
|
extractProperties(
|
|
|
|
SVGAnimationElement, svgAnimation, visited, descMap, SVG_PREFIX + 'animation', SVG_PREFIX);
|
|
|
|
extractProperties(
|
|
|
|
SVGGeometryElement, svgPath, visited, descMap, SVG_PREFIX + 'geometry', SVG_PREFIX);
|
|
|
|
extractProperties(
|
|
|
|
SVGComponentTransferFunctionElement, svgFeFuncA, visited, descMap,
|
|
|
|
SVG_PREFIX + 'componentTransferFunction', SVG_PREFIX);
|
|
|
|
extractProperties(
|
|
|
|
SVGGradientElement, svgGradient, visited, descMap, SVG_PREFIX + 'gradient', SVG_PREFIX);
|
|
|
|
extractProperties(
|
|
|
|
SVGTextContentElement, svgText, visited, descMap, SVG_PREFIX + 'textContent',
|
|
|
|
SVG_PREFIX + 'graphics');
|
|
|
|
extractProperties(
|
|
|
|
SVGTextPositioningElement, svgText, visited, descMap, SVG_PREFIX + 'textPositioning',
|
|
|
|
SVG_PREFIX + 'textContent');
|
2016-08-23 13:52:40 -04:00
|
|
|
|
|
|
|
// Get all element types
|
|
|
|
const types = Object.getOwnPropertyNames(window).filter(k => /^(HTML|SVG).*?Element$/.test(k));
|
|
|
|
|
|
|
|
types.sort();
|
|
|
|
|
|
|
|
types.forEach(type => { extractRecursiveProperties(visited, descMap, (window as any)[type]); });
|
2016-06-02 13:27:33 -04:00
|
|
|
|
|
|
|
return descMap;
|
2016-04-28 20:50:03 -04:00
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
function extractRecursiveProperties(
|
|
|
|
visited: {[name: string]: boolean}, descMap: Map<string, string[]>, type: Function): string {
|
2016-08-23 13:52:40 -04:00
|
|
|
const name = extractName(type);
|
|
|
|
|
|
|
|
if (visited[name]) {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
let superName: string;
|
|
|
|
switch (name) {
|
2016-09-01 20:32:47 -04:00
|
|
|
case ELEMENT_IF:
|
|
|
|
// ELEMENT_IF is the top most interface (Element | Node)
|
2016-08-23 13:52:40 -04:00
|
|
|
superName = '';
|
|
|
|
break;
|
2016-09-01 20:32:47 -04:00
|
|
|
case HTMLELEMENT_IF:
|
|
|
|
superName = ELEMENT_IF;
|
2016-08-23 13:52:40 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
superName =
|
|
|
|
extractRecursiveProperties(visited, descMap, type.prototype.__proto__.constructor);
|
2016-04-28 20:50:03 -04:00
|
|
|
}
|
|
|
|
|
2016-08-23 13:52:40 -04:00
|
|
|
let instance: HTMLElement = null;
|
2016-04-28 20:50:03 -04:00
|
|
|
name.split(',').forEach(tagName => {
|
2016-09-01 20:32:47 -04:00
|
|
|
instance = type['name'].startsWith('SVG') ?
|
2016-06-08 19:38:52 -04:00
|
|
|
document.createElementNS('http://www.w3.org/2000/svg', tagName.replace(SVG_PREFIX, '')) :
|
|
|
|
document.createElement(tagName);
|
2016-08-23 13:52:40 -04:00
|
|
|
|
|
|
|
let htmlType: Function;
|
|
|
|
|
|
|
|
switch (tagName) {
|
|
|
|
case 'cite':
|
2016-09-01 20:32:47 -04:00
|
|
|
// <cite> interface is `HTMLQuoteElement`
|
2016-08-23 13:52:40 -04:00
|
|
|
htmlType = HTMLElement;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
htmlType = type;
|
|
|
|
}
|
|
|
|
|
2016-04-28 20:50:03 -04:00
|
|
|
if (!(instance instanceof htmlType)) {
|
|
|
|
throw new Error(`Tag <${tagName}> is not an instance of ${htmlType['name']}`);
|
|
|
|
}
|
|
|
|
});
|
2016-08-23 13:52:40 -04:00
|
|
|
|
2016-06-02 13:27:33 -04:00
|
|
|
extractProperties(type, instance, visited, descMap, name, superName);
|
2016-08-23 13:52:40 -04:00
|
|
|
|
2016-04-28 20:50:03 -04:00
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
function extractProperties(
|
|
|
|
type: Function, instance: any, visited: {[name: string]: boolean},
|
|
|
|
descMap: Map<string, string[]>, name: string, superName: string) {
|
2016-04-28 20:50:03 -04:00
|
|
|
if (!type) return;
|
2016-08-23 13:52:40 -04:00
|
|
|
|
2016-04-28 20:50:03 -04:00
|
|
|
visited[name] = true;
|
2016-08-23 13:52:40 -04:00
|
|
|
|
2016-06-02 13:27:33 -04:00
|
|
|
const fullName = name + (superName ? '^' + superName : '');
|
2016-08-23 13:52:40 -04:00
|
|
|
|
|
|
|
const props: string[] = descMap.has(fullName) ? descMap.get(fullName) : [];
|
|
|
|
|
|
|
|
const prototype = type.prototype;
|
|
|
|
let keys = Object.getOwnPropertyNames(prototype);
|
|
|
|
|
2016-04-28 20:50:03 -04:00
|
|
|
keys.sort();
|
2016-08-23 13:52:40 -04:00
|
|
|
keys.forEach((name) => {
|
|
|
|
if (name.startsWith('on')) {
|
|
|
|
props.push('*' + name.substr(2));
|
2016-04-28 20:50:03 -04:00
|
|
|
} else {
|
2016-08-23 13:52:40 -04:00
|
|
|
const typeCh = _TYPE_MNEMONICS[typeof instance[name]];
|
|
|
|
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
|
|
const isSetter = descriptor && descriptor.set;
|
|
|
|
if (typeCh !== void 0 && !name.startsWith('webkit') && isSetter) {
|
|
|
|
props.push(typeCh + name);
|
2016-04-28 20:50:03 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-06-02 13:27:33 -04:00
|
|
|
|
|
|
|
// There is no point in using `Node.nodeValue`, filter it out
|
|
|
|
descMap.set(fullName, type === Node ? props.filter(p => p != '%nodeValue') : props);
|
2016-04-28 20:50:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function extractName(type: Function): string {
|
2016-08-23 13:52:40 -04:00
|
|
|
let name = type['name'];
|
|
|
|
|
|
|
|
switch (name) {
|
|
|
|
// see https://www.w3.org/TR/html5/index.html
|
|
|
|
// TODO(vicb): generate this map from all the element types
|
|
|
|
case 'Element':
|
2016-09-01 20:32:47 -04:00
|
|
|
return ELEMENT_IF;
|
2016-08-23 13:52:40 -04:00
|
|
|
case 'HTMLElement':
|
2016-09-01 20:32:47 -04:00
|
|
|
return HTMLELEMENT_IF;
|
2016-08-23 13:52:40 -04:00
|
|
|
case 'HTMLImageElement':
|
|
|
|
return 'img';
|
|
|
|
case 'HTMLAnchorElement':
|
|
|
|
return 'a';
|
|
|
|
case 'HTMLDListElement':
|
|
|
|
return 'dl';
|
|
|
|
case 'HTMLDirectoryElement':
|
|
|
|
return 'dir';
|
|
|
|
case 'HTMLHeadingElement':
|
|
|
|
return 'h1,h2,h3,h4,h5,h6';
|
|
|
|
case 'HTMLModElement':
|
|
|
|
return 'ins,del';
|
|
|
|
case 'HTMLOListElement':
|
|
|
|
return 'ol';
|
|
|
|
case 'HTMLParagraphElement':
|
|
|
|
return 'p';
|
|
|
|
case 'HTMLQuoteElement':
|
|
|
|
return 'q,blockquote,cite';
|
|
|
|
case 'HTMLTableCaptionElement':
|
|
|
|
return 'caption';
|
|
|
|
case 'HTMLTableCellElement':
|
|
|
|
return 'th,td';
|
|
|
|
case 'HTMLTableColElement':
|
|
|
|
return 'col,colgroup';
|
|
|
|
case 'HTMLTableRowElement':
|
|
|
|
return 'tr';
|
|
|
|
case 'HTMLTableSectionElement':
|
|
|
|
return 'tfoot,thead,tbody';
|
|
|
|
case 'HTMLUListElement':
|
|
|
|
return 'ul';
|
|
|
|
case 'SVGGraphicsElement':
|
|
|
|
return SVG_PREFIX + 'graphics';
|
|
|
|
case 'SVGMPathElement':
|
|
|
|
return SVG_PREFIX + 'mpath';
|
|
|
|
case 'SVGSVGElement':
|
|
|
|
return SVG_PREFIX + 'svg';
|
|
|
|
case 'SVGTSpanElement':
|
|
|
|
return SVG_PREFIX + 'tspan';
|
|
|
|
default:
|
|
|
|
const isSVG = name.startsWith('SVG');
|
|
|
|
if (name.startsWith('HTML') || isSVG) {
|
|
|
|
name = name.replace('HTML', '').replace('SVG', '').replace('Element', '');
|
|
|
|
if (isSVG && name.startsWith('FE')) {
|
|
|
|
name = 'fe' + name.substring(2);
|
|
|
|
} else if (name) {
|
|
|
|
name = name.charAt(0).toLowerCase() + name.substring(1);
|
|
|
|
}
|
|
|
|
return isSVG ? SVG_PREFIX + name : name.toLowerCase();
|
|
|
|
}
|
2016-04-28 20:50:03 -04:00
|
|
|
}
|
2016-08-23 13:52:40 -04:00
|
|
|
|
|
|
|
return null;
|
2016-04-28 20:50:03 -04:00
|
|
|
}
|
|
|
|
|
2016-08-23 13:52:40 -04:00
|
|
|
const _TYPE_MNEMONICS: {[type: string]: string} = {
|
|
|
|
'string': '',
|
|
|
|
'number': '#',
|
|
|
|
'boolean': '!',
|
|
|
|
'object': '%',
|
|
|
|
};
|