зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1528289 - part 1: Move selection at middle button down rather than middle button up r=edgar
Chrome and Safari move selection at middle button down and does not modify the range at middle button up. However, they handle middle button down with `Shift` key is "continue selection". So, we should handle selection in nsIFrame when `mousedown` event for middle mouse button is fired, but ignore multiple selection, drag and drop and maintaining selection for aligning the behavior to the other browsers. This patch splits `nsIFrame::HandlePress()` and calls new method which moves selection from `nsIFrame::HandleEvent()` when middle button is pressed. (Note that this patch does not check whether middle click paste is enabled because Chrome moves selection even on Windows which Chrome always disable middle click paste on.) With this change, "paste" event target is changed. Previously, we used target of the preceding `mouseup` event, but we start to use the target of the preceding `mousedown` event. Note that even with this patch, we still behave differently from Chrome even in the following cases: - middle mouse button down in selected range, we collapse it, but Chrome keeps the selection. - middle mouse button click in selected range, we dispatch "paste" event, but Chrome collapse selection and not dispatch "paste" event. - middle mouse button down in selected range and up in different element, Chrome does not modify the range nor dispatch "paste" event. - Shift + middle mouse button in editable `<table>` works as non-editable in Chrome, but our editor handles it with special path. Therefore, we don't modify the range but dispatch "paste" event in the selected range. Changing them requires bigger change and probably requires some other features' behavior changes. Therefore, we shouldn't touch these issues until they are actually reported as web-compat issues. Differential Revision: https://phabricator.services.mozilla.com/D103997
This commit is contained in:
Родитель
a2f8d0f50b
Коммит
2ad57f8fae
|
@ -5247,20 +5247,8 @@ nsresult EventStateManager::HandleMiddleClickPaste(
|
|||
}
|
||||
}
|
||||
|
||||
// Move selection to the clicked point.
|
||||
nsCOMPtr<nsIContent> container;
|
||||
int32_t offset;
|
||||
nsLayoutUtils::GetContainerAndOffsetAtEvent(
|
||||
aPresShell, aMouseEvent, getter_AddRefs(container), &offset);
|
||||
if (container) {
|
||||
// XXX If readonly or disabled <input> or <textarea> in contenteditable
|
||||
// designMode editor is clicked, the point is in the editor.
|
||||
// However, outer HTMLEditor and Selection should handle it.
|
||||
// So, in such case, Selection::Collapse() will fail.
|
||||
DebugOnly<nsresult> rv = selection->CollapseInLimiter(container, offset);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Failed to collapse Selection at middle clicked");
|
||||
}
|
||||
// Don't modify selection here because we've already set caret to the point
|
||||
// at "mousedown" event.
|
||||
|
||||
int32_t clipboardType = nsIClipboard::kGlobalClipboard;
|
||||
nsresult rv = NS_OK;
|
||||
|
|
|
@ -4347,7 +4347,23 @@ nsresult nsIFrame::HandleEvent(nsPresContext* aPresContext,
|
|||
} else if (aEvent->mMessage == eMouseUp || aEvent->mMessage == eTouchEnd) {
|
||||
HandleRelease(aPresContext, aEvent, aEventStatus);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// When middle button is down, we need to just move selection and focus at
|
||||
// the clicked point. Note that even if middle click paste is not enabled,
|
||||
// Chrome moves selection at middle mouse button down. So, we should follow
|
||||
// the behavior for the compatibility.
|
||||
if (aEvent->mMessage == eMouseDown) {
|
||||
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
|
||||
if (mouseEvent && mouseEvent->mButton == MouseButton::eMiddle) {
|
||||
if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
|
||||
return NS_OK;
|
||||
}
|
||||
return MoveCaretToEventPoint(aPresContext, mouseEvent, aEventStatus);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -4552,14 +4568,6 @@ nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// if we are in Navigator and the click is in a draggable node, we don't want
|
||||
// to start selection because we don't want to interfere with a potential
|
||||
// drag of said node and steal all its glory.
|
||||
int16_t isEditor = presShell->GetSelectionFlags();
|
||||
// weaaak. only the editor can display frame selection not just text and
|
||||
// images
|
||||
isEditor = isEditor == nsISelectionDisplay::DISPLAY_ALL;
|
||||
|
||||
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
|
||||
|
||||
if (!mouseEvent->IsAlt()) {
|
||||
|
@ -4577,6 +4585,35 @@ nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
|
|||
}
|
||||
}
|
||||
|
||||
return MoveCaretToEventPoint(aPresContext, mouseEvent, aEventStatus);
|
||||
}
|
||||
|
||||
nsresult nsIFrame::MoveCaretToEventPoint(nsPresContext* aPresContext,
|
||||
WidgetMouseEvent* aMouseEvent,
|
||||
nsEventStatus* aEventStatus) {
|
||||
MOZ_ASSERT(aPresContext);
|
||||
MOZ_ASSERT(aMouseEvent);
|
||||
MOZ_ASSERT(aMouseEvent->mMessage == eMouseDown);
|
||||
MOZ_ASSERT(aMouseEvent->mButton == MouseButton::ePrimary ||
|
||||
aMouseEvent->mButton == MouseButton::eMiddle);
|
||||
MOZ_ASSERT(aEventStatus);
|
||||
|
||||
mozilla::PresShell* presShell = aPresContext->GetPresShell();
|
||||
if (!presShell) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// if we are in Navigator and the click is in a draggable node, we don't want
|
||||
// to start selection because we don't want to interfere with a potential
|
||||
// drag of said node and steal all its glory.
|
||||
int16_t isEditor = presShell->GetSelectionFlags();
|
||||
// weaaak. only the editor can display frame selection not just text and
|
||||
// images
|
||||
isEditor = isEditor == nsISelectionDisplay::DISPLAY_ALL;
|
||||
|
||||
// Don't do something if it's moddle button down event.
|
||||
bool isPrimaryButtonDown = aMouseEvent->mButton == MouseButton::ePrimary;
|
||||
|
||||
// check whether style allows selection
|
||||
// if not, don't tell selection the mouse event even occurred.
|
||||
StyleUserSelect selectStyle;
|
||||
|
@ -4585,141 +4622,155 @@ nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
bool useFrameSelection = (selectStyle == StyleUserSelect::Text);
|
||||
|
||||
// If the mouse is dragged outside the nearest enclosing scrollable area
|
||||
// while making a selection, the area will be scrolled. To do this, capture
|
||||
// the mouse on the nearest scrollable frame. If there isn't a scrollable
|
||||
// frame, or something else is already capturing the mouse, there's no
|
||||
// reason to capture.
|
||||
if (!PresShell::GetCapturingContent()) {
|
||||
nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
|
||||
this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
|
||||
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
|
||||
if (scrollFrame) {
|
||||
nsIFrame* capturingFrame = do_QueryFrame(scrollFrame);
|
||||
PresShell::SetCapturingContent(capturingFrame->GetContent(),
|
||||
CaptureFlags::IgnoreAllowedState);
|
||||
if (isPrimaryButtonDown) {
|
||||
// If the mouse is dragged outside the nearest enclosing scrollable area
|
||||
// while making a selection, the area will be scrolled. To do this, capture
|
||||
// the mouse on the nearest scrollable frame. If there isn't a scrollable
|
||||
// frame, or something else is already capturing the mouse, there's no
|
||||
// reason to capture.
|
||||
if (!PresShell::GetCapturingContent()) {
|
||||
nsIScrollableFrame* scrollFrame =
|
||||
nsLayoutUtils::GetNearestScrollableFrame(
|
||||
this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
|
||||
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
|
||||
if (scrollFrame) {
|
||||
nsIFrame* capturingFrame = do_QueryFrame(scrollFrame);
|
||||
PresShell::SetCapturingContent(capturingFrame->GetContent(),
|
||||
CaptureFlags::IgnoreAllowedState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXX This is screwy; it really should use the selection frame, not the
|
||||
// event frame
|
||||
const nsFrameSelection* frameselection = nullptr;
|
||||
if (useFrameSelection)
|
||||
frameselection = GetConstFrameSelection();
|
||||
else
|
||||
frameselection = presShell->ConstFrameSelection();
|
||||
const nsFrameSelection* frameselection =
|
||||
selectStyle == StyleUserSelect::Text ? GetConstFrameSelection()
|
||||
: presShell->ConstFrameSelection();
|
||||
|
||||
if (!frameselection || frameselection->GetDisplaySelection() ==
|
||||
nsISelectionController::SELECTION_OFF)
|
||||
nsISelectionController::SELECTION_OFF) {
|
||||
return NS_OK; // nothing to do we cannot affect selection from here
|
||||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
if (mouseEvent->IsControl())
|
||||
return NS_OK; // short circuit. hard coded for mac due to time restraints.
|
||||
bool control = mouseEvent->IsMeta();
|
||||
// If Control key is pressed on macOS, it should be treated as right click.
|
||||
// So, don't change selection.
|
||||
if (aMouseEvent->IsControl()) {
|
||||
return NS_OK;
|
||||
}
|
||||
bool control = aMouseEvent->IsMeta();
|
||||
#else
|
||||
bool control = mouseEvent->IsControl();
|
||||
bool control = aMouseEvent->IsControl();
|
||||
#endif
|
||||
|
||||
RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection);
|
||||
if (mouseEvent->mClickCount > 1) {
|
||||
if (isPrimaryButtonDown && aMouseEvent->mClickCount > 1) {
|
||||
// These methods aren't const but can't actually delete anything,
|
||||
// so no need for AutoWeakFrame.
|
||||
fc->SetDragState(true);
|
||||
return HandleMultiplePress(aPresContext, mouseEvent, aEventStatus, control);
|
||||
return HandleMultiplePress(aPresContext, aMouseEvent, aEventStatus,
|
||||
control);
|
||||
}
|
||||
|
||||
nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent,
|
||||
nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aMouseEvent,
|
||||
RelativeTo{this});
|
||||
ContentOffsets offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
|
||||
|
||||
if (!offsets.content) return NS_ERROR_FAILURE;
|
||||
if (!offsets.content) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Let Ctrl/Cmd+mouse down do table selection instead of drag initiation
|
||||
nsCOMPtr<nsIContent> parentContent;
|
||||
int32_t contentOffset;
|
||||
TableSelectionMode target;
|
||||
nsresult rv;
|
||||
rv = GetDataForTableSelection(frameselection, presShell, mouseEvent,
|
||||
getter_AddRefs(parentContent), &contentOffset,
|
||||
&target);
|
||||
if (NS_SUCCEEDED(rv) && parentContent) {
|
||||
fc->SetDragState(true);
|
||||
return fc->HandleTableSelection(parentContent, contentOffset, target,
|
||||
mouseEvent);
|
||||
if (isPrimaryButtonDown) {
|
||||
// Let Ctrl/Cmd + left mouse down do table selection instead of drag
|
||||
// initiation.
|
||||
nsCOMPtr<nsIContent> parentContent;
|
||||
int32_t contentOffset;
|
||||
TableSelectionMode target;
|
||||
nsresult rv = GetDataForTableSelection(
|
||||
frameselection, presShell, aMouseEvent, getter_AddRefs(parentContent),
|
||||
&contentOffset, &target);
|
||||
if (NS_SUCCEEDED(rv) && parentContent) {
|
||||
fc->SetDragState(true);
|
||||
return fc->HandleTableSelection(parentContent, contentOffset, target,
|
||||
aMouseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
fc->SetDelayedCaretData(0);
|
||||
|
||||
// Check if any part of this frame is selected, and if the
|
||||
// user clicked inside the selected region. If so, we delay
|
||||
// starting a new selection since the user may be trying to
|
||||
// drag the selected region to some other app.
|
||||
if (isPrimaryButtonDown) {
|
||||
// Check if any part of this frame is selected, and if the user clicked
|
||||
// inside the selected region, and if it's the left button. If so, we delay
|
||||
// starting a new selection since the user may be trying to drag the
|
||||
// selected region to some other app.
|
||||
|
||||
if (GetContent() && GetContent()->IsMaybeSelected()) {
|
||||
bool inSelection = false;
|
||||
UniquePtr<SelectionDetails> details = frameselection->LookUpSelection(
|
||||
offsets.content, 0, offsets.EndOffset(), false);
|
||||
if (GetContent() && GetContent()->IsMaybeSelected()) {
|
||||
bool inSelection = false;
|
||||
UniquePtr<SelectionDetails> details = frameselection->LookUpSelection(
|
||||
offsets.content, 0, offsets.EndOffset(), false);
|
||||
|
||||
//
|
||||
// If there are any details, check to see if the user clicked
|
||||
// within any selected region of the frame.
|
||||
//
|
||||
|
||||
for (SelectionDetails* curDetail = details.get(); curDetail;
|
||||
curDetail = curDetail->mNext.get()) {
|
||||
//
|
||||
// If the user clicked inside a selection, then just
|
||||
// return without doing anything. We will handle placing
|
||||
// the caret later on when the mouse is released. We ignore
|
||||
// the spellcheck, find and url formatting selections.
|
||||
// If there are any details, check to see if the user clicked
|
||||
// within any selected region of the frame.
|
||||
//
|
||||
if (curDetail->mSelectionType != SelectionType::eSpellCheck &&
|
||||
curDetail->mSelectionType != SelectionType::eFind &&
|
||||
curDetail->mSelectionType != SelectionType::eURLSecondary &&
|
||||
curDetail->mSelectionType != SelectionType::eURLStrikeout &&
|
||||
curDetail->mStart <= offsets.StartOffset() &&
|
||||
offsets.EndOffset() <= curDetail->mEnd) {
|
||||
inSelection = true;
|
||||
|
||||
for (SelectionDetails* curDetail = details.get(); curDetail;
|
||||
curDetail = curDetail->mNext.get()) {
|
||||
//
|
||||
// If the user clicked inside a selection, then just
|
||||
// return without doing anything. We will handle placing
|
||||
// the caret later on when the mouse is released. We ignore
|
||||
// the spellcheck, find and url formatting selections.
|
||||
//
|
||||
if (curDetail->mSelectionType != SelectionType::eSpellCheck &&
|
||||
curDetail->mSelectionType != SelectionType::eFind &&
|
||||
curDetail->mSelectionType != SelectionType::eURLSecondary &&
|
||||
curDetail->mSelectionType != SelectionType::eURLStrikeout &&
|
||||
curDetail->mStart <= offsets.StartOffset() &&
|
||||
offsets.EndOffset() <= curDetail->mEnd) {
|
||||
inSelection = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (inSelection) {
|
||||
fc->SetDragState(false);
|
||||
fc->SetDelayedCaretData(aMouseEvent);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (inSelection) {
|
||||
fc->SetDragState(false);
|
||||
fc->SetDelayedCaretData(mouseEvent);
|
||||
return NS_OK;
|
||||
}
|
||||
fc->SetDragState(true);
|
||||
}
|
||||
|
||||
fc->SetDragState(true);
|
||||
|
||||
// Do not touch any nsFrame members after this point without adding
|
||||
// weakFrame checks.
|
||||
const nsFrameSelection::FocusMode focusMode = [&]() {
|
||||
// If "Shift" and "Ctrl" are both pressed, "Shift" is given precedence. This
|
||||
// mimics the old behaviour.
|
||||
if (mouseEvent->IsShift()) {
|
||||
if (aMouseEvent->IsShift()) {
|
||||
return nsFrameSelection::FocusMode::kExtendSelection;
|
||||
}
|
||||
|
||||
if (control) {
|
||||
if (isPrimaryButtonDown && control) {
|
||||
return nsFrameSelection::FocusMode::kMultiRangeSelection;
|
||||
}
|
||||
|
||||
return nsFrameSelection::FocusMode::kCollapseToNewPoint;
|
||||
}();
|
||||
|
||||
rv = fc->HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */,
|
||||
offsets.StartOffset(), offsets.EndOffset(), focusMode,
|
||||
offsets.associate);
|
||||
nsresult rv = fc->HandleClick(
|
||||
MOZ_KnownLive(offsets.content) /* bug 1636889 */, offsets.StartOffset(),
|
||||
offsets.EndOffset(), focusMode, offsets.associate);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
// We don't handle mouse button up if it's middle button.
|
||||
if (isPrimaryButtonDown && offsets.offset != offsets.secondaryOffset) {
|
||||
fc->MaintainSelection();
|
||||
}
|
||||
|
||||
if (offsets.offset != offsets.secondaryOffset) fc->MaintainSelection();
|
||||
|
||||
if (isEditor && !mouseEvent->IsShift() &&
|
||||
if (isPrimaryButtonDown && isEditor && !aMouseEvent->IsShift() &&
|
||||
(offsets.EndOffset() - offsets.StartOffset()) == 1) {
|
||||
// A single node is selected and we aren't extending an existing
|
||||
// selection, which means the user clicked directly on an object (either
|
||||
|
@ -4729,7 +4780,7 @@ nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
|
|||
fc->SetDragState(false);
|
||||
}
|
||||
|
||||
return rv;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsIFrame::SelectByTypeAtPoint(nsPresContext* aPresContext,
|
||||
|
|
|
@ -2160,6 +2160,20 @@ class nsIFrame : public nsQueryFrame {
|
|||
HandlePress(nsPresContext* aPresContext, mozilla::WidgetGUIEvent* aEvent,
|
||||
nsEventStatus* aEventStatus);
|
||||
|
||||
/**
|
||||
* MoveCaretToEventPoint() moves caret at the point of aMouseEvent.
|
||||
*
|
||||
* @param aPresContext Must not be nullptr.
|
||||
* @param aMouseEvent Must not be nullptr, the message must be
|
||||
* eMouseDown and its button must be primary or
|
||||
* middle button.
|
||||
* @param aEventStatus [out] Must not be nullptr. This method ignores
|
||||
* its initial value, but callees may refer it.
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT nsresult MoveCaretToEventPoint(
|
||||
nsPresContext* aPresContext, mozilla::WidgetMouseEvent* aMouseEvent,
|
||||
nsEventStatus* aEventStatus);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD HandleMultiplePress(
|
||||
nsPresContext* aPresContext, mozilla::WidgetGUIEvent* aEvent,
|
||||
nsEventStatus* aEventStatus, bool aControlHeld);
|
||||
|
|
|
@ -119,6 +119,7 @@ skip-if = (toolkit == "gtk") || (os == "win")
|
|||
support-files = page_scroll_with_fixed_pos_window.html
|
||||
[test_scroll_behavior.html]
|
||||
[test_scrollframe_abspos_interrupt.html]
|
||||
[test_selection_changes_with_middle_mouse_button.html]
|
||||
[test_selection_doubleclick.html]
|
||||
[test_selection_expanding.html]
|
||||
[test_selection_preventDefault.html]
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for selection changes with middle mouse button</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<style>
|
||||
span, td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none;">
|
||||
|
||||
</div>
|
||||
|
||||
<textarea id="textarea" cols="1" rows="1">copied to clipboard</textarea>
|
||||
|
||||
<div id="container">
|
||||
<span id="span1">first span.</span>
|
||||
<span id="span2">second span.</span>
|
||||
<table>
|
||||
<tr><td id="td1">first td.</td></tr>
|
||||
<tr><td id="td2">second td.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
|
||||
<script class="testbody" type="application/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const kIsMac = navigator.platform.includes("Mac");
|
||||
const selection = getSelection();
|
||||
|
||||
async function doTests(aEnableMiddlePaste, aEditable, aDescription) {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", aEnableMiddlePaste],
|
||||
["middlemouse.contentLoadURL", false],
|
||||
["general.autoScroll", false]]});
|
||||
|
||||
if (aEditable) {
|
||||
document.getElementById("container").setAttribute("contenteditable", "true");
|
||||
} else {
|
||||
document.getElementById("container").removeAttribute("contenteditable");
|
||||
}
|
||||
|
||||
let pasteEvents = [];
|
||||
function pasteEventHandler(event) {
|
||||
pasteEvents.push(event);
|
||||
event.preventDefault();
|
||||
}
|
||||
document.getElementById("container").addEventListener("paste", pasteEventHandler, true);
|
||||
|
||||
// We need to behave as same as Chromium as far as possible.
|
||||
// - middle mouse button down should be collapse selection at the point.
|
||||
// - middle mouse button down can expand only with mouse button down with Shift key.
|
||||
// - middle mouse button shouldn't select table cells.
|
||||
|
||||
function doTest(aMouseDown, aMouseUp,
|
||||
aExpectedSelectionAnchor, aExpectedSelectionFocus, aExpectedPastEventTarget,
|
||||
aAdditionalDescription) {
|
||||
pasteEvents = [];
|
||||
synthesizeMouseAtCenter(aMouseDown.target,
|
||||
{
|
||||
button: 1,
|
||||
type: "mousedown",
|
||||
shiftKey: aMouseDown.shiftKey,
|
||||
ctrlKey: aMouseDown.ctrlKey && !kIsMac,
|
||||
metaKey: aMouseDown.ctrlKey && kIsMac,
|
||||
});
|
||||
if (aExpectedSelectionAnchor === aExpectedSelectionFocus) {
|
||||
ok(selection.isCollapsed,
|
||||
aDescription + aAdditionalDescription + "Selection should be collapsed at mousedown");
|
||||
is(selection.focusNode, aExpectedSelectionFocus,
|
||||
aDescription + aAdditionalDescription + "Selection should be collapsed in the node at mousedown");
|
||||
} else {
|
||||
is(selection.anchorNode, aExpectedSelectionAnchor,
|
||||
aDescription + aAdditionalDescription + "Anchor node of Selection should be previous anchor node");
|
||||
is(selection.focusNode, aExpectedSelectionFocus,
|
||||
aDescription + aAdditionalDescription + "Focus node of Selection should be the node at mousedown");
|
||||
}
|
||||
is(pasteEvents.length, 0,
|
||||
aDescription + aAdditionalDescription + "paste event shouldn't be fired when middle mouse button down");
|
||||
|
||||
if (aMouseDown.target != aMouseUp.target) {
|
||||
synthesizeMouseAtCenter(aMouseUp.target, {type: "mousemove"});
|
||||
}
|
||||
synthesizeMouseAtCenter(aMouseUp.target,
|
||||
{
|
||||
button: 1,
|
||||
type: "mouseup",
|
||||
shiftKey: aMouseUp.shiftKey,
|
||||
ctrlKey: aMouseUp.ctrlKey && !kIsMac,
|
||||
metaKey: aMouseUp.ctrlKey && kIsMac,
|
||||
});
|
||||
is(selection.anchorNode, aExpectedSelectionAnchor,
|
||||
aDescription + aAdditionalDescription + "Anchor node of Selection shouldn't be modified at mouseup");
|
||||
is(selection.focusNode, aExpectedSelectionFocus,
|
||||
aDescription + aAdditionalDescription + "Focus node of Selection shouldn't be modified at mouseup");
|
||||
if (aEnableMiddlePaste) {
|
||||
if (aExpectedPastEventTarget === null) {
|
||||
is(pasteEvents.length, 0,
|
||||
aDescription + aAdditionalDescription + "paste event shouldn't be fired even when middle mouse button up");
|
||||
} else {
|
||||
is(pasteEvents.length, 1,
|
||||
aDescription + aAdditionalDescription + "paste event should be fired only once at mouse up");
|
||||
is(pasteEvents[0].target, aExpectedPastEventTarget,
|
||||
aDescription + aAdditionalDescription + "paste event should be fired on start of selection");
|
||||
}
|
||||
} else {
|
||||
is(pasteEvents.length, 0,
|
||||
aDescription + aAdditionalDescription + "paste event shouldn't be fired when middle mouse button up when middle mouse paste is disabled");
|
||||
}
|
||||
}
|
||||
|
||||
let span1 = document.getElementById("span1");
|
||||
let span2 = document.getElementById("span2");
|
||||
|
||||
selection.removeAllRanges();
|
||||
doTest({target: span1}, {target: span1},
|
||||
span1.firstChild, span1.firstChild, span1,
|
||||
"Clicking span1 when there is no selection: ");
|
||||
doTest({target: span2}, {target: span2},
|
||||
span2.firstChild, span2.firstChild, span2,
|
||||
"Clicking span2 when selection is collapsed in span1: ");
|
||||
doTest({target: span1}, {target: span2},
|
||||
span1.firstChild, span1.firstChild, span1,
|
||||
"Dragging from span1 to span2: ");
|
||||
doTest({target: span2}, {target: span1},
|
||||
span2.firstChild, span2.firstChild, span2,
|
||||
"Dragging from span2 to span1: ");
|
||||
doTest({target: span1, shiftKey: true}, {target: span1, shiftKey: true},
|
||||
span2.firstChild, span1.firstChild, span1,
|
||||
"Expanding selection with Shift key from span2 to span1: ");
|
||||
// "paste" event should be fired in the "start" of selection.
|
||||
selection.collapse(span1.firstChild, 3);
|
||||
doTest({target: span2, shiftKey: true}, {target: span2, shiftKey: true},
|
||||
span1.firstChild, span2.firstChild, span1,
|
||||
"Expanding selection with Shift key from span1 to span2: ");
|
||||
// XXX This case is different from Chrome for Linux.
|
||||
// In this case, Chrome does not collapse Selection at mousedown,
|
||||
// but collapse at click. So, if mouseup occurs different element,
|
||||
// Selection isn't modified.
|
||||
selection.selectAllChildren(span1);
|
||||
doTest({target: span1}, {target: span1},
|
||||
span1.firstChild, span1.firstChild, span1,
|
||||
"Clicking span1 when span1 is selected: ");
|
||||
|
||||
let td1 = document.getElementById("td1");
|
||||
let td2 = document.getElementById("td2");
|
||||
|
||||
selection.removeAllRanges();
|
||||
doTest({target: td1}, {target: td1},
|
||||
td1.firstChild, td1.firstChild, td1,
|
||||
"Clicking td1 when there is no selection: ");
|
||||
if (aEditable) {
|
||||
// XXX In this case, we don't allow to expand selection with Shift key
|
||||
// click across table cell boundary.
|
||||
doTest({target: td2, shiftKey: true}, {target: td2, shiftKey: true},
|
||||
td1.firstChild, td1.firstChild, td1,
|
||||
"Expanding selection with Shift key from td1 to td2: ");
|
||||
} else {
|
||||
doTest({target: td2, shiftKey: true}, {target: td2, shiftKey: true},
|
||||
td1.firstChild, td2.firstChild, td1,
|
||||
"Expanding selection with Shift key from td1 to td2: ");
|
||||
}
|
||||
// Shouldn't select per table cell when the button is middle mouse button.
|
||||
doTest({target: td1, ctrlKey: true}, {target: td1, ctrlKey: true},
|
||||
td1.firstChild, td1.firstChild, td1,
|
||||
"Click td1 with Control key: ");
|
||||
|
||||
document.getElementById("container").removeEventListener("paste", pasteEventHandler, true);
|
||||
}
|
||||
|
||||
async function runAllTests() {
|
||||
let textarea = document.getElementById("textarea");
|
||||
textarea.focus();
|
||||
await new Promise((resolve, reject) => {
|
||||
SimpleTest.waitForClipboard(textarea.value,
|
||||
() => {
|
||||
synthesizeKey("a", {accelKey: true});
|
||||
synthesizeKey("c", {accelKey: true});
|
||||
},
|
||||
() => {
|
||||
ok(true, `Succeeded to copy "${textarea.value}" to clipboard`);
|
||||
textarea.style.display = "none";
|
||||
resolve();
|
||||
},
|
||||
() => {
|
||||
ok(false, `Failed to copy "${textarea.value}" to clipboard`);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
|
||||
await doTests(true, false, "Testing with the middle paste enabled: ");
|
||||
await doTests(false, false, "Testing with the middle paste disabled: ");
|
||||
|
||||
await doTests(true, true, "Testing in editable content with the middle paste enabled: ");
|
||||
await doTests(false, true, "Testing in editable content with the middle paste disabled: ");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForFocus(runAllTests);
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,4 @@
|
|||
[modifying-selection-with-middle-mouse-button.tentative.html]
|
||||
[Middle click shouldn't move caret in an editable element if the default of pointerdown event is prevented]
|
||||
expected: FAIL
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[modifying-selection-with-primary-mouse-button.tentative.html]
|
||||
[Primary click should move caret in an editable element]
|
||||
expected: FAIL
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Testing default action of `mousedown` of middle button and `mouseup` of middle button</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-vendor.js"></script>
|
||||
<script src="/resources/testdriver-actions.js"></script>
|
||||
<style>
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div contenteditable></div>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
var editor = document.querySelector("div[contenteditable]");
|
||||
var span1, span2;
|
||||
var selection = getSelection();
|
||||
|
||||
function preventDefault(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
editor.addEventListener("paste", preventDefault, {capture: true});
|
||||
|
||||
function resetEditor() {
|
||||
editor.innerHTML =
|
||||
'<span id="span1">first span.</span><br><span id="span2">second span.</span>';
|
||||
span1 = document.getElementById("span1");
|
||||
span2 = document.getElementById("span2");
|
||||
}
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.blur();
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span1})
|
||||
.pointerDown({button: actions.ButtonType.MIDDLE})
|
||||
.pointerUp({button: actions.ButtonType.MIDDLE})
|
||||
.send();
|
||||
|
||||
assert_equals(document.activeElement, editor,
|
||||
"The clicked editor should get focus");
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection should be collapsed after middle button click");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should be collapsed in the first <span> element which was clicked by middle button");
|
||||
}, "Middle click should set focus to clicked editable element and collapse selection around the clicked point");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.MIDDLE})
|
||||
.pointerUp({button: actions.ButtonType.MIDDLE})
|
||||
.send();
|
||||
|
||||
assert_equals(document.activeElement, editor,
|
||||
"The clicked editor should keep having focus");
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection should be collapsed after middle button click");
|
||||
assert_equals(selection.focusNode, span2.firstChild,
|
||||
"Selection should be collapsed in the second <span> element which was clicked by middle button");
|
||||
}, "Middle click should move caret in an editable element");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
addEventListener("mousedown", preventDefault);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.MIDDLE})
|
||||
.pointerUp({button: actions.ButtonType.MIDDLE})
|
||||
.send();
|
||||
removeEventListener("mousedown", preventDefault);
|
||||
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should keep collapsed selection in the first <span> element");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should keep collapsed selection at 2 of the first <span> element");
|
||||
}, "Middle click shouldn't move caret in an editable element if the default of mousedown event is prevented");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
addEventListener("pointerdown", preventDefault);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.MIDDLE})
|
||||
.pointerUp({button: actions.ButtonType.MIDDLE})
|
||||
.send();
|
||||
removeEventListener("pointerdown", preventDefault);
|
||||
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should keep collapsed selection in the first <span> element");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should keep collapsed selection at 2 of the first <span> element");
|
||||
}, "Middle click shouldn't move caret in an editable element if the default of pointerdown event is prevented");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.keyDown("\uE008")
|
||||
.pointerDown({button: actions.ButtonType.MIDDLE})
|
||||
.pointerUp({button: actions.ButtonType.MIDDLE})
|
||||
.keyUp("\uE008")
|
||||
.send();
|
||||
|
||||
assert_equals(selection.anchorNode, span1.firstChild,
|
||||
"Selection#anchorNode should keep in the first <span> element");
|
||||
assert_equals(selection.anchorOffset, 2,
|
||||
"Selection#anchorNode should keep at 2 of the first <span> element");
|
||||
assert_equals(selection.focusNode, span2.firstChild,
|
||||
"Selection#focusNode should be in the second <span> element which was clicked by middle button");
|
||||
}, "Shift + Middle click should extend the selection");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
editor.addEventListener("pointerdown", () => {
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection shouldn't be modified before pointerdown event");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should stay in the first <span> element when pointerdown event is fired (focusNode)");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should stay in the first <span> element when pointerdown event is fired (focusOffset)");
|
||||
}, {once: true});
|
||||
editor.addEventListener("mousedown", () => {
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection shouldn't be modified before mousedown event");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should stay in the first <span> element when mousedown event is fired (focusNode)");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should stay in the first <span> element when mousedown event is fired (focusOffset)");
|
||||
}, {once: true});
|
||||
editor.addEventListener("pointerup", () => {
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection should be collapsed before pointerup event");
|
||||
assert_equals(selection.focusNode, span2.firstChild,
|
||||
"Selection should be collapsed in the second <span> element which was clicked by middle button before pointerup event ");
|
||||
}, {once: true});
|
||||
let focusOffsetAtMouseUp;
|
||||
editor.addEventListener("mouseup", () => {
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection should be collapsed before mouseup event");
|
||||
assert_equals(selection.focusNode, span2.firstChild,
|
||||
"Selection should be collapsed in the second <span> element which was clicked by middle button before mouseup event ");
|
||||
focusOffsetAtMouseUp = selection.focusOffset;
|
||||
}, {once: true});
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.MIDDLE})
|
||||
.pointerMove(0, 0, {origin: span1})
|
||||
.pointerUp({button: actions.ButtonType.MIDDLE})
|
||||
.send();
|
||||
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection shouldn't be extended by pointer moves during pressing middle button");
|
||||
assert_equals(selection.focusNode, span2.firstChild,
|
||||
"Selection#focusNode should stay in the second <span> element");
|
||||
assert_equals(selection.focusOffset, focusOffsetAtMouseUp,
|
||||
"Selection#focusOffset should stay in the second <span> element");
|
||||
}, "Middle mouse button down should move caret, but middle mouse button up shouldn't move caret");
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,191 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Testing default action of `mousedown` of primary button and `mouseup` of primary button</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-vendor.js"></script>
|
||||
<script src="/resources/testdriver-actions.js"></script>
|
||||
<style>
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div contenteditable></div>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
var editor = document.querySelector("div[contenteditable]");
|
||||
var span1, span2;
|
||||
var selection = getSelection();
|
||||
|
||||
function preventDefault(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
editor.addEventListener("paste", preventDefault, {capture: true});
|
||||
|
||||
function resetEditor() {
|
||||
editor.innerHTML =
|
||||
'<span id="span1">first span.</span><br><span id="span2">second span.</span>';
|
||||
span1 = document.getElementById("span1");
|
||||
span2 = document.getElementById("span2");
|
||||
}
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.blur();
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span1})
|
||||
.pointerDown({button: actions.ButtonType.LEFT})
|
||||
.pointerUp({button: actions.ButtonType.LEFT})
|
||||
.send();
|
||||
|
||||
assert_equals(document.activeElement, editor,
|
||||
"The clicked editor should get focus");
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection should be collapsed after primary button click");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should be collapsed in the first <span> element which was clicked by primary button");
|
||||
}, "Primary click should set focus to clicked editable element and collapse selection around the clicked point");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.LEFT})
|
||||
.pointerUp({button: actions.ButtonType.LEFT})
|
||||
.send();
|
||||
|
||||
assert_equals(document.activeElement, editor,
|
||||
"The clicked editor should keep having focus");
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection should be collapsed after primary button click");
|
||||
assert_equals(selection.focusNode, span2.firstChild,
|
||||
"Selection should be collapsed in the second <span> element which was clicked by primary button");
|
||||
}, "Primary click should move caret in an editable element");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
addEventListener("mousedown", preventDefault);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.LEFT})
|
||||
.pointerUp({button: actions.ButtonType.LEFT})
|
||||
.send();
|
||||
removeEventListener("mousedown", preventDefault);
|
||||
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should keep collapsed selection in the first <span> element");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should keep collapsed selection at 2 of the first <span> element");
|
||||
}, "Primary click shouldn't move caret in an editable element if the default of mousedown event is prevented");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
addEventListener("pointerdown", preventDefault);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.LEFT})
|
||||
.pointerUp({button: actions.ButtonType.LEFT})
|
||||
.send();
|
||||
removeEventListener("pointerdown", preventDefault);
|
||||
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should keep collapsed selection in the first <span> element");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should keep collapsed selection at 2 of the first <span> element");
|
||||
}, "Primary click shouldn't move caret in an editable element if the default of pointerdown event is prevented");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.keyDown("\uE008")
|
||||
.pointerDown({button: actions.ButtonType.LEFT})
|
||||
.pointerUp({button: actions.ButtonType.LEFT})
|
||||
.keyUp("\uE008")
|
||||
.send();
|
||||
|
||||
assert_equals(selection.anchorNode, span1.firstChild,
|
||||
"Selection#anchorNode should keep in the first <span> element");
|
||||
assert_equals(selection.anchorOffset, 2,
|
||||
"Selection#anchorNode should keep at 2 of the first <span> element");
|
||||
assert_equals(selection.focusNode, span2.firstChild,
|
||||
"Selection#focusNode should be in the second <span> element which was clicked by primary button");
|
||||
}, "Shift + Primary click should extend the selection");
|
||||
|
||||
promise_test(async () => {
|
||||
resetEditor();
|
||||
editor.focus();
|
||||
selection.collapse(span1.firstChild, 2);
|
||||
editor.addEventListener("pointerdown", () => {
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection shouldn't be modified before pointerdown event");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should stay in the first <span> element when pointerdown event is fired (focusNode)");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should stay in the first <span> element when pointerdown event is fired (focusOffset)");
|
||||
}, {once: true});
|
||||
editor.addEventListener("mousedown", () => {
|
||||
assert_true(selection.isCollapsed,
|
||||
"Selection shouldn't be modified before mousedown event");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should stay in the first <span> element when mousedown event is fired (focusNode)");
|
||||
assert_equals(selection.focusOffset, 2,
|
||||
"Selection should stay in the first <span> element when mousedown event is fired (focusOffset)");
|
||||
}, {once: true});
|
||||
editor.addEventListener("pointerup", () => {
|
||||
assert_true(!selection.isCollapsed,
|
||||
"Selection should be modified before pointerup event");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should be modified to extend the range before pointerup event ");
|
||||
}, {once: true});
|
||||
let anchorOffsetAtMouseUp;
|
||||
editor.addEventListener("mouseup", () => {
|
||||
assert_true(!selection.isCollapsed,
|
||||
"Selection should be modified before mouseup event");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection should be modified to extend the range before mouseup event ");
|
||||
anchorOffsetAtMouseUp = selection.anchorOffset;
|
||||
}, {once: true});
|
||||
let actions = new test_driver.Actions();
|
||||
await actions
|
||||
.pointerMove(0, 0)
|
||||
.pointerMove(0, 0, {origin: span2})
|
||||
.pointerDown({button: actions.ButtonType.LEFT})
|
||||
.pointerMove(0, 0, {origin: span1})
|
||||
.pointerUp({button: actions.ButtonType.LEFT})
|
||||
.send();
|
||||
|
||||
assert_equals(selection.anchorNode, span2.firstChild,
|
||||
"Selection#anchorNode should stay in the second <span> element which mousedown occurred on");
|
||||
assert_equals(selection.anchorOffset, anchorOffsetAtMouseUp,
|
||||
"Selection#anchorOffset should stay in the second <span> element which mousedown occurred on");
|
||||
assert_equals(selection.focusNode, span1.firstChild,
|
||||
"Selection#focusNode should be in the first <span> element which mouseup occurred on");
|
||||
}, "Primary mouse button down should move caret, and primary mouse button up should extend the selection");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче