update to state of the art

This commit is contained in:
s2
2020-10-10 15:18:01 +02:00
parent cf251a170f
commit 4cdcfd167c
1526 changed files with 48132 additions and 7268 deletions

View File

@@ -26,8 +26,8 @@ const Selection = require("../living/generated/Selection");
const reportException = require("../living/helpers/runtime-script-errors");
const { fireAnEvent } = require("../living/helpers/events");
const SessionHistory = require("../living/window/SessionHistory");
const { forEachMatchingSheetRuleOfElement, getResolvedValue, propertiesWithResolvedValueImplemented } =
require("../living/helpers/style-rules");
const { forEachMatchingSheetRuleOfElement, getResolvedValue, propertiesWithResolvedValueImplemented,
SHADOW_DOM_PSEUDO_REGEXP } = require("../living/helpers/style-rules.js");
const CustomElementRegistry = require("../living/generated/CustomElementRegistry");
const jsGlobals = require("./js-globals.json");
@@ -642,6 +642,20 @@ function Window(options) {
this.getComputedStyle = function (elt) {
elt = Element.convert(elt);
let pseudoElt = arguments[1];
if (pseudoElt !== undefined && pseudoElt !== null) {
pseudoElt = webIDLConversions.DOMString(pseudoElt);
}
if (pseudoElt !== undefined && pseudoElt !== null && pseudoElt !== "") {
// TODO: Parse pseudoElt
if (SHADOW_DOM_PSEUDO_REGEXP.test(pseudoElt)) {
throw new TypeError("Tried to get the computed style of a Shadow DOM pseudo-element.");
}
notImplemented("window.computedStyle(elt, pseudoElt)", this);
}
const declaration = new CSSStyleDeclaration();
const { forEach } = Array.prototype;

View File

@@ -1,35 +1,35 @@
"use strict";
const nodeType = require("../node-type.js");
const FocusEvent = require("../generated/FocusEvent.js");
const idlUtils = require("../generated/utils.js");
const { isDisabled } = require("./form-controls.js");
const { firstChildWithLocalName } = require("./traversal");
const { createAnEvent } = require("./events");
const { HTML_NS } = require("./namespaces");
const { HTML_NS, SVG_NS } = require("./namespaces");
const { isRenderedElement } = require("./svg/render");
const focusableFormElements = new Set(["input", "select", "textarea", "button"]);
// https://html.spec.whatwg.org/multipage/interaction.html#focusable-area, but also some of
// https://html.spec.whatwg.org/multipage/interaction.html#focusing-steps: e.g., Documents are not actually focusable
// areas, but their viewports are, and the first step of the latter algorithm translates Documents to their viewports.
// https://html.spec.whatwg.org/multipage/interaction.html#focusing-steps and some of
// https://svgwg.org/svg2-draft/interact.html#TermFocusable
exports.isFocusableAreaElement = elImpl => {
if (!elImpl._ownerDocument._defaultView && !elImpl._defaultView) {
return false;
}
if (elImpl._nodeType === nodeType.DOCUMENT_NODE) {
return true;
}
if (!Number.isNaN(parseInt(elImpl.getAttributeNS(null, "tabindex")))) {
return true;
}
// We implemented most of the suggested focusable elements found here:
// https://html.spec.whatwg.org/multipage/interaction.html#tabindex-value
// However, some suggested elements are not focusable in web browsers, as detailed here:
// https://github.com/whatwg/html/issues/5490
if (elImpl._namespaceURI === HTML_NS) {
if (!elImpl._ownerDocument._defaultView) {
return false;
}
if (!elImpl.isConnected) {
return false;
}
if (!Number.isNaN(parseInt(elImpl.getAttributeNS(null, "tabindex")))) {
return true;
}
if (elImpl._localName === "iframe") {
return true;
}
@@ -55,11 +55,26 @@ exports.isFocusableAreaElement = elImpl => {
if (elImpl.hasAttributeNS(null, "contenteditable")) {
return true;
}
return false;
// This does not check for a designMode Document as specified in
// https://html.spec.whatwg.org/multipage/interaction.html#editing-host because the designMode
// attribute is not implemented.
}
if (elImpl._namespaceURI === SVG_NS) {
if (!Number.isNaN(parseInt(elImpl.getAttributeNS(null, "tabindex"))) && isRenderedElement(elImpl)) {
return true;
}
if (elImpl._localName === "a" && elImpl.hasAttributeNS(null, "href")) {
return true;
}
return false;
}
return false;
};

View File

@@ -106,3 +106,5 @@ exports.getResolvedValue = (element, property) => {
// So we skip to "any other property: The resolved value is the computed value."
return getComputedValue(element, property);
};
exports.SHADOW_DOM_PSEUDO_REGEXP = /^::(?:part|slotted)\(/i;

View File

@@ -0,0 +1,46 @@
"use strict";
const { SVG_NS } = require("../namespaces");
// https://svgwg.org/svg2-draft/render.html#TermNeverRenderedElement
const neverRenderedElements = new Set([
"clipPath",
"defs",
"desc",
"linearGradient",
"marker",
"mask",
"metadata",
"pattern",
"radialGradient",
"script",
"style",
"title",
"symbol"
]);
// https://svgwg.org/svg2-draft/render.html#Rendered-vs-NonRendered
exports.isRenderedElement = elImpl => {
if (neverRenderedElements.has(elImpl._localName)) {
return false;
}
// This does not check for elements excluded because of conditional processing attributes or switch structures,
// because conditional processing is not implemented.
// https://svgwg.org/svg2-draft/struct.html#ConditionalProcessing
// This does not check for computed style of display being none, since that is not yet implemented for HTML
// focusability either (and there are no tests yet).
if (!elImpl.isConnected) {
return false;
}
// The spec is unclear about how to deal with non-SVG parents, so we only perform this check for SVG-namespace
// parents.
if (elImpl.parentElement && elImpl.parentElement._namespaceURI === SVG_NS &&
!exports.isRenderedElement(elImpl.parentNode)) {
return false;
}
return true;
};

View File

@@ -614,6 +614,10 @@ class DocumentImpl extends NodeImpl {
}
_runPreRemovingSteps(oldNode) {
// https://html.spec.whatwg.org/#focus-fixup-rule
if (oldNode === this.activeElement) {
this._lastFocusedElement = this.body;
}
for (const activeNodeIterator of this._workingNodeIterators) {
activeNodeIterator._preRemovingSteps(oldNode);
}

View File

@@ -78,6 +78,8 @@ class ElementImpl extends NodeImpl {
this._attributes = NamedNodeMap.createImpl(this._globalObject, [], {
element: this
});
this._cachedTagName = null;
}
_attach() {
@@ -133,11 +135,17 @@ class ElementImpl extends NodeImpl {
return this._prefix !== null ? this._prefix + ":" + this._localName : this._localName;
}
get tagName() {
let qualifiedName = this._qualifiedName;
if (this.namespaceURI === HTML_NS && this._ownerDocument._parsingMode === "html") {
qualifiedName = asciiUppercase(qualifiedName);
// This getter can be a hotpath in getComputedStyle.
// All these are invariants during the instance lifetime so we can safely cache the computed tagName.
// We could create it during construction but since we already identified this as potentially slow we do it lazily.
if (this._cachedTagName === null) {
if (this.namespaceURI === HTML_NS && this._ownerDocument._parsingMode === "html") {
this._cachedTagName = asciiUppercase(this._qualifiedName);
} else {
this._cachedTagName = this._qualifiedName;
}
}
return qualifiedName;
return this._cachedTagName;
}
get attributes() {

View File

@@ -1,10 +1,10 @@
"use strict";
const HTMLElementImpl = require("./HTMLElement-impl").implementation;
const { isDisabled, formOwner } = require("../helpers/form-controls");
const DefaultConstraintValidationImpl =
require("../constraint-validation/DefaultConstraintValidation-impl").implementation;
const { mixin } = require("../../utils");
const { getLabelsForLabelable } = require("../helpers/form-controls");
const { isDisabled, formOwner, getLabelsForLabelable } = require("../helpers/form-controls");
const { asciiLowercase } = require("../helpers/strings");
class HTMLButtonElementImpl extends HTMLElementImpl {
constructor(globalObject, args, privateData) {
@@ -42,7 +42,7 @@ class HTMLButtonElementImpl extends HTMLElementImpl {
}
get type() {
const typeAttr = (this.getAttributeNS(null, "type") || "").toLowerCase();
const typeAttr = asciiLowercase(this.getAttributeNS(null, "type") || "");
switch (typeAttr) {
case "submit":
case "reset":
@@ -54,7 +54,7 @@ class HTMLButtonElementImpl extends HTMLElementImpl {
}
set type(v) {
v = String(v).toLowerCase();
v = asciiLowercase(String(v));
switch (v) {
case "submit":
case "reset":

View File

@@ -8,6 +8,7 @@ const HTMLOrSVGElementImpl = require("./HTMLOrSVGElement-impl").implementation;
const { firstChildWithLocalName } = require("../helpers/traversal");
const { isDisabled } = require("../helpers/form-controls");
const { fireAnEvent } = require("../helpers/events");
const { asciiLowercase } = require("../helpers/strings");
class HTMLElementImpl extends ElementImpl {
constructor(globalObject, args, privateData) {
@@ -37,10 +38,11 @@ class HTMLElementImpl extends ElementImpl {
// https://html.spec.whatwg.org/multipage/dom.html#the-translate-attribute
get translate() {
const translateAttr = this.getAttributeNS(null, "translate");
const translateAttrString = asciiLowercase(translateAttr || "");
if (translateAttr === "yes" || translateAttr === "") {
if (translateAttrString === "yes" || (translateAttr && translateAttrString === "")) {
return true;
} else if (translateAttr === "no") {
} else if (translateAttrString === "no") {
return false;
}
@@ -86,7 +88,7 @@ class HTMLElementImpl extends ElementImpl {
}
get draggable() {
const attributeValue = this.getAttributeNS(null, "draggable");
const attributeValue = asciiLowercase(this.getAttributeNS(null, "draggable") || "");
if (attributeValue === "true") {
return true;

View File

@@ -5,7 +5,7 @@ const { serializeURL } = require("whatwg-url");
const HTMLElementImpl = require("./HTMLElement-impl").implementation;
const { domSymbolTree } = require("../helpers/internal-constants");
const { fireAnEvent } = require("../helpers/events");
const { isListed, isSubmittable, isSubmitButton } = require("../helpers/form-controls");
const { formOwner, isListed, isSubmittable, isSubmitButton } = require("../helpers/form-controls");
const HTMLCollection = require("../generated/HTMLCollection");
const notImplemented = require("../../browser/not-implemented");
const { parseURLToResultingURLRecord } = require("../helpers/document-base-url");
@@ -47,14 +47,24 @@ class HTMLFormElementImpl extends HTMLElementImpl {
super._descendantRemoved.apply(this, arguments);
}
_getElementNodes() {
return domSymbolTree.treeToArray(this.getRootNode({}), {
filter: node => {
if (!isListed(node) || (node._localName === "input" && node.type === "image")) {
return false;
}
return formOwner(node) === this;
}
});
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-elements
get elements() {
// TODO: Return a HTMLFormControlsCollection
return HTMLCollection.createImpl(this._globalObject, [], {
element: this,
query: () => domSymbolTree.treeToArray(this, {
filter: node => isListed(node) && (node._localName !== "input" || node.type !== "image")
})
element: this.getRootNode({}),
query: () => this._getElementNodes()
});
}

View File

@@ -6,6 +6,11 @@ const { Canvas } = require("../../utils");
const { parseURLToResultingURLRecord } = require("../helpers/document-base-url");
class HTMLImageElementImpl extends HTMLElementImpl {
constructor(...args) {
super(...args);
this._currentRequestState = "unavailable";
}
_attrModified(name, value, oldVal) {
// TODO: handle crossorigin
if (name === "src" || ((name === "srcset" || name === "width" || name === "sizes") && value !== oldVal)) {
@@ -50,7 +55,11 @@ class HTMLImageElementImpl extends HTMLElementImpl {
}
get complete() {
return Boolean(this._image && this._image.complete);
const srcAttributeValue = this.getAttributeNS(null, "src");
return srcAttributeValue === null ||
srcAttributeValue === "" ||
this._currentRequestState === "broken" ||
this._currentRequestState === "completely available";
}
get currentSrc() {
@@ -73,6 +82,7 @@ class HTMLImageElementImpl extends HTMLElementImpl {
this._image = new Canvas.Image();
}
this._currentSrc = null;
this._currentRequestState = "unavailable";
const srcAttributeValue = this.getAttributeNS(null, "src");
let urlString = null;
if (srcAttributeValue !== null && srcAttributeValue !== "") {
@@ -101,11 +111,15 @@ class HTMLImageElementImpl extends HTMLElementImpl {
throw new Error(error);
}
this._currentSrc = srcAttributeValue;
this._currentRequestState = "completely available";
};
request = resourceLoader.fetch(urlString, {
element: this,
onLoad: onLoadImage
onLoad: onLoadImage,
onError: () => {
this._currentRequestState = "broken";
}
});
} else {
this._image.src = "";

View File

@@ -332,11 +332,11 @@ class HTMLInputElementImpl extends HTMLElementImpl {
return null;
}
_isRadioGroupChecked() {
if (this.checked) {
_someInRadioGroup(name) {
if (this[name]) {
return true;
}
return this._otherRadioGroupElements.some(radioGroupElement => radioGroupElement.checked);
return this._otherRadioGroupElements.some(radioGroupElement => radioGroupElement[name]);
}
get _mutable() {
@@ -976,7 +976,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
// and all of the input elements in the radio button group have a checkedness
// that is false, then the element is suffering from being missing.
case "radio":
if (this._required && !this._isRadioGroupChecked()) {
if (this._someInRadioGroup("_required") && !this._someInRadioGroup("checked")) {
return true;
}
break;