Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
87e491d
initial trial of edge scrolling
rstanuwijaya May 20, 2025
e5a30c1
Try to change implementation into fake mouse event but failed
rstanuwijaya May 20, 2025
cdb8608
Reverted into scroll
rstanuwijaya May 20, 2025
f932117
Activate adjacent browser
rstanuwijaya May 20, 2025
73bde70
Try to synthesize mouse event again
rstanuwijaya May 20, 2025
a4ad405
Synthesize mouse click event
rstanuwijaya May 20, 2025
916079b
Merge branch 'dev' into fix-edge-scrolling
mr-cheffy May 20, 2025
2addbee
Merge branch 'dev' into fix-edge-scrolling
mr-cheffy May 21, 2025
416db95
Working actor implementation
rstanuwijaya May 21, 2025
67742d9
Refine code
rstanuwijaya May 21, 2025
25bc7bd
Add prefs zen.edgescroll.enabled
rstanuwijaya May 21, 2025
ae91fc4
fix: Change the mousemove and mouseup listener to window
rstanuwijaya May 21, 2025
84aa004
fix: Add restart prompt when updating edgescroll prefs
rstanuwijaya May 21, 2025
ee0a312
fix: Enable edgescroll on about:* pages
rstanuwijaya May 21, 2025
892c6a8
fix: Only display zen-edge-scroll-trigger when the sidebar is on the …
rstanuwijaya May 21, 2025
8868a4c
fix: Increase offset to 2 pixels
rstanuwijaya May 21, 2025
ebea8f6
chore: Remove logging
rstanuwijaya May 21, 2025
c100068
style: Lint code
rstanuwijaya May 22, 2025
025e563
Merge branch 'dev' into fix-edge-scrolling
rstanuwijaya May 22, 2025
5b44b34
Merge branch 'dev' into fix-edge-scrolling
mr-cheffy May 22, 2025
95abc99
Merge branch 'dev' into fix-edge-scrolling
mr-cheffy May 22, 2025
ead77dc
fix: Allow content window zoom
rstanuwijaya May 22, 2025
3c42ae3
Merge branch 'dev' into fix-edge-scrolling
mr-cheffy May 22, 2025
33162bd
test: Add tests for ZenEdgeScroll
rstanuwijaya May 23, 2025
83cd980
fix: Renamed var to gZenEdgeScrollManager and make _initialized as p…
rstanuwijaya May 23, 2025
976c206
Merge remote-tracking branch 'origin/fix-edge-scrolling' into fix-edg…
rstanuwijaya May 23, 2025
e63ca21
Discard changes to src/browser/components/preferences/zen-settings.js
mr-cheffy May 23, 2025
00145da
fix: Move edgescroll style to css
rstanuwijaya May 24, 2025
426b1d7
fix: Move trigger to zen-appcontent-wrapper
rstanuwijaya May 24, 2025
ea33664
fix: Removed _initialized
rstanuwijaya May 24, 2025
f6448d0
style: Formatting
rstanuwijaya May 24, 2025
83056ac
feat: Add observer for preference update
rstanuwijaya May 24, 2025
564f7e3
fix: Rename id to specify vertical trigger
rstanuwijaya May 25, 2025
460b979
fix: Modified logic for synthetic event
rstanuwijaya May 25, 2025
bc9cbab
fix: Specify edge scroll direction
rstanuwijaya May 25, 2025
b34da26
fix: Discard unused attribute
rstanuwijaya May 25, 2025
a7ac8a8
feat: Add horizontal trigger
rstanuwijaya May 25, 2025
0816774
fix: Change css position to absolute + make vertical on top
rstanuwijaya May 25, 2025
8ea7e05
fix: Correct Y offset
rstanuwijaya May 25, 2025
ee58617
Merge remote-tracking branch 'upstream/dev' into fix-edge-scrolling
rstanuwijaya May 25, 2025
f15fae0
Merge branch dev into fix-edge-scrolling
rstanuwijaya Jul 9, 2025
12c39bf
fix: move edgescroll.inc
rstanuwijaya Jul 18, 2025
0046eab
Merge branch 'dev' into fix-edge-scrolling
rstanuwijaya Jul 18, 2025
1252fe9
Update scrollwheel sendWheelEvent
rstanuwijaya Jul 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .formal-git/components
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ scripts
workflows
winsign
flatpak
configs
configs
edgescroll
2 changes: 2 additions & 0 deletions src/browser/app/profile/features.inc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pref('zen.glance.hold-duration', 300); // in ms
pref('zen.glance.open-essential-external-links', true);
pref('zen.glance.activation-method', 'alt'); // ctrl, alt, shift, none, hold

