2015-03-01 16:17:36 -05:00
|
|
|
var parse5 = require('parse5');
|
|
|
|
var parser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
|
|
|
var serializer = new parse5.Serializer(parse5.TreeAdapters.htmlparser2);
|
|
|
|
var treeAdapter = parser.treeAdapter;
|
|
|
|
|
2015-03-19 06:35:48 -04:00
|
|
|
var cssParse = require('css').parse;
|
2015-03-01 16:17:36 -05:00
|
|
|
|
|
|
|
var url = require('url');
|
|
|
|
|
|
|
|
import {List, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
|
|
import {DomAdapter, setRootDomAdapter} from './dom_adapter';
|
2015-05-28 17:56:40 -04:00
|
|
|
import {BaseException, isPresent, isBlank, global} from 'angular2/src/facade/lang';
|
2015-04-02 17:40:49 -04:00
|
|
|
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';
|
2015-03-01 16:17:36 -05:00
|
|
|
|
|
|
|
var _attrToPropMap = {
|
2015-03-12 12:28:35 -04:00
|
|
|
'innerHtml': 'innerHTML',
|
2015-03-01 16:17:36 -05:00
|
|
|
'readonly': 'readOnly',
|
|
|
|
'tabindex': 'tabIndex',
|
|
|
|
};
|
|
|
|
var defDoc = null;
|
|
|
|
|
|
|
|
function _notImplemented(methodName) {
|
|
|
|
return new BaseException('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Parse5DomAdapter extends DomAdapter {
|
2015-05-29 19:30:39 -04:00
|
|
|
static makeCurrent() { setRootDomAdapter(new Parse5DomAdapter()); }
|
2015-03-01 16:17:36 -05:00
|
|
|
|
2015-06-26 14:10:52 -04:00
|
|
|
hasProperty(element, name: string): boolean {
|
|
|
|
return _HTMLElementPropertyList.indexOf(name) > -1;
|
|
|
|
}
|
2015-06-18 18:44:44 -04:00
|
|
|
// 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: /*element*/ any, name: string, value: any) {
|
|
|
|
if (name === 'innerHTML') {
|
|
|
|
this.setInnerHTML(el, value);
|
|
|
|
} else {
|
|
|
|
el[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: /*element*/ any, name: string): any { return el[name]; }
|
|
|
|
|
2015-05-29 19:30:39 -04:00
|
|
|
logError(error) { console.error(error); }
|
2015-04-30 14:25:50 -04:00
|
|
|
|
2015-05-29 19:30:39 -04:00
|
|
|
get attrToPropMap() { return _attrToPropMap; }
|
2015-03-01 16:17:36 -05:00
|
|
|
|
2015-05-29 19:30:39 -04:00
|
|
|
query(selector) { throw _notImplemented('query'); }
|
2015-06-26 14:10:52 -04:00
|
|
|
querySelector(el, selector: string): any { return this.querySelectorAll(el, selector)[0]; }
|
|
|
|
querySelectorAll(el, selector: string): List<any> {
|
2015-06-17 14:17:21 -04:00
|
|
|
var res = [];
|
2015-03-09 12:41:49 -04:00
|
|
|
var _recursive = (result, node, selector, matcher) => {
|
2015-03-01 16:17:36 -05:00
|
|
|
var cNodes = node.childNodes;
|
|
|
|
if (cNodes && cNodes.length > 0) {
|
|
|
|
for (var i = 0; i < cNodes.length; i++) {
|
2015-05-28 17:58:24 -04:00
|
|
|
var childNode = cNodes[i];
|
|
|
|
if (this.elementMatches(childNode, selector, matcher)) {
|
2015-06-17 14:17:21 -04:00
|
|
|
result.push(childNode);
|
2015-05-28 17:58:24 -04:00
|
|
|
}
|
|
|
|
_recursive(result, childNode, selector, matcher);
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2015-03-09 12:41:49 -04:00
|
|
|
var matcher = new SelectorMatcher();
|
2015-03-19 12:01:42 -04:00
|
|
|
matcher.addSelectables(CssSelector.parse(selector));
|
2015-03-09 12:41:49 -04:00
|
|
|
_recursive(res, el, selector, matcher);
|
2015-03-01 16:17:36 -05:00
|
|
|
return res;
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
elementMatches(node, selector: string, matcher = null): boolean {
|
2015-03-09 12:41:49 -04:00
|
|
|
var result = false;
|
|
|
|
if (selector && selector.charAt(0) == "#") {
|
|
|
|
result = this.getAttribute(node, 'id') == selector.substring(1);
|
|
|
|
} else if (selector) {
|
|
|
|
var result = false;
|
|
|
|
if (matcher == null) {
|
|
|
|
matcher = new SelectorMatcher();
|
2015-03-19 12:01:42 -04:00
|
|
|
matcher.addSelectables(CssSelector.parse(selector));
|
2015-03-09 12:41:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var cssSelector = new CssSelector();
|
|
|
|
cssSelector.setElement(this.tagName(node));
|
|
|
|
if (node.attribs) {
|
|
|
|
for (var attrName in node.attribs) {
|
|
|
|
cssSelector.addAttribute(attrName, node.attribs[attrName]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var classList = this.classList(node);
|
|
|
|
for (var i = 0; i < classList.length; i++) {
|
|
|
|
cssSelector.addClassName(classList[i]);
|
|
|
|
}
|
|
|
|
|
2015-05-29 19:30:39 -04:00
|
|
|
matcher.match(cssSelector, function(selector, cb) { result = true; });
|
2015-03-09 12:41:49 -04:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2015-03-01 16:17:36 -05:00
|
|
|
on(el, evt, listener) {
|
2015-05-29 19:30:39 -04:00
|
|
|
var listenersMap: StringMap<any, any> = el._eventListenersMap;
|
2015-04-21 13:30:55 -04:00
|
|
|
if (isBlank(listenersMap)) {
|
2015-05-29 19:30:39 -04:00
|
|
|
var listenersMap: StringMap<any, any> = StringMapWrapper.create();
|
2015-04-21 13:30:55 -04:00
|
|
|
el._eventListenersMap = listenersMap;
|
|
|
|
}
|
|
|
|
var listeners = StringMapWrapper.get(listenersMap, evt);
|
|
|
|
if (isBlank(listeners)) {
|
2015-06-17 14:17:21 -04:00
|
|
|
listeners = [];
|
2015-04-21 13:30:55 -04:00
|
|
|
}
|
2015-06-17 14:17:21 -04:00
|
|
|
listeners.push(listener);
|
2015-04-21 13:30:55 -04:00
|
|
|
StringMapWrapper.set(listenersMap, evt, listeners);
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-04-02 09:56:58 -04:00
|
|
|
onAndCancel(el, evt, listener): Function {
|
2015-04-21 13:30:55 -04:00
|
|
|
this.on(el, evt, listener);
|
2015-05-29 19:30:39 -04:00
|
|
|
return () => {
|
|
|
|
ListWrapper.remove(StringMapWrapper.get(el._eventListenersMap, evt), listener);
|
|
|
|
};
|
2015-04-02 09:56:58 -04:00
|
|
|
}
|
2015-03-01 16:17:36 -05:00
|
|
|
dispatchEvent(el, evt) {
|
2015-04-21 13:30:55 -04:00
|
|
|
if (isBlank(evt.target)) {
|
|
|
|
evt.target = el;
|
|
|
|
}
|
|
|
|
if (isPresent(el._eventListenersMap)) {
|
2015-05-29 19:30:39 -04:00
|
|
|
var listeners: any = StringMapWrapper.get(el._eventListenersMap, evt.type);
|
2015-04-21 13:30:55 -04:00
|
|
|
if (isPresent(listeners)) {
|
|
|
|
for (var i = 0; i < listeners.length; i++) {
|
|
|
|
listeners[i](evt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isPresent(el.parent)) {
|
|
|
|
this.dispatchEvent(el.parent, evt);
|
|
|
|
}
|
|
|
|
if (isPresent(el._window)) {
|
|
|
|
this.dispatchEvent(el._window, evt);
|
|
|
|
}
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
createMouseEvent(eventType): Event { return this.createEvent(eventType); }
|
|
|
|
createEvent(eventType: string): Event {
|
|
|
|
var evt = <Event>{
|
2015-05-29 19:30:39 -04:00
|
|
|
type: eventType,
|
|
|
|
defaultPrevented: false,
|
2015-06-26 14:10:52 -04:00
|
|
|
preventDefault: () => { evt.defaultPrevented = true; }
|
2015-05-29 19:30:39 -04:00
|
|
|
};
|
2015-05-06 21:30:37 -04:00
|
|
|
return evt;
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
preventDefault(evt) { evt.returnValue = false; }
|
2015-06-26 14:10:52 -04:00
|
|
|
getInnerHTML(el): string { return serializer.serialize(this.templateAwareRoot(el)); }
|
|
|
|
getOuterHTML(el): string {
|
2015-03-01 16:17:36 -05:00
|
|
|
serializer.html = '';
|
|
|
|
serializer._serializeElement(el);
|
|
|
|
return serializer.html;
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
nodeName(node): string { return node.tagName; }
|
|
|
|
nodeValue(node): string { return node.nodeValue; }
|
|
|
|
type(node: any): string { throw _notImplemented('type'); }
|
2015-06-26 14:10:52 -04:00
|
|
|
content(node): string { return node.childNodes[0]; }
|
|
|
|
firstChild(el): Node { return el.firstChild; }
|
|
|
|
nextSibling(el): Node { return el.nextSibling; }
|
|
|
|
parentElement(el): Node { return el.parent; }
|
|
|
|
childNodes(el): Node[] { return el.childNodes; }
|
2015-05-29 19:30:39 -04:00
|
|
|
childNodesAsList(el): List<any> {
|
2015-03-01 16:17:36 -05:00
|
|
|
var childNodes = el.childNodes;
|
|
|
|
var res = ListWrapper.createFixedSize(childNodes.length);
|
|
|
|
for (var i = 0; i < childNodes.length; i++) {
|
|
|
|
res[i] = childNodes[i];
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
clearNodes(el) {
|
|
|
|
while (el.childNodes.length > 0) {
|
|
|
|
this.remove(el.childNodes[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
appendChild(el, node) {
|
|
|
|
this.remove(node);
|
|
|
|
treeAdapter.appendChild(this.templateAwareRoot(el), node);
|
|
|
|
}
|
|
|
|
removeChild(el, node) {
|
|
|
|
if (ListWrapper.contains(el.childNodes, node)) {
|
|
|
|
this.remove(node);
|
|
|
|
}
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
remove(el): HTMLElement {
|
2015-03-01 16:17:36 -05:00
|
|
|
var parent = el.parent;
|
|
|
|
if (parent) {
|
|
|
|
var index = parent.childNodes.indexOf(el);
|
|
|
|
parent.childNodes.splice(index, 1);
|
|
|
|
}
|
|
|
|
var prev = el.previousSibling;
|
|
|
|
var 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(el, node) {
|
|
|
|
this.remove(node);
|
|
|
|
treeAdapter.insertBefore(el.parent, node, el);
|
|
|
|
}
|
|
|
|
insertAllBefore(el, nodes) {
|
2015-05-29 19:30:39 -04:00
|
|
|
ListWrapper.forEach(nodes, (n) => { this.insertBefore(el, n); });
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
insertAfter(el, node) {
|
|
|
|
if (el.nextSibling) {
|
|
|
|
this.insertBefore(el.nextSibling, node);
|
|
|
|
} else {
|
|
|
|
this.appendChild(el.parent, node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setInnerHTML(el, value) {
|
|
|
|
this.clearNodes(el);
|
|
|
|
var content = parser.parseFragment(value);
|
|
|
|
for (var i = 0; i < content.childNodes.length; i++) {
|
|
|
|
treeAdapter.appendChild(el, content.childNodes[i]);
|
|
|
|
}
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getText(el): string {
|
2015-03-01 16:17:36 -05:00
|
|
|
if (this.isTextNode(el)) {
|
|
|
|
return el.data;
|
2015-06-10 13:31:38 -04:00
|
|
|
} else if (isBlank(el.childNodes) || el.childNodes.length == 0) {
|
2015-03-01 16:17:36 -05:00
|
|
|
return "";
|
|
|
|
} else {
|
|
|
|
var textContent = "";
|
|
|
|
for (var i = 0; i < el.childNodes.length; i++) {
|
|
|
|
textContent += this.getText(el.childNodes[i]);
|
|
|
|
}
|
|
|
|
return textContent;
|
|
|
|
}
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
setText(el, value: string) {
|
2015-03-01 16:17:36 -05:00
|
|
|
if (this.isTextNode(el)) {
|
2015-03-12 12:28:35 -04:00
|
|
|
el.data = value;
|
2015-03-01 16:17:36 -05:00
|
|
|
} else {
|
|
|
|
this.clearNodes(el);
|
2015-06-24 03:40:35 -04:00
|
|
|
if (value !== '') treeAdapter.insertText(el, value);
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getValue(el): string { return el.value; }
|
2015-05-29 19:30:39 -04:00
|
|
|
setValue(el, value: string) { el.value = value; }
|
2015-06-26 14:10:52 -04:00
|
|
|
getChecked(el): boolean { return el.checked; }
|
2015-05-29 19:30:39 -04:00
|
|
|
setChecked(el, value: boolean) { el.checked = value; }
|
2015-06-26 14:10:52 -04:00
|
|
|
createTemplate(html): HTMLElement {
|
2015-03-01 16:17:36 -05:00
|
|
|
var template = treeAdapter.createElement("template", 'http://www.w3.org/1999/xhtml', []);
|
|
|
|
var content = parser.parseFragment(html);
|
|
|
|
treeAdapter.appendChild(template, content);
|
|
|
|
return template;
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
createElement(tagName): HTMLElement {
|
2015-03-01 16:17:36 -05:00
|
|
|
return treeAdapter.createElement(tagName, 'http://www.w3.org/1999/xhtml', []);
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
createTextNode(text: string): Text { throw _notImplemented('createTextNode'); }
|
|
|
|
createScriptTag(attrName: string, attrValue: string): HTMLElement {
|
2015-05-29 19:30:39 -04:00
|
|
|
return treeAdapter.createElement("script", 'http://www.w3.org/1999/xhtml',
|
2015-06-03 16:42:57 -04:00
|
|
|
[{name: attrName, value: attrValue}]);
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
createStyleElement(css: string): HTMLStyleElement {
|
2015-03-01 16:17:36 -05:00
|
|
|
var style = this.createElement('style');
|
|
|
|
this.setText(style, css);
|
2015-06-26 14:10:52 -04:00
|
|
|
return <HTMLStyleElement>style;
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
createShadowRoot(el): HTMLElement {
|
2015-03-01 16:17:36 -05:00
|
|
|
el.shadowRoot = treeAdapter.createDocumentFragment();
|
|
|
|
el.shadowRoot.parent = el;
|
|
|
|
return el.shadowRoot;
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getShadowRoot(el): Element { return el.shadowRoot; }
|
|
|
|
getHost(el): string { return el.host; }
|
|
|
|
getDistributedNodes(el: any): List<Node> { throw _notImplemented('getDistributedNodes'); }
|
|
|
|
clone(node: Node): Node {
|
2015-05-28 17:58:52 -04:00
|
|
|
// e.g. document fragment
|
2015-06-26 14:10:52 -04:00
|
|
|
if ((<any>node).type === 'root') {
|
2015-05-28 17:58:52 -04:00
|
|
|
var serialized = serializer.serialize(node);
|
|
|
|
var newParser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
|
|
|
return newParser.parseFragment(serialized);
|
|
|
|
} else {
|
|
|
|
var temp = treeAdapter.createElement("template", null, []);
|
|
|
|
treeAdapter.appendChild(temp, node);
|
|
|
|
var serialized = serializer.serialize(temp);
|
|
|
|
var newParser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
|
|
|
return newParser.parseFragment(serialized).childNodes[0];
|
|
|
|
}
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getElementsByClassName(element, name: string): List<HTMLElement> {
|
2015-03-01 16:17:36 -05:00
|
|
|
return this.querySelectorAll(element, "." + name);
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getElementsByTagName(element: any, name: string): List<HTMLElement> {
|
2015-03-01 16:17:36 -05:00
|
|
|
throw _notImplemented('getElementsByTagName');
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
classList(element): List<string> {
|
2015-03-01 16:17:36 -05:00
|
|
|
var classAttrValue = null;
|
|
|
|
var attributes = element.attribs;
|
|
|
|
if (attributes && attributes.hasOwnProperty("class")) {
|
|
|
|
classAttrValue = attributes["class"];
|
|
|
|
}
|
|
|
|
return classAttrValue ? classAttrValue.trim().split(/\s+/g) : [];
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
addClass(element, classname: string) {
|
2015-03-01 16:17:36 -05:00
|
|
|
var classList = this.classList(element);
|
|
|
|
var index = classList.indexOf(classname);
|
|
|
|
if (index == -1) {
|
2015-06-17 14:17:21 -04:00
|
|
|
classList.push(classname);
|
2015-03-01 16:17:36 -05:00
|
|
|
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
|
|
|
|
}
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
removeClass(element, classname: string) {
|
2015-03-01 16:17:36 -05:00
|
|
|
var classList = this.classList(element);
|
|
|
|
var index = classList.indexOf(classname);
|
|
|
|
if (index > -1) {
|
|
|
|
classList.splice(index, 1);
|
|
|
|
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
|
|
|
|
}
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
hasClass(element, classname: string): boolean {
|
2015-03-01 16:17:36 -05:00
|
|
|
return ListWrapper.contains(this.classList(element), classname);
|
|
|
|
}
|
|
|
|
_readStyleAttribute(element) {
|
|
|
|
var styleMap = {};
|
|
|
|
var attributes = element.attribs;
|
|
|
|
if (attributes && attributes.hasOwnProperty("style")) {
|
|
|
|
var styleAttrValue = attributes["style"];
|
|
|
|
var styleList = styleAttrValue.split(/;+/g);
|
|
|
|
for (var i = 0; i < styleList.length; i++) {
|
|
|
|
if (styleList[i].length > 0) {
|
|
|
|
var elems = styleList[i].split(/:+/g);
|
|
|
|
styleMap[elems[0].trim()] = elems[1].trim();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return styleMap;
|
|
|
|
}
|
|
|
|
_writeStyleAttribute(element, styleMap) {
|
|
|
|
var styleAttrValue = "";
|
|
|
|
for (var key in styleMap) {
|
|
|
|
var newValue = styleMap[key];
|
|
|
|
if (newValue && newValue.length > 0) {
|
2015-03-12 12:28:35 -04:00
|
|
|
styleAttrValue += key + ":" + styleMap[key] + ";";
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
element.attribs["style"] = styleAttrValue;
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
setStyle(element, stylename: string, stylevalue: string) {
|
2015-03-01 16:17:36 -05:00
|
|
|
var styleMap = this._readStyleAttribute(element);
|
|
|
|
styleMap[stylename] = stylevalue;
|
|
|
|
this._writeStyleAttribute(element, styleMap);
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
removeStyle(element, stylename: string) { this.setStyle(element, stylename, null); }
|
2015-06-26 14:10:52 -04:00
|
|
|
getStyle(element, stylename: string): string {
|
2015-03-01 16:17:36 -05:00
|
|
|
var styleMap = this._readStyleAttribute(element);
|
|
|
|
return styleMap.hasOwnProperty(stylename) ? styleMap[stylename] : "";
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
tagName(element): string { return element.tagName == "style" ? "STYLE" : element.tagName; }
|
2015-06-26 14:10:52 -04:00
|
|
|
attributeMap(element): Map<string, string> {
|
2015-06-17 19:21:40 -04:00
|
|
|
var res = new Map();
|
2015-03-01 16:17:36 -05:00
|
|
|
var elAttrs = treeAdapter.getAttrList(element);
|
|
|
|
for (var i = 0; i < elAttrs.length; i++) {
|
|
|
|
var attrib = elAttrs[i];
|
2015-06-17 19:21:40 -04:00
|
|
|
res.set(attrib.name, attrib.value);
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
hasAttribute(element, attribute: string): boolean {
|
2015-05-01 07:41:56 -04:00
|
|
|
return element.attribs && element.attribs.hasOwnProperty(attribute);
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getAttribute(element, attribute: string): string {
|
2015-05-29 19:30:39 -04:00
|
|
|
return element.attribs && element.attribs.hasOwnProperty(attribute) ?
|
|
|
|
element.attribs[attribute] :
|
|
|
|
null;
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
setAttribute(element, attribute: string, value: string) {
|
2015-03-01 16:17:36 -05:00
|
|
|
if (attribute) {
|
|
|
|
element.attribs[attribute] = value;
|
|
|
|
}
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
removeAttribute(element, attribute: string) {
|
2015-03-01 16:17:36 -05:00
|
|
|
if (attribute) {
|
2015-05-29 19:30:39 -04:00
|
|
|
StringMapWrapper.delete(element.attribs, attribute);
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
templateAwareRoot(el): any { return this.isTemplateElement(el) ? this.content(el) : el; }
|
|
|
|
createHtmlDocument(): Document {
|
2015-04-16 12:44:34 -04:00
|
|
|
var newDoc = treeAdapter.createDocument();
|
|
|
|
newDoc.title = "fake title";
|
|
|
|
var head = treeAdapter.createElement("head", null, []);
|
|
|
|
var body = treeAdapter.createElement("body", 'http://www.w3.org/1999/xhtml', []);
|
|
|
|
this.appendChild(newDoc, head);
|
|
|
|
this.appendChild(newDoc, body);
|
|
|
|
StringMapWrapper.set(newDoc, "head", head);
|
|
|
|
StringMapWrapper.set(newDoc, "body", body);
|
2015-04-21 13:30:55 -04:00
|
|
|
StringMapWrapper.set(newDoc, "_window", StringMapWrapper.create());
|
2015-04-16 12:44:34 -04:00
|
|
|
return newDoc;
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
defaultDoc(): Document {
|
2015-03-01 16:17:36 -05:00
|
|
|
if (defDoc === null) {
|
2015-04-16 12:44:34 -04:00
|
|
|
defDoc = this.createHtmlDocument();
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
return defDoc;
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getBoundingClientRect(el): any { return {left: 0, top: 0, width: 0, height: 0}; }
|
|
|
|
getTitle(): string { return this.defaultDoc().title || ""; }
|
2015-05-29 19:30:39 -04:00
|
|
|
setTitle(newTitle: string) { this.defaultDoc().title = newTitle; }
|
|
|
|
isTemplateElement(el: any): boolean {
|
2015-03-01 16:17:36 -05:00
|
|
|
return this.isElementNode(el) && this.tagName(el) === "template";
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
isTextNode(node): boolean { return treeAdapter.isTextNode(node); }
|
|
|
|
isCommentNode(node): boolean { return treeAdapter.isCommentNode(node); }
|
|
|
|
isElementNode(node): boolean { return node ? treeAdapter.isElementNode(node) : false; }
|
|
|
|
hasShadowRoot(node): boolean { return isPresent(node.shadowRoot); }
|
|
|
|
isShadowRoot(node): boolean { return this.getShadowRoot(node) == node; }
|
2015-06-26 14:10:52 -04:00
|
|
|
importIntoDoc(node): any { return this.clone(node); }
|
2015-03-01 16:17:36 -05:00
|
|
|
isPageRule(rule): boolean {
|
2015-05-29 19:30:39 -04:00
|
|
|
return rule.type === 6; // CSSRule.PAGE_RULE
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
isStyleRule(rule): boolean {
|
2015-05-29 19:30:39 -04:00
|
|
|
return rule.type === 1; // CSSRule.MEDIA_RULE
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
isMediaRule(rule): boolean {
|
2015-05-29 19:30:39 -04:00
|
|
|
return rule.type === 4; // CSSRule.MEDIA_RULE
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
isKeyframesRule(rule): boolean {
|
2015-05-29 19:30:39 -04:00
|
|
|
return rule.type === 7; // CSSRule.KEYFRAMES_RULE
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
getHref(el): string { return el.href; }
|
|
|
|
resolveAndSetHref(el, baseUrl: string, href: string) {
|
2015-03-01 16:17:36 -05:00
|
|
|
if (href == null) {
|
|
|
|
el.href = baseUrl;
|
|
|
|
} else {
|
|
|
|
el.href = url.resolve(baseUrl, href);
|
|
|
|
}
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
_buildRules(parsedRules, css?) {
|
2015-06-17 14:17:21 -04:00
|
|
|
var rules = [];
|
2015-03-01 16:17:36 -05:00
|
|
|
for (var i = 0; i < parsedRules.length; i++) {
|
|
|
|
var parsedRule = parsedRules[i];
|
2015-05-29 19:30:39 -04:00
|
|
|
var rule: StringMap<string, any> = StringMapWrapper.create();
|
|
|
|
StringMapWrapper.set(rule, "cssText", css);
|
|
|
|
StringMapWrapper.set(rule, "style", {content: "", cssText: ""});
|
2015-03-01 16:17:36 -05:00
|
|
|
if (parsedRule.type == "rule") {
|
2015-05-29 19:30:39 -04:00
|
|
|
StringMapWrapper.set(rule, "type", 1);
|
|
|
|
StringMapWrapper.set(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"]'));
|
2015-03-01 16:17:36 -05:00
|
|
|
if (isBlank(parsedRule.declarations)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (var j = 0; j < parsedRule.declarations.length; j++) {
|
|
|
|
var declaration = parsedRule.declarations[j];
|
2015-05-29 19:30:39 -04:00
|
|
|
StringMapWrapper.set(StringMapWrapper.get(rule, "style"), declaration.property,
|
|
|
|
declaration.value);
|
|
|
|
StringMapWrapper.get(rule, "style").cssText +=
|
|
|
|
declaration.property + ": " + declaration.value + ";";
|
2015-03-12 12:28:35 -04:00
|
|
|
}
|
2015-03-01 16:17:36 -05:00
|
|
|
} else if (parsedRule.type == "media") {
|
2015-05-29 19:30:39 -04:00
|
|
|
StringMapWrapper.set(rule, "type", 4);
|
|
|
|
StringMapWrapper.set(rule, "media", {mediaText: parsedRule.media});
|
2015-03-01 16:17:36 -05:00
|
|
|
if (parsedRule.rules) {
|
2015-05-29 19:30:39 -04:00
|
|
|
StringMapWrapper.set(rule, "cssRules", this._buildRules(parsedRule.rules));
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
}
|
2015-06-17 14:17:21 -04:00
|
|
|
rules.push(rule);
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
return rules;
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
cssToRules(css: string): List<any> {
|
2015-03-01 16:17:36 -05:00
|
|
|
css = css.replace(/url\(\'(.+)\'\)/g, 'url($1)');
|
2015-06-17 14:17:21 -04:00
|
|
|
var rules = [];
|
2015-03-01 16:17:36 -05:00
|
|
|
var parsedCSS = cssParse(css, {silent: true});
|
|
|
|
if (parsedCSS.stylesheet && parsedCSS.stylesheet.rules) {
|
|
|
|
rules = this._buildRules(parsedCSS.stylesheet.rules, css);
|
|
|
|
}
|
|
|
|
return rules;
|
|
|
|
}
|
2015-05-29 19:30:39 -04:00
|
|
|
supportsDOMEvents(): boolean { return false; }
|
|
|
|
supportsNativeShadowDOM(): boolean { return false; }
|
2015-06-26 14:10:52 -04:00
|
|
|
getGlobalEventTarget(target: string): any {
|
2015-04-21 13:30:55 -04:00
|
|
|
if (target == "window") {
|
2015-06-26 14:10:52 -04:00
|
|
|
return (<any>this.defaultDoc())._window;
|
2015-04-21 13:30:55 -04:00
|
|
|
} else if (target == "document") {
|
|
|
|
return this.defaultDoc();
|
|
|
|
} else if (target == "body") {
|
|
|
|
return this.defaultDoc().body;
|
|
|
|
}
|
|
|
|
}
|
2015-06-26 14:10:52 -04:00
|
|
|
getHistory(): History { throw 'not implemented'; }
|
|
|
|
getLocation(): Location { throw 'not implemented'; }
|
|
|
|
getUserAgent(): string { return "Fake user agent"; }
|
2015-05-29 19:30:39 -04:00
|
|
|
getData(el, name: string): string { return this.getAttribute(el, 'data-' + name); }
|
|
|
|
setData(el, name: string, value: string) { this.setAttribute(el, 'data-' + name, value); }
|
2015-05-28 17:56:40 -04:00
|
|
|
// TODO(tbosch): move this into a separate environment class once we have it
|
2015-05-29 19:30:39 -04:00
|
|
|
setGlobalVar(name: string, value: any) { global[name] = value; }
|
2015-03-01 16:17:36 -05:00
|
|
|
}
|
|
|
|
|
2015-05-29 19:30:39 -04:00
|
|
|
// TODO: build a proper list, this one is all the keys of a HTMLInputElement
|
|
|
|
var _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"
|
|
|
|
];
|