mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-24 02:35:41 +00:00
Bug 1196479 - Fire selectstart and selectionchange events on the input node when the selection in that editor changes. r=ehsan
This commit is contained in:
parent
d5934c389f
commit
931d0d32a7
@ -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()) {
|
||||
|
@ -15,6 +15,10 @@
|
||||
This is a random block of text
|
||||
</div>
|
||||
|
||||
<input type="text" id="input" value="XXXXXXXXXXXXXXXXXXX" width="200"> <br>
|
||||
|
||||
<textarea id="textarea" width="200">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</textarea>
|
||||
|
||||
<script>
|
||||
// Call the testing methods from the parent window
|
||||
var is = parent.is;
|
||||
@ -36,6 +40,9 @@
|
||||
|
||||
var selectstart = 0;
|
||||
var selectionchange = 0;
|
||||
var inputSelectionchange = 0;
|
||||
var textareaSelectionchange = 0;
|
||||
|
||||
var cancel = false;
|
||||
var selectstartTarget = null;
|
||||
|
||||
@ -60,34 +67,69 @@
|
||||
});
|
||||
|
||||
function elt(aId) { return document.getElementById(aId); }
|
||||
function reset() { selectstart = 0; selectionchange = 0; cancel = false; }
|
||||
function reset() {
|
||||
selectstart = 0;
|
||||
selectionchange = 0;
|
||||
inputSelectionchange = 0;
|
||||
textareaSelectionchange = 0;
|
||||
cancel = false;
|
||||
}
|
||||
|
||||
function* mouseAction(aElement, aOffset, aType, aSelStart, aSelChng) {
|
||||
elt("input").addEventListener('selectionchange', function(aEvent) {
|
||||
is (aEvent.originalTarget, elt("input"),
|
||||
"The original target of selectionchange should be the input");
|
||||
console.log(inputSelectionchange);
|
||||
inputSelectionchange++;
|
||||
});
|
||||
elt("textarea").addEventListener('selectionchange', function(aEvent) {
|
||||
is (aEvent.originalTarget, elt("textarea"),
|
||||
"The original target of selectionchange should be the textarea");
|
||||
console.log(textareaSelectionchange);
|
||||
textareaSelectionchange++;
|
||||
});
|
||||
function* mouseAction(aElement, aOffset, aType,
|
||||
aSelStart, aSelChng, aISelChng, aTSelChng)
|
||||
{
|
||||
if (aType == "click") { // You can simulate a click event by sending undefined
|
||||
aType = undefined;
|
||||
}
|
||||
synthesizeMouse(aElement, aOffset, 10, { type: aType });
|
||||
yield spin();
|
||||
|
||||
is(selectstart, aSelStart,
|
||||
"SelStart Mouse Action (" + aOffset + " - " + aType + ")");
|
||||
is(selectionchange, aSelChng,
|
||||
"SelChng Mouse Action (" + aOffset + " - " + aType + ")");
|
||||
is(inputSelectionchange, aISelChng || 0,
|
||||
"ISelChng Mouse Action (" + aOffset + " - " + aType + ")");
|
||||
is(textareaSelectionchange, aTSelChng || 0,
|
||||
"TSelChng Mouse Action (" + aOffset + " - " + aType + ")");
|
||||
reset();
|
||||
}
|
||||
|
||||
function* keyAction(aKey, aShift, aAccel, aSelStart, aSelChng) {
|
||||
function* keyAction(aKey, aShift, aAccel,
|
||||
aSelStart, aSelChng, aISelChng, aTSelChng)
|
||||
{
|
||||
synthesizeKey(aKey, { shiftKey: aShift, accelKey: aAccel });
|
||||
yield spin();
|
||||
is(selectstart, aSelStart,
|
||||
"SelStart Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
|
||||
is(selectionchange, aSelChng,
|
||||
"SelChng Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
|
||||
is(inputSelectionchange, aISelChng || 0,
|
||||
"ISelChng Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
|
||||
is(textareaSelectionchange, aTSelChng || 0,
|
||||
"TSelChng Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
|
||||
reset();
|
||||
}
|
||||
|
||||
var selection = document.getSelection();
|
||||
function isCollapsed() { is(selection.isCollapsed, true, "Selection is collapsed"); }
|
||||
function isNotCollapsed() { is(selection.isCollapsed, false, "Selection is not collapsed"); }
|
||||
function isCollapsed() {
|
||||
is(selection.isCollapsed, true, "Selection is collapsed");
|
||||
}
|
||||
function isNotCollapsed() {
|
||||
is(selection.isCollapsed, false, "Selection is not collapsed");
|
||||
}
|
||||
|
||||
// Focus the contenteditable text
|
||||
yield* mouseAction(elt("ce"), 100, "click", 0, 1);
|
||||
@ -229,6 +271,139 @@
|
||||
is(selectionchange, 1, "Synthesized range mutations should change selectionchange");
|
||||
reset();
|
||||
isNotCollapsed();
|
||||
|
||||
// Remove the range
|
||||
s.removeAllRanges();
|
||||
yield spin();
|
||||
is(selectstart, 0, "Synthesized range removal");
|
||||
is(selectionchange, 1, "Synthesized range removal");
|
||||
reset();
|
||||
isCollapsed();
|
||||
|
||||
|
||||
/*
|
||||
Selection events mouse move on input type=text
|
||||
*/
|
||||
|
||||
// Select a region
|
||||
|
||||
yield* mouseAction(elt("input"), 50, "mousedown", 0, 1, 1, 0);
|
||||
|
||||
selectstartTarget = elt("input");
|
||||
yield* mouseAction(elt("input"), 100, "mousemove", 1, 0, 1, 0);
|
||||
|
||||
// Moving it more shouldn't trigger a start (move back to empty)
|
||||
yield* mouseAction(elt("input"), 75, "mousemove", 0, 0, 1, 0);
|
||||
yield* mouseAction(elt("input"), 50, "mousemove", 0, 0, 1, 0);
|
||||
|
||||
// Wiggling the mouse a little such that it doesn't select any
|
||||
// characters shouldn't trigger a selection
|
||||
yield* mouseAction(elt("input"), 49, "mousemove", 0, 0, 0, 0);
|
||||
|
||||
// Moving the mouse again from an empty selection should trigger a
|
||||
// selectstart
|
||||
selectstartTarget = elt("input");
|
||||
yield* mouseAction(elt("input"), 25, "mousemove", 1, 0, 1, 0);
|
||||
|
||||
// Releasing the mouse shouldn't do anything
|
||||
yield* mouseAction(elt("input"), 25, "mouseup", 0, 0, 0, 0);
|
||||
|
||||
// And neither should moving your mouse around when the mouse
|
||||
// button isn't pressed
|
||||
yield* mouseAction(elt("input"), 50, "mousemove", 0, 0, 0, 0);
|
||||
|
||||
// Clicking in an random location should move the selection, but
|
||||
// not perform a selectstart
|
||||
yield* mouseAction(elt("input"), 50, "click", 0, 0, 1, 0);
|
||||
|
||||
// Clicking there again should do nothing
|
||||
yield* mouseAction(elt("input"), 50, "click", 0, 0, 0, 0);
|
||||
|
||||
// Selecting a region, and canceling the selectstart should mean that the
|
||||
// selection remains collapsed
|
||||
yield* mouseAction(elt("input"), 75, "mousedown", 0, 0, 1, 0);
|
||||
cancel = true;
|
||||
selectstartTarget = elt("input");
|
||||
yield* mouseAction(elt("input"), 100, "mousemove", 1, 0, 1, 0);
|
||||
yield* mouseAction(elt("input"), 100, "mouseup", 0, 0, 0, 0);
|
||||
|
||||
|
||||
// Select a region
|
||||
// XXX For some reason we fire 2 selectchange events on the body
|
||||
// when switching from the input to the text area.
|
||||
yield* mouseAction(elt("textarea"), 50, "mousedown", 0, 2, 0, 1);
|
||||
|
||||
selectstartTarget = elt("textarea");
|
||||
yield* mouseAction(elt("textarea"), 100, "mousemove", 1, 0, 0, 1);
|
||||
|
||||
// Moving it more shouldn't trigger a start (move back to empty)
|
||||
yield* mouseAction(elt("textarea"), 75, "mousemove", 0, 0, 0, 1);
|
||||
yield* mouseAction(elt("textarea"), 50, "mousemove", 0, 0, 0, 1);
|
||||
|
||||
// Wiggling the mouse a little such that it doesn't select any
|
||||
// characters shouldn't trigger a selection
|
||||
yield* mouseAction(elt("textarea"), 49, "mousemove", 0, 0, 0, 0);
|
||||
|
||||
// Moving the mouse again from an empty selection should trigger a
|
||||
// selectstart
|
||||
selectstartTarget = elt("textarea");
|
||||
yield* mouseAction(elt("textarea"), 25, "mousemove", 1, 0, 0, 1);
|
||||
|
||||
// Releasing the mouse shouldn't do anything
|
||||
yield* mouseAction(elt("textarea"), 25, "mouseup", 0, 0, 0, 0);
|
||||
|
||||
// And neither should moving your mouse around when the mouse
|
||||
// button isn't pressed
|
||||
yield* mouseAction(elt("textarea"), 50, "mousemove", 0, 0, 0, 0);
|
||||
|
||||
// Clicking in an random location should move the selection, but not perform a
|
||||
// selectstart
|
||||
yield* mouseAction(elt("textarea"), 50, "click", 0, 0, 0, 1);
|
||||
|
||||
// Clicking there again should do nothing
|
||||
yield* mouseAction(elt("textarea"), 50, "click", 0, 0, 0, 0);
|
||||
|
||||
// Selecting a region, and canceling the selectstart should mean that the
|
||||
// selection remains collapsed
|
||||
yield* mouseAction(elt("textarea"), 75, "mousedown", 0, 0, 0, 1);
|
||||
cancel = true;
|
||||
selectstartTarget = elt("textarea");
|
||||
yield* mouseAction(elt("textarea"), 100, "mousemove", 1, 0, 0, 1);
|
||||
yield* mouseAction(elt("textarea"), 100, "mouseup", 0, 0, 0, 0);
|
||||
|
||||
// Marking the input and textarea as display: none and then as visible again
|
||||
// shouldn't trigger any changes, although the nodes will be re-framed
|
||||
elt("input").setAttribute("style", "display: none;");
|
||||
yield spin();
|
||||
is(selectstart, 0, "idn - ss 1");
|
||||
is(selectionchange, 0, "idn - sc 1");
|
||||
is(inputSelectionchange, 0, "idn - isc 1");
|
||||
is(textareaSelectionchange, 0, "idn - tsc 1");
|
||||
reset();
|
||||
|
||||
elt("input").setAttribute("style", "");
|
||||
yield spin();
|
||||
is(selectstart, 0, "idn - ss 2");
|
||||
is(selectionchange, 0, "idn - sc 2");
|
||||
is(inputSelectionchange, 0, "idn - isc 2");
|
||||
is(textareaSelectionchange, 0, "idn - tsc 2");
|
||||
reset();
|
||||
|
||||
elt("textarea").setAttribute("style", "display: none;");
|
||||
yield spin();
|
||||
is(selectstart, 0, "tdn - ss 1");
|
||||
is(selectionchange, 0, "tdn - sc 1");
|
||||
is(inputSelectionchange, 0, "tdn - isc 1");
|
||||
is(textareaSelectionchange, 0, "tdn - tsc 1");
|
||||
reset();
|
||||
|
||||
elt("textarea").setAttribute("style", "");
|
||||
yield spin();
|
||||
is(selectstart, 0, "tdn - ss 2");
|
||||
is(selectionchange, 0, "tdn - sc 2");
|
||||
is(inputSelectionchange, 0, "tdn - isc 2");
|
||||
is(textareaSelectionchange, 0, "tdn - tsc 2");
|
||||
reset();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@ -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.
|
||||
|
@ -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<nsITextControlElement> 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<nsITextControlElement> 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());
|
||||
|
@ -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<nsINode> 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<Selection> sel = static_cast<Selection*>(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<nsIDocument> 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<nsINode> 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<nsIContent> 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<nsIDocument> doc = do_QueryInterface(aDoc);
|
||||
target = doc.forget();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(doc, NS_LITERAL_STRING("selectionchange"), false);
|
||||
new AsyncEventDispatcher(target, NS_LITERAL_STRING("selectionchange"), false);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user