"use strict";
var Node = require('./Node');
var Element = require('./Element');
var CSSStyleDeclaration = require('./CSSStyleDeclaration');
var utils = require('./utils');
var URLUtils = require('./URLUtils');
var defineElement = require('./defineElement');
var htmlElements = exports.elements = {};
var htmlNameToImpl = Object.create(null);
exports.createElement = function(doc, localName, prefix) {
var impl = htmlNameToImpl[localName] || HTMLUnknownElement;
return new impl(doc, localName, prefix);
};
function define(spec) {
return defineElement(spec, HTMLElement, htmlElements, htmlNameToImpl);
}
function URL(attr) {
return {
get: function() {
var v = this._getattr(attr);
if (v === null) { return ''; }
var url = this.doc._resolve(v);
return (url === null) ? v : url;
},
set: function(value) {
this._setattr(attr, value);
}
};
}
function CORS(attr) {
return {
get: function() {
var v = this._getattr(attr);
if (v === null) { return null; }
if (v.toLowerCase() === 'use-credentials') { return 'use-credentials'; }
return 'anonymous';
},
set: function(value) {
if (value===null || value===undefined) {
this.removeAttribute(attr);
} else {
this._setattr(attr, value);
}
}
};
}
var REFERRER = {
type: ["", "no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "strict-origin", "origin-when-cross-origin", "strict-origin-when-cross-origin", "unsafe-url"],
missing: '',
};
// XXX: the default value for tabIndex should be 0 if the element is
// focusable and -1 if it is not. But the full definition of focusable
// is actually hard to compute, so for now, I'll follow Firefox and
// just base the default value on the type of the element.
var focusableElements = {
"A":true, "LINK":true, "BUTTON":true, "INPUT":true,
"SELECT":true, "TEXTAREA":true, "COMMAND":true
};
var HTMLFormElement = function(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
this._form = null; // Prevent later deoptimization
};
var HTMLElement = exports.HTMLElement = define({
superclass: Element,
ctor: function HTMLElement(doc, localName, prefix) {
Element.call(this, doc, localName, utils.NAMESPACE.HTML, prefix);
},
props: {
innerHTML: {
get: function() {
return this.serialize();
},
set: function(v) {
var parser = this.ownerDocument.implementation.mozHTMLParser(
this.ownerDocument._address,
this);
parser.parse(v===null ? '' : String(v), true);
// Remove any existing children of this node
var target = (this instanceof htmlNameToImpl.template) ?
this.content : this;
while(target.hasChildNodes())
target.removeChild(target.firstChild);
// Now copy newly parsed children to this node
target.appendChild(parser._asDocumentFragment());
}
},
style: { get: function() {
if (!this._style)
this._style = new CSSStyleDeclaration(this);
return this._style;
}, set: function(v) {
if (v===null||v===undefined) { v = ''; }
this._setattr('style', String(v));
}},
// These can't really be implemented server-side in a reasonable way.
blur: { value: function() {}},
focus: { value: function() {}},
forceSpellCheck: { value: function() {}},
click: { value: function() {
if (this._click_in_progress) return;
this._click_in_progress = true;
try {
if (this._pre_click_activation_steps)
this._pre_click_activation_steps();
var event = this.ownerDocument.createEvent("MouseEvent");
event.initMouseEvent("click", true, true,
this.ownerDocument.defaultView, 1,
0, 0, 0, 0,
// These 4 should be initialized with
// the actually current keyboard state
// somehow...
false, false, false, false,
0, null
);
// Dispatch this as an untrusted event since it is synthetic
var success = this.dispatchEvent(event);
if (success) {
if (this._post_click_activation_steps)
this._post_click_activation_steps(event);
}
else {
if (this._cancelled_activation_steps)
this._cancelled_activation_steps();
}
}
finally {
this._click_in_progress = false;
}
}},
submit: { value: utils.nyi },
},
attributes: {
title: String,
lang: String,
dir: {type: ["ltr", "rtl", "auto"], missing: ''},
accessKey: String,
hidden: Boolean,
tabIndex: {type: "long", default: function() {
if (this.tagName in focusableElements ||
this.contentEditable)
return 0;
else
return -1;
}}
},
events: [
"abort", "canplay", "canplaythrough", "change", "click", "contextmenu",
"cuechange", "dblclick", "drag", "dragend", "dragenter", "dragleave",
"dragover", "dragstart", "drop", "durationchange", "emptied", "ended",
"input", "invalid", "keydown", "keypress", "keyup", "loadeddata",
"loadedmetadata", "loadstart", "mousedown", "mousemove", "mouseout",
"mouseover", "mouseup", "mousewheel", "pause", "play", "playing",
"progress", "ratechange", "readystatechange", "reset", "seeked",
"seeking", "select", "show", "stalled", "submit", "suspend",
"timeupdate", "volumechange", "waiting",
// These last 5 event types will be overriden by HTMLBodyElement
"blur", "error", "focus", "load", "scroll"
]
});
// XXX: reflect contextmenu as contextMenu, with element type
// style: the spec doesn't call this a reflected attribute.
// may want to handle it manually.
// contentEditable: enumerated, not clear if it is actually
// reflected or requires custom getter/setter. Not listed as
// "limited to known values". Raises syntax_err on bad setting,
// so I think this is custom.
// contextmenu: content is element id, idl type is an element
// draggable: boolean, but not a reflected attribute
// dropzone: reflected SettableTokenList, experimental, so don't
// implement it right away.
// data-* attributes: need special handling in setAttribute?
// Or maybe that isn't necessary. Can I just scan the attribute list
// when building the dataset? Liveness and caching issues?
// microdata attributes: many are simple reflected attributes, but
// I'm not going to implement this now.
var HTMLUnknownElement = define({
ctor: function HTMLUnknownElement(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
}
});
var formAssociatedProps = {
// See http://www.w3.org/TR/html5/association-of-controls-and-forms.html#form-owner
form: { get: function() {
return this._form;
}}
};
define({
tag: 'a',
ctor: function HTMLAnchorElement(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
},
props: {
_post_click_activation_steps: { value: function(e) {
if (this.href) {
// Follow the link
// XXX: this is just a quick hack
// XXX: the HTML spec probably requires more than this
this.ownerDocument.defaultView.location = this.href;
}
}},
},
attributes: {
href: URL,
ping: String,
download: String,
target: String,
rel: String,
media: String,
hreflang: String,
type: String,
referrerPolicy: REFERRER,
// Obsolete
coords: String,
charset: String,
name: String,
rev: String,
shape: String,
}
});
// Latest WhatWG spec says these methods come via HTMLHyperlinkElementUtils
URLUtils._inherit(htmlNameToImpl.a.prototype);
define({
tag: 'area',
ctor: function HTMLAreaElement(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
},
attributes: {
alt: String,
target: String,
download: String,
rel: String,
media: String,
href: URL,
hreflang: String,
type: String,
shape: String,
coords: String,
ping: String,
// XXX: also reflect relList
referrerPolicy: REFERRER,
// Obsolete
noHref: Boolean,
}
});
// Latest WhatWG spec says these methods come via HTMLHyperlinkElementUtils
URLUtils._inherit(htmlNameToImpl.area.prototype);
define({
tag: 'br',
ctor: function HTMLBRElement(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
},
attributes: {
// Obsolete
clear: String
},
});
define({
tag: 'base',
ctor: function HTMLBaseElement(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
},
attributes: {
"target": String
}
});
define({
tag: 'body',
ctor: function HTMLBodyElement(doc, localName, prefix) {
HTMLElement.call(this, doc, localName, prefix);
},
// Certain event handler attributes on a
tag actually set
// handlers for the window rather than just that element. Define
// getters and setters for those here. Note that some of these override
// properties on HTMLElement.prototype.
// XXX: If I add support for