RESOLVED FIXED 45712
Add selectionchange event
https://bugs.webkit.org/show_bug.cgi?id=45712
Summary Add selectionchange event
Ryosuke Niwa
Reported Monday, September 13, 2010 11:21:36 PM UTC
There is currently no event for selection changes in the contenteditable area. However, many rich text editors want to detect selection change, and say change the state in their toolbar (e.g. bold, italic buttons) or do other operations based on new selection or based on how selection has changed.
Attachments
work in progress (6.40 KB, patch)
2010-10-19 18:36 PDT, Ryosuke Niwa
no flags
implements selectionchange event (18.44 KB, patch)
2010-10-20 16:15 PDT, Ryosuke Niwa
no flags
added onselectionchange to HTMLAttributesNames.idl (18.85 KB, patch)
2010-10-20 16:56 PDT, Ryosuke Niwa
no flags
async implementation (17.61 KB, patch)
2010-12-15 11:49 PST, Ryosuke Niwa
no flags
updated to apply on TOT (17.26 KB, patch)
2011-02-06 20:49 PST, Ryosuke Niwa
tkent: review+
Ojan Vafai
Comment 1 Monday, September 13, 2010 11:57:42 PM UTC
No need to restrict this to contentEditable regions. selectionChange events anywhere on the page are useful. Also, IE documentation claims to have this event already, but I remember there being issues with it: http://msdn.microsoft.com/en-us/library/ms536968(VS.85).aspx
Ryosuke Niwa
Comment 2 Wednesday, October 20, 2010 2:36:15 AM UTC
Created attachment 71240 [details] work in progress
Ryosuke Niwa
Comment 3 Wednesday, October 20, 2010 2:38:33 AM UTC
onselectionchange event is weird in that the event handler is always attached to document (even if it came from onselectionchange attribute of body element) and doesn't bubble.
Alexey Proskuryakov
Comment 4 Wednesday, October 20, 2010 9:34:09 PM UTC
The key to implementing this is making sure that we're ready for arbitrary DOM mutations after event dispatch. Synchronous events like this are a huge cause of WebKit crashes.
Ryosuke Niwa
Comment 5 Wednesday, October 20, 2010 11:32:36 PM UTC
(In reply to comment #4) > The key to implementing this is making sure that we're ready for arbitrary DOM mutations after event dispatch. Synchronous events like this are a huge cause of WebKit crashes. After making the event asynchronous, I just realized that SelectionController::setSelection already has a call to setFocusedNodeIfNeeded when the current selection is none. So dispatching one more event in this function should not introduce new vulnerability.
Alexey Proskuryakov
Comment 6 Wednesday, October 20, 2010 11:38:30 PM UTC
This is likely, but not a 100% guarantee of safety. The focus event isn't always dispatched, so we may only be prepared for DOM modifications in cases when it is.
Ryosuke Niwa
Comment 7 Thursday, October 21, 2010 12:15:36 AM UTC
Created attachment 71354 [details] implements selectionchange event
Ryosuke Niwa
Comment 8 Thursday, October 21, 2010 12:31:13 AM UTC
(In reply to comment #7) > Created an attachment (id=71354) [details] > implements selectionchange event The selectionchange event is already implemented by Internet Explorer (http://msdn.microsoft.com/en-us/library/ms536968(VS.85).aspx) and fired whenever selection is changed. The event is not cancelable and does not bubble. An event listener can be attached to a document by body element's onselectionchange attribute or via document.selectionchange. In this implementation, selection change event is fired inside SelectionController::setSelection, which is a function used to update selection in various parts of WebKit. Our implementation slightly differs from Internet Explorer in that we fire focus event before selection change event whereas Internet Explorer fires selectionchange event before focus event.
Early Warning System Bot
Comment 9 Thursday, October 21, 2010 12:43:34 AM UTC
Ryosuke Niwa
Comment 10 Thursday, October 21, 2010 12:56:54 AM UTC
Created attachment 71367 [details] added onselectionchange to HTMLAttributesNames.idl
Ryosuke Niwa
Comment 11 Thursday, October 21, 2010 1:15:15 AM UTC
(In reply to comment #6) > This is likely, but not a 100% guarantee of safety. The focus event isn't always dispatched, so we may only be prepared for DOM modifications in cases when it is. Do you think we should not add the event until we can make such a guarantee? I briefly looked at the call sites of setSelection and many call sites do modify DOM so they should be resilient to the changes in the DOM. However, there are simply too many to be "verified" in a reasonable amount of time. If your answer to this question is yes, it might make more sense to implement this event asynchronously.
Alexey Proskuryakov
Comment 12 Thursday, October 21, 2010 1:18:40 AM UTC
We certainly shouldn't add the event if there is a reason to believe that it introduces security bugs! I don't know this code well enough to have an informed opinion on the likelihood of that. I was just pointing out that the existence of focus event is not formally a guarantee that we aren't adding new problems.
Early Warning System Bot
Comment 13 Thursday, October 21, 2010 1:57:41 AM UTC
Eric Seidel (no email)
Comment 14 Thursday, October 21, 2010 7:11:32 AM UTC
Kent Tamura
Comment 15 Friday, October 22, 2010 8:17:13 AM UTC
Comment on attachment 71367 [details] added onselectionchange to HTMLAttributesNames.idl I agree with ap. For example, HTMLInputElement::setValue() updates selection only if the element has focus, and access a data member. If we apply this patch, HTMLInputElement::setValue() will have new crash. We need to check all of code paths to SelectionController::setSelection() if 'selectionchange' event is synchronous.
Ryosuke Niwa
Comment 16 Friday, October 22, 2010 9:25:48 PM UTC
(In reply to comment #12) > We certainly shouldn't add the event if there is a reason to believe that it introduces security bugs! > > I don't know this code well enough to have an informed opinion on the likelihood of that. I was just pointing out that the existence of focus event is not formally a guarantee that we aren't adding new problems. (In reply to comment #15) > (From update of attachment 71367 [details]) > I agree with ap. > For example, HTMLInputElement::setValue() updates selection only if the element has focus, and access a data member. If we apply this patch, HTMLInputElement::setValue() will have new crash. > We need to check all of code paths to SelectionController::setSelection() if 'selectionchange' event is synchronous. I talked with ojan & other folks today, and I think we want to add a setSelectionFiringEvent (tentative name) which calls the current setSelection and also fires the event. The event dispatch can be hidden behind ifdef and we can gradually migrate other caller to use the new version. That way, we can verify each change carefully rather than having to locate & fix 100+ possible call sites of setSelection.
Kent Tamura
Comment 17 Monday, October 25, 2010 3:20:51 AM UTC
> I talked with ojan & other folks today, and I think we want to add a setSelectionFiringEvent (tentative name) which calls the current setSelection and also fires the event. The event dispatch can be hidden behind ifdef and we can gradually migrate other caller to use the new version. That way, we can verify each change carefully rather than having to locate & fix 100+ possible call sites of setSelection. It sounds reasonable.
Darin Adler
Comment 18 Monday, October 25, 2010 5:37:46 PM UTC
(In reply to comment #16) > I talked with ojan & other folks today, and I think we want to add a setSelectionFiringEvent (tentative name) which calls the current setSelection and also fires the event. The event dispatch can be hidden behind ifdef and we can gradually migrate other caller to use the new version. That way, we can verify each change carefully rather than having to locate & fix 100+ possible call sites of setSelection. That seems like a strange plan. It means we’ll have an event that doesn’t fire correctly; depending on different end-user use patterns it will either work or fail. The project of changing all the setSelection call sites seems like a long one. Doesn’t seem OK to me. This doesn’t seem like the right way to make incremental progress on this task.
Ryosuke Niwa
Comment 19 Monday, October 25, 2010 6:40:51 PM UTC
(In reply to comment #18) > That seems like a strange plan. It means we’ll have an event that doesn’t fire correctly; depending on different end-user use patterns it will either work or fail. The project of changing all the setSelection call sites seems like a long one. > > Doesn’t seem OK to me. This doesn’t seem like the right way to make incremental progress on this task. But I don't think we can verify the safety of all 100+ call sites in one patch. It's true that we'll have half-broken event while we're implementing the event but the feature will be disabled by default so we won't expose it to users or to the Web.
Darin Adler
Comment 20 Monday, October 25, 2010 6:57:37 PM UTC
(In reply to comment #19) > (In reply to comment #18) > > That seems like a strange plan. It means we’ll have an event that doesn’t fire correctly; depending on different end-user use patterns it will either work or fail. The project of changing all the setSelection call sites seems like a long one. > > > > Doesn’t seem OK to me. This doesn’t seem like the right way to make incremental progress on this task. > > But I don't think we can verify the safety of all 100+ call sites in one patch. It's true that we'll have half-broken event while we're implementing the event but the feature will be disabled by default so we won't expose it to users or to the Web. An incremental approach is good, but the right incremental approach is one we can test. Changing one call site at a time won’t do any good unless we have actual test cases that change the DOM in response to the event. I think we should take an incremental approach with testing, then turn the event sending on once we can pass all the tests. Sending the event in certain places just invites us to ship broken software and helps create an illusion that we are testing incrementally.
Ryosuke Niwa
Comment 21 Monday, October 25, 2010 7:22:16 PM UTC
(In reply to comment #20) > An incremental approach is good, but the right incremental approach is one we can test. Changing one call site at a time won’t do any good unless we have actual test cases that change the DOM in response to the event. I think the point of breaking into smaller pieces is so that we can verify adding this event doesn't make a new security hole. It's not necessary that we want to incrementally implement the event. > Sending the event in certain places just invites us to ship broken software and helps create an illusion that we are testing incrementally. If you're talking about the completeness, then we can easily ensure it by simply deprecating (renaming or removing) the old function once we replaced all calls to setSelection. It's true that we won't be testing the safety of firing event while adding the event but I don't think we can test it. FYI, https://bug-45712-attachments.webkit.org/attachment.cgi?id=71367 implements selection change event in one patch without verifying the safety of call sites.
Darin Adler
Comment 22 Monday, October 25, 2010 9:05:09 PM UTC
(In reply to comment #21) > I think the point of breaking into smaller pieces is so that we can verify adding this event doesn't make a new security hole. It's not necessary that we want to incrementally implement the event. The verification comes from the testing, though, not the way we stage calls to each call site.
Ryosuke Niwa
Comment 23 Monday, October 25, 2010 11:09:08 PM UTC
(In reply to comment #22) > (In reply to comment #21) > > I think the point of breaking into smaller pieces is so that we can verify adding this event doesn't make a new security hole. It's not necessary that we want to incrementally implement the event. > > The verification comes from the testing, though, not the way we stage calls to each call site. But how do we add a feature incrementally if we were to expose it to DRT? Should we add a preference / setting that enables onselectionchange event?
Ojan Vafai
Comment 24 Monday, October 25, 2010 11:20:46 PM UTC
(In reply to comment #23) > (In reply to comment #22) > > (In reply to comment #21) > > > I think the point of breaking into smaller pieces is so that we can verify adding this event doesn't make a new security hole. It's not necessary that we want to incrementally implement the event. > > > > The verification comes from the testing, though, not the way we stage calls to each call site. > > But how do we add a feature incrementally if we were to expose it to DRT? Should we add a preference / setting that enables onselectionchange event? What's the problem with just using a #IF? Darin is just saying we shouldn't do something incremental. 1. Add the code change with enclosed in a #IF. 2. Add test cases for all the places where we set the selection to make sure we don't crash. 3. When you have reasonable confidence, remove the #IF. During step 2, there may be cases where tests are technically failing, but it's appropriate in those cases to check in a failing expectation.
Ryosuke Niwa
Comment 25 Monday, October 25, 2010 11:53:04 PM UTC
(In reply to comment #24) > 1. Add the code change with enclosed in a #IF. > 2. Add test cases for all the places where we set the selection to make sure we don't crash. > 3. When you have reasonable confidence, remove the #IF. > > During step 2, there may be cases where tests are technically failing, but it's appropriate in those cases to check in a failing expectation. How do make a test in step 2? WebKit doesn't crash without selectionchange event. How do we write a test for a crash that doesn't exist? Of course, I agree that we can and should write tests if we're modifying the existing code to avoid crashes.
Ojan Vafai
Comment 26 Monday, October 25, 2010 11:57:14 PM UTC
(In reply to comment #25) > (In reply to comment #24) > > 1. Add the code change with enclosed in a #IF. > > 2. Add test cases for all the places where we set the selection to make sure we don't crash. > > 3. When you have reasonable confidence, remove the #IF. > > > > During step 2, there may be cases where tests are technically failing, but it's appropriate in those cases to check in a failing expectation. > > How do make a test in step 2? WebKit doesn't crash without selectionchange event. How do we write a test for a crash that doesn't exist? Of course, I agree that we can and should write tests if we're modifying the existing code to avoid crashes. It shouldn't crash with or without the event, right? So you can write the test and check it in. When actually working on this, you'll need to modify your build locally to enable the event. The bots won't test the code until the feature is enable. This is how it is with every feature that's enabled behind a define.
Ryosuke Niwa
Comment 27 Tuesday, October 26, 2010 12:09:46 AM UTC
(In reply to comment #26) > It shouldn't crash with or without the event, right? So you can write the test and check it in. When actually working on this, you'll need to modify your build locally to enable the event. The bots won't test the code until the feature is enable. > Sure, but how do we write a test that might cause a crash under correct circumstances? I mean just attaching an event listener surely doesn't crash WebKit even in my native implementation. Maybe we should modify DOM in such a way to cause a crash? But how do we know how to cause a crash if we didn't make any change to the existing code (i.e. we believe that firing event is safe without modification)? Do we guess a sequence of DOM modifications that may or may not cause crash?
Ojan Vafai
Comment 28 Tuesday, October 26, 2010 12:15:55 AM UTC
(In reply to comment #27) > Sure, but how do we write a test that might cause a crash under correct circumstances? I mean just attaching an event listener surely doesn't crash WebKit even in my native implementation. Maybe we should modify DOM in such a way to cause a crash? But how do we know how to cause a crash if we didn't make any change to the existing code (i.e. we believe that firing event is safe without modification)? Do we guess a sequence of DOM modifications that may or may not cause crash? You can't capture every possible codepath, but you can look at each place where we use setSelection and see what expectations the code following the setSelection call expects of the state of the DOM. Then write tests for those cases. You're never going to get 100% confidence, but you'd like to have reasonable confidence that you're not introducing crashes.
Alexey Proskuryakov
Comment 29 Tuesday, October 26, 2010 12:24:50 AM UTC
Maybe document.open() to remove all document nodes, and then force garbage collection?
Ryosuke Niwa
Comment 30 Thursday, October 28, 2010 1:04:24 AM UTC
(In reply to comment #28) > You can't capture every possible codepath, but you can look at each place where we use setSelection and see what expectations the code following the setSelection call expects of the state of the DOM. Then write tests for those cases. You're never going to get 100% confidence, but you'd like to have reasonable confidence that you're not introducing crashes. (In reply to comment #29) > Maybe document.open() to remove all document nodes, and then force garbage collection? That sounds good to me. We still do want to implement it incrementally, correct?
Sam Weinig
Comment 31 Wednesday, November 10, 2010 12:02:34 AM UTC
It is not clear to me why the option of implementing this asynchronously has been dropped from discussion. Give that we wish to move away from synchronous mutation events, I am not sure it makes sense to add new events that could have many of the same issues.
Ryosuke Niwa
Comment 32 Wednesday, November 10, 2010 5:40:16 PM UTC
Erik, Maciej, Sam, & I talked on IRC, and came to the consensus that we should impalement selection change event as an asynchronous event to make it easier to verify that we're not making new security vulnerabilities. Because selection change event is not cancelable and does not bubble, it doesn't need to be synchronous. We haven't agreed on the exact method to implement the event, however. One major problem with completely asynchronous implementation of selection change event using a timer is that when a script modifies the selection by some function, say selectAllChildren, it may expect the corresponding selection change event to be fired before we return from the function, namely selectAllChildren in this case. If the script had attached an event handler to the selection change event, it may also expect certain global state or DOM be updated accordingly by this event handler and the fact we implemented selection change event asynchronously will be visible and developers may need to work-around this issue. To reconcile this problem, Erik suggested that we queue up events and fire them all when we cross WebCore and JavaScriptCore/V8 boundary. I suggested that we can also use this solution to resolve the bug 46936 since this approach hides the fact it's implemented asynchronously. Maciej suggested an alternative approach using a scope object but I didn't quite get what he really meant. Maciej & Sam, could you elaborate on how scoping object works?
Ryosuke Niwa
Comment 33 Tuesday, November 16, 2010 1:20:11 AM UTC
I talked with Maciej about his scoping object idea. Here's summary: We define a class, let's say SelectionOperationScope, which is used as a RAII object. SelectionOperationScope has a static counter which tracks the level of nesting. When there's at least one level of nesting, we'll queue up events, and when the nesting level goes to 0, we'll fire the queued events. We can define this scope object in functions that should prohibit event firing. For example, all editing commands will define this object.
Darin Adler
Comment 34 Tuesday, November 16, 2010 1:30:37 AM UTC
(In reply to comment #33) > We can define this scope object in functions that should prohibit event firing. For example, all editing commands will define this object. I’d use the word “defer” here rather than “prohibit”.
Ryosuke Niwa
Comment 35 Tuesday, November 16, 2010 7:17:31 AM UTC
(In reply to comment #34) > (In reply to comment #33) > > We can define this scope object in functions that should prohibit event firing. For example, all editing commands will define this object. > > I’d use the word “defer” here rather than “prohibit”. Right, that's a better word here. Anyway, here's my thought about implementing this event asynchronously. I'm currently in a conversation with other Googlers who are familiar with challenges in RTE applications, and I've got the feeling that what we really want is selectstart event (http://msdn.microsoft.com/en-us/library/ms536969(VS.85).aspx). This event is cancellable and bubbles so it can't be implemented as an asynchronous event. If we were ever to support selectstart event, then there isn't really a point in implementing selectionchange event asynchronously because we'll run into the same problem when we implement selectstart event. Any thoughts?
Ojan Vafai
Comment 36 Thursday, November 18, 2010 1:13:39 AM UTC
(In reply to comment #35) > If we were ever to support selectstart event, then there isn't really a point in implementing selectionchange event asynchronously because we'll run into the same problem when we implement selectstart event. selectionchange is useful even if we add a beforeselectionchange-style event. I think we should go ahead and implement it async. We can make it sync later if we really want to. Google's rich text library (http://closuretools.blogspot.com/2010/07/introducing-closure-library-editor.html) had a custom selectionchange event (using mouse and key events) that fires before the selection is changed. That library could be ported to use an async selectionchange event if it had a property that showed what the selection was before it changed.
Ryosuke Niwa
Comment 37 Wednesday, December 15, 2010 7:49:34 PM UTC
Created attachment 76675 [details] async implementation
Ryosuke Niwa
Comment 38 Friday, January 7, 2011 3:44:57 AM UTC
Could someone review my patch?
Ryosuke Niwa
Comment 39 Monday, February 7, 2011 4:49:38 AM UTC
Created attachment 81446 [details] updated to apply on TOT
Ryosuke Niwa
Comment 40 Thursday, February 17, 2011 7:55:05 AM UTC
Can someone review my patch? Or give me a feedback?
Ryosuke Niwa
Comment 41 Monday, February 21, 2011 11:24:57 AM UTC
Tim Down
Comment 42 Monday, April 30, 2012 12:41:29 PM UTC
The upshot is that the selectionchange event fires asynchronously, which leaves web developers (such as me, right now) with precisely the problem Ryosuke described in comment #32. Is there a solution that doesn't involve timers?
Ryosuke Niwa
Comment 43 Monday, April 30, 2012 4:50:33 PM UTC
(In reply to comment #42) > The upshot is that the selectionchange event fires asynchronously, which leaves web developers (such as me, right now) with precisely the problem Ryosuke described in comment #32. Is there a solution that doesn't involve timers? Unfortunately the ship has sailed already. I don't think we can change our behavior at this point.
Tim Down
Comment 44 Monday, April 30, 2012 5:10:11 PM UTC
(In reply to comment #43) > (In reply to comment #42) > > The upshot is that the selectionchange event fires asynchronously, which leaves web developers (such as me, right now) with precisely the problem Ryosuke described in comment #32. Is there a solution that doesn't involve timers? > > Unfortunately the ship has sailed already. I don't think we can change our behavior at this point. Yep. A dirty, dirty timer it is then. The only options I can think of to improve matters are: - a new event that is the same as selectionchange except that it fires only in response to a direct user action changing the selection, although I think it could be hard to pin down what should and shouldn't fire such an event. I don't really like it. - a new property of the selectionchange event object that indicates what kind of action initiated the event (e.g. mouse drag, menu option, keyboard, execCommand, method of Selection object)
Ryosuke Niwa
Comment 45 Monday, April 30, 2012 7:18:26 PM UTC
(In reply to comment #44) > - a new event that is the same as selectionchange except that it fires only in response to a direct user action changing the selection, although I think it could be hard to pin down what should and shouldn't fire such an event. I don't really like it. selectstart is like that although it fires before the selection changes. > - a new property of the selectionchange event object that indicates what kind of action initiated the event (e.g. mouse drag, menu option, keyboard, execCommand, method of Selection object) Possible. We should probably discuss this on a standard mailing list first.
Ojan Vafai
Comment 46 Monday, April 30, 2012 7:35:17 PM UTC
(In reply to comment #43) > (In reply to comment #42) > > The upshot is that the selectionchange event fires asynchronously, which leaves web developers (such as me, right now) with precisely the problem Ryosuke described in comment #32. Is there a solution that doesn't involve timers? > > Unfortunately the ship has sailed already. I don't think we can change our behavior at this point. Since this event is sync in IE, I expect we could get away with making it sync (but deferred, the way we defer mutation events) in WebKit without backwards compat issues. I don't have strong opinions on which way we implement it. In either case, the editing spec should probably cover this.
Aryeh Gregor
Comment 47 Tuesday, May 1, 2012 11:31:15 AM UTC
There's an open spec bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=13952 As always, if you're interested in working on this and are blocked by a spec bug, please say so explicitly on the bug and I'll see if I can prioritize it.
Note You need to log in before you can comment on or make changes to this bug.