+
+
@@ -305,6 +305,10 @@ export class ScreenshotsOverlay {
};
}
+ focus() {
+ this.previewCancelButton.focus({ focusVisible: true });
+ }
+
/**
* Returns the x and y coordinates of the event relative to both the
* viewport and the page.
@@ -503,35 +507,51 @@ export class ScreenshotsOverlay {
* @param {Event} event The keydown event
*/
handleKeyDown(event) {
- switch (event.key) {
- case "ArrowLeft":
- this.handleArrowLeftKeyDown(event);
+ if (event.key === "Escape") {
+ this.maybeCancelScreenshots();
+ return;
+ }
+
+ switch (this.#state) {
+ case STATES.CROSSHAIRS:
+ this.crosshairsKeyDown(event);
break;
- case "ArrowUp":
- this.handleArrowUpKeyDown(event);
+ case STATES.DRAGGING:
+ this.draggingKeyDown(event);
break;
- case "ArrowRight":
- this.handleArrowRightKeyDown(event);
+ case STATES.RESIZING:
+ this.resizingKeyDown(event);
break;
- case "ArrowDown":
- this.handleArrowDownKeyDown(event);
+ case STATES.SELECTED:
+ this.selectedKeyDown(event);
break;
- case "Tab":
- this.maybeLockFocus(event);
- break;
- case "Escape":
- this.maybeCancelScreenshots();
- break;
- case this.copyKey.toLowerCase():
- if (this.state === "selected" && this.getAccelKey(event)) {
- event.preventDefault();
- this.copySelectedRegion();
- }
- break;
- case this.downloadKey.toLowerCase():
- if (this.state === "selected" && this.getAccelKey(event)) {
- event.preventDefault();
- this.downloadSelectedRegion();
+ }
+ }
+
+ /**
+ * Handles when a keyup occurs in the screenshots component.
+ * All we need to do on keyup is set the state to selected.
+ * @param {Event} event The keydown event
+ */
+ handleKeyUp(event) {
+ switch (this.#state) {
+ case STATES.RESIZING:
+ switch (event.key) {
+ case "ArrowLeft":
+ case "ArrowUp":
+ case "ArrowRight":
+ case "ArrowDown":
+ switch (event.originalTarget.id) {
+ case "highlight":
+ case "mover-bottomLeft":
+ case "mover-bottomRight":
+ case "mover-topLeft":
+ case "mover-topRight":
+ event.preventDefault();
+ this.#setState(STATES.SELECTED, { doNotMoveFocus: true });
+ break;
+ }
+ break;
}
break;
}
@@ -550,6 +570,175 @@ export class ScreenshotsOverlay {
return event.ctrlKey;
}
+ crosshairsKeyDown(event) {
+ switch (event.key) {
+ case "ArrowLeft":
+ case "ArrowUp":
+ case "ArrowRight":
+ case "ArrowDown":
+ // Do nothing so we can prevent default below
+ break;
+ case "Tab":
+ this.maybeLockFocus(event);
+ return;
+ case "Enter":
+ if (this.hoverElementRegion.isRegionValid) {
+ event.preventDefault();
+ this.draggingReadyStart();
+ this.draggingReadyDragEnd();
+ return;
+ }
+ // eslint-disable-next-line no-fallthrough
+ case " ": {
+ if (Services.appinfo.isWayland) {
+ return;
+ }
+
+ if (event.originalTarget === this.previewCancelButton) {
+ return;
+ }
+
+ event.preventDefault();
+ // The left and top coordinates from cursorRegion are relative to
+ // the client window so we need to add the scroll offset of the page to
+ // get the correct coordinates.
+ let x = {};
+ let y = {};
+ this.window.windowUtils.getLastOverWindowPointerLocationInCSSPixels(
+ x,
+ y
+ );
+ this.crosshairsDragStart(
+ x.value + this.windowDimensions.scrollX,
+ y.value + this.windowDimensions.scrollY
+ );
+ this.#setState(STATES.DRAGGING);
+ break;
+ }
+ default:
+ return;
+ }
+
+ // Prevent scrolling with arrow keys
+ event.preventDefault();
+ }
+
+ /**
+ * Handles a keydown event for the dragging state.
+ * @param {Event} event The keydown event
+ */
+ draggingKeyDown(event) {
+ switch (event.key) {
+ case "ArrowLeft":
+ this.handleArrowLeftKeyDown(event);
+ break;
+ case "ArrowUp":
+ this.handleArrowUpKeyDown(event);
+ break;
+ case "ArrowRight":
+ this.handleArrowRightKeyDown(event);
+ break;
+ case "ArrowDown":
+ this.handleArrowDownKeyDown(event);
+ break;
+ case "Enter":
+ case " ":
+ event.preventDefault();
+ this.#setState(STATES.SELECTED);
+ return;
+ default:
+ return;
+ }
+
+ this.drawSelectionContainer();
+ }
+
+ /**
+ * Handles a keydown event for the resizing state.
+ * @param {Event} event The keydown event
+ */
+ resizingKeyDown(event) {
+ switch (event.key) {
+ case "ArrowLeft":
+ this.resizingArrowLeftKeyDown(event);
+ break;
+ case "ArrowUp":
+ this.resizingArrowUpKeyDown(event);
+ break;
+ case "ArrowRight":
+ this.resizingArrowRightKeyDown(event);
+ break;
+ case "ArrowDown":
+ this.resizingArrowDownKeyDown(event);
+ break;
+ }
+ }
+
+ selectedKeyDown(event) {
+ let isSelectionElement = event.originalTarget.closest(
+ "#selection-container"
+ );
+ switch (event.key) {
+ case "ArrowLeft":
+ if (isSelectionElement) {
+ this.resizingArrowLeftKeyDown(event);
+ }
+ break;
+ case "ArrowUp":
+ if (isSelectionElement) {
+ this.resizingArrowUpKeyDown(event);
+ }
+ break;
+ case "ArrowRight":
+ if (isSelectionElement) {
+ this.resizingArrowRightKeyDown(event);
+ }
+ break;
+ case "ArrowDown":
+ if (isSelectionElement) {
+ this.resizingArrowDownKeyDown(event);
+ }
+ break;
+ case "Tab":
+ this.maybeLockFocus(event);
+ break;
+ case " ":
+ if (!event.originalTarget.closest("#buttons-container")) {
+ event.preventDefault();
+ }
+ break;
+ case this.copyKey.toLowerCase():
+ if (this.state === "selected" && this.getAccelKey(event)) {
+ event.preventDefault();
+ this.copySelectedRegion();
+ }
+ break;
+ case this.downloadKey.toLowerCase():
+ if (this.state === "selected" && this.getAccelKey(event)) {
+ event.preventDefault();
+ this.downloadSelectedRegion();
+ }
+ break;
+ }
+ }
+
+ /**
+ * Move the region or its left or right side to the left.
+ * Just the arrow key will move the region by 1px.
+ * Arrow key + shift will move the region by 10px.
+ * Arrow key + control/meta will move to the edge of the window.
+ * @param {Event} event The keydown event
+ */
+ resizingArrowLeftKeyDown(event) {
+ this.handleArrowLeftKeyDown(event);
+
+ if (this.#state !== STATES.RESIZING) {
+ this.#setState(STATES.RESIZING);
+ }
+
+ this.drawSelectionContainer();
+ }
+
/**
* Move the region or its left or right side to the left.
* Just the arrow key will move the region by 1px.
@@ -558,6 +747,7 @@ export class ScreenshotsOverlay {
* @param {Event} event The keydown event
*/
handleArrowLeftKeyDown(event) {
+ let exponent = event.shiftKey ? 1 : 0;
switch (event.originalTarget.id) {
case "highlight":
if (this.getAccelKey(event)) {
@@ -567,7 +757,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.right -= 10 ** event.shiftKey;
+ this.selectionRegion.right -= 10 ** exponent;
// eslint-disable-next-line no-fallthrough
case "mover-topLeft":
case "mover-bottomLeft":
@@ -576,7 +766,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.left -= 10 ** event.shiftKey;
+ this.selectionRegion.left -= 10 ** exponent;
this.scrollIfByEdge(
this.selectionRegion.left,
this.windowDimensions.scrollY + this.windowDimensions.clientHeight / 2
@@ -596,7 +786,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.right -= 10 ** event.shiftKey;
+ this.selectionRegion.right -= 10 ** exponent;
if (this.selectionRegion.x1 >= this.selectionRegion.x2) {
this.selectionRegion.sortCoords();
if (event.originalTarget.id === "mover-topRight") {
@@ -610,11 +800,23 @@ export class ScreenshotsOverlay {
return;
}
+ event.preventDefault();
+ }
+
+ /**
+ * Move the region or its top or bottom side upward.
+ * Just the arrow key will move the region by 1px.
+ * Arrow key + shift will move the region by 10px.
+ * Arrow key + control/meta will move to the edge of the window.
+ * @param {Event} event The keydown event
+ */
+ resizingArrowUpKeyDown(event) {
+ this.handleArrowUpKeyDown(event);
+
if (this.#state !== STATES.RESIZING) {
this.#setState(STATES.RESIZING);
}
- event.preventDefault();
this.drawSelectionContainer();
}
@@ -626,6 +828,7 @@ export class ScreenshotsOverlay {
* @param {Event} event The keydown event
*/
handleArrowUpKeyDown(event) {
+ let exponent = event.shiftKey ? 1 : 0;
switch (event.originalTarget.id) {
case "highlight":
if (this.getAccelKey(event)) {
@@ -635,7 +838,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.bottom -= 10 ** event.shiftKey;
+ this.selectionRegion.bottom -= 10 ** exponent;
// eslint-disable-next-line no-fallthrough
case "mover-topLeft":
case "mover-topRight":
@@ -644,7 +847,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.top -= 10 ** event.shiftKey;
+ this.selectionRegion.top -= 10 ** exponent;
this.scrollIfByEdge(
this.windowDimensions.scrollX + this.windowDimensions.clientWidth / 2,
this.selectionRegion.top
@@ -664,7 +867,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.bottom -= 10 ** event.shiftKey;
+ this.selectionRegion.bottom -= 10 ** exponent;
if (this.selectionRegion.y1 >= this.selectionRegion.y2) {
this.selectionRegion.sortCoords();
if (event.originalTarget.id === "mover-bottomLeft") {
@@ -678,11 +881,23 @@ export class ScreenshotsOverlay {
return;
}
+ event.preventDefault();
+ }
+
+ /**
+ * Move the region or its left or right side to the right.
+ * Just the arrow key will move the region by 1px.
+ * Arrow key + shift will move the region by 10px.
+ * Arrow key + control/meta will move to the edge of the window.
+ * @param {Event} event The keydown event
+ */
+ resizingArrowRightKeyDown(event) {
+ this.handleArrowRightKeyDown(event);
+
if (this.#state !== STATES.RESIZING) {
this.#setState(STATES.RESIZING);
}
- event.preventDefault();
this.drawSelectionContainer();
}
@@ -694,6 +909,7 @@ export class ScreenshotsOverlay {
* @param {Event} event The keydown event
*/
handleArrowRightKeyDown(event) {
+ let exponent = event.shiftKey ? 1 : 0;
switch (event.originalTarget.id) {
case "highlight":
if (this.getAccelKey(event)) {
@@ -704,7 +920,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.left += 10 ** event.shiftKey;
+ this.selectionRegion.left += 10 ** exponent;
// eslint-disable-next-line no-fallthrough
case "mover-topRight":
case "mover-bottomRight":
@@ -714,7 +930,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.right += 10 ** event.shiftKey;
+ this.selectionRegion.right += 10 ** exponent;
this.scrollIfByEdge(
this.selectionRegion.right,
this.windowDimensions.scrollY + this.windowDimensions.clientHeight / 2
@@ -735,7 +951,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.left += 10 ** event.shiftKey;
+ this.selectionRegion.left += 10 ** exponent;
if (this.selectionRegion.x1 >= this.selectionRegion.x2) {
this.selectionRegion.sortCoords();
if (event.originalTarget.id === "mover-topLeft") {
@@ -749,12 +965,7 @@ export class ScreenshotsOverlay {
return;
}
- if (this.#state !== STATES.RESIZING) {
- this.#setState(STATES.RESIZING);
- }
-
event.preventDefault();
- this.drawSelectionContainer();
}
/**
@@ -764,7 +975,18 @@ export class ScreenshotsOverlay {
* Arrow key + control/meta will move to the edge of the window.
* @param {Event} event The keydown event
*/
+ resizingArrowDownKeyDown(event) {
+ this.handleArrowDownKeyDown(event);
+
+ if (this.#state !== STATES.RESIZING) {
+ this.#setState(STATES.RESIZING);
+ }
+
+ this.drawSelectionContainer();
+ }
+
handleArrowDownKeyDown(event) {
+ let exponent = event.shiftKey ? 1 : 0;
switch (event.originalTarget.id) {
case "highlight":
if (this.getAccelKey(event)) {
@@ -775,7 +997,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.top += 10 ** event.shiftKey;
+ this.selectionRegion.top += 10 ** exponent;
// eslint-disable-next-line no-fallthrough
case "mover-bottomLeft":
case "mover-bottomRight":
@@ -785,7 +1007,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.bottom += 10 ** event.shiftKey;
+ this.selectionRegion.bottom += 10 ** exponent;
this.scrollIfByEdge(
this.windowDimensions.scrollX + this.windowDimensions.clientWidth / 2,
this.selectionRegion.bottom
@@ -806,7 +1028,7 @@ export class ScreenshotsOverlay {
break;
}
- this.selectionRegion.top += 10 ** event.shiftKey;
+ this.selectionRegion.top += 10 ** exponent;
if (this.selectionRegion.y1 >= this.selectionRegion.y2) {
this.selectionRegion.sortCoords();
if (event.originalTarget.id === "mover-topLeft") {
@@ -820,12 +1042,7 @@ export class ScreenshotsOverlay {
return;
}
- if (this.#state !== STATES.RESIZING) {
- this.#setState(STATES.RESIZING);
- }
-
event.preventDefault();
- this.drawSelectionContainer();
}
/**
@@ -834,27 +1051,39 @@ export class ScreenshotsOverlay {
* @param {Event} event The keydown event
*/
maybeLockFocus(event) {
- if (this.#state !== STATES.SELECTED) {
- return;
- }
-
event.preventDefault();
- if (event.originalTarget.id === "highlight" && event.shiftKey) {
- this.downloadButton.focus({ focusVisible: true });
- } else if (event.originalTarget.id === "download" && !event.shiftKey) {
- this.highlightEl.focus({ focusVisible: true });
- } else {
- // The content document can listen for keydown events and prevent moving
- // focus so we manually move focus to the next element here.
- let direction = event.shiftKey
- ? Services.focus.MOVEFOCUS_BACKWARD
- : Services.focus.MOVEFOCUS_FORWARD;
- Services.focus.moveFocus(
- this.window,
- null,
- direction,
- Services.focus.FLAG_BYKEY
- );
+
+ switch (this.#state) {
+ case STATES.CROSSHAIRS:
+ if (event.shiftKey) {
+ this.#dispatchEvent("Screenshots:FocusPanel", {
+ direction: "backward",
+ });
+ } else {
+ this.#dispatchEvent("Screenshots:FocusPanel", {
+ direction: "forward",
+ });
+ }
+ break;
+ case STATES.SELECTED:
+ if (event.originalTarget.id === "highlight" && event.shiftKey) {
+ this.downloadButton.focus({ focusVisible: true });
+ } else if (event.originalTarget.id === "download" && !event.shiftKey) {
+ this.highlightEl.focus({ focusVisible: true });
+ } else {
+ // The content document can listen for keydown events and prevent moving
+ // focus so we manually move focus to the next element here.
+ let direction = event.shiftKey
+ ? Services.focus.MOVEFOCUS_BACKWARD
+ : Services.focus.MOVEFOCUS_FORWARD;
+ Services.focus.moveFocus(
+ this.window,
+ null,
+ direction,
+ Services.focus.FLAG_BYKEY
+ );
+ }
+ break;
}
}
@@ -870,31 +1099,6 @@ export class ScreenshotsOverlay {
}
}
- /**
- * Handles when a keydown occurs in the screenshots component.
- * All we need to do on keyup is set the state to selected.
- * @param {Event} event The keydown event
- */
- handleKeyUp(event) {
- switch (event.key) {
- case "ArrowLeft":
- case "ArrowUp":
- case "ArrowRight":
- case "ArrowDown":
- switch (event.originalTarget.id) {
- case "highlight":
- case "mover-bottomLeft":
- case "mover-bottomRight":
- case "mover-topLeft":
- case "mover-topRight":
- event.preventDefault();
- this.#setState(STATES.SELECTED);
- break;
- }
- break;
- }
- }
-
/**
* All of the selection ranges were recorded at initialization. The ranges
* are removed when focus is set to the buttons so we add the selection
@@ -926,8 +1130,9 @@ export class ScreenshotsOverlay {
/**
* Set a new state for the overlay
* @param {String} newState
+ * @param {Object} options (optional) Options for calling start of state method
*/
- #setState(newState) {
+ #setState(newState, options = {}) {
if (this.#state === STATES.SELECTED && newState === STATES.CROSSHAIRS) {
this.#dispatchEvent("Screenshots:RecordEvent", {
eventName: "started",
@@ -936,7 +1141,13 @@ export class ScreenshotsOverlay {
}
if (newState !== this.#state) {
this.#dispatchEvent("Screenshots:OverlaySelection", {
- hasSelection: newState == STATES.SELECTED,
+ hasSelection: [
+ STATES.DRAGGING_READY,
+ STATES.DRAGGING,
+ STATES.RESIZING,
+ STATES.SELECTED,
+ ].includes(newState),
+ overlayState: newState,
});
}
this.#state = newState;
@@ -955,7 +1166,7 @@ export class ScreenshotsOverlay {
break;
}
case STATES.SELECTED: {
- this.selectedStart();
+ this.selectedStart(options);
break;
}
case STATES.RESIZING: {
@@ -1015,11 +1226,16 @@ export class ScreenshotsOverlay {
* Hide the preview and hover element containers.
* Draw the selection and buttons containers.
*/
- selectedStart() {
+ selectedStart(options) {
+ this.selectionRegion.sortCoords();
this.hidePreviewContainer();
this.hideHoverElementContainer();
this.drawSelectionContainer();
this.drawButtonsContainer();
+
+ if (!options.doNotMoveFocus) {
+ this.setFocusToActionButton();
+ }
}
/**
@@ -1246,7 +1462,6 @@ export class ScreenshotsOverlay {
if (this.hoverElementRegion.isRegionValid) {
this.selectionRegion.dimensions = this.hoverElementRegion.dimensions;
this.#setState(STATES.SELECTED);
- this.setFocusToActionButton();
this.#dispatchEvent("Screenshots:RecordEvent", {
eventName: "selected",
reason: "element",
@@ -1267,11 +1482,9 @@ export class ScreenshotsOverlay {
right: pageX,
bottom: pageY,
};
- this.selectionRegion.sortCoords();
this.#setState(STATES.SELECTED);
this.maybeRecordRegionSelected();
this.#methodsUsed.region += 1;
- this.setFocusToActionButton();
}
/**
@@ -1282,9 +1495,7 @@ export class ScreenshotsOverlay {
*/
resizingDragEnd(pageX, pageY) {
this.resizingDrag(pageX, pageY);
- this.selectionRegion.sortCoords();
this.#setState(STATES.SELECTED);
- this.setFocusToActionButton();
this.maybeRecordRegionSelected();
if (this.#moverId === "highlight") {
this.#methodsUsed.move += 1;
@@ -1491,6 +1702,10 @@ export class ScreenshotsOverlay {
this.buttonsContainer.hidden = true;
}
+ updateCursorRegion(left, top) {
+ this.cursorRegion = { left, top, right: left, bottom: top };
+ }
+
/**
* Set the pointer events to none on the screenshots elements so
* elementFromPoint can find the real element at the given point.
diff --git a/browser/components/screenshots/ScreenshotsUtils.sys.mjs b/browser/components/screenshots/ScreenshotsUtils.sys.mjs
index 6e19194c4036..476126b412be 100644
--- a/browser/components/screenshots/ScreenshotsUtils.sys.mjs
+++ b/browser/components/screenshots/ScreenshotsUtils.sys.mjs
@@ -91,6 +91,7 @@ export class ScreenshotsComponentParent extends JSWindowActorParent {
case "Screenshots:OverlaySelection":
ScreenshotsUtils.setPerBrowserState(browser, {
hasOverlaySelection: message.data.hasSelection,
+ overlayState: message.data.overlayState,
});
break;
case "Screenshots:ShowPanel":
@@ -99,6 +100,9 @@ export class ScreenshotsComponentParent extends JSWindowActorParent {
case "Screenshots:HidePanel":
ScreenshotsUtils.closePanel(browser);
break;
+ case "Screenshots:MoveFocusToParent":
+ ScreenshotsUtils.focusPanel(browser, message.data);
+ break;
}
}
@@ -191,11 +195,7 @@ export var ScreenshotsUtils = {
handleEvent(event) {
switch (event.type) {
case "keydown":
- if (event.key === "Escape") {
- // Escape should cancel and exit
- let browser = event.view.gBrowser.selectedBrowser;
- this.cancel(browser, "escape");
- }
+ this.handleKeyDownEvent(event);
break;
case "TabSelect":
this.handleTabSelect(event);
@@ -209,6 +209,33 @@ export var ScreenshotsUtils = {
}
},
+ handleKeyDownEvent(event) {
+ let browser =
+ event.view.browsingContext.topChromeWindow.gBrowser.selectedBrowser;
+ if (!browser) {
+ return;
+ }
+
+ switch (event.key) {
+ case "Escape":
+ // The chromeEventHandler in the child actor will handle events that
+ // don't match this
+ if (event.target.parentElement === this.panelForBrowser(browser)) {
+ this.cancel(browser, "escape");
+ }
+ break;
+ case "ArrowLeft":
+ case "ArrowUp":
+ case "ArrowRight":
+ case "ArrowDown":
+ this.handleArrowKeyDown(event, browser);
+ break;
+ case "Tab":
+ this.maybeLockFocus(event);
+ break;
+ }
+ },
+
/**
* When we swap docshells for a given screenshots browser, we need to update
* the browserToScreenshotsState WeakMap to the correct browser. If the old
@@ -273,6 +300,104 @@ export var ScreenshotsUtils = {
}
},
+ /**
+ * If the overlay state is crosshairs or dragging, move the native cursor
+ * respective to the arrow key pressed.
+ * @param {Event} event A keydown event
+ * @param {Browser} browser The selected browser
+ * @returns
+ */
+ handleArrowKeyDown(event, browser) {
+ if (Services.appinfo.isWayland) {
+ return;
+ }
+
+ let { overlayState } = this.browserToScreenshotsState.get(browser);
+
+ if (!["crosshairs", "dragging"].includes(overlayState)) {
+ return;
+ }
+
+ let left = 0;
+ let top = 0;
+ let exponent = event.shiftKey ? 1 : 0;
+ switch (event.key) {
+ case "ArrowLeft":
+ left -= 10 ** exponent;
+ break;
+ case "ArrowUp":
+ top -= 10 ** exponent;
+ break;
+ case "ArrowRight":
+ left += 10 ** exponent;
+ break;
+ case "ArrowDown":
+ top += 10 ** exponent;
+ break;
+ default:
+ return;
+ }
+
+ // Clear and move focus to browser so the child actor can capture events
+ this.clearContentFocus(browser);
+ Services.focus.clearFocus(browser.ownerGlobal);
+ Services.focus.setFocus(browser, 0);
+
+ let x = {};
+ let y = {};
+ let win = browser.ownerGlobal;
+ win.windowUtils.getLastOverWindowPointerLocationInCSSPixels(x, y);
+
+ this.moveCursor(
+ {
+ left: (x.value + left) * win.devicePixelRatio,
+ top: (y.value + top) * win.devicePixelRatio,
+ },
+ browser
+ );
+ },
+
+ /**
+ * Move the native cursor to the given position. Clamp the position to the
+ * window just in case.
+ * @param {Object} position An object containing the left and top position
+ * @param {Browser} browser The selected browser
+ */
+ moveCursor(position, browser) {
+ let { left, top } = position;
+ let win = browser.ownerGlobal;
+
+ const windowLeft = win.mozInnerScreenX * win.devicePixelRatio;
+ const windowTop = win.mozInnerScreenY * win.devicePixelRatio;
+ const contentTop =
+ (win.mozInnerScreenY + (win.innerHeight - browser.clientHeight)) *
+ win.devicePixelRatio;
+ const windowRight =
+ (win.mozInnerScreenX + win.innerWidth) * win.devicePixelRatio;
+ const windowBottom =
+ (win.mozInnerScreenY + win.innerHeight) * win.devicePixelRatio;
+
+ left += windowLeft;
+ top += windowTop;
+
+ // Clamp left and top to content dimensions
+ let parsedLeft = Math.round(
+ Math.min(Math.max(left, windowLeft), windowRight)
+ );
+ let parsedTop = Math.round(
+ Math.min(Math.max(top, contentTop), windowBottom)
+ );
+
+ win.windowUtils.sendNativeMouseEvent(
+ parsedLeft,
+ parsedTop,
+ win.windowUtils.NATIVE_MOUSE_MESSAGE_MOVE,
+ 0,
+ 0,
+ win.document.documentElement
+ );
+ },
+
observe(subj, topic, data) {
let { gBrowser } = subj;
let browser = gBrowser.selectedBrowser;
@@ -335,6 +460,12 @@ export var ScreenshotsUtils = {
browser.addEventListener("SwapDocShells", this);
let gBrowser = browser.getTabBrowser();
gBrowser.tabContainer.addEventListener("TabSelect", this);
+
+ // Wayland doesn't support `sendNativeMouseEvent` which is what the
+ // keydown event is used for.
+ if (!Services.appinfo.isWayland) {
+ browser.addEventListener("keydown", this);
+ }
break;
}
case UIPhases.INITIAL:
@@ -364,6 +495,9 @@ export var ScreenshotsUtils = {
browser.removeEventListener("SwapDocShells", this);
const gBrowser = browser.getTabBrowser();
gBrowser.tabContainer.removeEventListener("TabSelect", this);
+ if (!Services.appinfo.isWayland) {
+ browser.removeEventListener("keydown", this);
+ }
this.browserToScreenshotsState.delete(browser);
if (Cu.isInAutomation) {
@@ -396,6 +530,53 @@ export var ScreenshotsUtils = {
Object.assign(perBrowserState, nameValues);
},
+ maybeLockFocus(event) {
+ let browser = event.view.gBrowser.selectedBrowser;
+
+ if (!Services.focus.focusedElement) {
+ event.preventDefault();
+ this.focusPanel(browser);
+ return;
+ }
+
+ let target = event.explicitOriginalTarget;
+
+ if (!target.closest("moz-button-group")) {
+ return;
+ }
+
+ let isElementFirst = !!target.nextElementSibling;
+
+ if (
+ (isElementFirst && event.shiftKey) ||
+ (!isElementFirst && !event.shiftKey)
+ ) {
+ event.preventDefault();
+ this.moveFocusToContent(browser);
+ }
+ },
+
+ focusPanel(browser, { direction } = {}) {
+ let buttonsPanel = this.panelForBrowser(browser);
+ if (direction) {
+ buttonsPanel
+ .querySelector("screenshots-buttons")
+ .focusButton(direction === "forward" ? "first" : "last");
+ } else {
+ buttonsPanel
+ .querySelector("screenshots-buttons")
+ .focusButton(lazy.SCREENSHOTS_LAST_SCREENSHOT_METHOD);
+ }
+ },
+
+ moveFocusToContent(browser) {
+ this.getActor(browser).sendAsyncMessage("Screenshots:MoveFocusToContent");
+ },
+
+ clearContentFocus(browser) {
+ this.getActor(browser).sendAsyncMessage("Screenshots:ClearFocus");
+ },
+
/**
* Attempt to place focus on the element that had focus before screenshots UI was shown
*
diff --git a/browser/components/screenshots/screenshots-buttons.css b/browser/components/screenshots/screenshots-buttons.css
index b63308d8b4e0..ccb092174ed6 100644
--- a/browser/components/screenshots/screenshots-buttons.css
+++ b/browser/components/screenshots/screenshots-buttons.css
@@ -14,15 +14,15 @@
border-radius: var(--arrowpanel-border-radius);
}
-.full-page {
+#full-page {
background-image: url("chrome://browser/content/screenshots/menu-fullpage.svg");
}
-.visible-page {
+#visible-page {
background-image: url("chrome://browser/content/screenshots/menu-visible.svg");
}
-.full-page, .visible-page {
+#full-page, #visible-page {
-moz-context-properties: fill, stroke;
fill: currentColor;
/* stroke is the secondary fill color used to define the viewport shape in the SVGs */
diff --git a/browser/components/screenshots/screenshots-buttons.js b/browser/components/screenshots/screenshots-buttons.js
index 9ac8dab2cf4b..e501da5a51bc 100644
--- a/browser/components/screenshots/screenshots-buttons.js
+++ b/browser/components/screenshots/screenshots-buttons.js
@@ -20,9 +20,10 @@
-
-
+
+
+
`;
}
@@ -41,12 +42,12 @@
this.shadowRoot.append(ScreenshotsButtons.fragment);
- let visibleButton = this.shadowRoot.querySelector(".visible-page");
+ let visibleButton = shadowRoot.getElementById("visible-page");
visibleButton.onclick = function () {
ScreenshotsUtils.doScreenshot(gBrowser.selectedBrowser, "visible");
};
- let fullpageButton = this.shadowRoot.querySelector(".full-page");
+ let fullpageButton = shadowRoot.getElementById("full-page");
fullpageButton.onclick = function () {
ScreenshotsUtils.doScreenshot(gBrowser.selectedBrowser, "full_page");
};
@@ -65,11 +66,19 @@
await this.shadowRoot.querySelector("moz-button-group").updateComplete;
if (buttonToFocus === "fullpage") {
this.shadowRoot
- .querySelector(".full-page")
+ .getElementById("full-page")
.focus({ focusVisible: true });
+ } else if (buttonToFocus === "first") {
+ this.shadowRoot
+ .querySelector("moz-button-group")
+ .firstElementChild.focus({ focusVisible: true });
+ } else if (buttonToFocus === "last") {
+ this.shadowRoot
+ .querySelector("moz-button-group")
+ .lastElementChild.focus({ focusVisible: true });
} else {
this.shadowRoot
- .querySelector(".visible-page")
+ .getElementById("visible-page")
.focus({ focusVisible: true });
}
}
diff --git a/browser/components/screenshots/tests/browser/browser.toml b/browser/components/screenshots/tests/browser/browser.toml
index 976a0037c805..5923088b3146 100644
--- a/browser/components/screenshots/tests/browser/browser.toml
+++ b/browser/components/screenshots/tests/browser/browser.toml
@@ -21,6 +21,8 @@ skip-if = ["os == 'linux'"]
["browser_keyboard_shortcuts.js"]
+["browser_keyboard_tests.js"]
+
["browser_overlay_keyboard_test.js"]
["browser_screenshots_drag_scroll_test.js"]
diff --git a/browser/components/screenshots/tests/browser/browser_keyboard_shortcuts.js b/browser/components/screenshots/tests/browser/browser_keyboard_shortcuts.js
index bca96f333f51..66ab25f1c6cc 100644
--- a/browser/components/screenshots/tests/browser/browser_keyboard_shortcuts.js
+++ b/browser/components/screenshots/tests/browser/browser_keyboard_shortcuts.js
@@ -56,7 +56,7 @@ add_task(async function test_download_shortcut() {
"screenshots-preview-ready"
);
- let visibleButton = await helper.getPanelButton(".visible-page");
+ let visibleButton = await helper.getPanelButton("#visible-page");
visibleButton.click();
await screenshotReady;
@@ -108,7 +108,7 @@ add_task(async function test_copy_shortcut() {
"screenshots-preview-ready"
);
- let visibleButton = await helper.getPanelButton(".visible-page");
+ let visibleButton = await helper.getPanelButton("#visible-page");
visibleButton.click();
await screenshotReady;
diff --git a/browser/components/screenshots/tests/browser/browser_keyboard_tests.js b/browser/components/screenshots/tests/browser/browser_keyboard_tests.js
new file mode 100644
index 000000000000..fa75f7d13397
--- /dev/null
+++ b/browser/components/screenshots/tests/browser/browser_keyboard_tests.js
@@ -0,0 +1,489 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const KEY_TO_EXPECTED_POSITION_ARRAY = [
+ [
+ "ArrowRight",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 110,
+ },
+ ],
+ [
+ "ArrowDown",
+ {
+ top: 100,
+ left: 100,
+ bottom: 110,
+ right: 110,
+ },
+ ],
+ [
+ "ArrowLeft",
+ {
+ top: 100,
+ left: 100,
+ bottom: 110,
+ right: 100,
+ },
+ ],
+ [
+ "ArrowUp",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 100,
+ },
+ ],
+ ["ArrowDown", { top: 100, left: 100, bottom: 110, right: 100 }],
+ [
+ "ArrowRight",
+ {
+ top: 100,
+ left: 100,
+ bottom: 110,
+ right: 110,
+ },
+ ],
+ [
+ "ArrowUp",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 110,
+ },
+ ],
+ [
+ "ArrowLeft",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 100,
+ },
+ ],
+];
+
+const SHIFT_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY = [
+ [
+ "ArrowRight",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 200,
+ },
+ ],
+ [
+ "ArrowDown",
+ {
+ top: 100,
+ left: 100,
+ bottom: 200,
+ right: 200,
+ },
+ ],
+ [
+ "ArrowLeft",
+ {
+ top: 100,
+ left: 100,
+ bottom: 200,
+ right: 100,
+ },
+ ],
+ [
+ "ArrowUp",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 100,
+ },
+ ],
+ ["ArrowDown", { top: 100, left: 100, bottom: 200, right: 100 }],
+ [
+ "ArrowRight",
+ {
+ top: 100,
+ left: 100,
+ bottom: 200,
+ right: 200,
+ },
+ ],
+ [
+ "ArrowUp",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 200,
+ },
+ ],
+ [
+ "ArrowLeft",
+ {
+ top: 100,
+ left: 100,
+ bottom: 100,
+ right: 100,
+ },
+ ],
+];
+
+add_task(async function test_focusMovesToContentOnArrowKeydown() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_PAGE,
+ },
+ async browser => {
+ let helper = new ScreenshotsHelper(browser);
+ helper.triggerUIFromToolbar();
+ await helper.waitForOverlay();
+
+ let visibleButton = await helper.getPanelButton("#visible-page");
+ visibleButton.focus();
+
+ await BrowserTestUtils.waitForCondition(() => {
+ return visibleButton.getRootNode().activeElement === visibleButton;
+ }, "The visible button in the panel should have focus");
+ info(
+ `Actual focused id: ${Services.focus.focusedElement.id}. Expected focused id: ${visibleButton.id}`
+ );
+ is(
+ Services.focus.focusedElement,
+ visibleButton,
+ "The visible button in the panel should have focus"
+ );
+
+ EventUtils.synthesizeKey("ArrowLeft");
+
+ // Focus should move to the content document
+ let fullpageButton = await helper.getPanelButton("#full-page");
+ await BrowserTestUtils.waitForCondition(() => {
+ return (
+ fullpageButton.getRootNode().activeElement !== fullpageButton &&
+ visibleButton.getRootNode().activeElement !== visibleButton
+ );
+ }, "The visible and full page buttons do not have focus");
+ Assert.notEqual(
+ Services.focus.focusedElement,
+ visibleButton,
+ "The visible button does not have focus"
+ );
+ Assert.notEqual(
+ Services.focus.focusedElement,
+ fullpageButton,
+ "The full page button does not have focus"
+ );
+
+ await ContentTask.spawn(browser, null, async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+
+ let { left: currentCursorX, top: currentCursorY } =
+ screenshotsChild.overlay.cursorRegion;
+
+ let rect = content.document
+ .getElementById("testPageElement")
+ .getBoundingClientRect();
+
+ let repeatShiftLeft = Math.round((currentCursorX - rect.right) / 10);
+ EventUtils.synthesizeKey(
+ "ArrowLeft",
+ { repeat: repeatShiftLeft, shiftKey: true },
+ content
+ );
+
+ let repeatLeft = (currentCursorX - rect.right) % 10;
+ EventUtils.synthesizeKey("ArrowLeft", { repeat: repeatLeft }, content);
+
+ let repeatShiftRight = Math.round((currentCursorY - rect.bottom) / 10);
+ EventUtils.synthesizeKey(
+ "ArrowUp",
+ { repeat: repeatShiftRight, shiftKey: true },
+ content
+ );
+
+ let repeatRight = (currentCursorY - rect.bottom) % 10;
+ EventUtils.synthesizeKey("ArrowUp", { repeat: repeatRight }, content);
+
+ await ContentTaskUtils.waitForCondition(() => {
+ return screenshotsChild.overlay.hoverElementRegion.isRegionValid;
+ }, "Wait for hover element region to be valid");
+
+ EventUtils.synthesizeKey("Enter", {}, content);
+ });
+
+ let rect = await helper.getTestPageElementRect();
+ let region = await helper.getSelectionRegionDimensions();
+
+ is(
+ region.left,
+ rect.left,
+ "The selected region left is the same as the element left"
+ );
+ is(
+ region.right,
+ rect.right,
+ "The selected region right is the same as the element right"
+ );
+ is(
+ region.top,
+ rect.top,
+ "The selected region top is the same as the element top"
+ );
+ is(
+ region.bottom,
+ rect.bottom,
+ "The selected region bottom is the same as the element bottom"
+ );
+ }
+ );
+});
+
+add_task(async function test_createRegionWithKeyboard() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_PAGE,
+ },
+ async browser => {
+ let helper = new ScreenshotsHelper(browser);
+
+ helper.triggerUIFromToolbar();
+ await helper.waitForOverlay();
+
+ mouse.move(100, 100);
+
+ await SpecialPowers.spawn(
+ browser,
+ [KEY_TO_EXPECTED_POSITION_ARRAY],
+ async keyToExpectedPositionArray => {
+ function assertSelectionRegionDimensions(
+ actualDimensions,
+ expectedDimensions
+ ) {
+ is(
+ actualDimensions.top,
+ expectedDimensions.top,
+ "Top dimension is correct"
+ );
+ is(
+ actualDimensions.left,
+ expectedDimensions.left,
+ "Left dimension is correct"
+ );
+ is(
+ actualDimensions.bottom,
+ expectedDimensions.bottom,
+ "Bottom dimension is correct"
+ );
+ is(
+ actualDimensions.right,
+ expectedDimensions.right,
+ "Right dimension is correct"
+ );
+ }
+
+ let stateChangePromise = expectedState =>
+ ContentTaskUtils.waitForCondition(() => {
+ info(
+ `got ${screenshotsChild.overlay.state}. expected ${expectedState}`
+ );
+ return screenshotsChild.overlay.state === expectedState;
+ }, `Wait for overlay state to be ${expectedState}`);
+
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ screenshotsChild.overlay.cursorRegion.left === 100 &&
+ screenshotsChild.overlay.cursorRegion.top === 100
+ );
+ }, "Wait for cursor region to be at 10, 10");
+
+ EventUtils.synthesizeKey(" ", {}, content);
+
+ let expectedState = "dragging";
+ await stateChangePromise(expectedState);
+
+ for (let [key, expectedDimensions] of keyToExpectedPositionArray) {
+ EventUtils.synthesizeKey(key, { repeat: 10 }, content);
+ info(`Key: ${key}`);
+ info(
+ `Actual dimensions: ${JSON.stringify(
+ screenshotsChild.overlay.selectionRegion.dimensions,
+ null,
+ 2
+ )}`
+ );
+ info(
+ `Expected dimensions: ${JSON.stringify(
+ expectedDimensions,
+ null,
+ 2
+ )}`
+ );
+ assertSelectionRegionDimensions(
+ screenshotsChild.overlay.selectionRegion.dimensions,
+ expectedDimensions
+ );
+ }
+
+ EventUtils.synthesizeKey("ArrowRight", { repeat: 10 }, content);
+ EventUtils.synthesizeKey("ArrowDown", { repeat: 10 }, content);
+
+ EventUtils.synthesizeKey(" ", {}, content);
+ expectedState = "selected";
+ await stateChangePromise(expectedState);
+ }
+ );
+
+ let region = await helper.getSelectionRegionDimensions();
+
+ is(region.left, 100, "The selected region left is 100");
+ is(region.right, 110, "The selected region right is 110");
+ is(region.top, 100, "The selected region top is 100");
+ is(region.bottom, 110, "The selected region bottom is 110");
+
+ helper.triggerUIFromToolbar();
+ await helper.waitForOverlayClosed();
+ }
+ );
+});
+
+add_task(async function test_createRegionWithKeyboardWithShift() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_PAGE,
+ },
+ async browser => {
+ let helper = new ScreenshotsHelper(browser);
+
+ helper.triggerUIFromToolbar();
+ await helper.waitForOverlay();
+
+ mouse.move(100, 100);
+
+ await SpecialPowers.spawn(
+ browser,
+ [SHIFT_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY],
+ async keyToExpectedPositionArray => {
+ function assertSelectionRegionDimensions(
+ actualDimensions,
+ expectedDimensions
+ ) {
+ is(
+ actualDimensions.top,
+ expectedDimensions.top,
+ "Top dimension is correct"
+ );
+ is(
+ actualDimensions.left,
+ expectedDimensions.left,
+ "Left dimension is correct"
+ );
+ is(
+ actualDimensions.bottom,
+ expectedDimensions.bottom,
+ "Bottom dimension is correct"
+ );
+ is(
+ actualDimensions.right,
+ expectedDimensions.right,
+ "Right dimension is correct"
+ );
+ }
+
+ let stateChangePromise = expectedState =>
+ ContentTaskUtils.waitForCondition(() => {
+ info(
+ `got ${screenshotsChild.overlay.state}. expected ${expectedState}`
+ );
+ return screenshotsChild.overlay.state === expectedState;
+ }, `Wait for overlay state to be ${expectedState}`);
+
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ screenshotsChild.overlay.cursorRegion.left === 100 &&
+ screenshotsChild.overlay.cursorRegion.top === 100
+ );
+ }, "Wait for cursor region to be at 10, 10");
+
+ EventUtils.synthesizeKey(" ", {}, content);
+
+ let expectedState = "dragging";
+ await stateChangePromise(expectedState);
+
+ for (let [key, expectedDimensions] of keyToExpectedPositionArray) {
+ EventUtils.synthesizeKey(
+ key,
+ { repeat: 10, shiftKey: true },
+ content
+ );
+ info(`Key: ${key}`);
+ info(
+ `Actual dimensions: ${JSON.stringify(
+ screenshotsChild.overlay.selectionRegion.dimensions,
+ null,
+ 2
+ )}`
+ );
+ info(
+ `Expected dimensions: ${JSON.stringify(
+ expectedDimensions,
+ null,
+ 2
+ )}`
+ );
+ assertSelectionRegionDimensions(
+ screenshotsChild.overlay.selectionRegion.dimensions,
+ expectedDimensions
+ );
+ }
+
+ EventUtils.synthesizeKey(
+ "ArrowRight",
+ { repeat: 10, shiftKey: true },
+ content
+ );
+ EventUtils.synthesizeKey(
+ "ArrowDown",
+ { repeat: 10, shiftKey: true },
+ content
+ );
+
+ EventUtils.synthesizeKey(" ", {}, content);
+ expectedState = "selected";
+ await stateChangePromise(expectedState);
+ }
+ );
+
+ let region = await helper.getSelectionRegionDimensions();
+
+ is(region.left, 100, "The selected region left is 100");
+ is(region.right, 200, "The selected region right is 200");
+ is(region.top, 100, "The selected region top is 100");
+ is(region.bottom, 200, "The selected region bottom is 200");
+
+ helper.triggerUIFromToolbar();
+ await helper.waitForOverlayClosed();
+ }
+ );
+});
diff --git a/browser/components/screenshots/tests/browser/browser_overlay_keyboard_test.js b/browser/components/screenshots/tests/browser/browser_overlay_keyboard_test.js
index 592587a67d7d..71b93b5c0656 100644
--- a/browser/components/screenshots/tests/browser/browser_overlay_keyboard_test.js
+++ b/browser/components/screenshots/tests/browser/browser_overlay_keyboard_test.js
@@ -136,9 +136,6 @@ const SHIFT_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY = [
],
];
-/**
- *
- */
add_task(async function test_moveRegionWithKeyboard() {
await BrowserTestUtils.withNewTab(
{
diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js b/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js
index e2b412dc1c90..c8e3142c601a 100644
--- a/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js
+++ b/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js
@@ -46,7 +46,7 @@ async function restoreFocusOnEscape(initialFocusElem, helper) {
);
EventUtils.synthesizeKey("s", { shiftKey: true, accelKey: true });
- let button = await helper.getPanelButton(".visible-page");
+ let button = await helper.getPanelButton("#visible-page");
info("Panel is now visible, got button: " + button.className);
info(
`focusedElement: ${Services.focus.focusedElement.localName}.${Services.focus.focusedElement.className}`
@@ -88,7 +88,7 @@ add_task(async function testPanelFocused() {
info("Opening Screenshots and waiting for the panel");
helper.triggerUIFromToolbar();
- let button = await helper.getPanelButton(".visible-page");
+ let button = await helper.getPanelButton("#visible-page");
info("Panel is now visible, got button: " + button.className);
info(
`focusedElement: ${Services.focus.focusedElement.localName}.${Services.focus.focusedElement.className}`
@@ -215,7 +215,7 @@ add_task(async function test_focusLastUsedMethod() {
helper.triggerUIFromToolbar();
await helper.waitForOverlay();
- let expectedFocusedButton = await helper.getPanelButton(".visible-page");
+ let expectedFocusedButton = await helper.getPanelButton("#visible-page");
await BrowserTestUtils.waitForCondition(() => {
return (
@@ -233,7 +233,7 @@ add_task(async function test_focusLastUsedMethod() {
let screenshotReady = TestUtils.topicObserved(
"screenshots-preview-ready"
);
- let fullpageButton = await helper.getPanelButton(".full-page");
+ let fullpageButton = await helper.getPanelButton("#full-page");
fullpageButton.click();
await screenshotReady;
@@ -242,7 +242,7 @@ add_task(async function test_focusLastUsedMethod() {
await helper.waitForOverlay();
- expectedFocusedButton = await helper.getPanelButton(".full-page");
+ expectedFocusedButton = await helper.getPanelButton("#full-page");
await BrowserTestUtils.waitForCondition(() => {
return (
@@ -258,7 +258,7 @@ add_task(async function test_focusLastUsedMethod() {
);
screenshotReady = TestUtils.topicObserved("screenshots-preview-ready");
- let visiblepageButton = await helper.getPanelButton(".visible-page");
+ let visiblepageButton = await helper.getPanelButton("#visible-page");
visiblepageButton.click();
await screenshotReady;
@@ -267,7 +267,7 @@ add_task(async function test_focusLastUsedMethod() {
await helper.waitForOverlay();
- expectedFocusedButton = await helper.getPanelButton(".visible-page");
+ expectedFocusedButton = await helper.getPanelButton("#visible-page");
await BrowserTestUtils.waitForCondition(() => {
return (
@@ -309,7 +309,7 @@ add_task(async function test_focusLastUsedMethod() {
helper.triggerUIFromToolbar();
await helper.waitForOverlay();
- let visibleButton = await helper.getPanelButton(".visible-page");
+ let visibleButton = await helper.getPanelButton("#visible-page");
screenshotReady = TestUtils.topicObserved("screenshots-preview-ready");
visibleButton.click();
@@ -341,7 +341,7 @@ add_task(async function test_focusLastUsedMethod() {
helper.triggerUIFromToolbar();
await helper.waitForOverlay();
- visibleButton = await helper.getPanelButton(".visible-page");
+ visibleButton = await helper.getPanelButton("#visible-page");
screenshotReady = TestUtils.topicObserved("screenshots-preview-ready");
visibleButton.click();
@@ -370,3 +370,89 @@ add_task(async function test_focusLastUsedMethod() {
await SpecialPowers.popPrefEnv();
});
+
+add_task(async function testFocusedIsLocked() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_PAGE,
+ },
+ async browser => {
+ let helper = new ScreenshotsHelper(browser);
+ helper.triggerUIFromToolbar();
+ await helper.waitForOverlay();
+
+ let panel = await helper.waitForPanel();
+ let mozButtonGroup = panel
+ .querySelector("screenshots-buttons")
+ .shadowRoot.querySelector("moz-button-group");
+ let firstButton = mozButtonGroup.firstElementChild;
+ let lastButton = mozButtonGroup.lastElementChild;
+
+ firstButton.focus();
+
+ await BrowserTestUtils.waitForCondition(() => {
+ return firstButton.getRootNode().activeElement === firstButton;
+ }, "The first button in the panel should have focus");
+ info(
+ `Actual focused id: ${Services.focus.focusedElement.id}. Expected focused id: ${firstButton.id}`
+ );
+ is(
+ Services.focus.focusedElement,
+ firstButton,
+ "The first button in the panel should have focus"
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab");
+
+ await BrowserTestUtils.waitForCondition(() => {
+ return lastButton.getRootNode().activeElement === lastButton;
+ }, "The last button in the panel should have focus");
+ info(
+ `Actual focused id: ${Services.focus.focusedElement.id}. Expected focused id: ${lastButton.id}`
+ );
+ is(
+ Services.focus.focusedElement,
+ lastButton,
+ "The last button in the panel should have focus"
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab");
+
+ // Focus should move to the content document
+ await BrowserTestUtils.waitForCondition(() => {
+ return (
+ firstButton.getRootNode().activeElement !== firstButton &&
+ lastButton.getRootNode().activeElement !== lastButton
+ );
+ }, "The first and last buttons do not have focus");
+ Assert.notEqual(
+ Services.focus.focusedElement,
+ firstButton,
+ "The first button does not have focus"
+ );
+ Assert.notEqual(
+ Services.focus.focusedElement,
+ lastButton,
+ "The last button does not have focus"
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab");
+
+ info(
+ `Actual focused id: ${Services.focus.focusedElement.id}. Expected focused id: ${firstButton.id}`
+ );
+ await BrowserTestUtils.waitForCondition(() => {
+ return firstButton.getRootNode().activeElement === firstButton;
+ }, "The first button in the panel should have focus");
+ info(
+ `Actual focused id: ${Services.focus.focusedElement.id}. Expected focused id: ${firstButton.id}`
+ );
+ is(
+ Services.focus.focusedElement,
+ firstButton,
+ "The first button in the panel should have focus"
+ );
+ }
+ );
+});
diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js b/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js
index 0ce55eb08e15..1b9ecd0e7977 100644
--- a/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js
+++ b/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js
@@ -205,7 +205,7 @@ add_task(async function test_started_retry() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
await screenshotReady;
@@ -242,7 +242,7 @@ add_task(async function test_canceled() {
// click the full page button in panel
let fullPageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".full-page");
+ .shadowRoot.querySelector("#full-page");
fullPageButton.click();
await screenshotReady;
@@ -303,7 +303,7 @@ add_task(async function test_copy() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
info("clicked visible page, waiting for screenshots-preview-ready");
await screenshotReady;
@@ -413,7 +413,7 @@ add_task(async function test_extra_telemetry() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
info("clicked visible page, waiting for screenshots-preview-ready");
await screenshotReady;
@@ -429,7 +429,7 @@ add_task(async function test_extra_telemetry() {
// click the full page button in panel
let fullPageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".full-page");
+ .shadowRoot.querySelector("#full-page");
fullPageButton.click();
await screenshotReady;
diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js
index 66959baa5bd0..b98499c51682 100644
--- a/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js
+++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js
@@ -109,7 +109,7 @@ add_task(async function test_download_without_filepicker() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
await screenshotReady;
diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js
index 8cbda11e32e5..92b136d3a6ed 100644
--- a/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js
+++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js
@@ -41,7 +41,7 @@ add_task(async function test_fullpageScreenshot() {
// click the full page button in panel
let visiblePage = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".full-page");
+ .shadowRoot.querySelector("#full-page");
visiblePage.click();
await screenshotReady;
@@ -136,7 +136,7 @@ add_task(async function test_fullpageScreenshotScrolled() {
// click the full page button in panel
let visiblePage = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".full-page");
+ .shadowRoot.querySelector("#full-page");
visiblePage.click();
await screenshotReady;
diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js
index d2bbd4b906e7..7c207cc8cf41 100644
--- a/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js
+++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js
@@ -45,7 +45,7 @@ add_task(async function test_visibleScreenshot() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
await screenshotReady;
@@ -142,7 +142,7 @@ add_task(async function test_visibleScreenshotScrolledY() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
await screenshotReady;
@@ -239,7 +239,7 @@ add_task(async function test_visibleScreenshotScrolledX() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
await screenshotReady;
@@ -336,7 +336,7 @@ add_task(async function test_visibleScreenshotScrolledXAndY() {
// click the visible page button in panel
let visiblePageButton = panel
.querySelector("screenshots-buttons")
- .shadowRoot.querySelector(".visible-page");
+ .shadowRoot.querySelector("#visible-page");
visiblePageButton.click();
await screenshotReady;
diff --git a/browser/components/screenshots/tests/browser/head.js b/browser/components/screenshots/tests/browser/head.js
index 93652b2be90b..177b986b6a5b 100644
--- a/browser/components/screenshots/tests/browser/head.js
+++ b/browser/components/screenshots/tests/browser/head.js
@@ -28,8 +28,8 @@ const { MAX_CAPTURE_DIMENSION, MAX_CAPTURE_AREA } = ChromeUtils.importESModule(
const gScreenshotUISelectors = {
panel: "#screenshotsPagePanel",
- fullPageButton: "button.full-page",
- visiblePageButton: "button.visible-page",
+ fullPageButton: "button#full-page",
+ visiblePageButton: "button#visible-page",
copyButton: "button.#copy",
};