Bug 1801957 - Create region with keyboard for screenshots. r=sfoster,firefox-desktop-core-reviewers ,accessibility-frontend-reviewers,Gijs,morgan

The problem I'm trying to solve: There is currently no way to create a selected region in the screenshots overlay with the keyboard.
I recently added support for resizing a selected region in bug 1801954, but a user still needs to create a region with the mouse before the keyboard can be used.

I tried to look at what other browsers are doing in this scenario. Unfortunately there isn't much to go off. Most browsers, I can't even take a screenshot and the only browser that I found to have keyboard support for screenshots is MS Edge. In Edge, when you open screenshots, if your mouse isn't over the content area it will immediately move your mouse to the center of the page. If your mouse is over the content, it remains in place. Then, arrow keys will move the cursor around and you can hit space/enter to start creating a region.

I didn't like that Edge moved the mouse immediately after opening screenshots so I took a different approach.

My approach:
Screenshots will not move the mouse until an arrow key is pressed.
How it works:
If your cursor is above the content and an arrow key is pressed, the cursor will move in the direction of the arrow key that was pressed.
If your cursor is not above the content and a arrow key is pressed, the cursor will be moved to the middle of the content. Screenshots will not move the cursor until an arrow key is pressed.

When moving around the overlay with the keyboard: only hitting an arrow key will move the cursor around by 1px. If shift + arrow key, the cursor will move around by 10px.
When space is clicked while moving the cursor, it will start a region. Moving the arrow keys allows the region to sized.
When the cursor is above an element and the hover element rect is visible, hitting enter will select that region. If no hover element region exists, enter will behave the same as space.

I am also keeping the screenshots UI focused in this patch. Tab/shift + tab will keep focus to screenshots UI. Shift + F6 will escape the focus loop I've made in this patch if needed. Although, if a user has entered screenshots, it makes sense that for the current time, only screenshots UI is focusable.

Differential Revision: https://phabricator.services.mozilla.com/D197703
This commit is contained in:
Niklas Baumgardner 2024-05-02 15:34:39 +00:00
parent 8ec65c16e0
commit 83b479b588
16 changed files with 1147 additions and 162 deletions

View file

@ -48,6 +48,11 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
return this.removeEventListeners();
case "Screenshots:AddEventListeners":
return this.addEventListeners();
case "Screenshots:MoveFocusToContent":
return this.focusOverlay();
case "Screenshots:ClearFocus":
Services.focus.clearFocus(this.contentWindow);
return null;
}
return null;
}
@ -99,8 +104,8 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
this.requestDownloadScreenshot(event.detail.region);
break;
case "Screenshots:OverlaySelection": {
let { hasSelection } = event.detail;
this.sendOverlaySelection({ hasSelection });
let { hasSelection, overlayState } = event.detail;
this.sendOverlaySelection({ hasSelection, overlayState });
break;
}
case "Screenshots:RecordEvent": {
@ -109,10 +114,13 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
break;
}
case "Screenshots:ShowPanel":
this.showPanel();
this.sendAsyncMessage("Screenshots:ShowPanel");
break;
case "Screenshots:HidePanel":
this.hidePanel();
this.sendAsyncMessage("Screenshots:HidePanel");
break;
case "Screenshots:FocusPanel":
this.sendAsyncMessage("Screenshots:MoveFocusToParent", event.detail);
break;
}
}
@ -151,14 +159,6 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
this.endScreenshotsOverlay({ doNotResetMethods: true });
}
showPanel() {
this.sendAsyncMessage("Screenshots:ShowPanel");
}
hidePanel() {
this.sendAsyncMessage("Screenshots:HidePanel");
}
getDocumentTitle() {
return this.document.title;
}
@ -173,6 +173,11 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
return methodsUsed;
}
focusOverlay() {
this.contentWindow.focus();
this.#overlay.focus();
}
/**
* Resolves when the document is ready to have an overlay injected into it.
*

View file

@ -732,6 +732,7 @@ let JSWINDOWACTORS = {
"Screenshots:OverlaySelection": {},
"Screenshots:RecordEvent": {},
"Screenshots:ShowPanel": {},
"Screenshots:FocusPanel": {},
},
},
enablePreference: "screenshots.browser.component.enabled",

View file

@ -133,19 +133,19 @@ export class ScreenshotsOverlay {
<div id="mover-topRight" class="mover-target direction-topRight" tabindex="0">
<div class="mover"></div>
</div>
<div id="mover-left" class="mover-target direction-left">
<div class="mover"></div>
</div>
<div id="mover-right" class="mover-target direction-right">
<div class="mover"></div>
</div>
<div id="mover-bottomLeft" class="mover-target direction-bottomLeft" tabindex="0">
<div id="mover-bottomRight" class="mover-target direction-bottomRight" tabindex="0">
<div class="mover"></div>
</div>
<div id="mover-bottom" class="mover-target direction-bottom">
<div class="mover"></div>
</div>
<div id="mover-bottomRight" class="mover-target direction-bottomRight" tabindex="0">
<div id="mover-bottomLeft" class="mover-target direction-bottomLeft" tabindex="0">
<div class="mover"></div>
</div>
<div id="mover-left" class="mover-target direction-left">
<div class="mover"></div>
</div>
<div id="selection-size-container">
@ -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.

View file

@ -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
*

View file

@ -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 */

View file

@ -20,9 +20,10 @@
<html:link rel="stylesheet" href="chrome://global/skin/global.css" />
<html:link rel="stylesheet" href="chrome://browser/content/screenshots/screenshots-buttons.css" />
<html:moz-button-group>
<html:button class="visible-page footer-button" data-l10n-id="screenshots-save-visible-button"></html:button>
<html:button class="full-page footer-button primary" data-l10n-id="screenshots-save-page-button"></html:button>
<html:button id="visible-page" class="screenshot-button footer-button" data-l10n-id="screenshots-save-visible-button"></html:button>
<html:button id="full-page" class="screenshot-button footer-button primary" data-l10n-id="screenshots-save-page-button"></html:button>
</html:moz-button-group>
`;
}
@ -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 });
}
}

View file

@ -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"]

View file

@ -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;

View file

@ -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();
}
);
});

View file

@ -136,9 +136,6 @@ const SHIFT_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY = [
],
];
/**
*
*/
add_task(async function test_moveRegionWithKeyboard() {
await BrowserTestUtils.withNewTab(
{

View file

@ -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"
);
}
);
});

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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",
};