DOM - implement selection events (fire selectstart and selectionchange events on the input node when the selection in that editor changes)

This commit is contained in:
janekptacijarabaci
2017-07-05 02:15:40 +02:00
committed by Roy Tam
parent c9c922e580
commit 6ba8f06d1a
4 changed files with 240 additions and 16 deletions
+5
View File
@@ -92,6 +92,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).
@@ -1208,6 +1211,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,28 +67,62 @@
});
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,
aYOffset)
{
if (aType == "click") { // You can simulate a click event by sending undefined
aType = undefined;
}
synthesizeMouse(aElement, aOffset, 10, { type: aType });
if (!aYOffset) {
aYOffset = 10;
}
synthesizeMouse(aElement, aOffset, aYOffset, { 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();
}
@@ -105,8 +146,12 @@
}
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");
}
// Make sure setting the element to contentEditable doesn't cause any selectionchange events
yield* contentEditableAction(elt("ce"), true, 0, 0, 0, 0);
@@ -156,7 +201,7 @@
// Wiggling the mouse a little such that it doesn't select any
// characters shouldn't trigger a selection
yield* mouseAction(aElement, 49, "mousemove", 0, 0);
yield* mouseAction(aElement, 50, "mousemove", 0, 0, 0, 0, 11);
isCollapsed();
// Moving the mouse again from an empty selection should trigger a
@@ -257,6 +302,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"), 50, "mousemove", 0, 0, 0, 0, 11);
// 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"), 50, "mousemove", 0, 0, 0, 0, 11);
// 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>
+8 -2
View File
@@ -46,6 +46,7 @@
#include "nsStyleSet.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/MathAlgorithms.h"
#include "nsFrameSelection.h"
#define DEFAULT_COLUMN_WIDTH 20
@@ -258,6 +259,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()
@@ -287,8 +295,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());
+42 -7
View File
@@ -3674,8 +3674,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);
@@ -6330,7 +6336,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++) {
@@ -6351,11 +6358,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();
}