Bug 1723614 - P2: Robustify name change events and use events in name tests. r=Jamie

Changed the browser and mochitest name tests to rely exclusively on name change
events. To make this happen, I fixed all the cases where we were
event-deficient in the code:

* Examine target in PushNameOrDescriptionChange if it has eNameFromSubtreeRule.
  Fixes cases where a text change event happens with the subtree name root as target.
* Change in aria-labelledby should always result in a name change event because
  that attribute has highest prescedence.
* Add eHasNameDependent/eHasDescriptionDependent context flags when dependee accessible
  is added after dependent accessible to tree.
* Handle value attribute change in HTML buttons and determine if they should trigger a
  name changed event.
* Use accessible tree instead of content tree when calculating HTMLSelectOptionAccessible
  name, this keeps the PushNameOrDescriptionChange sees in name flags consistent with
  the actual tree.
* Handle label attribute change in select options and determine if they should trigger
  a name changed event.
* Determine if s summary attribute change on a table triggers a name change event.
* If a title attribute is changed, reliably fire a name change event if
  it is used in name calculation.

Differential Revision: https://phabricator.services.mozilla.com/D121580
This commit is contained in:
Eitan Isaacson 2021-08-05 23:04:17 +00:00
Родитель caafeaea2a
Коммит 7255c6bf8e
13 изменённых файлов: 191 добавлений и 153 удалений

Просмотреть файл

