gh-14830: Fixed dragging on text makes the entire window to move (gh-14833)

This commit is contained in:
mr. m
2026-08-04 01:32:36 +02:00
committed by GitHub
parent c74486102f
commit 36c71c5c2b
13 changed files with 170 additions and 111 deletions

View File

@@ -23,10 +23,10 @@
# How far (in CSS pixels) the mouse may travel past the window bounds after
# leaving the window before the hovered sidebar/toolbar is collapsed
- name: zen.view.compact.outside-window-edge-offset.horizontal
value: 250
value: 200
- name: zen.view.compact.outside-window-edge-offset.vertical
value: 150
value: 100
- name: zen.view.compact.animate-sidebar
value: true

View File

@@ -50,7 +50,7 @@
value: true
- name: zen.view.drag-window-from-content.height-percentage
value: 30
value: 10
- name: zen.view.borderless-fullscreen
value: true

View File

@@ -998,7 +998,7 @@ window.gZenCompactModeManager = {
try {
lazy.zenMouseTracker.registerWindow(window, screenEdge, maxEdgeOffset);
} catch (e) {
// The platform can't track the global mouse position (e.g. Wayland)
// The platform can't track the global mouse position (e.g. Linux)
return false;
}
this._outsideTrackedElement = target;

View File

@@ -1,52 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenMouseTracker.h"
#include "ZenMouseTrackerInternal.h"
#include "mozilla/WidgetUtilsGtk.h"
#include <gdk/gdk.h>
#include <glib.h>
namespace zen {
// X11 has no way to observe global pointer motion events without pulling in
// extra library dependencies (XInput2 raw events need libXi, XRecord needs
// libXtst), so we poll the pointer from a GLib timeout instead. This only
// runs during the short periods a window is registered, i.e. while the
// pointer is outside of the window with an edge element held open.
static guint sTimerId = 0;
static gboolean OnTimer(gpointer) {
GdkDevice* pointer = mozilla::widget::GdkGetPointer();
if (pointer) {
gint x = 0, y = 0;
gdk_device_get_position(pointer, nullptr, &x, &y);
ZenMouseTracker::OnNativePointerMove(
mozilla::DesktopPoint(float(x), float(y)));
}
return G_SOURCE_CONTINUE;
}
nsresult ZenNativeMouseMonitor::Start() {
if (!mozilla::widget::GdkIsX11Display()) {
// Wayland doesn't expose the pointer position while it is outside of our
// surfaces
return NS_ERROR_NOT_AVAILABLE;
}
if (!sTimerId) {
sTimerId = g_timeout_add(1000 / 60, OnTimer, nullptr);
}
return NS_OK;
}
void ZenNativeMouseMonitor::Stop() {
if (sTimerId) {
g_source_remove(sTimerId);
sTimerId = 0;
}
}
} // namespace zen

View File

@@ -7,7 +7,10 @@
#include "nscore.h"
#if defined(XP_MACOSX) || defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
// On Linux there's no reliable way to observe the global pointer (Wayland
// doesn't expose it at all), so we don't track there and callers fall back
// to their timeout based behavior
#if defined(XP_MACOSX) || defined(XP_WIN)
# define NS_ZEN_CAN_TRACK_POINTER 1
#endif
@@ -25,7 +28,7 @@ class ZenNativeMouseMonitor final {
* @brief Start delivering pointer moves. Safe to call while already
* started.
* @throws NS_ERROR_NOT_AVAILABLE when the platform cannot observe the
* global pointer (e.g. on Wayland).
* global pointer (e.g. on Linux).
*/
static nsresult Start();
/**

View File

@@ -33,9 +33,5 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
SOURCES += ["ZenMouseTrackerWin.cpp"]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
SOURCES += ["ZenMouseTrackerGtk.cpp"]
CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
FINAL_LIBRARY = "xul"
XPIDL_MODULE = "zen_compact_mode"

View File

@@ -28,7 +28,7 @@ interface nsIZenMouseTracker : nsISupports {
* @param maxEdgeOffset How far (in CSS pixels) the pointer may travel past
* screenEdge before the exit notification is fired.
* @throws NS_ERROR_NOT_AVAILABLE when the platform cannot track the global
* pointer position (e.g. on Wayland).
* pointer position (e.g. on Linux).
*/
void registerWindow(in mozIDOMWindowProxy window, in ACString screenEdge,
in float maxEdgeOffset);

View File

@@ -10,36 +10,25 @@ XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"dragRegionHeightPercentage",
"zen.view.drag-window-from-content.height-percentage",
30
10
);
// A small threshold to allow for minor mouse jitter during a normal click.
// Anything beyond this is considered an intentional window drag.
const DRAG_START_THRESHOLD_PX = 4;
XPCOMUtils.defineLazyServiceGetter(
lazy,
"zenWindowDragUtils",
"@mozilla.org/zen/window-drag-utils;1",
Ci.nsIZenWindowDragUtils
);
const kInteractiveTags = new Set([
"a",
"area",
"audio",
"button",
"canvas",
"details",
"dialog",
"embed",
"frame",
"iframe",
"img",
"input",
"label",
"menu",
"object",
"optgroup",
"option",
"select",
"summary",
"textarea",
"video",
]);
// Movement below this is considered a click, not a window drag. Fast
// clicks commonly slide a few pixels (especially on trackpads), and once
// the native move starts the OS swallows the mouseup — so keep this
// comfortably above click jitter or clicks in the region get lost.
const DRAG_START_THRESHOLD_PX = 10;
// Content that drives its own mouse interaction without being
// interactive HTML content in the spec sense.
const kAppContentTags = new Set(["audio", "canvas", "video"]);
const kInteractiveRoles = new Set([
"button",
@@ -282,21 +271,41 @@ export class ZenWindowDragChild extends JSWindowActorChild {
return true;
}
}
return this.#hasInteractiveCursor(target);
return (
this.#hasInteractiveCursor(target) || this.#isOverSelectableText(event)
);
}
/**
* A drag starting over selectable text should select it, not move the
* window. rangeParent is the caret position Gecko computed for the
* event, so this also covers empty space on the same line, where
* dragging extends a selection.
*
* @param {MouseEvent} event
*/
#isOverSelectableText(event) {
const node = event.rangeParent;
if (node?.nodeType !== Node.TEXT_NODE) {
return false;
}
const parent = node.parentElement;
return (
!parent ||
this.contentWindow.getComputedStyle(parent).userSelect !== "none"
);
}
#isInteractiveElement(element) {
if (kInteractiveTags.has(element.localName)) {
// Gecko's own notion of interactive, editable or draggable content.
if (lazy.zenWindowDragUtils.isInteractiveContent(element)) {
return true;
}
// Covers [draggable="true"] and elements draggable by default,
// like links and images.
if (element.draggable) {
return true;
}
if (element.isContentEditable) {
if (kAppContentTags.has(element.localName)) {
return true;
}
// Declarative signals the engine check doesn't cover: ARIA widget
// roles and explicit tab stops.
if (
element.tabIndex >= 0 &&
element !== this.document.body &&
@@ -305,19 +314,7 @@ export class ZenWindowDragChild extends JSWindowActorChild {
return true;
}
const role = element.getAttribute?.("role");
if (role && kInteractiveRoles.has(role.toLowerCase())) {
return true;
}
// Inline event handlers are a strong hint of a custom widget.
if (
element.onclick ||
element.onmousedown ||
element.onpointerdown ||
element.ondragstart
) {
return true;
}
return false;
return !!role && kInteractiveRoles.has(role.toLowerCase());
}
#hasInteractiveCursor(element) {

View File

@@ -0,0 +1,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Classes = [
{
'cid': '{13df8214-70b4-4d7b-af8c-86081cbc5801}',
'interfaces': ['nsIZenWindowDragUtils'],
'contract_ids': ['@mozilla.org/zen/window-drag-utils;1'],
'type': 'zen::nsZenWindowDragUtils',
'headers': ['mozilla/nsZenWindowDragUtils.h'],
},
]

View File

@@ -6,3 +6,22 @@ FINAL_TARGET_FILES.actors += [
"actors/ZenWindowDragChild.sys.mjs",
"actors/ZenWindowDragParent.sys.mjs",
]
XPIDL_SOURCES += [
"nsIZenWindowDragUtils.idl",
]
EXPORTS.mozilla += [
"nsZenWindowDragUtils.h",
]
SOURCES += [
"nsZenWindowDragUtils.cpp",
]
XPCOM_MANIFESTS += [
"components.conf",
]
FINAL_LIBRARY = "xul"
XPIDL_MODULE = "zen_window_drag"

View File

@@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
webidl Node;
/**
* @brief Utilities for deciding when a content-area gesture may move
* the window.
*/
[scriptable, uuid(595ba6e8-abe2-4619-acad-cf492306f608)]
interface nsIZenWindowDragUtils : nsISupports {
/**
* @brief Whether the node is content the page expects the user to
* interact with: interactive HTML content (links, form controls,
* etc.), editable content, or content that can be dragged
* (draggable attribute, links and loaded images).
* @param node The node to check.
*/
boolean isInteractiveContent(in Node node);
};

View File

@@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsZenWindowDragUtils.h"
#include "mozilla/dom/Element.h"
#include "nsContentUtils.h"
#include "nsIContent.h"
namespace zen {
NS_IMPL_ISUPPORTS(nsZenWindowDragUtils, nsIZenWindowDragUtils)
NS_IMETHODIMP
nsZenWindowDragUtils::IsInteractiveContent(nsINode* aNode, bool* aResult) {
*aResult = false;
NS_ENSURE_ARG_POINTER(aNode);
nsIContent* content = nsIContent::FromNode(aNode);
if (!content) {
return NS_OK;
}
if (content->IsEditable() || nsContentUtils::ContentIsDraggable(content)) {
*aResult = true;
return NS_OK;
}
mozilla::dom::Element* element = mozilla::dom::Element::FromNode(content);
*aResult = element && element->IsInteractiveHTMLContent();
return NS_OK;
}
} // namespace zen

View File

@@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef zen_nsZenWindowDragUtils_h_
#define zen_nsZenWindowDragUtils_h_
#include "nsIZenWindowDragUtils.h"
#define ZEN_WINDOW_DRAG_UTILS_CONTRACTID "@mozilla.org/zen/window-drag-utils;1"
namespace zen {
class nsZenWindowDragUtils final : public nsIZenWindowDragUtils {
NS_DECL_ISUPPORTS
NS_DECL_NSIZENWINDOWDRAGUTILS
public:
nsZenWindowDragUtils() = default;
private:
~nsZenWindowDragUtils() = default;
};
} // namespace zen
#endif