Bug 97825 - AX: labelForElement is slow when there are a lot of DOM elements
Summary: AX: labelForElement is slow when there are a lot of DOM elements
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Accessibility (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Dominic Mazzoni
URL:
Keywords:
Depends on: 99126
Blocks:
  Show dependency treegraph
 
Reported: 2012-09-27 15:46 PDT by Dominic Mazzoni
Modified: 2012-10-25 11:08 PDT (History)
13 users (show)

See Also:


Attachments
Patch (28.74 KB, patch)
2012-09-28 01:56 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (28.07 KB, patch)
2012-09-28 10:37 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (29.79 KB, patch)
2012-09-28 14:49 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Fix compile (29.83 KB, patch)
2012-09-30 23:43 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (27.81 KB, patch)
2012-10-02 17:33 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (30.01 KB, patch)
2012-10-09 15:41 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (27.44 KB, patch)
2012-10-09 23:42 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (27.90 KB, patch)
2012-10-09 23:49 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch for landing (27.94 KB, patch)
2012-10-11 13:41 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch for landing (27.85 KB, patch)
2012-10-11 14:25 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (32.76 KB, patch)
2012-10-16 16:06 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch (28.00 KB, patch)
2012-10-16 19:42 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch for landing (19.11 KB, patch)
2012-10-18 23:53 PDT, Dominic Mazzoni
no flags Details | Formatted Diff | Diff
Patch for landing (28.05 KB, patch)
2012-10-19 00:29 PDT, Dominic Mazzoni
webkit.review.bot: commit-queue-
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Dominic Mazzoni 2012-09-27 15:46:41 PDT
See related bug: https://bugs.webkit.org/show_bug.cgi?id=33253

Currently AccessibilityNodeObject::labelForElement, which is used to find the <label> associated with a focusable control, scans all of the nodes in the DOM tree to find possible labels, which can be inefficient. This function shows up as a bottleneck when I run various Chrome benchmarks with accessibility enabled.

Proposed alternative: AXObjectCache should maintain a hash map from label id to label element. It should be generated lazily the first time it's needed, then updated every time a label is modified or deleted.
Comment 1 Dominic Mazzoni 2012-09-28 01:56:22 PDT
Created attachment 166175 [details]
Patch
Comment 2 Dominic Mazzoni 2012-09-28 10:37:09 PDT
Created attachment 166275 [details]
Patch
Comment 3 chris fleizach 2012-09-28 10:46:08 PDT
Comment on attachment 166275 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=166275&action=review

If the <label> element itself is removed from the tree, how is the m_idToLabelMapping being updated? i couldn't find where that's happening

> Source/WebCore/accessibility/AXObjectCache.h:213
> +    HashMap<Document*, HashMap<String, HTMLLabelElement*>* > m_idToLabelMapping;

Can we make HashMap a RefPtr<> so we don't have to worry about deleting it after removing from the cache

> Source/WebCore/dom/Document.cpp:635
>      clearAXObjectCache();

i think we should try to leverage just one AX method when Document is destroyed to do all the necessary cleanup

> Source/WebCore/html/HTMLLabelElement.cpp:174
> +        document()->axObjectCache()->updateLabelElementForId(document(), attribute.value(), this);

it seems that we will update the cache twice when a forAttr is added (once in willModify with the newValue, then once in attributedChanged)
Is it possible to do all the updating in attributeChanged, at the cost of perhaps searching the label cache for the correct label element.
that way, willModify won't have to be come virtual
Comment 4 Early Warning System Bot 2012-09-28 10:57:42 PDT
Comment on attachment 166275 [details]
Patch

Attachment 166275 [details] did not pass qt-ews (qt):
Output: http://queues.webkit.org/results/14060402
Comment 5 Early Warning System Bot 2012-09-28 11:01:44 PDT
Comment on attachment 166275 [details]
Patch

Attachment 166275 [details] did not pass qt-wk2-ews (qt):
Output: http://queues.webkit.org/results/14059420
Comment 6 Gyuyoung Kim 2012-09-28 11:13:49 PDT
Comment on attachment 166275 [details]
Patch

Attachment 166275 [details] did not pass efl-ews (efl):
Output: http://queues.webkit.org/results/14056481
Comment 7 Peter Beverloo (cr-android ews) 2012-09-28 11:27:29 PDT
Comment on attachment 166275 [details]
Patch

Attachment 166275 [details] did not pass cr-android-ews (chromium-android):
Output: http://queues.webkit.org/results/14061412
Comment 8 Dominic Mazzoni 2012-09-28 14:49:35 PDT
Created attachment 166314 [details]
Patch
Comment 9 Dominic Mazzoni 2012-09-28 14:49:55 PDT
(In reply to comment #3)
> (From update of attachment 166275 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=166275&action=review
> 
> If the <label> element itself is removed from the tree, how is the m_idToLabelMapping being updated? i couldn't find where that's happening

Ah, you're right - it wasn't. The test was still passing because the AccessibilityObject that was returned was detached. Fixed.

> > Source/WebCore/accessibility/AXObjectCache.h:213
> > +    HashMap<Document*, HashMap<String, HTMLLabelElement*>* > m_idToLabelMapping;
> 
> Can we make HashMap a RefPtr<> so we don't have to worry about deleting it after removing from the cache

Unfortunately no - RefPtr can only be used with classes that have ref() and deref() methods. 

> > Source/WebCore/dom/Document.cpp:635
> >      clearAXObjectCache();
> 
> i think we should try to leverage just one AX method when Document is destroyed to do all the necessary cleanup

The problem is that there's only one ax cache but in a page with iframes, there are lots of Documents. We need to handle the case where an iframe is deleted but the main frame is still there.

> > Source/WebCore/html/HTMLLabelElement.cpp:174
> > +        document()->axObjectCache()->updateLabelElementForId(document(), attribute.value(), this);
> 
> it seems that we will update the cache twice when a forAttr is added (once in willModify with the newValue, then once in attributedChanged)
> Is it possible to do all the updating in attributeChanged, at the cost of perhaps searching the label cache for the correct label element.
> that way, willModify won't have to be come virtual

I can't figure out an easy way to do it in attributeChanged, because attributeChanged doesn't have access to the old value. Instead I was able to get rid of attributeChanged and only handle willModify.
Comment 10 Early Warning System Bot 2012-09-28 15:03:00 PDT
Comment on attachment 166314 [details]
Patch

Attachment 166314 [details] did not pass qt-ews (qt):
Output: http://queues.webkit.org/results/14070390
Comment 11 Early Warning System Bot 2012-09-28 15:09:54 PDT
Comment on attachment 166314 [details]
Patch

Attachment 166314 [details] did not pass qt-wk2-ews (qt):
Output: http://queues.webkit.org/results/14071303
Comment 12 Peter Beverloo (cr-android ews) 2012-09-28 15:16:55 PDT
Comment on attachment 166314 [details]
Patch

Attachment 166314 [details] did not pass cr-android-ews (chromium-android):
Output: http://queues.webkit.org/results/14068381
Comment 13 Dominic Mazzoni 2012-09-30 23:43:44 PDT
Created attachment 166411 [details]
Fix compile
Comment 14 chris fleizach 2012-10-01 09:38:45 PDT
Comment on attachment 166411 [details]
Fix compile

View in context: https://bugs.webkit.org/attachment.cgi?id=166411&action=review

I think it looks good from an accessibility perspective. We might want to get someone more familiar with DOM code to ensure that there aren't any other edge cases missed and that virtualizing willModifyAttribute is OK

> Source/WebCore/ChangeLog:19
> +        (WebCore):

remove extraneous (WebCore): lines

> Source/WebCore/dom/Document.cpp:633
> +        topDocument()->m_axObjectCache->documentDestroyed(this);

can we put these two lines into a method like "cleanupAccessibility()" and put clearAXObjectCache() logic in that too

> Source/WebCore/dom/Element.h:320
> +    virtual void willModifyAttribute(const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);

we should confirm that making this virtual won't have terrible performance implications
Comment 15 Ryosuke Niwa 2012-10-01 14:12:13 PDT
Comment on attachment 166411 [details]
Fix compile

View in context: https://bugs.webkit.org/attachment.cgi?id=166411&action=review

Sorry, I don't want to be a pain but I don't think we should land the patch as is.

> Source/WebCore/ChangeLog:12
> +        Adds a new accessibility cache that maps ids to label elements
> +        that label that id. The cache is built the first time it's
> +        queried (per document), and then updated every time a label
> +        element on the page is created, deleted, or modified.
> +        This allows for very fast lookup of the label for any element.

We don't normally add new code that's unused. Where is this code used? Or where will it be used?
It seems like we don't need this hash map at all if we had used LabelsNodeList and then made it cache named ids like we do for HTMLCollection.

> Source/WebCore/accessibility/AXObjectCache.cpp:687
> +        HashMap<String, HTMLLabelElement*>* documentMapping = new HashMap<String, HTMLLabelElement*>();

Please use OwnPtr.
Comment 16 Ryosuke Niwa 2012-10-01 14:12:48 PDT
Oops, ignore the following line:
> We don't normally add new code that's unused. Where is this code used? Or where will it be used?
Comment 17 Dominic Mazzoni 2012-10-01 14:29:41 PDT
(In reply to comment #15)
> Sorry, I don't want to be a pain but I don't think we should land the patch as is.

Not a pain. Sorry, I didn't know about LabelsNodeList, I'd be happy to reuse it.

> It seems like we don't need this hash map at all if we had used LabelsNodeList and then made it cache named ids like we do for HTMLCollection.

Agreed. Let me try it and measure the performance.
Comment 18 Ryosuke Niwa 2012-10-01 14:34:33 PDT
For your reference, you can take a look at HTMLCollection::updateNameCache in WebCore/html/HTMLCollection.cpp and DynamicNodeList::itemWithName in WebCore/dom/DynamicNodeList.cpp.

Right now, LabelsNodeList uses DynamicNodeList, which doesn't cache all the labels by id but it's still fast when each id is unique (i.e. there are no elements with the same id) so that might be acceptable for your use case. If not, then we can always transition DynamicNodeList to use the model HTMLCollection uses where we cache the list of all elements with ids/names with a label.
Comment 19 Dominic Mazzoni 2012-10-02 00:43:35 PDT
(In reply to comment #18)
> For your reference, you can take a look at HTMLCollection::updateNameCache in WebCore/html/HTMLCollection.cpp and DynamicNodeList::itemWithName in WebCore/dom/DynamicNodeList.cpp.
> 
> Right now, LabelsNodeList uses DynamicNodeList, which doesn't cache all the labels by id but it's still fast when each id is unique (i.e. there are no elements with the same id) so that might be acceptable for your use case. If not, then we can always transition DynamicNodeList to use the model HTMLCollection uses where we cache the list of all elements with ids/names with a label.

I just tried it, it isn't fast enough.

The case I need to optimize is the m x n case: when the document first loads, every form control needs to find its corresponding label - and then keep it updated as the document mutates.

Caching labels by id (or rather, by the value of the for attr) would work, but the question is, is there any reason that this belongs in DynamicNodeList? Is there any other use inside WebCore for this functionality? If it's only needed for accessibility, I still wonder if it wouldn't be of more use there.

At first glance, HTMLCollection's name cache doesn't look to be as efficient as the hash map in this patch - it only seems to cache from one call to the next, it doesn't maintain a persistent hash map that updates quickly as the document mutates. The solution I built is closer to the cache used in TreeScope::getElementById - it scans the document for all labels once and then updates the map in O(1) time every time a node is attached, detached, or modifies its attributes.
Comment 20 Ryosuke Niwa 2012-10-02 01:11:10 PDT
(In reply to comment #19)
> Caching labels by id (or rather, by the value of the for attr) would work, but the question is, is there any reason that this belongs in DynamicNodeList? Is there any other use inside WebCore for this functionality? If it's only needed for accessibility, I still wonder if it wouldn't be of more use there.
> 
> At first glance, HTMLCollection's name cache doesn't look to be as efficient as the hash map in this patch - it only seems to cache from one call to the next, it doesn't maintain a persistent hash map that updates quickly as the document mutates.

It maintains a persistent map. It clears all the cache and re-populates lazily when the document changes.

> The solution I built is closer to the cache used in TreeScope::getElementById - it scans the document for all labels once and then updates the map in O(1) time every time a node is attached, detached, or modifies its attributes.

I see. Sounds like what you want is DocumentOrderedMap then.
Comment 21 Dominic Mazzoni 2012-10-02 17:33:53 PDT
Created attachment 166786 [details]
Patch
Comment 22 chris fleizach 2012-10-03 10:31:10 PDT
Comment on attachment 166786 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=166786&action=review

looks ok from an ax perspective

> Tools/DumpRenderTree/chromium/TestRunner/AccessibilityUIElementChromium.cpp:744
> +

extraneous newline
Comment 23 Ryosuke Niwa 2012-10-08 10:06:14 PDT
Comment on attachment 166786 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=166786&action=review

This approach looks much better! There are quite few nits but it looks very promising.

> Source/WebCore/dom/Element.cpp:2139
> +    else if (hasTagName(labelTag) && name == HTMLNames::forAttr) {

I would check "name == HTMLNames::forAttr" first to avoid the unnecessary function call to hasTagName.

> Source/WebCore/dom/Element.h:319
> +    void updateForAttr(TreeScope*, const AtomicString& oldForAttr, const AtomicString& newForAttr);

I would have named this function updateLabel.

Also, please don't use the abbreviation "attr" for attribute. "Attr" is a special object in DOM:
http://trac.webkit.org/browser/trunk/Source/WebCore/dom/Attr.idl

> Source/WebCore/dom/TreeScope.cpp:164
> +        RefPtr<NodeList> list = rootNode()->getElementsByTagName("label");

This is very inefficient way of finding label elements. There is a lot of overhead for creating NodeList, etc..
We should traverse nodes directly.

> Source/WebCore/dom/TreeScope.cpp:165
> +        unsigned len = list->length();

In particular, this forces NodeList to iterate through the entire document.

> Source/WebCore/dom/TreeScope.cpp:167
> +            HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i));

And this second calls to item will traverse the document for the second time.

> Source/WebCore/dom/TreeScope.h:105
> +    // Look up a label element by the id in its "for" attribute, for accessibility.

It seems redundant to put the same comment at two places. Please remove either one.

> LayoutTests/accessibility/title-ui-element-correctness-expected.txt:12
> +PASS control1.titleUIElement().isEqual(label1) is true
> +PASS control2.titleUIElement().isEqual(label2) is true

It's not great that these outputs don't make obvious what we're testing here.

> LayoutTests/accessibility/title-ui-element-correctness.html:10
> +    <label id="l1" for="c1">Label 1</label>
> +    <input id="c1" type="text">

Please don't use one letter ids.

> LayoutTests/accessibility/title-ui-element-correctness.html:54
> +    shouldBe("control1.titleUIElement().isEqual(label1)", "true");

Instead of creating a local variables like this, we can do:
shouldBe('accessibilityController.accessibleElementById("input1").titleUIElement().isEqual(accessibilityController.accessibleElementById("labelForInput1"))', 'true');
so that the expected result is self-explanatory.
Maybe you can make a local helper function for accessibilityController.accessibleElementById; e.g. control().

> LayoutTests/accessibility/title-ui-element-correctness.html:65
> +    document.getElementById("l3").setAttribute("for", "c3");
> +    shouldBe("control3.titleUIElement().isEqual(label3)", "true");

Similarly, it'll be better if you had included the both lines in shouldBe as in:
shouldBe("document.getElementById("l3").setAttribute("for", "c3"); control3.titleUIElement().isEqual(label3)", "true");
(with more descriptive ids for label and input elements).
so that we can see what's happening between two shouldBe.

> LayoutTests/accessibility/title-ui-element-correctness.html:72
> +    var label4Element = document.createElement("label");
> +    label4Element.id = "l4";
> +    label4Element.setAttribute("for", "c4");
> +    label4Element.innerText = "Label 4";

Ditto about putting this in shouldBe. Here, you probably want to create a descriptive helper function like createLabelForInput4WithForAttribute().

> LayoutTests/accessibility/title-ui-element-speed.html:36
> +    // Create a bunch of labels, each of the form:
> +    //   <label for='c0'>Label 0</label>
> +    // Create corresponding controls, each of this form:
> +    //   <input id='c0' type='text'>

This comment repeats what the code tells us. In general, the WebKit community prefers only adding "why" comments.

> LayoutTests/accessibility/title-ui-element-speed.html:62
> +    // Now add more junk to the page. This is to make sure that titleUIElement doesn't take
> +    // time proportional to the number of DOM elements on the page anymore.
> +    for (i = 0; i < 10000; i++) {
> +      var div = document.createElement('div');
> +      div.innerHTML = "<p></p><p></p><p></p><p></p><p></p>";
> +      junkContainer.appendChild(div);
> +    }

It seems like we want to add a test in LayoutTests/perf/ using magnitude-perf.js.

> LayoutTests/accessibility/title-ui-element-speed.html:64
> +    // Save the total time to build the DOM.

This is also obvious from the code.

> LayoutTests/accessibility/title-ui-element-speed.html:65
> +    var midTime = new Date();

It's better to do Date.now() since creating Date object incurs a high runtime cost.

> LayoutTests/accessibility/title-ui-element-speed.html:68
> +    // Now call titleUIElement on each control, and check the correctness of the result.

This comment also repeats the code.

> LayoutTests/accessibility/title-ui-element-speed.html:73
> +            debug("FAIL: control " + i + " has no titleUIElement.");

Please use testFailed.

> LayoutTests/accessibility/title-ui-element-speed.html:78
> +            debug("FAIL: control " + i + " titleUIElement value is '" +

Ditto.

> LayoutTests/accessibility/title-ui-element-speed.html:79
> +                  title + "' , expecting it to end with 'Label " + i + "'");

Wrong indentation. We don't align subsequent lines like this. "title +" should be exactly 4 spaces to the right of "debug(".

> LayoutTests/accessibility/title-ui-element-speed.html:94
> +    if (titleUIElementTime < buildTime) {
> +      debug("PASS Queried titleUIElement in less time than it took to build the DOM.");
> +    } else {

No curly brackets around single statements.

> LayoutTests/accessibility/title-ui-element-speed.html:96
> +            titleUIElementTime + " ms to query titleUIElement on each control.");

Ditto about wrong indentation.

> LayoutTests/accessibility/title-ui-element-speed.html:99
> +    // Remove the labels from the page so the text dump of the page isn't gigantic.

This is also obvious from the code.
Comment 24 Dominic Mazzoni 2012-10-09 15:41:19 PDT
Comment on attachment 166786 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=166786&action=review

>> Source/WebCore/dom/Element.cpp:2139
>> +    else if (hasTagName(labelTag) && name == HTMLNames::forAttr) {
> 
> I would check "name == HTMLNames::forAttr" first to avoid the unnecessary function call to hasTagName.

Good idea, done.

>> Source/WebCore/dom/Element.h:319
>> +    void updateForAttr(TreeScope*, const AtomicString& oldForAttr, const AtomicString& newForAttr);
> 
> I would have named this function updateLabel.
> 
> Also, please don't use the abbreviation "attr" for attribute. "Attr" is a special object in DOM:
> http://trac.webkit.org/browser/trunk/Source/WebCore/dom/Attr.idl

Sure, changed.

>> Source/WebCore/dom/TreeScope.cpp:164
>> +        RefPtr<NodeList> list = rootNode()->getElementsByTagName("label");
> 
> This is very inefficient way of finding label elements. There is a lot of overhead for creating NodeList, etc..
> We should traverse nodes directly.

Fixed.

>> Source/WebCore/dom/TreeScope.h:105
>> +    // Look up a label element by the id in its "for" attribute, for accessibility.
> 
> It seems redundant to put the same comment at two places. Please remove either one.

Kept it by the methods.

>> Tools/DumpRenderTree/chromium/TestRunner/AccessibilityUIElementChromium.cpp:744
>> +
> 
> extraneous newline

Fixed.

>> LayoutTests/accessibility/title-ui-element-correctness-expected.txt:12
>> +PASS control2.titleUIElement().isEqual(label2) is true
> 
> It's not great that these outputs don't make obvious what we're testing here.

titleUIElement() returns an object. This is asserting that it returns the object actually corresponding to the correct label element.

There are a lot of platform-specific differences in accessibility, so at the moment there's no easy way to print the string value of control2.titleUIElement() and assert that it's equal to "Label 1" in a cross-platform way. Chris and I are working on changing that, but for now I think this is the least brittle solution.

>> LayoutTests/accessibility/title-ui-element-correctness.html:10
>> +    <input id="c1" type="text">
> 
> Please don't use one letter ids.

Changed to "label1", "control1", etc.

>> LayoutTests/accessibility/title-ui-element-correctness.html:54
>> +    shouldBe("control1.titleUIElement().isEqual(label1)", "true");
> 
> Instead of creating a local variables like this, we can do:
> shouldBe('accessibilityController.accessibleElementById("input1").titleUIElement().isEqual(accessibilityController.accessibleElementById("labelForInput1"))', 'true');
> so that the expected result is self-explanatory.
> Maybe you can make a local helper function for accessibilityController.accessibleElementById; e.g. control().

Changed throughout.

>> LayoutTests/accessibility/title-ui-element-speed.html:36
>> +    //   <input id='c0' type='text'>
> 
> This comment repeats what the code tells us. In general, the WebKit community prefers only adding "why" comments.

Deleted all extraneous comments

>> LayoutTests/accessibility/title-ui-element-speed.html:62
>> +    }
> 
> It seems like we want to add a test in LayoutTests/perf/ using magnitude-perf.js.

Neat, I hadn't tried writing one of those before. Added a complementary test.

I'm a little hesitant to use that test instead, because I'm finding the perf tests to be flaky - not just the one I added, but others are failing a small fraction of the time.

>> LayoutTests/accessibility/title-ui-element-speed.html:65
>> +    var midTime = new Date();
> 
> It's better to do Date.now() since creating Date object incurs a high runtime cost.

Fixed throughout

>> LayoutTests/accessibility/title-ui-element-speed.html:73
>> +            debug("FAIL: control " + i + " has no titleUIElement.");
> 
> Please use testFailed.

Fixed throughout

>> LayoutTests/accessibility/title-ui-element-speed.html:79
>> +                  title + "' , expecting it to end with 'Label " + i + "'");
> 
> Wrong indentation. We don't align subsequent lines like this. "title +" should be exactly 4 spaces to the right of "debug(".

Fixed.

>> LayoutTests/accessibility/title-ui-element-speed.html:94
>> +    } else {
> 
> No curly brackets around single statements.

Fixed

>> LayoutTests/accessibility/title-ui-element-speed.html:96
>> +            titleUIElementTime + " ms to query titleUIElement on each control.");
> 
> Ditto about wrong indentation.

Fixed

>> LayoutTests/accessibility/title-ui-element-speed.html:99
>> +    // Remove the labels from the page so the text dump of the page isn't gigantic.
> 
> This is also obvious from the code.

I think this comment is a "why" comment, ok to leave it?
Comment 25 Dominic Mazzoni 2012-10-09 15:41:35 PDT
Created attachment 167864 [details]
Patch
Comment 26 Ryosuke Niwa 2012-10-09 16:10:12 PDT
Comment on attachment 167864 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167864&action=review

> Source/WebCore/dom/Element.cpp:2125
> +    if (!inDocument())
> +        return;

I forgot to comment on the previous patch but why does it need to be in the document?
Surely, you can remove a TreeScope that used to be in the document and has the map?
If we stop updating the map at the point, it may go stale and introduce a subtle security bug down the road.
For that reason, I would prefer checking the existence of m_labelsByForAttr instead.

> Source/WebCore/dom/TreeScope.cpp:159
> +HTMLLabelElement* TreeScope::getLabel(const AtomicString& forAttrValue)

"get" prefix is used only for functions with out arguments: http://www.webkit.org/coding/coding-style.html#names-out-argument
I would call this function labelElementForId.

> Source/WebCore/dom/TreeScope.cpp:164
> +        for (Node* node = root; node; node = node->traverseNextNode(root)) {

You shouldn't need to pass in root to traverseNextNode since traverseNextNode doesn't cross shadow boundary by default.

> Source/WebCore/dom/TreeScope.h:66
> +    // Look up a label element by the id in its "for" attribute, for accessibility.

I would just say it's for accessibility because the fact it looks up a label element by an id is obvious from the code.

> Source/WebCore/dom/TreeScope.h:70
> +    // m_shouldCacheLabelsByForAttr is set to true when this method is called the first time.

This comment repeats an implementation detail that's obvious from the code. Please remove it.

> LayoutTests/accessibility/title-ui-element-correctness.html:34
> +  </div>

Please add a test case where there are multiple label elements with the same "for" value.
And make sure that adding and removing such elements update the map correctly.

> LayoutTests/accessibility/title-ui-element-speed.html:17
> +if (window.testRunner && window.accessibilityController) {

I still think we should just use magnitude-perf.js. Granted, some tests are flaky but that's a problem with the tests, not the framework itself.
If the framework is making some tests flaky, then we should fix the framework instead of re-implementing it in each test.
Comment 27 Dominic Mazzoni 2012-10-09 17:31:58 PDT
> Please add a test case where there are multiple label elements with the same "for" value.
> And make sure that adding and removing such elements update the map correctly.

I just realized the spec allows multiple label elements to point to the same form control. It looks like I need to modify DocumentOrderedMap to return all matching nodes.

- Dominic
Comment 28 Ryosuke Niwa 2012-10-09 17:59:39 PDT
(In reply to comment #27)
> > Please add a test case where there are multiple label elements with the same "for" value.
> > And make sure that adding and removing such elements update the map correctly.
> 
> I just realized the spec allows multiple label elements to point to the same form control. It looks like I need to modify DocumentOrderedMap to return all matching nodes.

Is that what spec says? As far as I know, the first label element with the matching "for" attribute supersedes other label elements with the same for attribute.
Comment 29 Dominic Mazzoni 2012-10-09 19:47:35 PDT
(In reply to comment #28)
> (In reply to comment #27)
> > > Please add a test case where there are multiple label elements with the same "for" value.
> > > And make sure that adding and removing such elements update the map correctly.
> > 
> > I just realized the spec allows multiple label elements to point to the same form control. It looks like I need to modify DocumentOrderedMap to return all matching nodes.
> 
> Is that what spec says? As far as I know, the first label element with the matching "for" attribute supersedes other label elements with the same for attribute.

w3c says: More than one LABEL may be associated with the same control by creating multiple references via the for attribute.
http://www.w3.org/TR/html401/interact/forms.html#h-17.9.1

The html5 accessibility pages notes that it's conforming, and Firefox supports multiple labels, concatenating their strings for accessibility - but they recommend against authors using it because some assistive technology and other user agents don't support it:
http://www.html5accessibility.com/tests/mulitple-labels.html

There's also the "labels" attribute of a labelable element, which returns *all* labels associated with a given control.

I guess it's fine to just use the first one.
Comment 30 Dominic Mazzoni 2012-10-09 19:51:00 PDT
> I guess it's fine to just use the first one.

To clarify, I'm mostly swayed by the fact that Firefox has implemented its interpretation of the spec but authors are still discouraged from using the feature because it could make things worse rather than better.
http://www.html5accessibility.com/tests/mulitple-labels.html

In addition, the platform accessibility implementation guide is noticeably silent on it, as far as I can tell - it only mentions one label:
http://dvcs.w3.org/hg/html-api-map/raw-file/tip/Overview.html
Comment 31 Dominic Mazzoni 2012-10-09 23:42:29 PDT
Created attachment 167935 [details]
Patch
Comment 32 Dominic Mazzoni 2012-10-09 23:49:06 PDT
Created attachment 167936 [details]
Patch
Comment 33 Dominic Mazzoni 2012-10-09 23:49:43 PDT
View in context: https://bugs.webkit.org/attachment.cgi?id=167864&action=review

>> Source/WebCore/dom/Element.cpp:2125
>> +        return;
> 
> I forgot to comment on the previous patch but why does it need to be in the document?
> Surely, you can remove a TreeScope that used to be in the document and has the map?
> If we stop updating the map at the point, it may go stale and introduce a subtle security bug down the road.
> For that reason, I would prefer checking the existence of m_labelsByForAttr instead.

Good point. I removed the inDocument check here and added it to getLabel (now labelElementForId).

I don't think there's a need to check m_labelsByForAttr again, it's checked in the callers - which seems more efficient.

>> Source/WebCore/dom/TreeScope.cpp:159
>> +HTMLLabelElement* TreeScope::getLabel(const AtomicString& forAttrValue)
> 
> "get" prefix is used only for functions with out arguments: http://www.webkit.org/coding/coding-style.html#names-out-argument
> I would call this function labelElementForId.

Fixed.

>> Source/WebCore/dom/TreeScope.cpp:164
>> +        for (Node* node = root; node; node = node->traverseNextNode(root)) {
> 
> You shouldn't need to pass in root to traverseNextNode since traverseNextNode doesn't cross shadow boundary by default.

Fixed.

>> Source/WebCore/dom/TreeScope.h:66
>> +    // Look up a label element by the id in its "for" attribute, for accessibility.
> 
> I would just say it's for accessibility because the fact it looks up a label element by an id is obvious from the code.

Done.

>> Source/WebCore/dom/TreeScope.h:70
>> +    // m_shouldCacheLabelsByForAttr is set to true when this method is called the first time.
> 
> This comment repeats an implementation detail that's obvious from the code. Please remove it.

You don't think it's useful to mention that there's an added performance cost from then on if this method is called?

>> LayoutTests/accessibility/title-ui-element-correctness.html:34
>> +  </div>
> 
> Please add a test case where there are multiple label elements with the same "for" value.
> And make sure that adding and removing such elements update the map correctly.

Done. That was a good catch, I needed to make changes to DocumetOrderedMap.

>> LayoutTests/accessibility/title-ui-element-speed.html:17
>> +if (window.testRunner && window.accessibilityController) {
> 
> I still think we should just use magnitude-perf.js. Granted, some tests are flaky but that's a problem with the tests, not the framework itself.
> If the framework is making some tests flaky, then we should fix the framework instead of re-implementing it in each test.

Done.
Comment 34 Ryosuke Niwa 2012-10-11 13:13:59 PDT
Comment on attachment 167936 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167936&action=review

> LayoutTests/accessibility/title-ui-element-correctness-expected.txt:6
> +Label 1  
> +Label 2  
> +Label 3  
> +
> +Label 5  
> +Label 6b Label 6c  

Please hide these once the test is done to avoid cluttering the test output.
Namely, you can set display: none on #container or clear its innerHTML.
Comment 35 Dominic Mazzoni 2012-10-11 13:41:11 PDT
Created attachment 168269 [details]
Patch for landing
Comment 36 WebKit Review Bot 2012-10-11 13:44:10 PDT
Comment on attachment 168269 [details]
Patch for landing

Rejecting attachment 168269 [details] from commit-queue.

Failed to run "['/mnt/git/webkit-commit-queue/Tools/Scripts/webkit-patch', '--status-host=queues.webkit.org', '-..." exit_code: 2

Last 500 characters of output:
i.html
patching file LayoutTests/accessibility/title-ui-element-correctness-expected.txt
patching file LayoutTests/accessibility/title-ui-element-correctness.html
patching file LayoutTests/perf/accessibility-title-ui-element-expected.txt
patching file LayoutTests/perf/accessibility-title-ui-element.html
patching file LayoutTests/platform/chromium/TestExpectations

Failed to run "[u'/mnt/git/webkit-commit-queue/Tools/Scripts/svn-apply', u'--force']" exit_code: 1 cwd: /mnt/git/webkit-commit-queue

Full output: http://queues.webkit.org/results/14250839
Comment 37 Dominic Mazzoni 2012-10-11 14:25:38 PDT
Created attachment 168278 [details]
Patch for landing
Comment 38 WebKit Review Bot 2012-10-11 16:13:23 PDT
Comment on attachment 168278 [details]
Patch for landing

Clearing flags on attachment: 168278

Committed r131107: <http://trac.webkit.org/changeset/131107>
Comment 39 WebKit Review Bot 2012-10-11 16:13:29 PDT
All reviewed patches have been landed.  Closing bug.
Comment 40 Adam Barth 2012-10-11 19:02:30 PDT
This patch causes an ASSERT.

crash log for DumpRenderTree (pid 13686):
STDOUT: <empty>
STDERR: SHOULD NEVER BE REACHED
STDERR: third_party/WebKit/Source/WebCore/dom/DocumentOrderedMap.cpp(136) : WebCore::Element* WebCore::DocumentOrderedMap::get(WTF::AtomicStringImpl*, const WebCore::TreeScope*) const [with bool (* keyMatches)(WTF::AtomicStringImpl*, WebCore::Element*) = WebCore::keyMatchesLabelForAttribute]
STDERR: 1   0x7f41298b1aa1
STDERR: 2   0x7f41298b0c7f
STDERR: 3   0x7f412996bac6
STDERR: 4   0x7f4129fd9de8
STDERR: 5   0x7f4129fe8a54
STDERR: 6   0x7f4128f5efba WebKit::WebAccessibilityObject::titleUIElement() const
STDERR: 7   0x4ed593
STDERR: 8   0x4ef528
STDERR: 9   0x4effc4
STDERR: 10  0x4ef6a3
STDERR: 11  0x7f412a052d73
STDERR: 12  0x7f412a052f1a
STDERR: 13  0x7f4126f0680e
STDERR: 14  0x7f4126f01429
STDERR: 15  0x7f4126f013fa
STDERR: 16  0x1eec34c0618e
STDERR: [13686:13686:17469496058466:ERROR:process_util_posix.cc(144)] Received signal 11
STDERR: 	base::debug::StackTrace::StackTrace() [0x7f4126a81ff2]
STDERR: 	base::(anonymous namespace)::StackDumpSignalHandler() [0x7f4126aec4d1]
STDERR: 	0x7f411fd09af0
STDERR: 	WebCore::DocumentOrderedMap::get<>() [0x7f41298b1aab]
STDERR: 	WebCore::DocumentOrderedMap::getElementByLabelForAttribute() [0x7f41298b0c7f]
STDERR: 	WebCore::TreeScope::labelElementForId() [0x7f412996bac6]
STDERR: 	WebCore::AccessibilityNodeObject::labelForElement() [0x7f4129fd9de8]
STDERR: 	WebCore::AccessibilityRenderObject::titleUIElement() [0x7f4129fe8a54]
STDERR: 	WebKit::WebAccessibilityObject::titleUIElement() [0x7f4128f5efba]
STDERR: 	AccessibilityUIElement::titleUIElementCallback() [0x4ed593]
STDERR: 	CppBoundClass::MemberCallback<>::run() [0x4ef528]
STDERR: 	CppBoundClass::invoke() [0x4effc4]
STDERR: 	CppNPObject::invoke() [0x4ef6a3]
STDERR: 	WebCore::npObjectInvokeImpl() [0x7f412a052d73]
STDERR: 	WebCore::npObjectMethodHandler() [0x7f412a052f1a]
STDERR: 	v8::internal::HandleApiCallHelper<>() [0x7f4126f0680e]
STDERR: 	v8::internal::Builtin_Impl_HandleApiCall() [0x7f4126f01429]
STDERR: 	v8::internal::Builtin_HandleApiCall() [0x7f4126f013fa]
STDERR: 	0x1eec34c0618e
Comment 41 WebKit Review Bot 2012-10-11 19:04:15 PDT
Re-opened since this is blocked by bug 99126
Comment 42 Dominic Mazzoni 2012-10-16 16:06:06 PDT
Created attachment 169046 [details]
Patch
Comment 43 Dominic Mazzoni 2012-10-16 16:08:52 PDT
Ryosuke, could you take another look?

You suggested that I update the label map whether or not an element is in the document or not, but that breaks a DocumentOrderedMap assertion, and I don't think DocumentOrderedMap is intended to be used that way. It should only keep track of elements actually in the document.

I put the code basically back the way I had it, and I think it's correct. The label gets added to the map when the element is inserted into the tree, it gets updated when an attribute changes (but only if it's in the document at that time), and it gets removed from the map when the element is removed from the tree.

The test covers these cases, it fails if I take any of these out.

What do you think?

- Dominic
Comment 44 Build Bot 2012-10-16 16:33:25 PDT
Comment on attachment 169046 [details]
Patch

Attachment 169046 [details] did not pass win-ews (win):
Output: http://queues.webkit.org/results/14392328
Comment 45 Ryosuke Niwa 2012-10-16 18:13:43 PDT
Comment on attachment 169046 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=169046&action=review

> Tools/ChangeLog:883
> +=======
> +>>>>>>> 97825 merged

Something is wrong with this patch.
Comment 46 Dominic Mazzoni 2012-10-16 19:42:54 PDT
Created attachment 169074 [details]
Patch
Comment 47 Dominic Mazzoni 2012-10-16 19:45:19 PDT
(In reply to comment #45)
> > +>>>>>>> 97825 merged
> Something is wrong with this patch.

Argh, sorry about that. Fixed.
Comment 48 WebKit Review Bot 2012-10-18 19:14:00 PDT
Comment on attachment 169074 [details]
Patch

Rejecting attachment 169074 [details] from commit-queue.

Failed to run "['/mnt/git/webkit-commit-queue/Tools/Scripts/webkit-patch', '--status-host=queues.webkit.org', '-..." exit_code: 2

Last 500 characters of output:
ibility/title-ui-element-correctness.html
patching file LayoutTests/perf/accessibility-title-ui-element-expected.txt
patching file LayoutTests/perf/accessibility-title-ui-element.html
patching file LayoutTests/platform/chromium/TestExpectations
Hunk #1 succeeded at 1402 (offset -5 lines).
Hunk #2 succeeded at 1415 (offset -5 lines).

Failed to run "[u'/mnt/git/webkit-commit-queue/Tools/Scripts/svn-apply', u'--force', u'--reviewer', u'Ryosuke Ni..." exit_code: 1 cwd: /mnt/git/webkit-commit-queue

Full output: http://queues.webkit.org/results/14412827
Comment 49 Dominic Mazzoni 2012-10-18 23:53:24 PDT
Created attachment 169562 [details]
Patch for landing
Comment 50 WebKit Review Bot 2012-10-18 23:55:01 PDT
Comment on attachment 169562 [details]
Patch for landing

Rejecting attachment 169562 [details] from commit-queue.

Failed to run "['/mnt/git/webkit-commit-queue/Tools/Scripts/webkit-patch', '--status-host=queues.webkit.org', '-..." exit_code: 2

Last 500 characters of output:
mpRenderTree/chromium/TestRunner/AccessibilityUIElementChromium.cpp.rej
patching file LayoutTests/ChangeLog
Hunk #1 succeeded at 1 with fuzz 3.
patching file LayoutTests/accessibility/secure-textfield-title-ui.html
patching file LayoutTests/platform/chromium/TestExpectations
Hunk #1 succeeded at 1402 (offset -5 lines).
Hunk #2 succeeded at 1415 (offset -5 lines).

Failed to run "[u'/mnt/git/webkit-commit-queue/Tools/Scripts/svn-apply', u'--force']" exit_code: 1 cwd: /mnt/git/webkit-commit-queue

Full output: http://queues.webkit.org/results/14456378
Comment 51 Dominic Mazzoni 2012-10-19 00:29:38 PDT
Created attachment 169566 [details]
Patch for landing
Comment 52 WebKit Review Bot 2012-10-19 00:33:35 PDT
Comment on attachment 169566 [details]
Patch for landing

Rejecting attachment 169566 [details] from commit-queue.

Failed to run "['/mnt/git/webkit-commit-queue/Tools/Scripts/webkit-patch', '--status-host=queues.webkit.org', '-..." exit_code: 1

ERROR: /mnt/git/webkit-commit-queue/Source/WebKit/chromium/ChangeLog neither lists a valid reviewer nor contains the string "Unreviewed" or "Rubber stamp" (case insensitive).

Full output: http://queues.webkit.org/results/14424863
Comment 53 Dominic Mazzoni 2012-10-19 01:01:18 PDT
Committed r131871: <http://trac.webkit.org/changeset/131871>
Comment 54 Roger Fong 2012-10-25 11:02:32 PDT
Still crashes on Windows.
See http://build.webkit.org/results/Apple%20Win%207%20Release%20(Tests)/r132381%20(29225)/results.html.

Is this another one of those should be skipped on Windows accessibility tests? I'm wondering if we should have some sort of accessibility feature so we can disable these things on Windows since I don't think anyone is working on accessibility support for that port.
Comment 55 Dominic Mazzoni 2012-10-25 11:08:55 PDT
Since there's no owner, I think it might make sense to skip all of the accessibility tests on Windows.

If a non-accessibility test crashes as a result of accessibility code (definitely possible), that should obviously be fixed. But actually running the accessibility tests doesn't make much sense anymore.