@ -63,12 +63,11 @@ bool EventQueue::PushNameOrDescriptionChange(LocalAccessible* aTarget) {
// Only continue traversing up the tree if it's possible that the parent
// LocalAccessible's name (or a LocalAccessible being labelled by this
// LocalAccessible or an ancestor) can depend on this LocalAccessible's name.
LocalAccessible* parent = aTarget->LocalParent();
while (parent &&
nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) {
LocalAccessible* parent = aTarget;
do {
// Test possible name dependent parent.
if (doName) {
if (nameCheckAncestor &&
if (nameCheckAncestor && parent != aTarget &&
nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
nsAutoString name;
ENameValueFlag nameFlag = parent->Name(name);
@ -99,7 +98,9 @@ bool EventQueue::PushNameOrDescriptionChange(LocalAccessible* aTarget) {
}
parent = parent->LocalParent();
}
} while (parent &&
nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule));
return pushed;
}

Просмотреть файл

@ -1239,11 +1239,14 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
mDoc->Controller()->ScheduleRelocation(this);
}
// Fire name change and description change events. XXX: it's not complete and
// dupes the code logic of accessible name and description calculation, we do
// that for performance reasons.
// Fire name change and description change events.
if (aAttribute == nsGkAtoms::aria_label) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
// A valid aria-labelledby would take precedence so an aria-label change
// won't change the name.
IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
if (!iter.NextElem()) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
}
return;
}
@ -1256,21 +1259,13 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
// the eHasDescriptionDependent flag on all Accessibles in these subtrees.
IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
while (LocalAccessible* target = iter.Next()) {
Pivot pivot(target);
LocalAccInSameDocRule rule;
for (Accessible* anchor = target; anchor;
anchor = pivot.Next(anchor, rule)) {
LocalAccessible* acc = anchor->AsLocal();
MOZ_ASSERT(acc);
acc->mContextFlags |= eHasDescriptionDependent;
}
target->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
}
}
return;
}
if (aAttribute == nsGkAtoms::aria_labelledby &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
if (aAttribute == nsGkAtoms::aria_labelledby) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
@ -1279,14 +1274,7 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
// the eHasNameDependent flag on all Accessibles in these subtrees.
IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
while (LocalAccessible* target = iter.Next()) {
Pivot pivot(target);
LocalAccInSameDocRule rule;
for (Accessible* anchor = target; anchor;
anchor = pivot.Next(anchor, rule)) {
LocalAccessible* acc = anchor->AsLocal();
MOZ_ASSERT(acc);
acc->mContextFlags |= eHasNameDependent;
}
target->ModifySubtreeContextFlags(eHasNameDependent, true);
}
}
return;
@ -1300,11 +1288,14 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
}
if (aAttribute == nsGkAtoms::title) {
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
return;
nsAutoString name;
ARIAName(name);
if (name.IsEmpty()) {
NativeName(name);
if (name.IsEmpty()) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
return;
}
}
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby)) {
@ -2381,6 +2372,23 @@ void LocalAccessible::BindToParent(LocalAccessible* aParent,
mContextFlags &= ~eHasDescriptionDependent;
}
// Add name/description dependent flags for dependent content once
// a name/description provider is added to doc.
Relation rel = RelationByType(RelationType::LABELLED_BY);
LocalAccessible* relTarget = nullptr;
while ((relTarget = rel.Next())) {
if (!relTarget->HasNameDependent()) {
relTarget->ModifySubtreeContextFlags(eHasNameDependent, true);
}
}
rel = RelationByType(RelationType::DESCRIBED_BY);
while ((relTarget = rel.Next())) {
if (!relTarget->HasDescriptionDependent()) {
relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
}
}
mContextFlags |=
static_cast<uint32_t>((mParent->IsAlert() || mParent->IsInsideAlert())) &
eInsideAlert;
@ -2885,6 +2893,21 @@ LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset,
return child;
}
void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags,
bool aAdd) {
Pivot pivot(this);
LocalAccInSameDocRule rule;
for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) {
MOZ_ASSERT(anchor->IsLocal());
LocalAccessible* acc = anchor->AsLocal();
if (aAdd) {
acc->mContextFlags |= aContextFlags;
} else {
acc->mContextFlags &= ~aContextFlags;
}
}
}
double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const {
const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) {

Просмотреть файл

@ -968,6 +968,8 @@ class LocalAccessible : public nsISupports, public Accessible {
virtual LocalAccessible* GetSiblingAtOffset(int32_t aOffset,
nsresult* aError = nullptr) const;
void ModifySubtreeContextFlags(uint32_t aContextFlags, bool aAdd);
/**
* Flags used to describe the state of this accessible.
*/

Просмотреть файл

@ -204,6 +204,28 @@ ENameValueFlag HTMLButtonAccessible::NativeName(nsString& aName) const {
return eNameOK;
}
void HTMLButtonAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue,
uint64_t aOldState) {
HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute,
aModType, aOldValue, aOldState);
if (aAttribute == nsGkAtoms::value) {
dom::Element* elm = Elm();
if (elm->IsHTMLElement(nsGkAtoms::input) ||
(elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::image,
eCaseMatters) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt))) {
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// HTMLButtonAccessible: Widgets

Просмотреть файл

@ -65,6 +65,11 @@ class HTMLButtonAccessible : public HyperTextAccessibleWrap {
protected:
// LocalAccessible
virtual ENameValueFlag NativeName(nsString& aName) const override;
virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue,
uint64_t aOldState) override;
};
/**

Просмотреть файл

@ -123,7 +123,8 @@ ENameValueFlag HTMLSelectOptionAccessible::NativeName(nsString& aName) const {
// CASE #2 -- no label parameter, get the first child,
// use it if it is a text node
nsIContent* text = mContent->GetFirstChild();
LocalAccessible* firstChild = LocalFirstChild();
nsIContent* text = firstChild ? firstChild->GetContent() : nullptr;
if (text && text->IsText()) {
nsTextEquivUtils::AppendTextEquivFromTextContent(text, &aName);
aName.CompressWhitespace();
@ -133,6 +134,21 @@ ENameValueFlag HTMLSelectOptionAccessible::NativeName(nsString& aName) const {
return eNameOK;
}
void HTMLSelectOptionAccessible::DOMAttributeChanged(
int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType,
const nsAttrValue* aOldValue, uint64_t aOldState) {
HyperTextAccessibleWrap::DOMAttributeChanged(aNameSpaceID, aAttribute,
aModType, aOldValue, aOldState);
if (aAttribute == nsGkAtoms::label) {
dom::Element* elm = Elm();
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
}
}
}
uint64_t HTMLSelectOptionAccessible::NativeState() const {
// As a HTMLSelectOptionAccessible we can have the following states:
// SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN

Просмотреть файл

@ -81,6 +81,10 @@ class HTMLSelectOptionAccessible : public HyperTextAccessibleWrap {
protected:
// LocalAccessible
virtual ENameValueFlag NativeName(nsString& aName) const override;
virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue,
uint64_t aOldState) override;
private:
/**

Просмотреть файл

@ -415,6 +415,15 @@ void HTMLTableAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
aModType, aOldValue, aOldState);
if (aAttribute == nsGkAtoms::summary) {
nsAutoString name;
ARIAName(name);
if (name.IsEmpty()) {
if (!Caption()) {
// XXX: Should really be checking if caption provides a name.
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
}
}
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
this);
}

Просмотреть файл

@ -19,19 +19,11 @@ loadScripts({ name: "name.js", dir: MOCHITESTS_DIR });
* { elm } - calculated from another element
* { fromsubtree } - calculated from element's subtree
*
*
* Options include:
* * waitFor - changes in the subtree will result in an accessible event
* being fired, the test must only continue after the event
* is receieved.
*/
const ARIARule = [
{ attr: "aria-labelledby" },
{ attr: "aria-label", waitFor: EVENT_NAME_CHANGE },
];
const HTMLControlHeadRule = [...ARIARule, { elm: "label", isSibling: true }];
const ARIARule = [{ attr: "aria-labelledby" }, { attr: "aria-label" }];
const HTMLControlHeadRule = [...ARIARule, { elm: "label" }];
const rules = {
CSSContent: [{ elm: "style", isSibling: true }, { fromsubtree: true }],
CSSContent: [{ elm: "style" }, { fromsubtree: true }],
HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
HTMLControl: [
...HTMLControlHeadRule,
@ -39,11 +31,7 @@ const rules = {
{ attr: "title" },
],
HTMLElm: [...ARIARule, { attr: "title" }],
HTMLImg: [
...ARIARule,
{ attr: "alt", waitFor: EVENT_NAME_CHANGE },
{ attr: "title" },
],
HTMLImg: [...ARIARule, { attr: "alt" }, { attr: "title" }],
HTMLImgEmptyAlt: [...ARIARule, { attr: "title" }, { attr: "alt" }],
HTMLInputButton: [
...HTMLControlHeadRule,
@ -52,25 +40,19 @@ const rules = {
],
HTMLInputImage: [
...HTMLControlHeadRule,
{ attr: "alt", waitFor: EVENT_NAME_CHANGE },
{ attr: "alt" },
{ attr: "value" },
{ attr: "title" },
],
HTMLInputImageNoValidSrc: [
...HTMLControlHeadRule,
{ attr: "alt", waitFor: EVENT_NAME_CHANGE },
{ attr: "alt" },
{ attr: "value" },
],
HTMLInputReset: [
...HTMLControlHeadRule,
{ attr: "value", waitFor: EVENT_TEXT_INSERTED },
],
HTMLInputSubmit: [
...HTMLControlHeadRule,
{ attr: "value", waitFor: EVENT_TEXT_INSERTED },
],
HTMLInputReset: [...HTMLControlHeadRule, { attr: "value" }],
HTMLInputSubmit: [...HTMLControlHeadRule, { attr: "value" }],
HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
HTMLLinkImage: [...ARIARule, { elm: "img" }, { attr: "title" }],
HTMLLinkImage: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
HTMLOption: [
...ARIARule,
{ attr: "label" },
@ -396,34 +378,26 @@ const markupTests = [
* becomes defunct, update its reference using the one that is attached to one
* of the above events.
* @param {Object} browser current "tabbrowser" element
* @param {Object} target { acc, parent, id } structure that contains an
* accessible, its parent and its content element
* @param {Object} target { acc, id } structure that contains an
* accessible and its content element
* id.
* @param {Object} rule current attr rule for name calculation
* @param {[type]} expected expected name value
*/
async function testAttrRule(browser, target, rule, expected) {
let { id, parent, acc } = target;
let { waitFor, attr } = rule;
let { id, acc } = target;
let { attr } = rule;
testName(acc, expected);
if (waitFor) {
let [event] = await contentSpawnMutation(
browser,
{
expected: [[waitFor, waitFor === EVENT_REORDER ? parent : id]],
},
(contentId, contentAttr) =>
content.document.getElementById(contentId).removeAttribute(contentAttr),
[id, attr]
);
let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
await invokeContentTask(browser, [id, attr], (contentId, contentAttr) => {
content.document.getElementById(contentId).removeAttribute(contentAttr);
});
let event = await nameChange;
// Update accessible just in case it is now defunct.
target.acc = findAccessibleChildByID(event.accessible, id);
} else {
await invokeSetAttribute(browser, id, attr);
}
// Update accessible just in case it is now defunct.
target.acc = findAccessibleChildByID(event.accessible, id);
}
/**
@ -432,25 +406,23 @@ async function testAttrRule(browser, target, rule, expected) {
* in a reorder event - wait for it. If accessible becomes defunct, update its
* reference using the one that is attached to a possible reorder event.
* @param {Object} browser current "tabbrowser" element
* @param {Object} target { acc, parent, id } structure that contains an
* accessible, its parent and its content element
* @param {Object} target { acc, id } structure that contains an
* accessible and its content element
* id.
* @param {Object} rule current elm rule for name calculation
* @param {[type]} expected expected name value
*/
async function testElmRule(browser, target, rule, expected) {
let { id, parent, acc } = target;
let { isSibling, elm } = rule;
let { id, acc } = target;
let { elm } = rule;
testName(acc, expected);
let [event] = await contentSpawnMutation(
browser,
{
expected: [[EVENT_REORDER, isSibling ? parent : id]],
},
contentElm => content.document.querySelector(`${contentElm}`).remove(),
[elm]
);
let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
await invokeContentTask(browser, [elm], contentElm => {
content.document.querySelector(`${contentElm}`).remove();
});
let event = await nameChange;
// Update accessible just in case it is now defunct.
target.acc = findAccessibleChildByID(event.accessible, id);
@ -462,8 +434,8 @@ async function testElmRule(browser, target, rule, expected) {
* accessible becomes defunct, update its reference using the one that is
* attached to a reorder event.
* @param {Object} browser current "tabbrowser" element
* @param {Object} target { acc, parent, id } structure that contains an
* accessible, its parent and its content element
* @param {Object} target { acc, id } structure that contains an
* accessible and its content element
* id.
* @param {Object} rule current subtree rule for name calculation
* @param {[type]} expected expected name value
@ -472,19 +444,15 @@ async function testSubtreeRule(browser, target, rule, expected) {
let { id, acc } = target;
testName(acc, expected);
let [event] = await contentSpawnMutation(
browser,
{
expected: [[EVENT_REORDER, id]],
},
contentId => {
let elm = content.document.getElementById(contentId);
while (elm.firstChild) {
elm.firstChild.remove();
}
},
[id]
);
let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
await invokeContentTask(browser, [id], contentId => {
let elm = content.document.getElementById(contentId);
while (elm.firstChild) {
elm.firstChild.remove();
}
});
let event = await nameChange;
// Update accessible just in case it is now defunct.
target.acc = findAccessibleChildByID(event.accessible, id);
@ -494,8 +462,8 @@ async function testSubtreeRule(browser, target, rule, expected) {
* Iterate over a list of rules and test accessible names for each one of the
* rules.
* @param {Object} browser current "tabbrowser" element
* @param {Object} target { acc, parent, id } structure that contains an
* accessible, its parent and its content element
* @param {Object} target { acc, id } structure that contains an
* accessible and its content element
* id.
* @param {Array} ruleset A list of rules to test a target with
* @param {Array} expected A list of expected name value for each rule
@ -528,9 +496,7 @@ markupTests.forEach(({ id, ruleset, markup, expected }) =>
Services.obs.addObserver(observer, "accessible-event");
// Find a target accessible from an accessible subtree.
let acc = findAccessibleChildByID(accDoc, id);
// Find target's parent accessible from an accessible subtree.
let parent = getAccessibleDOMNodeID(acc.parent);
let target = { id, parent, acc };
let target = { id, acc };
await testNameRule(browser, target, rules[ruleset], expected);
Services.obs.removeObserver(observer, "accessible-event");
},

Просмотреть файл

@ -59,8 +59,9 @@
gQueue.push(new setAttr("tst1", "title", "title",
new unexpectedInvokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1")));
// A title has lower priority over text content. There should be no name change event.
gQueue.push(new setAttr("tst2", "title", "title",
new invokerChecker(EVENT_NAME_CHANGE, "tst2")));
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
gQueue.invoke();
await queueFinished;

Просмотреть файл

@ -74,12 +74,12 @@
gQueue.push(new setAttr("tst1", "aria-label", "hi",
new invokerChecker(EVENT_NAME_CHANGE, "tst1")));
gQueue.push(new setAttr("tst1", "aria-labelledby", "display",
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
gQueue.push(new setAttr("tst1", "alt", "alt",
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
gQueue.push(new setAttr("tst1", "title", "title",
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
gQueue.push(new setAttr("tst1", "aria-labelledby", "display",
new invokerChecker(EVENT_NAME_CHANGE, "tst1")));
gQueue.push(new setAttr("tst2", "aria-labelledby", "display",
new invokerChecker(EVENT_NAME_CHANGE, "tst2")));
@ -87,6 +87,8 @@
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
gQueue.push(new setAttr("tst2", "title", "title",
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
gQueue.push(new setAttr("tst2", "aria-label", "hi",
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
// When `alt` attribute is added or removed from a broken img,
// the accessible is recreated.

Просмотреть файл

@ -259,32 +259,14 @@ function testNameForAttrRule(aElm, aRule) {
testAbsentAttrs(aElm, { "explicit-name": "true" });
}
// If @recreated attribute is used then this attribute change recreates an
// accessible. Wait for reorder event in this case or otherwise proceed next
// test immediately.
if (aRule.hasAttribute("recreated")) {
waitForEvent(
EVENT_REORDER,
aElm.parentNode,
gTestIterator.iterateNext,
gTestIterator
);
aElm.removeAttribute(attr);
} else if (aRule.hasAttribute("textchanged")) {
waitForEvent(
EVENT_TEXT_INSERTED,
aElm,
gTestIterator.iterateNext,
gTestIterator
);
aElm.removeAttribute(attr);
} else if (aRule.hasAttribute("contentchanged")) {
waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
aElm.removeAttribute(attr);
} else {
aElm.removeAttribute(attr);
gTestIterator.iterateNext();
}
waitForEvent(
EVENT_NAME_CHANGE,
aElm,
gTestIterator.iterateNext,
gTestIterator
);
aElm.removeAttribute(attr);
}
function testNameForElmRule(aElm, aRule) {
@ -338,14 +320,14 @@ function testNameForElmRule(aElm, aRule) {
if (gDumpToConsole) {
dump(
"\nProcessed elm rule. Wait for reorder event on " +
prettyName(parentNode) +
"\nProcessed elm rule. Wait for name change event on " +
prettyName(aElm) +
"\n"
);
}
waitForEvent(
EVENT_REORDER,
parentNode,
EVENT_NAME_CHANGE,
aElm,
gTestIterator.iterateNext,
gTestIterator
);
@ -365,7 +347,12 @@ function testNameForSubtreeRule(aElm, aRule) {
"\n"
);
}
waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
waitForEvent(
EVENT_NAME_CHANGE,
aElm,
gTestIterator.iterateNext,
gTestIterator
);
while (aElm.firstChild) {
aElm.firstChild.remove();

Просмотреть файл

@ -124,25 +124,25 @@
<ruleset id="HTMLInputSubmit" defaultName="Submit Query">
<ruleset ref="HTMLControl:Head"/>
<rule attr="value" type="string" explict-name="false" textchanged="true"/>
<rule attr="value" type="string" explict-name="false"/>
</ruleset>
<ruleset id="HTMLInputReset" defaultName="Reset">
<ruleset ref="HTMLControl:Head"/>
<rule attr="value" type="string" explict-name="false" textchanged="true"/>
<rule attr="value" type="string" explict-name="false"/>
</ruleset>
<ruleset id="HTMLInputImage">
<ruleset ref="HTMLControl:Head"/>
<rule attr="alt" type="string" textchanged="true"/>
<rule attr="value" type="string" textchanged="true"/>
<rule attr="alt" type="string"/>
<rule attr="value" type="string"/>
<rule attr="title" type="string"/>
</ruleset>
<ruleset id="HTMLInputImageNoValidSrc" defaultName="Submit Query">
<ruleset ref="HTMLControl:Head"/>
<rule attr="alt" type="string" explict-name="false" textchanged="true"/>
<rule attr="value" type="string" explict-name="false" textchanged="true"/>
<rule attr="alt" type="string" explict-name="false"/>
<rule attr="value" type="string" explict-name="false"/>
</ruleset>
<ruleset id="HTMLOption">
@ -154,14 +154,14 @@
<ruleset id="HTMLImg">
<ruleset ref="ARIA"/>
<rule attr="alt" type="string" recreated="true"/>
<rule attr="alt" type="string"/>
<rule attr="title" type="string"/>
</ruleset>
<ruleset id="HTMLImgEmptyAlt">
<ruleset ref="ARIA"/>
<rule attr="title" type="string"/>
<rule attr="alt" type="string" recreated="true"/>
<rule attr="alt" type="string"/>
</ruleset>
<ruleset id="HTMLTable">