Closed Bug 280498 Opened 21 years ago Closed 20 years ago

Invalidate accessibility subtree for frame visibility/display style changes

Categories

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

x86
All
defect

Tracking

()

RESOLVED FIXED

People

(Reporter: aaronlev, Assigned: aaronlev)

References

Details

(Keywords: access, crash)

Attachments

(3 files, 4 obsolete files)

A frame can be destroyed without even a DOM mutation event. We have to be careful because: 1) We cache the frame in nsHTMLTextAccessible 2) It indicates that something important has changed about the node -- very likely display:none was set on it. The accessible tree now needs to reflect this. Caching the frame is an optimization for text accessibles, because GetPrimaryFrameFor() does a lot more calculation for text frames than what the accessible tree needs. Doing GetPFF on text frames would increase the size of the primary frame map since those are normally not stored there. We already have the text frames as the accessible tree walker iterates through, and cache those on the accessibles. I'd like to keep the optimzation if possible. Boris says "You could set the NS_FRAME_EXTERNAL_REFERENCE bit on the framestate and add a hook to PresShell::ClearFrameRefs that's #ifdef ACCESSIBILITY."
Priority: -- → P1
We should also invalidate based on some attribute changes, but I'm not sure what HTML or XUL attribute changes to ignore for performance reasons (or if we should attempt that).
Actually for the important attribute changes that would really affect the accessibility tree, we should see frames being destroyed or created. For example, if a script changes the type attribute on an input element.
Sounds like you just need a list of attributes that accessibility actually depends on and deal with them changing, basically... I suspect doing a grep for GetAttr in /accessibility will give you such a list, no?
It's not that simple, because the type of accessible that's created could depend on the frame itself, which could depend on an attribute. For example, the type attribute of an input tag. Or, the computed display: property for an element could change because an attribute changed. The XBL binding used for a XUL element can also be affected by an attribute. The XBL binding is responsible for deciding what kind of accessible to create. Also, some attributes used in the accessibility code only affect the state -- for example, the checked is may be used to determine if something is checked. We don't want to destroy the current accessible in that case, just throw a state change event. Yes, we can take all of these things into account, and might have to, but we need to be careful. Future coders modifying Mozilla may not remember to add the special casing in the right place. Maybe we can get around having to special case all of these attributes by just looking for frame changes?
It sounds like you need to look for frame changes anyway... But don't you need to change accesibles when, say, the src of an image changes? That doesn't cause a frame change.
(In reply to comment #5) > It sounds like you need to look for frame changes anyway... But don't you need > to change accesibles when, say, the src of an image changes? That doesn't cause > a frame change. If the src changes we don't need to destroy and create a new accessible. We just need to fire a LOCATIONCHANGE if it's bounds change, a NAMECHANGE if it's ALT text changes, a STATECHANGE if one of its states changes, and a VALUECHANGE since it has a new URL. Then the screen reader or other assistive technology can call back in to get the new data if it wishes.
OK. So then you just need to audit accessible creation, I guess, and see what attributes the type of accessible depends on. Then decide whether those attribute changes will always cause frame reconstructs...
Boris, I looked into NS_FRAME_EXTERNAL_REFERENCE, which causes a call into ClearFrameRefs(). It's not actually being set anywhere in the codebase. Maybe we should use that free bit for something else. Once accesssibility is active I need to find out about all frame changes, so I'd rather hook in without setting the state bit on each and every frame. I can just use a static bool in layout like gIsAccessibilityOn.
Duh, I take it back. We do use NS_FRAME_EXTERNAL_REFERENCE in esm::SetFrameExternalReference() http://lxr.mozilla.org/seamonkey/source/content/events/src/nsEventStateManager.cpp#396
Yeah, if you need to know about every single frame change then you want to just hook in directly into frame destruction....
I'm running into problems because ClearFrameRefs() isn't really a good place to hook in. It doesn't catch visibility style changes, or when display: none is cleared and the frame is created.
Visibility style changes only affect painting -- they don't affect the frame tree at all. And frame creation doesn't go through ClearFrameRefs of course (note summary of this bug; I thought that's what we were talking about).
Resummarizing to reflect what this bug should really be for. How about a new style hint like NS_STYLE_HINT_ACCESSIBILITY_CHANGE. Then we would do something like: #ifdef ACCESSIBILITY if (gIsAccessibilityActive && (frameHint & NS_STYLE_HINT_ACCESSIBILITY_CHANGE)) { mAccService->InvalidateSubteeForFrame(frame); }
Summary: Invalidate accessibility subtree when frame destroyed → Invalidate accessibility subtree for frame visibility/display style changes
Frame changes can happen for reasons other than a style change (content removal/insertion, possibly non-style content state changes (eg images being replaced by their replaced content)). But yes, if all you care about is style changes, we could probably add a new change hint for this. Note that it'd only be guaranteed to happen for the "rootmost" frame that had the relevant style change.
(In reply to comment #14) > Frame changes can happen for reasons other than a style change (content > removal/insertion, possibly non-style content state changes (eg images being > replaced by their replaced content)). I'm not sure if we care about those -- we'll still have an image accessible for that anyway. Will have to test. > But yes, if all you care about is style changes, we could probably add a new > change hint for this. Note that it'd only be guaranteed to happen for the > "rootmost" frame that had the relevant style change. Yes that's actually what I want.
I'm not going to be able to do a thorough review for probably about two weeks, but that patch doesn't look like it should compile, if nothing else...
(In reply to comment #18) > I'm not going to be able to do a thorough review for probably about two weeks, > but that patch doesn't look like it should compile, if nothing else... It will unless I forgot something in the patch. I built and ran through the tests for me. What looks like it won't compile?
NS_STYLE_HINT_ACCESSIBLECHANGE is never defined, if nothing else. Also, the XXX comment there is wrong. The old NS_STYLE_HINT constants are just that -- old and kept just to avoid having to change lots of code that uses them. New code should be using nsChangeHint values as needed, and we shouldn't be adding more NS_STYLE_HINT constants.
Attached patch Fix compile errors (obsolete) — Splinter Review
The previous one didn't compile, just like bz said.
Attachment #177772 - Attachment is obsolete: true
Attachment #177830 - Flags: superreview?(roc)
Attachment #177830 - Flags: review?(bzbarsky)
Comment on attachment 177830 [details] [diff] [review] Fix compile errors Boris can do the sr on this
Attachment #177830 - Flags: superreview?(roc) → superreview?(bzbarsky)
I don't have a testcase right now, but this almost certainly fixes potential crashes in the accessibility module. We need to make sure our accessible object tree remains valid.
Severity: normal → critical
Keywords: crash
Comment on attachment 177830 [details] [diff] [review] Fix compile errors >Index: layout/base/nsCSSFrameConstructor.cpp >+#ifdef ACCESSIBILITY >+ if ((frameChange & nsChangeHint_AccessibleChange) && >+ mPresShell->IsAccessibilityActive()) { That only handles the case when the actual node that had an attribute change or whatever it was that triggered the style change has the nsChangeHint_AccessibleChange hint. What if it doesn't but one of its descendants does? Seems to me like you want this code to be in ProcessRestyledFrames (which is called from a lot more places than just right here, by the way), not here. >Index: layout/base/nsChangeHint.h > #define NS_STYLE_HINT_FRAMECHANGE \ > nsChangeHint(NS_STYLE_HINT_REFLOW | nsChangeHint_ReconstructFrame) > >- Random whitespace change? Please take that out. >Index: layout/base/nsIPresShell.h >+#ifdef ACCESSIBILITY >+ NS_IMETHOD_(PRBool) IsAccessibilityActive(); >+#endif Why NS_IMETHOD? Why not just virtual PRBool? >Index: layout/base/nsPresShell.cpp So accessibility never becomes inactive? >Index: layout/style/nsStyleStruct.cpp > if ((mVisible != aOther.mVisible) && Want to remove this check while you're here? Given the previous |if|, it's redundant... You need to fix the MaxDifference() method for this struct; otherwise style reresolution will assert. >Index: accessible/public/nsIAccessibilityService.idl >+ [noscript] void invalidateSubtreeFor(in nsIPresShell aPresShell, in nsIContent aContent); Document what this method should do, please. >Index: accessible/public/nsPIAccessibleDocument.idl >+ void invalidateCacheSubtree(in nsIDOMNode aStartNode, in PRUint32 aChangeEvent); Same here. What is aChangeEvent, exactly? >Index: accessible/src/base/nsAccessibilityService.cpp > nsDocAccessible::GetAccessibleInParentChain(nsIDOMNode *aNode, The changes to this method look wrong. What if aNode is the document node? >-#ifdef XP_WIN >- // Windows MSAA clients crash if they listen to create or destroy events >- aAccessibleEventType = nsIAccessibleEvent::EVENT_REORDER; >-#endif Why is this code not needed at the spot to which you moved this chunk of code? >Index: accessible/src/base/nsDocAccessible.h >+ void nsDocAccessible::ShutdownNodes(nsIDOMNode I'm pretty sure the redundant class name here will make this not compile on at least some of our platforms.
Attachment #177830 - Flags: superreview?(bzbarsky)
Attachment #177830 - Flags: superreview-
Attachment #177830 - Flags: review?(bzbarsky)
Attachment #177830 - Flags: review-
> >-#ifdef XP_WIN > >- // Windows MSAA clients crash if they listen to create or destroy events > >- aAccessibleEventType = nsIAccessibleEvent::EVENT_REORDER; > >-#endif > > Why is this code not needed at the spot to which you moved this chunk of code? We're going to use EVENT_HIDE and EVENT_SHOW now, because they are safe. From the AT or user's point of view it doesn't matter whether something was created vs. shown or destroyed vs. hidden -- that's an implementation detail in our app. We don't fire EVENT_CREATE and EVENT_DESTROY on Windows because they are unsafe so screen readers won't listen for them. Apparently listening for them can can cause crashes due to race conditions, in some applications. We don't need to force everything into EVENT_REORDER anymore.
Attached patch Address bz's comments (obsolete) — Splinter Review
Bz, I decided to leave the differentiation of show/hide/reorder events coming from layout for another bug. I'm not sure if we'll need to know the difference yet. The invalidation of parts of the accessibility cache happens either way, and the AT can look at the changes after they've happened.
Attachment #177830 - Attachment is obsolete: true
Attachment #179976 - Flags: superreview?(bzbarsky)
Attachment #179976 - Flags: review?(bzbarsky)
So you don't need to deal with ReframeContainingBlock being called from RecreateFramesForContent? Why not? In ProcessRestyledFrames, why is your code running if we had a frame reconstruct hint? That seems wrong. Why is IsAccessibilityActive virtual, if the member is on nsIPresShell? Should be one or the other, but not both, no? The style struct changes will miss returning an accessible change if both the visibility and the lang group or direction have changed.
Comment on attachment 179976 [details] [diff] [review] Address bz's comments >Index: accessible/public/nsPIAccessible.idl >+ /** >+ * Set the child count to -1 (unknown) and cached pointers to children Is the second half of that sentence missing? If not, what is this supposed to mean?
Attachment #179976 - Flags: superreview?(bzbarsky)
Attachment #179976 - Flags: superreview-
Attachment #179976 - Flags: review?(bzbarsky)
Attachment #179976 - Flags: review-
Attached patch Another round (obsolete) — Splinter Review
Attachment #179976 - Attachment is obsolete: true
Attachment #180341 - Flags: superreview?(bzbarsky)
Attachment #180341 - Flags: review?(bzbarsky)
The stuff from bug 280154 accidentally leaked into the last patch. Please ignore it.
Blocks: 289902
Attachment #180341 - Flags: superreview?(bzbarsky)
Attachment #180341 - Flags: review?(bzbarsky)
Attachment #180363 - Flags: superreview?(bzbarsky)
Attachment #180363 - Flags: review?(bzbarsky)
Attachment #180341 - Attachment is obsolete: true
Blocks: deera11y
Comment on attachment 180363 [details] [diff] [review] Remove extra presshell code from other patch >Index: layout/base/nsCSSFrameConstructor.cpp >@@ -10156,12 +10159,25 @@ nsCSSFrameConstructor::ProcessRestyledFr > } >- >+ > #ifdef DEBUG Nix that whitespace change. >@@ -11593,10 +11609,22 @@ nsCSSFrameConstructor::RecreateFramesFor >+#ifdef ACCESSIBILITY >+ if (mPresShell->IsAccessibilityActive()) { So... If we're actually blowing away all frames to the containing block, don't we need to invalidate the subtree for the containing block's content, not aContent? That is, are those accessibles possibly holding refs to frames that are between the frame of aContent and its containing block? If so, we need to clear those refs, right? >Index: layout/style/nsStyleContext.cpp > nsChangeHint maxHint = nsChangeHint(NS_STYLE_HINT_FRAMECHANGE | >- nsChangeHint_UpdateCursor); >+ nsChangeHint_UpdateCursor | nsChangeHint_AccessibleChange); Hmm... I guess this is OK for now, but the Visibility struct is actually misplaced in the style context code. Could you file a followup bug on me for that? >Index: accessible/public/nsIAccessibilityService.idl >+ [noscript] void invalidateSubtreeFor(in nsIPresShell aPresShell, in nsIContent aContent); This needs a uuid change. >Index: accessible/public/nsPIAccessible.idl >-[scriptable, uuid(52F1BE88-84F7-4f7f-B31C-062AFE7DF15D)] >+[scriptable, uuid(EC88FE71-8065-4310-9189-9E3B7A3D74E4)] Whereas here you just added comments; not sure why the uuid got changed here. >Index: accessible/public/nsPIAccessibleDocument.idl >- void invalidateCacheSubtree(in nsIDOMNode aStartNode); Change the uuid. With those comments addressed, r+sr=bzbarsky
Attachment #180363 - Flags: superreview?(bzbarsky)
Attachment #180363 - Flags: superreview+
Attachment #180363 - Flags: review?(bzbarsky)
Attachment #180363 - Flags: review+
Attachment #180363 - Flags: approval1.8b2?
Comment on attachment 180363 [details] [diff] [review] Remove extra presshell code from other patch a=asa
Attachment #180363 - Flags: approval1.8b2? → approval1.8b2+
> >@@ -11593,10 +11609,22 @@ nsCSSFrameConstructor::RecreateFramesFor > > >+#ifdef ACCESSIBILITY > >+ if (mPresShell->IsAccessibilityActive()) { > > So... If we're actually blowing away all frames to the containing block, don't > we need to invalidate the subtree for the containing block's content, not > aContent? That is, are those accessibles possibly holding refs to frames that > are between the frame of aContent and its containing block? If so, we need to > clear those refs, right? We actually do deal with the parent aContent -- but that's done in the implementation of nsAccessibilityService::InvalidateSubtreeFor(presshell, content); We conservatively assume that the changing of a node could affect the accessibles based on their siblings. The impl goes up to the parent. However, I suddenly noticed that RecreateFrames has a bad error. The |if (container)| block appears to have confusion between the variables parent and frame. Sent you email about that.
> We actually do deal with the parent aContent The containing block doesn't have to be the parent; it could be arbitrarily far up the parent chain. We could be blowing away the whole frame tree here, in fact. > However, I suddenly noticed that RecreateFrames has a bad error. Bug 289377. The error doesn't really affect anything, just disables a questionable performance optimization.
Bz, you want something like this? if (accService) { nsIFrame* containingBlock = GetIBContainingBlockFor(frame); nsCOMPtr<nsIContent> parentContent = frame->GetContent(); accService->InvalidateSubtreeFor(mPresShell, parentContent); } Just want to be clear before I check in.
> Bz, you want something like this? Yes, if it's done _before_ the frame is recreated. Doing it after will crash, since |frame| will have been destroyed. Oh, and you want to GetContent on the containing block, not on |frame|.
Bz, I remembered that I actually want to call InvalidateSubtreeFor() after the new frames have been created. The reason is, it will fire an event that may result in the external assistive technology to ask for the new objects immediately -- so the new frames need to be ready at that point.
> I actually want to call InvalidateSubtreeFor() after Make sense. That diff looks good to me.
Checking in layout/base/nsCSSFrameConstructor.cpp; /cvsroot/mozilla/layout/base/nsCSSFrameConstructor.cpp,v <-- nsCSSFrameConstructor.cpp new revision: 1.1094; previous revision: 1.1093 done Checking in layout/base/nsChangeHint.h; /cvsroot/mozilla/layout/base/nsChangeHint.h,v <-- nsChangeHint.h new revision: 3.9; previous revision: 3.8 done Checking in layout/base/nsIPresShell.h; /cvsroot/mozilla/layout/base/nsIPresShell.h,v <-- nsIPresShell.h new revision: 3.154; previous revision: 3.153 done Checking in layout/base/nsPresShell.cpp; /cvsroot/mozilla/layout/base/nsPresShell.cpp,v <-- nsPresShell.cpp new revision: 3.836; previous revision: 3.835 done Checking in layout/style/nsStyleContext.cpp; /cvsroot/mozilla/layout/style/nsStyleContext.cpp,v <-- nsStyleContext.cpp new revision: 3.258; previous revision: 3.257 done Checking in layout/style/nsStyleStruct.cpp; /cvsroot/mozilla/layout/style/nsStyleStruct.cpp,v <-- nsStyleStruct.cpp new revision: 3.127; previous revision: 3.126 done Checking in accessible/public/nsIAccessibilityService.idl; /cvsroot/mozilla/accessible/public/nsIAccessibilityService.idl,v <-- nsIAccessibilityService.idl new revision: 1.46; previous revision: 1.45 done Checking in accessible/public/nsPIAccessible.idl; /cvsroot/mozilla/accessible/public/nsPIAccessible.idl,v <-- nsPIAccessible.idl new revision: 1.3; previous revision: 1.2 done Checking in accessible/public/nsPIAccessibleDocument.idl; /cvsroot/mozilla/accessible/public/nsPIAccessibleDocument.idl,v <-- nsPIAccessibleDocument.idl new revision: 1.4; previous revision: 1.3 done Checking in accessible/src/base/nsAccessibilityService.cpp; /cvsroot/mozilla/accessible/src/base/nsAccessibilityService.cpp,v <-- nsAccessibilityService.cpp new revision: 1.138; previous revision: 1.137 done Checking in accessible/src/base/nsDocAccessible.cpp; /cvsroot/mozilla/accessible/src/base/nsDocAccessible.cpp,v <-- nsDocAccessible.cpp new revision: 1.59; previous revision: 1.58 done Checking in accessible/src/base/nsDocAccessible.h; /cvsroot/mozilla/accessible/src/base/nsDocAccessible.h,v <-- nsDocAccessible.h new revision: 1.25; previous revision: 1.24 done
Status: NEW → RESOLVED
Closed: 20 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: