-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Normalize start Text pointer to support Text Cursor Indicator #10333
base: main
Are you sure you want to change the base?
Conversation
I see change on lines 1813-15. So you get the bounding rectangle, then potentially change it and then return the previous rectangle values? Doesn't sound like the intended API experience. I also don't see how changing the order of Normalize and GetBoundingRectangle in this method changes the invariant after exiting the method. Even if the adapter called back for a clone, it would be after Normalize before the change, so putting Normalize at the beginning of Clone should have not fixed it. Can you share some of the investigation on what breaks the invariant or when? |
I agree with miloush. I'm a bit puzzled. Sharing the investigation would be great to understand this properly.
And this exactly concerns me. |
So the Flow:
|
Building upon the above comment, basically the idea of the change is that How do we certain that the start is going back to its original value post Normalize: |
@harshit7962 sorry, somehow managed to miss this. From a brief overview it makes sense with your explanation but I'm still not sure whether that's the correct solution; imho it shouldn't happen in the first place. Do we have a repro? I'd just like to step through. |
Thanks for willing to take a look into it, for the repro, use the following xaml <RichTextBox>
<FlowDocument>
<Paragraph>
<Bold>a</Bold>
<Bold>b</Bold>
</Paragraph>
</FlowDocument>
</RichTextBox> Turn on the text cursor indicator from settings. Settings -> Accessibility -> Text Cursor |
@harshit7962 Apologies, didn't have the bandwidth yet, I'll take a look today/tomorrow max. to no longer delay this. |
checking now |
@harshit7962 I can repro on .NET 9 but not on .NET Framework, is that expected? |
@miloush so in the framework code, the But yeah, that change is not available since .NET core, and hence you would be able to repro it in any of the core versions. |
@harshit7962 Alright, I can see that calling However I'm not keen on spending more time on stuff I don't particularly understand right now (never worked with the internal document nodes - I have no idea why the Hopefully miloush knows more. |
OK I managed to have a look. This is the Normalize() method: wpf/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/TextRangeAdaptor.cs Lines 1489 to 1499 in d411234
MoveToInsertionPosition() is the bit that manipulates the start/end separately and can potentially break the invariant. Therefore, after that is done, you need to make sure that end >= start. Now in the context of this scenario, the only point where MoveToInsertionPosition() is called outside of Normalize is in GetBoundingRectangles(), before the actual rectangles are calculated: wpf/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/TextAdaptor.cs Lines 115 to 116 in d411234
So I suggest we enforce the invariant after these two lines that can break it, just like in Normalize(): if (start.CompareTo(end) > 0)
{
end.MoveToPosition(start);
} |
@miloush thanks for the suggestion. A quick couple of things though
Tbh I am still trying to reason the 2nd point out completely, so had just mentioned it here to get your inputs. |
The issue we had with the solution in this PR is that you calculated the rectangles first and then you moved around the pointers, so the caller would have rectangles that do not correspond to the pointers they have. It is quite important aspect of the situation that the caller and callee are different classes, the ownership of the pointers is critical to the bug. It is a bit evil that the method modifiers the pointers it receives. The solution is to establish a convention. Either the GetBoundingRectangles owns the pointers and is allowed to do what it wants with them, in which case it should be mentioned in the comments and if the caller has requirements on them such as upholding an invariant, it needs to deal with them being changed or call the method with a copy, or, we say that the caller owns the pointers and the method cannot touch them. In such case, the method should make a copy for its own use if it needs to change them. You are correct, the GetBoundingRectangles method itself does not need the invariant to hold, it doesn't know the two positions are part of a range. It is a better solution and separation of concerns to keep the ownership with the caller. Therefore a better solution than I suggested would be to add start = start.CreatePointer();
end = end.CreatePointer(); before wpf/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/TextAdaptor.cs Lines 115 to 116 in d411234
(if you want you can optimize it a bit because in some cases you are already creating copies above) This also voids your first question. |
Description
Using accessibility option of text cursor indicator in
RichTextBox
sometimes lead to crashing applications due to Invariant assert here. AddingNormalize()
call in changed location moves theend
to thestart
if this invariant assert fails and hence essentially corrects the cursor position.Customer Impact
Accessibility fix. Allowing the usage of text cursor indicator with RichTextBox.
Regression
None
Testing
Local Build Pass
Sample Application Testing
Test Pass
Risk
Low
Microsoft Reviewers: Open in CodeFlow