Closed Bug 472662 Opened 16 years ago Closed 15 years ago

no reorder event for most display property & DOM changes

Categories

(Core :: Disability Access APIs, defect, P2)

defect

Tracking

()

VERIFIED FIXED

People

(Reporter: surkov, Assigned: surkov)

References

(Blocks 1 open bug)

Details

(Keywords: verified1.9.1, Whiteboard: Major affect on screen reader support)

Attachments

(3 files, 5 obsolete files)

There is no reorder event when html:link display property is changed from 'none' to 'inline'.

Example is https://bugzilla.mozilla.org/attachment.cgi?id=240526 (toggle display:none for link #1 button).
the check

 +  if (childAccessible) {
+     fire reorder event

was introduced in bug 434857 (see bug 434857 comment #20).
Assignee: nobody → surkov.alexander
Attached patch patch (obsolete) — Splinter Review
Attachment #355981 - Flags: review?(aaronleventhal)
Attachment #355981 - Flags: review?(marco.zehe)
Surkov, why do you think we have the |if (childAccessible)| condition?

Also, what is gained by removing it? I need more of a description as to what is going on :P .... my head is not as much in the code these days.
(In reply to comment #3)
> Surkov, why do you think we have the |if (childAccessible)| condition?
> 
> Also, what is gained by removing it? I need more of a description as to what is
> going on :P .... my head is not as much in the code these days.

Aaron, no idea actually. Reorder event has been introduced in bug 392243 where you added this condition without any clarification. It was removed in bug 390692 in huge patch where I can't track the existence of similar condition. I assumed it prevents reorder events when there are DOM changes and there are not accessible tree changes but my mochitests shows all is ok.
Attached patch patch2 (obsolete) — Splinter Review
with additional mochitests to test more reorder events. Aaron if you can see cases I missed then I'm glad to add them.
Attachment #355981 - Attachment is obsolete: true
Attachment #356159 - Flags: review?(aaronleventhal)
Attachment #355981 - Flags: review?(marco.zehe)
Attachment #355981 - Flags: review?(aaronleventhal)
Attachment #356159 - Flags: review?(marco.zehe)
Attachment #356159 - Attachment is obsolete: true
Attachment #356159 - Flags: review?(marco.zehe)
Attachment #356159 - Flags: review?(aaronleventhal)
Comment on attachment 356159 [details] [diff] [review]
patch2

let me think about tests a bit more
(In reply to comment #4)
 I
> assumed it prevents reorder events when there are DOM changes and there are not
> accessible tree changes but my mochitests shows all is ok.

That's right. I have not idea how to fix this bug at the moment because when we fire delayed reorder we don't know if appended/changed DOM node is accessible. Also it's not easy to move reorder event firing when we process delayed events because of coalesce events from queue.
Blocks: eventa11y
I will get back to this once we get mochitests for this (bug 469985).
Can we fix the summary of this bug so it describes all the broken cases?
The current summary makes it sound a lot less important than it is.

What if a major website, say Yahoo! is changing the display property onload. Doesn't that mean that a screen reader which needs REORDER events might present the wrong page content in their virtual buffer? That is what is happening in at least one major screen reader right now, and this bug could be the reason.

I'm wondering if this should be blocking 1.9.1.
I see, so the problem with firing the reorder event only |if (childAccessible)| is that layout hasn't updated yet, and we don't know if there will be a childAccessible yet.

So if we fire it on a delay it is better. 

With patch 2, we may get a few extra REORDER events, however. With patch2, there could be an extra event if there was no childAccessible, and there still isn't a childAccessible after the delay. I'm not sure if that matters, but it would be good to think of a solution for that.

Perhaps a reorder event could track the number of children in the container. If the number is 0 and there are still no children, then FlushPendingEvents wouldn't fire it.
> Perhaps a reorder event could track the number of children in the container. 
Or, just whether there are any children at all.
How about this.

First, when firing delayed reorder event:
reorderEvent->SetWasThereAChildAccessible(childAccessible != nsnull);

Second:
In nsAcceEvent::ApplyEventRules(), if two reorder events are combined:
eventWeKeep->SetWasThereAchildAccessible(wasChildAccessibleInEventWeRemove);

Finally:
In FlushPendingEvents()
if (!wasThereAChildAccessible && !isThereAChildAccessible)
  continue; // Don't fire this one
Changes:
First, when firing delayed reorder event:
reorderEvent->SetMustFire(childAccessible != nsnull);

Second:
In nsAcceEvent::ApplyEventRules(), if two reorder events are combined:
eventWeKeep->SetMustFire(PR_TRUE);

Finally:
In FlushPendingEvents()
if (!requiredToFire && !isThereAChildAccessible)
  continue; // Don't fire this one, nothing has changed

--------------

This takes a balanced approach between code simplicity and removing most of the extra events we don't need. There will be some extra REORDER events when >1 children are changed and there are still no accessibles for those children, but it's not a correctness or perf problem to have it happen once in those cases. 

This is better than a complicated solution to determine when the multi-child change situation should result in no reorder event. By complicated, I mean that reorder events would need to cache which DOM nodes were changed and whether they had accessibles. Lists would need to be combined when REORDER events were combined, and then FlushPendingEvents() would need to look to see if there were still no accessibles created for those.

I like the simple solution best. We can change later if necessary, but I don't see why it would be.
Summary: no reorder event when html:link display property is changed from 'none' to 'inline' → no reorder event when display property is changed from 'none'
Summary: no reorder event when display property is changed from 'none' → no reorder event when display property is changed
Summary: no reorder event when display property is changed → no reorder event for most display property & DOM changes
Whiteboard: Major affect on screen reader support
Attached patch wip (obsolete) — Splinter Review
This is a lot of code. Is this the simple solution?
(In reply to comment #15)
> This is a lot of code. Is this the simple solution?

Simpler I can't see :). We shouldn't fire excess reorder events and should fire them for show events. But I didn't tests yet. I guess it's very similar to you proposed in comment 13, excepting isThereAChildAccessible is placed on more complex check HasAccessibleInReasonSubtree.
Attached patch patch (obsolete) — Splinter Review
Attachment #357177 - Attachment is obsolete: true
Attachment #359018 - Flags: review?(marco.zehe)
Attachment #359018 - Flags: superreview?(neil)
Attachment #359018 - Flags: review?(david.bolter)
Status: NEW → ASSIGNED
Comment on attachment 359018 [details] [diff] [review]
patch

A few nits/questions:
+  // Do not emit descedant event if this event is unconditional.

"descedant" should be "descendant" (missing n). You have this in several places in both comment and variable names.

+   * Return true if changed DOM node has accessible is its tree.

"...in its tree".

+      <span id="child1"></span>

Did you mean to put role="listitem" here like you have for the other spans in this container?
(In reply to comment #18)

> +      <span id="child1"></span>
> 
> Did you mean to put role="listitem" here like you have for the other spans in
> this container?

No, this is intended. I need non accessible node to check we don't fire any events on change.
This patch looks OK but then again I don't really understand a11y code... was there a particular reason you requested my sr?
(In reply to comment #20)
> This patch looks OK but then again I don't really understand a11y code... was
> there a particular reason you requested my sr?

No special reason actually. Just when patch is getting complex then I ask for superreview usually. It makes me to feel safe.
Attachment #359018 - Flags: superreview?(neil)
Comment on attachment 359018 [details] [diff] [review]
patch

>+  return accessible ? PR_TRUE : nsAccUtils::HasAccessibleChildren(mReasonNode);
Well, I don't really understand what's going on here so if you don't mind I'll just refuse to review but I'll just note that you could write this as
return accessible || nsAccUtils::HasAccessibleChildren(mReasonNode);

>+  PRBool isUnconditionalEvent = childAccessible ?
>+    PR_TRUE : (aChild ? nsAccUtils::HasAccessibleChildren(childNode) : PR_FALSE);
And this could be written
PRBool isUnconditionalEvent = childAccessible ||
    (aChild && nsAccUtils::HasAccessibleChildren(childNode);
(In reply to comment #22)

> Well, I don't really understand what's going on here so if you don't mind I'll
> just refuse to review

Sure, I don't mind. Thanks for comments.
Comment on attachment 359018 [details] [diff] [review]
patch

Aside from the two spelling errors I found, looks and works fine! I agree with Neil's suggestions to change those nested : ? constructs.

With that fixed, r=me.
Attachment #359018 - Flags: review?(marco.zehe) → review+
(In reply to comment #24)
> (From update of attachment 359018 [details] [diff] [review])
> Aside from the two spelling errors I found, looks and works fine! I agree with
> Neil's suggestions to change those nested : ? constructs.

I fixed these locally already.
Comment on attachment 359018 [details] [diff] [review]
patch

Impressive.

>+  nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(presShell));
>+  nsAccessibleTreeWalker walker(weakShell, aNode, PR_FALSE);

I don't (yet) know why the tree walker needs a weakly referenced shell but I'll take your word for it. Are we worried about cyclic references here?

>+/* static */
>+void
>+nsAccEvent::DupeReorderEvents(nsAccEvent *aAccEvent1,

Since this method does something... can we make the name a verb? (If 'Dupe' is a verb here it is incorrect, meaning "to fool someone" or to "duplicate")

How about ProcessDupeROEvents?

>+  // Do not emit event2 if event1 is not valid, otherwise do not emit event1.

Nit:
Should this be "Do not emit event2 if event1 is valid, otherwise do not emit event1" ?

>+  if (reorderEvent1->HasAccessibleInReasonSubtree())
>+    aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit;
>+  else
>+    aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit;
>+}
>+
>+void
>+nsAccEvent::CoalesceReorderEvents(nsAccEvent *aAccEvent,
>+                                  nsAccEvent *aDescedantAccEvent)
>+{
>+  // Do not emit descedant event if this event is unconditional.

nit: do a find and replace descedant -> descendant


>+PRBool
>+nsAccReorderEvent::HasAccessibleInReasonSubtree()
>+{
>+  if (!mReasonNode)
>+    return PR_FALSE;
>+
>+  nsCOMPtr<nsIAccessible> accessible;
>+  nsAccessNode::GetAccService()->GetAccessibleFor(mReasonNode,
>+                                                  getter_AddRefs(accessible));
>+
>+  return accessible ? PR_TRUE : nsAccUtils::HasAccessibleChildren(mReasonNode);
>+}
>+
>+  /**
>+   * Do not emit one of two given reorder events fired for the same DOM node.
>+   */
>+  static void DupeReorderEvents(nsAccEvent *aAccEvent1,
>+                                nsAccEvent *aAccEvent2);

CoalesceReorderEventsFromSameSource

>+
>+  /**
>+   * Do not emit one of two given reorder events fired for DOM nodes in the case
>+   * when one DOM node is in parent chain of second one.
>+   */
>+  static void CoalesceReorderEvents(nsAccEvent *aAccEvent,
>+                                    nsAccEvent *aDescedantAccEvent);

CoalesceReorderEventsFromSameTree

>+private:
>+  PRBool mUnconditionalEvent;
>+  nsCOMPtr<nsIDOMNode> mReasonNode;

What is the meaning of "Reason" in mReasonNode?

>+      else if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
>+        nsAccReorderEvent* reorderEvent = nsnull;
>+        CallQueryInterface(accessibleEvent, &reorderEvent);
>+        if (reorderEvent->IsUnconditionalEvent() ||
>+            reorderEvent->HasAccessibleInReasonSubtree()) {
>+          nsAccEvent::PrepareForEvent(accessibleEvent);
>+          FireAccessibleEvent(accessibleEvent);

Why do we check HasAccessibleInReasonSubtree when we've already checked for target accessible and accessible in subtree (captured in unconditional variable) before we put it on the queue?

I need to understand the need for HasAccessibleInReasonSubtree.

>+  // Fire an event so the MSAA clients know the children have changed. Also
>+  // the event is used internally by MSAA part.

maybe "internally by nsIAccessible* layer"?

+ Neil's nits.
(In reply to comment #26)

> >+  nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(presShell));
> >+  nsAccessibleTreeWalker walker(weakShell, aNode, PR_FALSE);
> 
> I don't (yet) know why the tree walker needs a weakly referenced shell but I'll
> take your word for it.

I never looked seriously at a11y tree walker implementation. Do you concern why it needs shell or weakly referenced shell?

> Are we worried about cyclic references here?

I don't think so. A11y tree walker is created on stack.

> >+  // Do not emit event2 if event1 is not valid, otherwise do not emit event1.
> 
> Nit:
> Should this be "Do not emit event2 if event1 is valid, otherwise do not emit
> event1" ?

yes.
 
> What is the meaning of "Reason" in mReasonNode?

I didn't find good word for this. I say reason node is node that was changed (shown or hidden) and the change of this node is a reason of the fact we fire reorder event.

> 
> >+      else if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
> >+        nsAccReorderEvent* reorderEvent = nsnull;
> >+        CallQueryInterface(accessibleEvent, &reorderEvent);
> >+        if (reorderEvent->IsUnconditionalEvent() ||
> >+            reorderEvent->HasAccessibleInReasonSubtree()) {
> >+          nsAccEvent::PrepareForEvent(accessibleEvent);
> >+          FireAccessibleEvent(accessibleEvent);
> 
> Why do we check HasAccessibleInReasonSubtree when we've already checked for
> target accessible and accessible in subtree (captured in unconditional
> variable) before we put it on the queue?

initially we call HasAccessibleInReasonSubtree() only if there is accessible, after timeout we call HasAccessibleInReasonSubtree only if there wasn't accessible.

> 
> I need to understand the need for HasAccessibleInReasonSubtree.

Ok, refer to examples in mochitests. So how it should be (but possibly how it isn't in the patch).

1) We need to prepare all checks after timeout for show events because frame may be not created at initial point
2) We need to prepare all checks initially for hide events because accessible isn't destroyed yet.

<div role="bla"><span id="1"></span><div>

1) Clone and append @id=1 I shouldn't get any events - we ensure after timeout @id=1 isn't accessible and doens't contain any accessible children.
2) Remove @id="1" shouldn't fire any events - we ensure initially there is no accessible and there is no accessible children.

<div role="bla"><span id="2"><span role="bla"></span></span></div>
1) clone and add @id="2", we should get reorder event because child of @id=2 is accessible (HasAccessibleInReasonSubtree - check after timeout)
2) remove id=2 - we should get reorder event because child of id=2 is accessible (again :) ) (
check HasAccessibleInReasonSubtree initially)

> >+  // Fire an event so the MSAA clients know the children have changed. Also
> >+  // the event is used internally by MSAA part.
> 
> maybe "internally by nsIAccessible* layer"?

why you don't like internally by MSAA part, I mean we should save word "MSAA" here, right?
> + Neil's nits.
Attached patch patch2 (obsolete) — Splinter Review
Marco, Neil and David comments
Attachment #359018 - Attachment is obsolete: true
Attachment #359018 - Flags: review?(david.bolter)
Comment on attachment 359069 [details] [diff] [review]
patch2

Ok, David, requesting review. I'm not sure my patch code is optimal (there is no excess checks). So if you like then you can get back to review when I'll clear this point in my mind.
Attachment #359069 - Flags: review?(david.bolter)
Attachment #359069 - Flags: review?(david.bolter) → review+
Comment on attachment 359069 [details] [diff] [review]
patch2

Thanks for the changes -- it would be nice to get Aaron's signoff.

BTW I'm OK with "reason" now, the alternatives aren't much better ("trigger" etc).

Nit:

>+      else if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
>+        nsAccReorderEvent* reorderEvent = nsnull;
>+        CallQueryInterface(accessibleEvent, &reorderEvent);
>+        if (reorderEvent->IsUnconditionalEvent() ||
>+            reorderEvent->HasAccessibleInReasonSubtree()) {

Maybe put a comment here as to why we re-check for an accessible (see below)

<snip>

>+  // Create and put reorder event into queue processed after timeout. If
>+  // the target node is accessible or contains accessible children then mark
>+  // reorder event as unconditional. If after timeout reorder event is
>+  // unconditional or target node is accessible or has accessible children then
>+  // reorder event will be fired.

Maybe move the last part of the comment to where we process the reorder event off the queue? (see above).

r=me
Attached patch patch3Splinter Review
I looked today the logic seems to be correct. I improved the comments for reorder event. What do you think?
Attachment #359069 - Attachment is obsolete: true
Attachment #359209 - Flags: review?(david.bolter)
Attachment #359209 - Flags: review?(aaronleventhal)
Comment on attachment 359209 [details] [diff] [review]
patch3

>+      else if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
>+        // Fire reorder event if it's unconditional (see InvalidateCacheSubtree
>+        // method) or if changed node (that is the reason of this reorder event)
>+        // is accessible or has accessible children.
>+        nsAccReorderEvent* reorderEvent = nsnull;

Thanks.

>+  // We need to fire reorder event for accessible parent of the changed node if
>+  // the changed node is accessible or has accessible children. In this case
>+  // we fire delayed unconditional reorder event which means it will be fired
>+  // after timeout in any case (of course if it won't be coalesced from event
>+  // queue). But at this point in the case of show events accessible object may
>+  // be not created for generally accessible changed node (because its frame may
>+  // be not constructed yet). Therefore we can to check whether the changed node
>+  // is accessible or has accessible children after timeout only. In the case we
>+  // fire conditional reorder event.

Thanks. It is good as is, or you can tweak it:

=begin=
We need to fire a delayed reorder event for the accessible parent of the changed node. We fire an unconditional reorder event if the changed node or one of its children is already accessible. In the case of show events, the accessible object might not be created yet for an otherwise accessible changed node (because its frame might not be constructed yet). In this case we fire a conditional reorder event, so that we will later check whether the changed node is accessible or has accessible children. Filtering/coalescing of these events happens during the queue flush.
=end=

r=me
Attachment #359209 - Flags: review?(david.bolter) → review+
Comment on attachment 359209 [details] [diff] [review]
patch3

Since we've talked about it a lot on IRC, and Neil, Marco and David looked at it, I'm going to pass on the review.
Attachment #359209 - Flags: review?(aaronleventhal)
http://hg.mozilla.org/mozilla-central/rev/02577adddd3e plus
http://hg.mozilla.org/mozilla-central/rev/2b33a85857db for last David's comment.
Status: ASSIGNED → RESOLVED
Closed: 15 years ago
Resolution: --- → FIXED
+  nsCOMPtr<nsIAccessibleEvent> reorderEvent =
+    new nsAccReorderEvent(containerAccessible, isAsynch,
+                          isUnconditionalEvent,
+                          aChild ? childNode : nsnull);

should be 
+                          aChild ? childNode.get() : nsnull);
The a11y tests started leaking after the checkin for this bug. Could you fix or back out?
Attached patch Fixes leaksSplinter Review
Peterv, is this what you had in mind from our talk on IRC?
Attachment #360693 - Flags: review?
Attachment #360693 - Flags: review? → review+
Verified fixed in Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2a1pre) Gecko/20090210 Minefield/3.2a1pre. Example: When changing a bug's status to "Resolved", the resolution field is now automatically being picked up by JAWS 10, where it didn't do that a few days ago.
Status: RESOLVED → VERIFIED
Flags: in-testsuite+
This proves to be a big improvement for screen readers in dynamic web apps and other in-place page updates. Requesting blocking to make sure we get this into 3.1. Surkov is preparing a port of the patch.
Flags: blocking1.9.1?
Attached patch patch1.9.1Splinter Review
includes patch3 and fixes leaks (ported to 1.9.1 cleanly, without any hunk), as well imports mochitests changes from trunk needed for successful work.
Attachment #362661 - Flags: approval1.9.1?
Flags: blocking1.9.1? → blocking1.9.1+
Priority: -- → P2
Attachment #362661 - Flags: approval1.9.1?
fixed1.9.1 keyword?
Keywords: fixed1.9.1
This caused build bustage on the Solaris Firefox-Ports tinderboxen:

"/export/home/x86slave/xslave/mozilla-1.9.1-solaris-x86/build/accessible/src/base/nsDocAccessible.cpp", line 2077: Error: Ambiguous "?:" expression, second operand of type "nsCOMPtr<nsIDOMNode>" and third operand of type "int" can be converted to one another.
gmake[6]: *** [nsDocAccessible.o] Error 1


Fix is easy:

 +  nsCOMPtr<nsIAccessibleEvent> reorderEvent =
 +    new nsAccReorderEvent(containerAccessible, isAsynch,
 +                          isUnconditionalEvent,
 +                          aChild ? childNode : nsnull);

Change childNode to childNode.get().
It is http://hg.mozilla.org/mozilla-central/rev/212ac2e73103
It is missed in 1.9.1 branch.
(In reply to comment #47)
> Bustage fix pushed to 1.9.1
> http://hg.mozilla.org/releases/mozilla-1.9.1/rev/e36b32238767

thanks
Verified fixed in Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b3pre) Gecko/20090222 Shiretoko/3.1b3pre using my Bugzilla "Change Status" combobox steps. Thanks!
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: