Backed out 2 changesets (bug 1528289) for breaking navigation with clicks after user scrolled by clicking with middle mouse button. a=backout

Backed out changeset 33c7b633ada2 (bug 1528289)
Backed out changeset be5cf87707f9 (bug 1528289)
This commit is contained in:
Sebastian Hengst
2021-03-03 14:29:12 +01:00
parent a1b518fd9a
commit 139551ac5f
18 changed files with 144 additions and 1325 deletions

View File

@@ -3232,30 +3232,6 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
}
// If MouseEvent::PreventClickEvent() was called by chrome script,
// we need to forget the clicking content and click count for the
// following eMouseUp event.
if (mouseEvent->mClickEventPrevented) {
RefPtr<EventStateManager> esm =
ESMFromContentOrThis(aOverrideClickTarget);
switch (mouseEvent->mButton) {
case MouseButton::ePrimary:
esm->mLastLeftMouseDownContent = nullptr;
esm->mLClickCount = 0;
break;
case MouseButton::eSecondary:
esm->mLastMiddleMouseDownContent = nullptr;
esm->mMClickCount = 0;
break;
case MouseButton::eMiddle:
esm->mLastRightMouseDownContent = nullptr;
esm->mRClickCount = 0;
break;
default:
break;
}
}
nsCOMPtr<nsIContent> activeContent;
// When content calls PreventDefault on pointerdown, we also call
// PreventDefault on the subsequent mouse events to suppress default
@@ -4941,14 +4917,11 @@ nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
switch (aEvent->mButton) {
case MouseButton::ePrimary:
if (aEvent->mMessage == eMouseDown) {
mLastLeftMouseDownContent =
!aEvent->mClickEventPrevented ? mouseContent : nullptr;
mLastLeftMouseDownContent = mouseContent;
} else if (aEvent->mMessage == eMouseUp) {
aEvent->mClickTarget =
!aEvent->mClickEventPrevented
? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastLeftMouseDownContent)
: nullptr;
nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastLeftMouseDownContent);
if (aEvent->mClickTarget) {
aEvent->mClickCount = mLClickCount;
mLClickCount = 0;
@@ -4961,14 +4934,11 @@ nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
case MouseButton::eMiddle:
if (aEvent->mMessage == eMouseDown) {
mLastMiddleMouseDownContent =
!aEvent->mClickEventPrevented ? mouseContent : nullptr;
mLastMiddleMouseDownContent = mouseContent;
} else if (aEvent->mMessage == eMouseUp) {
aEvent->mClickTarget =
!aEvent->mClickEventPrevented
? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastMiddleMouseDownContent)
: nullptr;
nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastMiddleMouseDownContent);
if (aEvent->mClickTarget) {
aEvent->mClickCount = mMClickCount;
mMClickCount = 0;
@@ -4981,14 +4951,11 @@ nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
case MouseButton::eSecondary:
if (aEvent->mMessage == eMouseDown) {
mLastRightMouseDownContent =
!aEvent->mClickEventPrevented ? mouseContent : nullptr;
mLastRightMouseDownContent = mouseContent;
} else if (aEvent->mMessage == eMouseUp) {
aEvent->mClickTarget =
!aEvent->mClickEventPrevented
? nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastRightMouseDownContent)
: nullptr;
nsContentUtils::GetCommonAncestorUnderInteractiveContent(
mouseContent, mLastRightMouseDownContent);
if (aEvent->mClickTarget) {
aEvent->mClickCount = mRClickCount;
mRClickCount = 0;
@@ -5019,10 +4986,6 @@ bool EventStateManager::EventCausesClickEvents(
if (!aMouseEvent.mClickCount || !aMouseEvent.mClickTarget) {
return false;
}
// If click event was explicitly prevented, we shouldn't dispatch it.
if (aMouseEvent.mClickEventPrevented) {
return false;
}
// Check that the window isn't disabled before firing a click
// (see bug 366544).
return !(aMouseEvent.mWidget && !aMouseEvent.mWidget->IsEnabled());
@@ -5235,8 +5198,20 @@ nsresult EventStateManager::HandleMiddleClickPaste(
}
}
// Don't modify selection here because we've already set caret to the point
// at "mousedown" event.
// 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");
}
int32_t clipboardType = nsIClipboard::kGlobalClipboard;
nsresult rv = NS_OK;

View File

@@ -154,12 +154,6 @@ void MouseEvent::InitNSMouseEvent(const nsAString& aType, bool aCanBubble,
mouseEventBase->mInputSource = aInputSource;
}
void MouseEvent::PreventClickEvent() {
if (WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent()) {
mouseEvent->mClickEventPrevented = true;
}
}
int16_t MouseEvent::Button() {
switch (mEvent->mClass) {
case eMouseEventClass:

View File

@@ -76,7 +76,6 @@ class MouseEvent : public UIEvent {
bool aAltKey, bool aShiftKey, bool aMetaKey,
uint16_t aButton, EventTarget* aRelatedTarget,
float aPressure, uint16_t aInputSource);
void PreventClickEvent();
protected:
~MouseEvent() = default;

View File

@@ -116,11 +116,5 @@ partial interface MouseEvent
optional float pressure = 0,
optional unsigned short inputSourceArg = 0);
/**
* preventClickEvent() prevents the following "click", "auxclick" and
* "dblclick" events of "mousedown" and "mouseup" events.
*/
[ChromeOnly]
void preventClickEvent();
};

View File

@@ -4341,23 +4341,7 @@ 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;
}
@@ -4562,6 +4546,14 @@ 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()) {
@@ -4579,35 +4571,6 @@ 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;
@@ -4616,155 +4579,141 @@ nsresult nsIFrame::MoveCaretToEventPoint(nsPresContext* aPresContext,
return NS_OK;
}
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);
}
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);
}
}
// XXX This is screwy; it really should use the selection frame, not the
// event frame
const nsFrameSelection* frameselection =
selectStyle == StyleUserSelect::Text ? GetConstFrameSelection()
: presShell->ConstFrameSelection();
const nsFrameSelection* frameselection = nullptr;
if (useFrameSelection)
frameselection = GetConstFrameSelection();
else
frameselection = 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 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();
if (mouseEvent->IsControl())
return NS_OK; // short circuit. hard coded for mac due to time restraints.
bool control = mouseEvent->IsMeta();
#else
bool control = aMouseEvent->IsControl();
bool control = mouseEvent->IsControl();
#endif
RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection);
if (isPrimaryButtonDown && aMouseEvent->mClickCount > 1) {
if (mouseEvent->mClickCount > 1) {
// These methods aren't const but can't actually delete anything,
// so no need for AutoWeakFrame.
fc->SetDragState(true);
return HandleMultiplePress(aPresContext, aMouseEvent, aEventStatus,
control);
return HandleMultiplePress(aPresContext, mouseEvent, aEventStatus, control);
}
nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aMouseEvent,
nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent,
RelativeTo{this});
ContentOffsets offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
if (!offsets.content) {
return NS_ERROR_FAILURE;
}
if (!offsets.content) return NS_ERROR_FAILURE;
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);
}
// 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);
}
fc->SetDelayedCaretData(0);
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.
// 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 (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 there are any details, check to see if the user clicked
// within any selected region of the frame.
// 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.
//
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 (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;
}
}
fc->SetDragState(true);
if (inSelection) {
fc->SetDragState(false);
fc->SetDelayedCaretData(mouseEvent);
return NS_OK;
}
}
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 (aMouseEvent->IsShift()) {
if (mouseEvent->IsShift()) {
return nsFrameSelection::FocusMode::kExtendSelection;
}
if (isPrimaryButtonDown && control) {
if (control) {
return nsFrameSelection::FocusMode::kMultiRangeSelection;
}
return nsFrameSelection::FocusMode::kCollapseToNewPoint;
}();
nsresult rv = fc->HandleClick(
MOZ_KnownLive(offsets.content) /* bug 1636889 */, offsets.StartOffset(),
offsets.EndOffset(), focusMode, offsets.associate);
if (NS_FAILED(rv)) {
return rv;
}
rv = fc->HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */,
offsets.StartOffset(), offsets.EndOffset(), focusMode,
offsets.associate);
// We don't handle mouse button up if it's middle button.
if (isPrimaryButtonDown && offsets.offset != offsets.secondaryOffset) {
fc->MaintainSelection();
}
if (NS_FAILED(rv)) return rv;
if (isPrimaryButtonDown && isEditor && !aMouseEvent->IsShift() &&
if (offsets.offset != offsets.secondaryOffset) fc->MaintainSelection();
if (isEditor && !mouseEvent->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
@@ -4774,7 +4723,7 @@ nsresult nsIFrame::MoveCaretToEventPoint(nsPresContext* aPresContext,
fc->SetDragState(false);
}
return NS_OK;
return rv;
}
nsresult nsIFrame::SelectByTypeAtPoint(nsPresContext* aPresContext,

View File

@@ -2175,20 +2175,6 @@ 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);

View File

@@ -119,7 +119,6 @@ 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]

View File

@@ -1,211 +0,0 @@
<!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>

View File

@@ -1,4 +0,0 @@
[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

View File

@@ -1,4 +0,0 @@
[modifying-selection-with-primary-mouse-button.tentative.html]
[Primary click should move caret in an editable element]
expected: FAIL

View File

@@ -1,192 +0,0 @@
<!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>

View File

@@ -1,191 +0,0 @@
<!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>

View File

@@ -204,7 +204,6 @@ class AutoScrollChild extends JSWindowActorChild {
}
Services.els.addSystemEventListener(this.document, "mousemove", this, true);
Services.els.addSystemEventListener(this.document, "mouseup", this, true);
this.document.addEventListener("pagehide", this, true);
this._ignoreMouseEvents = true;
@@ -254,12 +253,6 @@ class AutoScrollChild extends JSWindowActorChild {
this,
true
);
Services.els.removeSystemEventListener(
this.document,
"mouseup",
this,
true
);
this.document.removeEventListener("pagehide", this, true);
if (this._autoscrollHandledByApz) {
Services.obs.removeObserver(
@@ -331,38 +324,26 @@ class AutoScrollChild extends JSWindowActorChild {
}
handleEvent(event) {
switch (event.type) {
case "mousemove":
this._screenX = event.screenX;
this._screenY = event.screenY;
break;
case "mousedown":
if (
event.isTrusted &&
!event.defaultPrevented &&
event.button === 1 &&
!this._scrollable &&
!this.isAutoscrollBlocker(event.originalTarget)
) {
this.startScroll(event);
if (event.type == "mousemove") {
this._screenX = event.screenX;
this._screenY = event.screenY;
} else if (event.type == "mousedown") {
if (
event.isTrusted & !event.defaultPrevented &&
event.button == 1 &&
!this._scrollable &&
!this.isAutoscrollBlocker(event.originalTarget)
) {
this.startScroll(event);
}
} else if (event.type == "pagehide") {
if (this._scrollable) {
var doc = this._scrollable.ownerDocument || this._scrollable.document;
if (doc == event.target) {
this.sendAsyncMessage("Autoscroll:Cancel");
this.stopScroll();
}
// fallthrough
case "mouseup":
if (this._ignoreMouseEvents) {
// Middle mouse click event shouldn't be fired in web content for
// compatibility with Chrome.
event.preventClickEvent();
}
break;
case "pagehide":
if (this._scrollable) {
var doc = this._scrollable.ownerDocument || this._scrollable.document;
if (doc == event.target) {
this.sendAsyncMessage("Autoscroll:Cancel");
this.stopScroll();
}
}
break;
}
}
}

View File

@@ -61,8 +61,6 @@ skip-if = true # bug 1399845 tracks re-enabling this test.
[browser_bug594509.js]
[browser_bug982298.js]
[browser_charsetMenu_swapBrowsers.js]
[browser_click_event_during_autoscrolling.js]
skip-if = !e10s
[browser_content_url_annotation.js]
skip-if = !e10s || !crashreporter
[browser_contentTitle.js]

View File

@@ -1,438 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function() {
await SpecialPowers.pushPrefEnv({
set: [
["general.autoScroll", true],
["middlemouse.contentLoadURL", false],
["test.events.async.enabled", false],
],
});
await BrowserTestUtils.withNewTab(
"https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
async function(browser) {
ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
await SpecialPowers.spawn(browser, [], () => {
content.document.body.innerHTML =
'<div style="height: 10000px;"></div>';
content.document.documentElement.scrollTop = 500;
content.document.documentElement.scrollTop; // Flush layout.
// Prevent to open context menu when testing the secondary button click.
content.window.addEventListener(
"contextmenu",
event => event.preventDefault(),
{ capture: true }
);
});
function promiseFlushLayoutInContent() {
return SpecialPowers.spawn(browser, [], () => {
content.document.documentElement.scrollTop; // Flush layout in the remote content.
});
}
let autoScroller;
function promiseWaitForAutoScrollerOpen() {
if (autoScroller?.state == "open") {
info("The autoscroller has already been open");
return Promise.resolve();
}
return BrowserTestUtils.waitForEvent(
window,
"popupshown",
{ capture: true },
event => {
if (event.originalTarget.id != "autoscroller") {
return false;
}
autoScroller = event.originalTarget;
info('"popupshown" event is fired');
autoScroller.getBoundingClientRect(); // Flush layout of the autoscroller
return true;
}
);
}
function promiseWaitForAutoScrollerClosed() {
if (!autoScroller || autoScroller.state == "closed") {
info("The autoscroller has already been closed");
return Promise.resolve();
}
return BrowserTestUtils.waitForEvent(
autoScroller,
"popuphidden",
{ capture: true },
() => {
info('"popuphidden" event is fired');
return true;
}
);
}
// Unfortunately, we cannot use synthesized mouse events for starting and
// stopping autoscrolling because they may run different path from user
// operation especially when there is a popup.
/**
* Instead of using `waitForContentEvent`, we use `addContentEventListener`
* for checking which events are fired because `waitForContentEvent` cannot
* detect redundant event since it's removed automatically at first event
* or timeout if the expected count is 0.
*/
class ContentEventCounter {
constructor(aBrowser, aEventTypes) {
this.eventData = new Map();
for (let eventType of aEventTypes) {
const removeEventListener = BrowserTestUtils.addContentEventListener(
aBrowser,
eventType,
() => {
let eventData = this.eventData.get(eventType);
eventData.count++;
},
{ capture: true }
);
this.eventData.set(eventType, {
count: 0, // how many times the event fired.
removeEventListener, // function to remove the event listener.
});
}
}
getCountAndRemoveEventListener(aEventType) {
let eventData = this.eventData.get(aEventType);
if (eventData.removeEventListener) {
eventData.removeEventListener();
eventData.removeEventListener = null;
}
return eventData.count;
}
promiseMouseEvents(aEventTypes, aMessage) {
let needsToWait = [];
for (const eventType of aEventTypes) {
let eventData = this.eventData.get(eventType);
if (eventData.count > 0) {
info(`${aMessage}: Waiting "${eventType}" event in content...`);
needsToWait.push(
// Let's use `waitForCondition` here. "timeout" is not worthwhile
// to debug this test. We want clearer failure log.
TestUtils.waitForCondition(
() => eventData.count > 0,
`${aMessage}: "${eventType}" should be fired, but timed-out`
)
);
break;
}
}
return Promise.all(needsToWait);
}
}
await (async function testMouseEventsAtStartingAutoScrolling() {
info(
"Waiting autoscroller popup for testing mouse events at starting autoscrolling"
);
await promiseFlushLayoutInContent();
let eventsInContent = new ContentEventCounter(browser, [
"click",
"auxclick",
"mousedown",
"mouseup",
"paste",
]);
// Ensure that the event listeners added in the content with accessing
// the remote content.
await promiseFlushLayoutInContent();
await EventUtils.promiseNativeMouseEvent({
type: "mousemove",
target: browser,
atCenter: true,
});
const waitForOpenAutoScroll = promiseWaitForAutoScrollerOpen();
await EventUtils.promiseNativeMouseEvent({
type: "mousedown",
target: browser,
atCenter: true,
button: 1, // middle button
});
await waitForOpenAutoScroll;
// In the wild, native "mouseup" event occurs after the popup is open.
await EventUtils.promiseNativeMouseEvent({
type: "mouseup",
target: browser,
atCenter: true,
button: 1, // middle button
});
await promiseFlushLayoutInContent();
await eventsInContent.promiseMouseEvents(
["mouseup"],
"At starting autoscrolling"
);
for (let eventType of ["click", "auxclick", "paste"]) {
is(
eventsInContent.getCountAndRemoveEventListener(eventType),
0,
`"${eventType}" event shouldn't be fired in the content when a middle click starts autoscrolling`
);
}
for (let eventType of ["mousedown", "mouseup"]) {
is(
eventsInContent.getCountAndRemoveEventListener(eventType),
1,
`"${eventType}" event should be fired in the content when a middle click starts autoscrolling`
);
}
info("Waiting autoscroller close for preparing the following tests");
let waitForAutoScrollEnd = promiseWaitForAutoScrollerClosed();
EventUtils.synthesizeKey("KEY_Escape");
await waitForAutoScrollEnd;
})();
async function doTestMouseEventsAtStoppingAutoScrolling({
aButton = 0,
aClickOutsideAutoScroller = false,
aDescription = "Unspecified",
}) {
info(
`Starting autoscrolling for testing to stop autoscrolling with ${aDescription}`
);
await promiseFlushLayoutInContent();
await EventUtils.promiseNativeMouseEvent({
type: "mousemove",
target: browser,
atCenter: true,
});
const waitForOpenAutoScroll = promiseWaitForAutoScrollerOpen();
await EventUtils.promiseNativeMouseEvent({
type: "mousedown",
target: browser,
atCenter: true,
button: 1, // middle button
});
// In the wild, native "mouseup" event occurs after the popup is open.
await waitForOpenAutoScroll;
await EventUtils.promiseNativeMouseEvent({
type: "mouseup",
target: browser,
atCenter: true,
button: 1, // middle button
});
await promiseFlushLayoutInContent();
// Just to be sure, wait for a tick for wait APZ stable.
await TestUtils.waitForTick();
let eventsInContent = new ContentEventCounter(browser, [
"click",
"auxclick",
"mousedown",
"mouseup",
"paste",
"contextmenu",
]);
// Ensure that the event listeners added in the content with accessing
// the remote content.
await promiseFlushLayoutInContent();
aDescription = `Stop autoscrolling with ${aDescription}`;
info(
`${aDescription}: Synthesizing primary mouse button event on the autoscroller`
);
const autoScrollerRect = autoScroller.getOuterScreenRect();
info(
`${aDescription}: autoScroller: { left: ${autoScrollerRect.left}, top: ${autoScrollerRect.top}, width: ${autoScrollerRect.width}, height: ${autoScrollerRect.height} }`
);
const waitForCloseAutoScroller = promiseWaitForAutoScrollerClosed();
if (aClickOutsideAutoScroller) {
info(
`${aDescription}: Synthesizing mousemove move cursor outside the autoscroller...`
);
await EventUtils.promiseNativeMouseEvent({
type: "mousemove",
target: autoScroller,
offsetX: -10,
offsetY: -10,
elementOnWidget: browser, // use widget for the parent window of the autoscroller
});
info(
`${aDescription}: Synthesizing mousedown to stop autoscrolling...`
);
await EventUtils.promiseNativeMouseEvent({
type: "mousedown",
target: autoScroller,
offsetX: -10,
offsetY: -10,
button: aButton,
elementOnWidget: browser, // use widget for the parent window of the autoscroller
});
} else {
info(
`${aDescription}: Synthesizing mousemove move cursor onto the autoscroller...`
);
await EventUtils.promiseNativeMouseEvent({
type: "mousemove",
target: autoScroller,
atCenter: true,
elementOnWidget: browser, // use widget for the parent window of the autoscroller
});
info(
`${aDescription}: Synthesizing mousedown to stop autoscrolling...`
);
await EventUtils.promiseNativeMouseEvent({
type: "mousedown",
target: autoScroller,
atCenter: true,
button: aButton,
elementOnWidget: browser, // use widget for the parent window of the autoscroller
});
}
// In the wild, native "mouseup" event occurs after the popup is closed.
await waitForCloseAutoScroller;
info(
`${aDescription}: Synthesizing mouseup event for preceding mousedown which is for stopping autoscrolling`
);
await EventUtils.promiseNativeMouseEvent({
type: "mouseup",
target: browser,
atCenter: true,
button: aButton,
});
await promiseFlushLayoutInContent();
await eventsInContent.promiseMouseEvents(
aButton != 2 ? ["mouseup"] : ["mouseup", "contextmenu"],
aDescription
);
is(
autoScroller.state,
"closed",
`${aDescription}: The autoscroller should've been closed`
);
// - On macOS, when clicking outside autoscroller, nsChildView
// intentionally blocks both "mousedown" and "mouseup" events in the
// case of the primary button click, and only "mousedown" for the
// middle button when the "mousedown". I'm not sure how it should work
// on macOS for conforming to the platform manner. Note that autoscroll
// isn't available on the other browsers on macOS. So, there is no
// reference, but for consistency between platforms, it may be better
// to ignore the platform manner.
// - On Windows, when clicking outside autoscroller, nsWindow
// intentionally blocks only "mousedown" events for the primary button
// and the middle button. But this behavior is different from Chrome
// so that we need to fix this in the future.
// - On Linux, when clicking outside autoscroller, nsWindow
// intentionally blocks only "mousedown" events for any buttons. But
// on Linux, autoscroll isn't available by the default settings. So,
// not so urgent, but should be fixed in the future for consistency
// between platforms and compatibility with Chrome on Windows.
const rollingUpPopupConsumeMouseDown =
aClickOutsideAutoScroller &&
(aButton != 2 || navigator.platform.includes("Linux"));
const rollingUpPopupConsumeMouseUp =
aClickOutsideAutoScroller &&
aButton == 0 &&
navigator.platform.includes("Mac");
const checkFuncForClick =
aClickOutsideAutoScroller &&
aButton == 2 &&
!navigator.platform.includes("Linux")
? todo_is
: is;
for (let eventType of ["click", "auxclick"]) {
checkFuncForClick(
eventsInContent.getCountAndRemoveEventListener(eventType),
0,
`${aDescription}: "${eventType}" event shouldn't be fired in the remote content`
);
}
is(
eventsInContent.getCountAndRemoveEventListener("paste"),
0,
`${aDescription}: "paste" event shouldn't be fired in the remote content`
);
const checkFuncForMouseDown = rollingUpPopupConsumeMouseDown
? todo_is
: is;
checkFuncForMouseDown(
eventsInContent.getCountAndRemoveEventListener("mousedown"),
1,
`${aDescription}: "mousedown" event should be fired in the remote content`
);
const checkFuncForMouseUp = rollingUpPopupConsumeMouseUp ? todo_is : is;
checkFuncForMouseUp(
eventsInContent.getCountAndRemoveEventListener("mouseup"),
1,
`${aDescription}: "mouseup" event should be fired in the remote content`
);
const checkFuncForContextMenu =
aButton == 2 &&
aClickOutsideAutoScroller &&
navigator.platform.includes("Linux")
? todo_is
: is;
checkFuncForContextMenu(
eventsInContent.getCountAndRemoveEventListener("contextmenu"),
aButton == 2 ? 1 : 0,
`${aDescription}: "contextmenu" event should${
aButton != 2 ? " not" : ""
} be fired in the remote content`
);
}
// Clicking the primary button to stop autoscrolling.
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 0,
aClickOutsideAutoScroller: false,
aDescription: "a primary button click on autoscroller",
});
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 0,
aClickOutsideAutoScroller: true,
aDescription: "a primary button click outside autoscroller",
});
// Clicking the secondary button to stop autoscrolling.
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 2,
aClickOutsideAutoScroller: false,
aDescription: "a secondary button click on autoscroller",
});
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 2,
aClickOutsideAutoScroller: true,
aDescription: "a secondary button click outside autoscroller",
});
// Clicking the middle button to stop autoscrolling.
await SpecialPowers.pushPrefEnv({ set: [["middlemouse.paste", true]] });
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 1,
aClickOutsideAutoScroller: false,
aDescription:
"a middle button click on autoscroller (middle click paste enabled)",
});
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 1,
aClickOutsideAutoScroller: true,
aDescription:
"a middle button click outside autoscroller (middle click paste enabled)",
});
await SpecialPowers.pushPrefEnv({ set: [["middlemouse.paste", false]] });
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 1,
aClickOutsideAutoScroller: false,
aDescription:
"a middle button click on autoscroller (middle click paste disabled)",
});
await doTestMouseEventsAtStoppingAutoScrolling({
aButton: 1,
aClickOutsideAutoScroller: true,
aDescription:
"a middle button click outside autoscroller (middle click paste disabled)",
});
}
);
});

View File

@@ -1446,10 +1446,6 @@
}
case "mouseup":
case "mousedown":
// The following mouse click/auxclick event on the autoscroller
// shouldn't be fired in web content for compatibility with Chrome.
aEvent.preventClickEvent();
// fallthrough
case "contextmenu": {
if (!this._ignoreMouseEvents) {
// Use a timeout to prevent the mousedown from opening the popup again.
@@ -1466,9 +1462,6 @@
break;
}
case "popuphidden": {
// TODO: When the autoscroller is closed by clicking outside of it,
// we need to prevent following click event for compatibility
// with Chrome. However, there is no way to do that for now.
this._autoScrollPopup.removeEventListener(
"popuphidden",
this,

View File

@@ -202,20 +202,18 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
WidgetMouseEvent()
: mReason(eReal),
mContextMenuTrigger(eNormal),
mClickCount(0),
mIgnoreRootScrollFrame(false),
mUseLegacyNonPrimaryDispatch(false),
mClickEventPrevented(false) {}
mClickCount(0),
mUseLegacyNonPrimaryDispatch(false) {}
WidgetMouseEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
EventClassID aEventClassID, Reason aReason)
: WidgetMouseEventBase(aIsTrusted, aMessage, aWidget, aEventClassID),
mReason(aReason),
mContextMenuTrigger(eNormal),
mClickCount(0),
mIgnoreRootScrollFrame(false),
mUseLegacyNonPrimaryDispatch(false),
mClickEventPrevented(false) {}
mClickCount(0),
mUseLegacyNonPrimaryDispatch(false) {}
public:
virtual WidgetMouseEvent* AsMouseEvent() override { return this; }
@@ -226,10 +224,9 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
: WidgetMouseEventBase(aIsTrusted, aMessage, aWidget, eMouseEventClass),
mReason(aReason),
mContextMenuTrigger(aContextMenuTrigger),
mClickCount(0),
mIgnoreRootScrollFrame(false),
mUseLegacyNonPrimaryDispatch(false),
mClickEventPrevented(false) {
mClickCount(0),
mUseLegacyNonPrimaryDispatch(false) {
if (aMessage == eContextMenu) {
mButton = (mContextMenuTrigger == eNormal) ? MouseButton::eSecondary
: MouseButton::ePrimary;
@@ -279,30 +276,26 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
// a child widget or a puppet widget.
Maybe<ExitFrom> mExitFrom;
// Whether the event should ignore scroll frame bounds during dispatch.
bool mIgnoreRootScrollFrame;
// mClickCount may be non-zero value when mMessage is eMouseDown, eMouseUp,
// eMouseClick or eMouseDoubleClick. The number is count of mouse clicks.
// Otherwise, this must be 0.
uint32_t mClickCount;
// Whether the event should ignore scroll frame bounds during dispatch.
bool mIgnoreRootScrollFrame;
// Indicates whether the event should dispatch click events for non-primary
// mouse buttons on window and document.
bool mUseLegacyNonPrimaryDispatch;
// Whether the event shouldn't cause click event.
bool mClickEventPrevented;
void AssignMouseEventData(const WidgetMouseEvent& aEvent, bool aCopyTargets) {
AssignMouseEventBaseData(aEvent, aCopyTargets);
AssignPointerHelperData(aEvent, /* aCopyCoalescedEvents */ true);
mExitFrom = aEvent.mExitFrom;
mClickCount = aEvent.mClickCount;
mIgnoreRootScrollFrame = aEvent.mIgnoreRootScrollFrame;
mClickCount = aEvent.mClickCount;
mUseLegacyNonPrimaryDispatch = aEvent.mUseLegacyNonPrimaryDispatch;
mClickEventPrevented = aEvent.mClickEventPrevented;
}
/**

View File

@@ -243,7 +243,6 @@ struct ParamTraits<mozilla::WidgetMouseEvent> {
WriteParam(aMsg, static_cast<const mozilla::WidgetMouseEventBase&>(aParam));
WriteParam(aMsg, static_cast<const mozilla::WidgetPointerHelper&>(aParam));
WriteParam(aMsg, aParam.mIgnoreRootScrollFrame);
WriteParam(aMsg, aParam.mClickEventPrevented);
WriteParam(aMsg, static_cast<paramType::ReasonType>(aParam.mReason));
WriteParam(aMsg, static_cast<paramType::ContextMenuTriggerType>(
aParam.mContextMenuTrigger));
@@ -266,7 +265,6 @@ struct ParamTraits<mozilla::WidgetMouseEvent> {
ReadParam(aMsg, aIter,
static_cast<mozilla::WidgetPointerHelper*>(aResult)) &&
ReadParam(aMsg, aIter, &aResult->mIgnoreRootScrollFrame) &&
ReadParam(aMsg, aIter, &aResult->mClickEventPrevented) &&
ReadParam(aMsg, aIter, &reason) &&
ReadParam(aMsg, aIter, &contextMenuTrigger);
aResult->mReason = static_cast<paramType::Reason>(reason);