pref('zen.edgescroll.enabled', true);

pref('zen.view.sidebar-height-throttle', 200); // in ms
pref('zen.view.sidebar-expanded.max-width', 500);

Expand Down
1 change: 1 addition & 0 deletions src/browser/base/content/zen-assets.inc.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/Zen
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenGlanceManager.mjs", this);
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenMediaController.mjs", this);
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenDownloadAnimation.mjs", this);
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenEdgeScrollManager.mjs", this);
</script>
4 changes: 4 additions & 0 deletions src/browser/base/content/zen-assets.jar.inc.mn
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
content/browser/zen-components/actors/ZenGlanceChild.sys.mjs (../../zen/glance/actors/ZenGlanceChild.sys.mjs)
content/browser/zen-components/actors/ZenGlanceParent.sys.mjs (../../zen/glance/actors/ZenGlanceParent.sys.mjs)

content/browser/zen-components/ZenEdgeScrollManager.mjs (../../zen/edgescroll/ZenEdgeScrollManager.mjs)
content/browser/zen-components/actors/ZenEdgeScrollChild.sys.mjs (../../zen/edgescroll/actors/ZenEdgeScrollChild.sys.mjs)
content/browser/zen-components/actors/ZenEdgeScrollParent.sys.mjs (../../zen/edgescroll/actors/ZenEdgeScrollParent.sys.mjs)

content/browser/zen-components/ZenFolders.mjs (../../zen/folders/ZenFolders.mjs)
content/browser/zen-styles/zen-folders.css (../../zen/folders/zen-folders.css)

Expand Down
7 changes: 7 additions & 0 deletions src/browser/components/preferences/zen-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@ var gZenWorkspacesSettings = {
};
Services.prefs.addObserver('zen.tab-unloader.enabled', tabsUnloaderPrefListener);
Services.prefs.addObserver('zen.glance.enabled', tabsUnloaderPrefListener); // We can use the same listener for both prefs
Services.prefs.addObserver('zen.edgescroll.enabled', tabsUnloaderPrefListener);
Services.prefs.addObserver(
'zen.workspaces.container-specific-essentials-enabled',
tabsUnloaderPrefListener
Expand All @@ -785,6 +786,7 @@ var gZenWorkspacesSettings = {
window.addEventListener('unload', () => {
Services.prefs.removeObserver('zen.tab-unloader.enabled', tabsUnloaderPrefListener);
Services.prefs.removeObserver('zen.glance.enabled', tabsUnloaderPrefListener);
Services.prefs.removeObserver('zen.edgescroll.enabled', tabsUnloaderPrefListener);
Services.prefs.removeObserver('zen.glance.activation-method', tabsUnloaderPrefListener);
Services.prefs.removeObserver(
'zen.workspaces.container-specific-essentials-enabled',
Expand Down Expand Up @@ -1169,6 +1171,11 @@ Preferences.addAll([
type: 'bool',
default: true,
},
{
id: 'zen.edgescroll.enabled',
type: 'bool',
default: true,
},
{
id: 'zen.theme.color-prefs.use-workspace-colors',
type: 'bool',
Expand Down
2 changes: 1 addition & 1 deletion src/zen/common/zen-sets.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@ document.addEventListener(
});
},
{ once: true }
);
);
257 changes: 257 additions & 0 deletions src/zen/edgescroll/ZenEdgeScrollManager.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
{
const EDGE_INTERACTION_WIDTH_PX = Services.prefs.getIntPref("zen.theme.border-radius", 8);
const SYNTHETIC_EVENT_X_OFFSET_FROM_RIGHT_EDGE = 2;
const ACTOR_NAME = "ZenEdgeScroll"; // Name used for actor registration

class ZenEdgeScrollManager extends ZenDOMOperatedFeature {
init() {
this.isSynthesizingDrag = false;
this.dragInitialModel = {
targetBrowserDuringDrag: null,
targetBrowsingContextDuringDrag: null,
};
this.edgeScrollTriggerDiv = null; // Added for the trigger div

this._boundHandleMouseDown = this.handleMouseDown.bind(this);
this._boundHandleSyntheticDrag = this.handleSyntheticDrag.bind(this);
this._boundHandleSyntheticDragEnd = this.handleSyntheticDragEnd.bind(this);
this._boundHandleWheel = this.handleWheel.bind(this);
this._boundUpdateTriggerDivDisplay = this._updateTriggerDivDisplay.bind(this); // Added

if (window.gZenEdgeScrollManagerInitialized) {
return;
}
window.gZenEdgeScrollManagerInitialized = true;

// Create and append the edge scroll trigger div
this.edgeScrollTriggerDiv = window.document.createElement("div");
this.edgeScrollTriggerDiv.id = "zen-edge-scroll-trigger";
Object.assign(this.edgeScrollTriggerDiv.style, {
position: "fixed",
top: "0px",
right: "0px",
width: `${EDGE_INTERACTION_WIDTH_PX}px`,
height: "100%",
zIndex: "2147483647", // Max z-index
userSelect: "none",
// backgroundColor: "rgba(255,0,0,0.1)", // For debugging visibility
});
window.document.documentElement.appendChild(this.edgeScrollTriggerDiv);

this.edgeScrollTriggerDiv.addEventListener('mousedown', this._boundHandleMouseDown, true);
this.edgeScrollTriggerDiv.addEventListener('wheel', this._boundHandleWheel, { capture: true, passive: false });

this._updateTriggerDivDisplay(); // Added: Set initial display state
Services.prefs.addObserver("zen.tabs.vertical.right-side", this._boundUpdateTriggerDivDisplay); // Added: Observe preference
}

destroy() {
if (this.edgeScrollTriggerDiv) {
this.edgeScrollTriggerDiv.removeEventListener('mousedown', this._boundHandleMouseDown, true);
this.edgeScrollTriggerDiv.removeEventListener('wheel', this._boundHandleWheel, true);
if (this.edgeScrollTriggerDiv.parentNode) {
this.edgeScrollTriggerDiv.parentNode.removeChild(this.edgeScrollTriggerDiv); // Corrected removeChild call
}
this.edgeScrollTriggerDiv = null;
}
// These listeners are added to window, not edgeScrollTriggerDiv in handleMouseDown
window.removeEventListener('mousemove', this._boundHandleSyntheticDrag, true);
window.removeEventListener('mouseup', this._boundHandleSyntheticDragEnd, true);
Services.prefs.removeObserver("zen.tabs.vertical.right-side", this._boundUpdateTriggerDivDisplay); // Added: Remove observer
window.gZenEdgeScrollManagerInitialized = false;
}

_updateTriggerDivDisplay() { // Added method
if (!this.edgeScrollTriggerDiv) {
return;
}
if (window.gZenCompactModeManager && gZenCompactModeManager.sidebarIsOnRight) {
this.edgeScrollTriggerDiv.style.display = "none";
} else {
this.edgeScrollTriggerDiv.style.display = "block";
}
}

_getParentActor() {
if (!gBrowser.selectedBrowser.browsingContext.currentWindowGlobal) {
return null;
}
try {
const actor = gBrowser.selectedBrowser.browsingContext.currentWindowGlobal.getActor(ACTOR_NAME);
return actor;
} catch (e) {
console.error(`Error getting actor ${ACTOR_NAME}:`, e);
return null;
}
}

getGapZoneInfo(event) {
const windowWidth = window.innerWidth;
const eventClientY = event.clientY;

let potentialTargetBrowser = null;
let potentialTargetBrowserRect = null;

const selectedBrowser = gBrowser.selectedBrowser;
if (selectedBrowser && selectedBrowser.getAttribute("primary") === "true") {
const selectedBrowserRect = selectedBrowser.getBoundingClientRect();
if (selectedBrowserRect.width > 0 && selectedBrowserRect.height > 0) {
// Check if the browser's right edge is very close to the window's right edge
const isBrowserAtRightEdge = (windowWidth - selectedBrowserRect.right) <= EDGE_INTERACTION_WIDTH_PX + 1;
const isEventYWithinBrowser = eventClientY >= selectedBrowserRect.top && eventClientY <= selectedBrowserRect.bottom;
if (isBrowserAtRightEdge && isEventYWithinBrowser) {
potentialTargetBrowser = selectedBrowser;
potentialTargetBrowserRect = selectedBrowserRect;
}
}
}

// The event is on the trigger div, so it is "in gap". We return the browser found.
return { isInGap: true, targetBrowser: potentialTargetBrowser, browserRect: potentialTargetBrowserRect };
}

createSyntheticEventData(originalEvent, targetBrowserRect, eventType) {
const clientXInContent = Math.max(0, Math.floor(targetBrowserRect.width - SYNTHETIC_EVENT_X_OFFSET_FROM_RIGHT_EDGE));
const clientYInContent = Math.max(0, Math.min(Math.floor(originalEvent.clientY - targetBrowserRect.top), Math.floor(targetBrowserRect.height - 1)));
const screenX = Math.floor(window.screenX + targetBrowserRect.left + clientXInContent);
const screenY = Math.floor(window.screenY + targetBrowserRect.top + clientYInContent);

return {
type: eventType, clientX: clientXInContent, clientY: clientYInContent,
screenX: screenX, screenY: screenY, button: originalEvent.button,
buttons: (eventType === 'mousemove' || eventType === 'mousedown') ? 1 : 0,
ctrlKey: originalEvent.ctrlKey, altKey: originalEvent.altKey,
shiftKey: originalEvent.shiftKey, metaKey: originalEvent.metaKey,
};
}

handleMouseDown(event) {
if (event.button !== 0) return;
const gapInfo = this.getGapZoneInfo(event); // event is from edgeScrollTriggerDiv

if (!gapInfo.targetBrowser) {
return;
}
let targetBrowser = gapInfo.targetBrowser;
let targetBrowserRect = gapInfo.browserRect;

const parentActor = this._getParentActor();
if (!parentActor || !targetBrowser.browsingContext) {
return;
}

event.preventDefault();
this.isSynthesizingDrag = true;
this.dragInitialModel.targetBrowserDuringDrag = targetBrowser;
this.dragInitialModel.targetBrowsingContextDuringDrag = targetBrowser.browsingContext;

const eventData = this.createSyntheticEventData(event, targetBrowserRect, 'mousedown');
parentActor.sendEventToChild(targetBrowser.browsingContext, "ZenEdgeScroll:SynthesizeMouseEvent", eventData);

window.addEventListener('mousemove', this._boundHandleSyntheticDrag, true);
window.addEventListener('mouseup', this._boundHandleSyntheticDragEnd, true);
}

handleSyntheticDrag(event) {
if (!this.isSynthesizingDrag || !this.dragInitialModel.targetBrowsingContextDuringDrag) return;

const targetBrowser = this.dragInitialModel.targetBrowserDuringDrag;
const targetBrowsingContext = this.dragInitialModel.targetBrowsingContextDuringDrag;

if (gBrowser.selectedBrowser !== targetBrowser) {
this.handleSyntheticDragEnd(event); return;
}

const parentActor = this._getParentActor();
if (!parentActor) {
this.handleSyntheticDragEnd(event); return;
}

event.preventDefault(); event.stopPropagation();
const currentTargetBrowserRect = targetBrowser.getBoundingClientRect();
if (currentTargetBrowserRect.width === 0 || currentTargetBrowserRect.height === 0) {
this.handleSyntheticDragEnd(event); return;
}
const eventData = this.createSyntheticEventData(event, currentTargetBrowserRect, 'mousemove');
parentActor.sendEventToChild(targetBrowsingContext, "ZenEdgeScroll:SynthesizeMouseEvent", eventData);
}

handleSyntheticDragEnd(event) {
if (this.isSynthesizingDrag && this.dragInitialModel.targetBrowsingContextDuringDrag) {
const targetBrowser = this.dragInitialModel.targetBrowserDuringDrag;
const targetBrowsingContext = this.dragInitialModel.targetBrowsingContextDuringDrag;
const parentActor = this._getParentActor();

if (parentActor && event) { // If called by an event
event.preventDefault(); event.stopPropagation();
const currentTargetBrowserRect = targetBrowser.getBoundingClientRect();
if (currentTargetBrowserRect.width > 0 && currentTargetBrowserRect.height > 0) {
const eventData = this.createSyntheticEventData(event, currentTargetBrowserRect, 'mouseup');
parentActor.sendEventToChild(targetBrowsingContext, "ZenEdgeScroll:SynthesizeMouseEvent", eventData);
} else {
}
} else if (parentActor && !event) { // Called without event (e.g. drag cancelled)
// Optionally send a generic mouseup if needed, or just clean up.
}
}
this.isSynthesizingDrag = false;
this.dragInitialModel.targetBrowserDuringDrag = null;
this.dragInitialModel.targetBrowsingContextDuringDrag = null;
window.removeEventListener('mousemove', this._boundHandleSyntheticDrag, true);
window.removeEventListener('mouseup', this._boundHandleSyntheticDragEnd, true);
}

handleWheel(event) {
const gapInfo = this.getGapZoneInfo(event); // event is from edgeScrollTriggerDiv

if (!gapInfo.targetBrowser) {
return;
}

const targetBrowser = gapInfo.targetBrowser;
const targetBrowserRect = gapInfo.browserRect;
const parentActor = this._getParentActor();

event.preventDefault(); event.stopPropagation();
const wheelData = {
deltaX: event.deltaX, deltaY: event.deltaY, deltaZ: event.deltaZ, deltaMode: event.deltaMode,
ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, metaKey: event.metaKey,
clientX: Math.max(0, Math.floor(targetBrowserRect.width - SYNTHETIC_EVENT_X_OFFSET_FROM_RIGHT_EDGE)),
clientY: Math.max(0, Math.min(Math.floor(event.clientY - targetBrowserRect.top), Math.floor(targetBrowserRect.height - 1)))
};
parentActor.sendEventToChild(targetBrowser.browsingContext, "ZenEdgeScroll:DispatchWheel", { wheelData });
}
}

// Actor Registration (must happen before manager instantiation if manager relies on actors being ready)
// This is modeled after ZenGlanceManager's registerWindowActors
function registerEdgeScrollActors() {
if (Services.prefs.getBoolPref('zen.edgescroll.enabled', true)) {
window.gZenEdgeScrollManagerInstance = new ZenEdgeScrollManager();

const actorConfig = {
parent: {
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenEdgeScrollParent.sys.mjs',
},
child: {
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenEdgeScrollChild.sys.mjs',
},
allFrames: true,
matches: [
'*://*/*',
'about:*', // For about: pages
],
includeChrome: true, // <--- ENSURE THIS LINE IS PRESENT AND SET TO TRUE
};

if (window.gZenActorsManager && typeof window.gZenActorsManager.addJSWindowActor === 'function') {
window.gZenActorsManager.addJSWindowActor(ACTOR_NAME, actorConfig);
} else {
console.error(`Failed to register ${ACTOR_NAME} actors:`, e);
}
}
}

registerEdgeScrollActors();

}
Loading