From 931d0d32a785b177362c019634b12a494369c6a4 Mon Sep 17 00:00:00 2001 From: Michael Layzell Date: Wed, 2 Sep 2015 13:39:39 -0400 Subject: [PATCH] Bug 1196479 - Fire selectstart and selectionchange events on the input node when the selection in that editor changes. r=ehsan --- dom/html/nsTextEditorState.cpp | 5 + .../mochitest/general/frameSelectEvents.html | 185 +++++++++++++++++- dom/webidl/Document.webidl | 2 - layout/forms/nsTextControlFrame.cpp | 10 +- layout/generic/nsSelection.cpp | 50 ++++- 5 files changed, 235 insertions(+), 17 deletions(-) diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index 8e3fe8696c63..6a249f1abc91 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -93,6 +93,9 @@ public: return NS_OK; } + AutoHideSelectionChanges hideSelectionChanges + (mFrame->GetConstFrameSelection()); + if (mFrame) { // SetSelectionRange leads to Selection::AddRange which flushes Layout - // need to block script to avoid nested PrepareEditor calls (bug 642800). @@ -1248,6 +1251,8 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue) return NS_OK; } + AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection()); + // Don't attempt to initialize recursively! InitializationGuard guard(*this); if (guard.IsInitializingRecursively()) { diff --git a/dom/tests/mochitest/general/frameSelectEvents.html b/dom/tests/mochitest/general/frameSelectEvents.html index c21105215399..2d7b5176be93 100644 --- a/dom/tests/mochitest/general/frameSelectEvents.html +++ b/dom/tests/mochitest/general/frameSelectEvents.html @@ -15,6 +15,10 @@ This is a random block of text +
+ + + diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl index 2f48f944e4a2..4cc7256f1f3b 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -152,8 +152,6 @@ partial interface Document { attribute EventHandler onbeforescriptexecute; attribute EventHandler onafterscriptexecute; - [Pref="dom.select_events.enabled"] - attribute EventHandler onselectionchange; /** * True if this document is synthetic : stand alone image, video, audio file, * etc. diff --git a/layout/forms/nsTextControlFrame.cpp b/layout/forms/nsTextControlFrame.cpp index ea4d48f11a6d..96f1137c1fb3 100644 --- a/layout/forms/nsTextControlFrame.cpp +++ b/layout/forms/nsTextControlFrame.cpp @@ -46,6 +46,7 @@ #include "nsStyleSet.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/MathAlgorithms.h" +#include "nsFrameSelection.h" #define DEFAULT_COLUMN_WIDTH 20 @@ -259,6 +260,13 @@ nsTextControlFrame::EnsureEditorInitialized() // Make sure that editor init doesn't do things that would kill us off // (especially off the script blockers it'll create for its DOM mutations). { + nsCOMPtr txtCtrl = do_QueryInterface(GetContent()); + MOZ_ASSERT(txtCtrl, "Content not a text control element"); + + // Hide selection changes during the initialization, as webpages should not + // be aware of these initializations + AutoHideSelectionChanges hideSelectionChanges(txtCtrl->GetConstFrameSelection()); + nsAutoScriptBlocker scriptBlocker; // Time to mess with our security context... See comments in GetValue() @@ -288,8 +296,6 @@ nsTextControlFrame::EnsureEditorInitialized() #endif // Create an editor for the frame, if one doesn't already exist - nsCOMPtr txtCtrl = do_QueryInterface(GetContent()); - NS_ASSERTION(txtCtrl, "Content not a text control element"); nsresult rv = txtCtrl->CreateEditor(); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_STATE(weakFrame.IsAlive()); diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index e9f68b8aa32a..898bd7e520a6 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -3725,8 +3725,14 @@ Selection::AddItem(nsRange* aItem, int32_t* aOutIndex, bool aNoStartSelect) // and the selection is becoming uncollapsed, and this is caused by a user // initiated event. bool defaultAction = true; - nsContentUtils::DispatchTrustedEvent(GetParentObject(), - aItem->GetStartParent(), + + // Get the first element which isn't in a native anonymous subtree + nsCOMPtr target = aItem->GetStartParent(); + while (target && target->IsInNativeAnonymousSubtree()) { + target = target->GetParent(); + } + + nsContentUtils::DispatchTrustedEvent(GetParentObject(), target, NS_LITERAL_STRING("selectstart"), true, true, &defaultAction); @@ -6377,7 +6383,6 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(SelectionChangeListener) NS_IMPL_CYCLE_COLLECTING_RELEASE(SelectionChangeListener) - NS_IMETHODIMP SelectionChangeListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel, int16_t aReason) @@ -6387,7 +6392,8 @@ SelectionChangeListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsRefPtr sel = static_cast(aSel); // Check if the ranges have actually changed - if (mOldRanges.Length() == sel->RangeCount()) { + // Don't bother checking this if we are hiding changes. + if (mOldRanges.Length() == sel->RangeCount() && !sel->IsBlockingSelectionChangeEvents()) { bool changed = false; for (size_t i = 0; i < mOldRanges.Length(); i++) { @@ -6408,11 +6414,39 @@ SelectionChangeListener::NotifySelectionChanged(nsIDOMDocument* aDoc, mOldRanges.AppendElement(RawRangeData(sel->GetRangeAt(i))); } - // Actually fire off the event - nsCOMPtr doc = do_QueryInterface(aDoc); - if (doc) { + // If we are hiding changes, then don't do anything else. We do this after we + // update mOldRanges so that changes after the changes stop being hidden don't + // incorrectly trigger a change, even though they didn't change anything + if (sel->IsBlockingSelectionChangeEvents()) { + return NS_OK; + } + + nsCOMPtr target; + + // Check if we should be firing this event to a different node than the + // document. The limiter of the nsFrameSelection will be within the native + // anonymous subtree of the node we want to fire the event on. We need to + // climb up the parent chain to escape the native anonymous subtree, and then + // fire the event. + if (nsFrameSelection* fs = sel->GetFrameSelection()) { + if (nsCOMPtr root = fs->GetLimiter()) { + while (root && root->IsInNativeAnonymousSubtree()) { + root = root->GetParent(); + } + + target = root.forget(); + } + } + + // If we didn't get a target before, we can instead fire the event at the document. + if (!target) { + nsCOMPtr doc = do_QueryInterface(aDoc); + target = doc.forget(); + } + + if (target) { nsRefPtr asyncDispatcher = - new AsyncEventDispatcher(doc, NS_LITERAL_STRING("selectionchange"), false); + new AsyncEventDispatcher(target, NS_LITERAL_STRING("selectionchange"), false); asyncDispatcher->PostDOMEvent(); }