Merge pull request #17062 from calixteman/resize_keyboard
[Editor] Support resizing editors with the keyboard (bug 1854340)
This commit is contained in:
		
						commit
						40d6b0e1bf
					
				@ -280,3 +280,21 @@ editor_alt_text_save_button=Save
 | 
				
			|||||||
editor_alt_text_decorative_tooltip=Marked as decorative
 | 
					editor_alt_text_decorative_tooltip=Marked as decorative
 | 
				
			||||||
# This is a placeholder for the alt text input area
 | 
					# This is a placeholder for the alt text input area
 | 
				
			||||||
editor_alt_text_textarea.placeholder=For example, “A young man sits down at a table to eat a meal”
 | 
					editor_alt_text_textarea.placeholder=For example, “A young man sits down at a table to eat a meal”
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Editor resizers
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_topLeft): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_topLeft=Top left corner — resize
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_topMiddle): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_topMiddle=Top middle — resize
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_topRight): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_topRight=Top right corner — resize
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_middleRight): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_middleRight=Middle right — resize
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_bottomRight): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_bottomRight=Bottom right corner — resize
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_bottomMiddle): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_bottomMiddle=Bottom middle — resize
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_bottomLeft): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_bottomLeft=Bottom left corner — resize
 | 
				
			||||||
 | 
					# LOCALIZATION NOTE (editor_resizer_label_middleLeft): This is used in an aria label to help to understand the role of the resizer.
 | 
				
			||||||
 | 
					editor_resizer_label_middleLeft=Middle left — resize
 | 
				
			||||||
 | 
				
			|||||||
@ -15,10 +15,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line max-len
 | 
					// eslint-disable-next-line max-len
 | 
				
			||||||
/** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */
 | 
					/** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */
 | 
				
			||||||
// eslint-disable-next-line max-len
 | 
					 | 
				
			||||||
/** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { bindEvents, ColorManager } from "./tools.js";
 | 
					import {
 | 
				
			||||||
 | 
					  AnnotationEditorUIManager,
 | 
				
			||||||
 | 
					  bindEvents,
 | 
				
			||||||
 | 
					  ColorManager,
 | 
				
			||||||
 | 
					  KeyboardManager,
 | 
				
			||||||
 | 
					} from "./tools.js";
 | 
				
			||||||
import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
 | 
					import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
 | 
				
			||||||
import { noContextMenu } from "../display_utils.js";
 | 
					import { noContextMenu } from "../display_utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -35,6 +38,8 @@ import { noContextMenu } from "../display_utils.js";
 | 
				
			|||||||
 * Base class for editors.
 | 
					 * Base class for editors.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class AnnotationEditor {
 | 
					class AnnotationEditor {
 | 
				
			||||||
 | 
					  #allResizerDivs = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #altText = "";
 | 
					  #altText = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #altTextDecorative = false;
 | 
					  #altTextDecorative = false;
 | 
				
			||||||
@ -51,16 +56,22 @@ class AnnotationEditor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  #resizersDiv = null;
 | 
					  #resizersDiv = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #savedDimensions = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #boundFocusin = this.focusin.bind(this);
 | 
					  #boundFocusin = this.focusin.bind(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #boundFocusout = this.focusout.bind(this);
 | 
					  #boundFocusout = this.focusout.bind(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #focusedResizerName = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #hasBeenClicked = false;
 | 
					  #hasBeenClicked = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #isEditing = false;
 | 
					  #isEditing = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #isInEditMode = false;
 | 
					  #isInEditMode = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #isResizerEnabledForKeyboard = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #moveInDOMTimeout = null;
 | 
					  #moveInDOMTimeout = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _initialOptions = Object.create(null);
 | 
					  _initialOptions = Object.create(null);
 | 
				
			||||||
@ -85,6 +96,39 @@ class AnnotationEditor {
 | 
				
			|||||||
  // button to edit the alt text is visually moved outside of the editor.
 | 
					  // button to edit the alt text is visually moved outside of the editor.
 | 
				
			||||||
  static SMALL_EDITOR_SIZE = 0;
 | 
					  static SMALL_EDITOR_SIZE = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static get _resizerKeyboardManager() {
 | 
				
			||||||
 | 
					    const resize = AnnotationEditor.prototype._resizeWithKeyboard;
 | 
				
			||||||
 | 
					    const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
 | 
				
			||||||
 | 
					    const big = AnnotationEditorUIManager.TRANSLATE_BIG;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return shadow(
 | 
				
			||||||
 | 
					      this,
 | 
				
			||||||
 | 
					      "_resizerKeyboardManager",
 | 
				
			||||||
 | 
					      new KeyboardManager([
 | 
				
			||||||
 | 
					        [["ArrowLeft", "mac+ArrowLeft"], resize, { args: [-small, 0] }],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					          ["ctrl+ArrowLeft", "mac+shift+ArrowLeft"],
 | 
				
			||||||
 | 
					          resize,
 | 
				
			||||||
 | 
					          { args: [-big, 0] },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [["ArrowRight", "mac+ArrowRight"], resize, { args: [small, 0] }],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					          ["ctrl+ArrowRight", "mac+shift+ArrowRight"],
 | 
				
			||||||
 | 
					          resize,
 | 
				
			||||||
 | 
					          { args: [big, 0] },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [["ArrowUp", "mac+ArrowUp"], resize, { args: [0, -small] }],
 | 
				
			||||||
 | 
					        [["ctrl+ArrowUp", "mac+shift+ArrowUp"], resize, { args: [0, -big] }],
 | 
				
			||||||
 | 
					        [["ArrowDown", "mac+ArrowDown"], resize, { args: [0, small] }],
 | 
				
			||||||
 | 
					        [["ctrl+ArrowDown", "mac+shift+ArrowDown"], resize, { args: [0, big] }],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					          ["Escape", "mac+Escape"],
 | 
				
			||||||
 | 
					          AnnotationEditor.prototype._stopResizingWithKeyboard,
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ])
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @param {AnnotationEditorParameters} parameters
 | 
					   * @param {AnnotationEditorParameters} parameters
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
@ -157,6 +201,14 @@ class AnnotationEditor {
 | 
				
			|||||||
        "editor_alt_text_button_label",
 | 
					        "editor_alt_text_button_label",
 | 
				
			||||||
        "editor_alt_text_edit_button_label",
 | 
					        "editor_alt_text_edit_button_label",
 | 
				
			||||||
        "editor_alt_text_decorative_tooltip",
 | 
					        "editor_alt_text_decorative_tooltip",
 | 
				
			||||||
 | 
					        "editor_resizer_label_topLeft",
 | 
				
			||||||
 | 
					        "editor_resizer_label_topMiddle",
 | 
				
			||||||
 | 
					        "editor_resizer_label_topRight",
 | 
				
			||||||
 | 
					        "editor_resizer_label_middleRight",
 | 
				
			||||||
 | 
					        "editor_resizer_label_bottomRight",
 | 
				
			||||||
 | 
					        "editor_resizer_label_bottomMiddle",
 | 
				
			||||||
 | 
					        "editor_resizer_label_bottomLeft",
 | 
				
			||||||
 | 
					        "editor_resizer_label_middleLeft",
 | 
				
			||||||
      ].map(str => [str, l10n.get(str)])
 | 
					      ].map(str => [str, l10n.get(str)])
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    if (options?.strings) {
 | 
					    if (options?.strings) {
 | 
				
			||||||
@ -277,6 +329,9 @@ class AnnotationEditor {
 | 
				
			|||||||
    if (parent !== null) {
 | 
					    if (parent !== null) {
 | 
				
			||||||
      this.pageIndex = parent.pageIndex;
 | 
					      this.pageIndex = parent.pageIndex;
 | 
				
			||||||
      this.pageDimensions = parent.pageDimensions;
 | 
					      this.pageDimensions = parent.pageDimensions;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // The editor is being removed from the DOM, so we need to stop resizing.
 | 
				
			||||||
 | 
					      this.#stopResizing();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.parent = parent;
 | 
					    this.parent = parent;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -600,19 +655,32 @@ class AnnotationEditor {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    this.#resizersDiv = document.createElement("div");
 | 
					    this.#resizersDiv = document.createElement("div");
 | 
				
			||||||
    this.#resizersDiv.classList.add("resizers");
 | 
					    this.#resizersDiv.classList.add("resizers");
 | 
				
			||||||
    const classes = ["topLeft", "topRight", "bottomRight", "bottomLeft"];
 | 
					    // When the resizers are used with the keyboard, they're focusable, hence
 | 
				
			||||||
    if (!this._willKeepAspectRatio) {
 | 
					    // we want to have them in this order (top left, top middle, top right, ...)
 | 
				
			||||||
      classes.push("topMiddle", "middleRight", "bottomMiddle", "middleLeft");
 | 
					    // in the DOM to have the focus order correct.
 | 
				
			||||||
    }
 | 
					    const classes = this._willKeepAspectRatio
 | 
				
			||||||
 | 
					      ? ["topLeft", "topRight", "bottomRight", "bottomLeft"]
 | 
				
			||||||
 | 
					      : [
 | 
				
			||||||
 | 
					          "topLeft",
 | 
				
			||||||
 | 
					          "topMiddle",
 | 
				
			||||||
 | 
					          "topRight",
 | 
				
			||||||
 | 
					          "middleRight",
 | 
				
			||||||
 | 
					          "bottomRight",
 | 
				
			||||||
 | 
					          "bottomMiddle",
 | 
				
			||||||
 | 
					          "bottomLeft",
 | 
				
			||||||
 | 
					          "middleLeft",
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
    for (const name of classes) {
 | 
					    for (const name of classes) {
 | 
				
			||||||
      const div = document.createElement("div");
 | 
					      const div = document.createElement("div");
 | 
				
			||||||
      this.#resizersDiv.append(div);
 | 
					      this.#resizersDiv.append(div);
 | 
				
			||||||
      div.classList.add("resizer", name);
 | 
					      div.classList.add("resizer", name);
 | 
				
			||||||
 | 
					      div.setAttribute("data-resizer-name", name);
 | 
				
			||||||
      div.addEventListener(
 | 
					      div.addEventListener(
 | 
				
			||||||
        "pointerdown",
 | 
					        "pointerdown",
 | 
				
			||||||
        this.#resizerPointerdown.bind(this, name)
 | 
					        this.#resizerPointerdown.bind(this, name)
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      div.addEventListener("contextmenu", noContextMenu);
 | 
					      div.addEventListener("contextmenu", noContextMenu);
 | 
				
			||||||
 | 
					      div.tabIndex = -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.div.prepend(this.#resizersDiv);
 | 
					    this.div.prepend(this.#resizersDiv);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -659,40 +727,7 @@ class AnnotationEditor {
 | 
				
			|||||||
      this.parent.div.style.cursor = savedParentCursor;
 | 
					      this.parent.div.style.cursor = savedParentCursor;
 | 
				
			||||||
      this.div.style.cursor = savedCursor;
 | 
					      this.div.style.cursor = savedCursor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const newX = this.x;
 | 
					      this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight);
 | 
				
			||||||
      const newY = this.y;
 | 
					 | 
				
			||||||
      const newWidth = this.width;
 | 
					 | 
				
			||||||
      const newHeight = this.height;
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        newX === savedX &&
 | 
					 | 
				
			||||||
        newY === savedY &&
 | 
					 | 
				
			||||||
        newWidth === savedWidth &&
 | 
					 | 
				
			||||||
        newHeight === savedHeight
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.addCommands({
 | 
					 | 
				
			||||||
        cmd: () => {
 | 
					 | 
				
			||||||
          this.width = newWidth;
 | 
					 | 
				
			||||||
          this.height = newHeight;
 | 
					 | 
				
			||||||
          this.x = newX;
 | 
					 | 
				
			||||||
          this.y = newY;
 | 
					 | 
				
			||||||
          const [parentWidth, parentHeight] = this.parentDimensions;
 | 
					 | 
				
			||||||
          this.setDims(parentWidth * newWidth, parentHeight * newHeight);
 | 
					 | 
				
			||||||
          this.fixAndSetPosition();
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        undo: () => {
 | 
					 | 
				
			||||||
          this.width = savedWidth;
 | 
					 | 
				
			||||||
          this.height = savedHeight;
 | 
					 | 
				
			||||||
          this.x = savedX;
 | 
					 | 
				
			||||||
          this.y = savedY;
 | 
					 | 
				
			||||||
          const [parentWidth, parentHeight] = this.parentDimensions;
 | 
					 | 
				
			||||||
          this.setDims(parentWidth * savedWidth, parentHeight * savedHeight);
 | 
					 | 
				
			||||||
          this.fixAndSetPosition();
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        mustExec: true,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    window.addEventListener("pointerup", pointerUpCallback);
 | 
					    window.addEventListener("pointerup", pointerUpCallback);
 | 
				
			||||||
    // If the user switches to another window (with alt+tab), then we end the
 | 
					    // If the user switches to another window (with alt+tab), then we end the
 | 
				
			||||||
@ -700,6 +735,43 @@ class AnnotationEditor {
 | 
				
			|||||||
    window.addEventListener("blur", pointerUpCallback);
 | 
					    window.addEventListener("blur", pointerUpCallback);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight) {
 | 
				
			||||||
 | 
					    const newX = this.x;
 | 
				
			||||||
 | 
					    const newY = this.y;
 | 
				
			||||||
 | 
					    const newWidth = this.width;
 | 
				
			||||||
 | 
					    const newHeight = this.height;
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      newX === savedX &&
 | 
				
			||||||
 | 
					      newY === savedY &&
 | 
				
			||||||
 | 
					      newWidth === savedWidth &&
 | 
				
			||||||
 | 
					      newHeight === savedHeight
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.addCommands({
 | 
				
			||||||
 | 
					      cmd: () => {
 | 
				
			||||||
 | 
					        this.width = newWidth;
 | 
				
			||||||
 | 
					        this.height = newHeight;
 | 
				
			||||||
 | 
					        this.x = newX;
 | 
				
			||||||
 | 
					        this.y = newY;
 | 
				
			||||||
 | 
					        const [parentWidth, parentHeight] = this.parentDimensions;
 | 
				
			||||||
 | 
					        this.setDims(parentWidth * newWidth, parentHeight * newHeight);
 | 
				
			||||||
 | 
					        this.fixAndSetPosition();
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      undo: () => {
 | 
				
			||||||
 | 
					        this.width = savedWidth;
 | 
				
			||||||
 | 
					        this.height = savedHeight;
 | 
				
			||||||
 | 
					        this.x = savedX;
 | 
				
			||||||
 | 
					        this.y = savedY;
 | 
				
			||||||
 | 
					        const [parentWidth, parentHeight] = this.parentDimensions;
 | 
				
			||||||
 | 
					        this.setDims(parentWidth * savedWidth, parentHeight * savedHeight);
 | 
				
			||||||
 | 
					        this.fixAndSetPosition();
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      mustExec: true,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #resizerPointermove(name, event) {
 | 
					  #resizerPointermove(name, event) {
 | 
				
			||||||
    const [parentWidth, parentHeight] = this.parentDimensions;
 | 
					    const [parentWidth, parentHeight] = this.parentDimensions;
 | 
				
			||||||
    const savedX = this.x;
 | 
					    const savedX = this.x;
 | 
				
			||||||
@ -1205,12 +1277,12 @@ class AnnotationEditor {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * If it returns true, then this editor handle the keyboard
 | 
					   * If it returns true, then this editor handles the keyboard
 | 
				
			||||||
   * events itself.
 | 
					   * events itself.
 | 
				
			||||||
   * @returns {boolean}
 | 
					   * @returns {boolean}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  shouldGetKeyboardEvents() {
 | 
					  shouldGetKeyboardEvents() {
 | 
				
			||||||
    return false;
 | 
					    return this.#isResizerEnabledForKeyboard;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -1303,6 +1375,7 @@ class AnnotationEditor {
 | 
				
			|||||||
      clearTimeout(this.#moveInDOMTimeout);
 | 
					      clearTimeout(this.#moveInDOMTimeout);
 | 
				
			||||||
      this.#moveInDOMTimeout = null;
 | 
					      this.#moveInDOMTimeout = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this.#stopResizing();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -1319,9 +1392,140 @@ class AnnotationEditor {
 | 
				
			|||||||
    if (this.isResizable) {
 | 
					    if (this.isResizable) {
 | 
				
			||||||
      this.#createResizers();
 | 
					      this.#createResizers();
 | 
				
			||||||
      this.#resizersDiv.classList.remove("hidden");
 | 
					      this.#resizersDiv.classList.remove("hidden");
 | 
				
			||||||
 | 
					      bindEvents(this, this.div, ["keydown"]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * onkeydown callback.
 | 
				
			||||||
 | 
					   * @param {KeyboardEvent} event
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  keydown(event) {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      !this.isResizable ||
 | 
				
			||||||
 | 
					      event.target !== this.div ||
 | 
				
			||||||
 | 
					      event.key !== "Enter"
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this._uiManager.setSelected(this);
 | 
				
			||||||
 | 
					    this.#savedDimensions = {
 | 
				
			||||||
 | 
					      savedX: this.x,
 | 
				
			||||||
 | 
					      savedY: this.y,
 | 
				
			||||||
 | 
					      savedWidth: this.width,
 | 
				
			||||||
 | 
					      savedHeight: this.height,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const children = this.#resizersDiv.children;
 | 
				
			||||||
 | 
					    if (!this.#allResizerDivs) {
 | 
				
			||||||
 | 
					      this.#allResizerDivs = Array.from(children);
 | 
				
			||||||
 | 
					      const boundResizerKeydown = this.#resizerKeydown.bind(this);
 | 
				
			||||||
 | 
					      const boundResizerBlur = this.#resizerBlur.bind(this);
 | 
				
			||||||
 | 
					      for (const div of this.#allResizerDivs) {
 | 
				
			||||||
 | 
					        const name = div.getAttribute("data-resizer-name");
 | 
				
			||||||
 | 
					        div.addEventListener("keydown", boundResizerKeydown);
 | 
				
			||||||
 | 
					        div.addEventListener("blur", boundResizerBlur);
 | 
				
			||||||
 | 
					        div.addEventListener("focus", this.#resizerFocus.bind(this, name));
 | 
				
			||||||
 | 
					        AnnotationEditor._l10nPromise
 | 
				
			||||||
 | 
					          .get(`editor_resizer_label_${name}`)
 | 
				
			||||||
 | 
					          .then(msg => div.setAttribute("aria-label", msg));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We want to have the resizers in the visual order, so we move the first
 | 
				
			||||||
 | 
					    // (top-left) to the right place.
 | 
				
			||||||
 | 
					    const first = this.#allResizerDivs[0];
 | 
				
			||||||
 | 
					    let firstPosition = 0;
 | 
				
			||||||
 | 
					    for (const div of children) {
 | 
				
			||||||
 | 
					      if (div === first) {
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      firstPosition++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const nextFirstPosition =
 | 
				
			||||||
 | 
					      (((360 - this.rotation + this.parentRotation) % 360) / 90) *
 | 
				
			||||||
 | 
					      (this.#allResizerDivs.length / 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (nextFirstPosition !== firstPosition) {
 | 
				
			||||||
 | 
					      // We need to reorder the resizers in the DOM in order to have the focus
 | 
				
			||||||
 | 
					      // on the top-left one.
 | 
				
			||||||
 | 
					      if (nextFirstPosition < firstPosition) {
 | 
				
			||||||
 | 
					        for (let i = 0; i < firstPosition - nextFirstPosition; i++) {
 | 
				
			||||||
 | 
					          this.#resizersDiv.append(this.#resizersDiv.firstChild);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else if (nextFirstPosition > firstPosition) {
 | 
				
			||||||
 | 
					        for (let i = 0; i < nextFirstPosition - firstPosition; i++) {
 | 
				
			||||||
 | 
					          this.#resizersDiv.firstChild.before(this.#resizersDiv.lastChild);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let i = 0;
 | 
				
			||||||
 | 
					      for (const child of children) {
 | 
				
			||||||
 | 
					        const div = this.#allResizerDivs[i++];
 | 
				
			||||||
 | 
					        const name = div.getAttribute("data-resizer-name");
 | 
				
			||||||
 | 
					        AnnotationEditor._l10nPromise
 | 
				
			||||||
 | 
					          .get(`editor_resizer_label_${name}`)
 | 
				
			||||||
 | 
					          .then(msg => child.setAttribute("aria-label", msg));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.#setResizerTabIndex(0);
 | 
				
			||||||
 | 
					    this.#isResizerEnabledForKeyboard = true;
 | 
				
			||||||
 | 
					    this.#resizersDiv.firstChild.focus({ focusVisible: true });
 | 
				
			||||||
 | 
					    event.preventDefault();
 | 
				
			||||||
 | 
					    event.stopImmediatePropagation();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #resizerKeydown(event) {
 | 
				
			||||||
 | 
					    AnnotationEditor._resizerKeyboardManager.exec(this, event);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #resizerBlur(event) {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      this.#isResizerEnabledForKeyboard &&
 | 
				
			||||||
 | 
					      event.relatedTarget?.parentNode !== this.#resizersDiv
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      this.#stopResizing();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #resizerFocus(name) {
 | 
				
			||||||
 | 
					    this.#focusedResizerName = this.#isResizerEnabledForKeyboard ? name : "";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #setResizerTabIndex(value) {
 | 
				
			||||||
 | 
					    if (!this.#allResizerDivs) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (const div of this.#allResizerDivs) {
 | 
				
			||||||
 | 
					      div.tabIndex = value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _resizeWithKeyboard(x, y) {
 | 
				
			||||||
 | 
					    if (!this.#isResizerEnabledForKeyboard) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.#resizerPointermove(this.#focusedResizerName, {
 | 
				
			||||||
 | 
					      movementX: x,
 | 
				
			||||||
 | 
					      movementY: y,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #stopResizing() {
 | 
				
			||||||
 | 
					    this.#isResizerEnabledForKeyboard = false;
 | 
				
			||||||
 | 
					    this.#setResizerTabIndex(-1);
 | 
				
			||||||
 | 
					    if (this.#savedDimensions) {
 | 
				
			||||||
 | 
					      const { savedX, savedY, savedWidth, savedHeight } = this.#savedDimensions;
 | 
				
			||||||
 | 
					      this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight);
 | 
				
			||||||
 | 
					      this.#savedDimensions = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _stopResizingWithKeyboard() {
 | 
				
			||||||
 | 
					    this.#stopResizing();
 | 
				
			||||||
 | 
					    this.div.focus();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Select this editor.
 | 
					   * Select this editor.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 | 
				
			|||||||
@ -1021,7 +1021,7 @@ class AnnotationEditorUIManager {
 | 
				
			|||||||
   * @param {KeyboardEvent} event
 | 
					   * @param {KeyboardEvent} event
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  keydown(event) {
 | 
					  keydown(event) {
 | 
				
			||||||
    if (!this.getActive()?.shouldGetKeyboardEvents()) {
 | 
					    if (!this.isEditorHandlingKeyboard) {
 | 
				
			||||||
      AnnotationEditorUIManager._keyboardManager.exec(this, event);
 | 
					      AnnotationEditorUIManager._keyboardManager.exec(this, event);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -1732,6 +1732,14 @@ class AnnotationEditorUIManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get isEditorHandlingKeyboard() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      this.getActive()?.shouldGetKeyboardEvents() ||
 | 
				
			||||||
 | 
					      (this.#selectedEditors.size === 1 &&
 | 
				
			||||||
 | 
					        this.#selectedEditors.values().next().value.shouldGetKeyboardEvents())
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Is the current editor the one passed as argument?
 | 
					   * Is the current editor the one passed as argument?
 | 
				
			||||||
   * @param {AnnotationEditor} editor
 | 
					   * @param {AnnotationEditor} editor
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ const {
 | 
				
			|||||||
  closePages,
 | 
					  closePages,
 | 
				
			||||||
  getEditorDimensions,
 | 
					  getEditorDimensions,
 | 
				
			||||||
  getEditorSelector,
 | 
					  getEditorSelector,
 | 
				
			||||||
 | 
					  getFirstSerialized,
 | 
				
			||||||
  loadAndWait,
 | 
					  loadAndWait,
 | 
				
			||||||
  serializeBitmapDimensions,
 | 
					  serializeBitmapDimensions,
 | 
				
			||||||
  waitForAnnotationEditorLayer,
 | 
					  waitForAnnotationEditorLayer,
 | 
				
			||||||
@ -59,6 +60,53 @@ const waitForImage = async (page, selector) => {
 | 
				
			|||||||
  await page.waitForSelector(`${selector} .altText`);
 | 
					  await page.waitForSelector(`${selector} .altText`);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const copyImage = async (page, imagePath, number) => {
 | 
				
			||||||
 | 
					  const data = fs
 | 
				
			||||||
 | 
					    .readFileSync(path.join(__dirname, imagePath))
 | 
				
			||||||
 | 
					    .toString("base64");
 | 
				
			||||||
 | 
					  await page.evaluate(async imageData => {
 | 
				
			||||||
 | 
					    const resp = await fetch(`data:image/png;base64,${imageData}`);
 | 
				
			||||||
 | 
					    const blob = await resp.blob();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await navigator.clipboard.write([
 | 
				
			||||||
 | 
					      new ClipboardItem({
 | 
				
			||||||
 | 
					        [blob.type]: blob,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					  }, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let hasPasteEvent = false;
 | 
				
			||||||
 | 
					  while (!hasPasteEvent) {
 | 
				
			||||||
 | 
					    // We retry to paste if nothing has been pasted before 500ms.
 | 
				
			||||||
 | 
					    const promise = Promise.race([
 | 
				
			||||||
 | 
					      page.evaluate(
 | 
				
			||||||
 | 
					        () =>
 | 
				
			||||||
 | 
					          new Promise(resolve => {
 | 
				
			||||||
 | 
					            document.addEventListener(
 | 
				
			||||||
 | 
					              "paste",
 | 
				
			||||||
 | 
					              e => resolve(e.clipboardData.items.length !== 0),
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                once: true,
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      page.evaluate(
 | 
				
			||||||
 | 
					        () =>
 | 
				
			||||||
 | 
					          new Promise(resolve => {
 | 
				
			||||||
 | 
					            setTimeout(() => resolve(false), 500);
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    await page.keyboard.down("Control");
 | 
				
			||||||
 | 
					    await page.keyboard.press("v");
 | 
				
			||||||
 | 
					    await page.keyboard.up("Control");
 | 
				
			||||||
 | 
					    hasPasteEvent = await promise;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await waitForImage(page, getEditorSelector(number));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Stamp Editor", () => {
 | 
					describe("Stamp Editor", () => {
 | 
				
			||||||
  describe("Basic operations", () => {
 | 
					  describe("Basic operations", () => {
 | 
				
			||||||
    let pages;
 | 
					    let pages;
 | 
				
			||||||
@ -235,50 +283,7 @@ describe("Stamp Editor", () => {
 | 
				
			|||||||
        pages.map(async ([browserName, page]) => {
 | 
					        pages.map(async ([browserName, page]) => {
 | 
				
			||||||
          await page.click("#editorStamp");
 | 
					          await page.click("#editorStamp");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const data = fs
 | 
					          await copyImage(page, "../images/firefox_logo.png", 0);
 | 
				
			||||||
            .readFileSync(path.join(__dirname, "../images/firefox_logo.png"))
 | 
					 | 
				
			||||||
            .toString("base64");
 | 
					 | 
				
			||||||
          await page.evaluate(async imageData => {
 | 
					 | 
				
			||||||
            const resp = await fetch(`data:image/png;base64,${imageData}`);
 | 
					 | 
				
			||||||
            const blob = await resp.blob();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await navigator.clipboard.write([
 | 
					 | 
				
			||||||
              new ClipboardItem({
 | 
					 | 
				
			||||||
                [blob.type]: blob,
 | 
					 | 
				
			||||||
              }),
 | 
					 | 
				
			||||||
            ]);
 | 
					 | 
				
			||||||
          }, data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          let hasPasteEvent = false;
 | 
					 | 
				
			||||||
          while (!hasPasteEvent) {
 | 
					 | 
				
			||||||
            // We retry to paste if nothing has been pasted before 500ms.
 | 
					 | 
				
			||||||
            const promise = Promise.race([
 | 
					 | 
				
			||||||
              page.evaluate(
 | 
					 | 
				
			||||||
                () =>
 | 
					 | 
				
			||||||
                  new Promise(resolve => {
 | 
					 | 
				
			||||||
                    document.addEventListener(
 | 
					 | 
				
			||||||
                      "paste",
 | 
					 | 
				
			||||||
                      e => resolve(e.clipboardData.items.length !== 0),
 | 
					 | 
				
			||||||
                      {
 | 
					 | 
				
			||||||
                        once: true,
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                  })
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              page.evaluate(
 | 
					 | 
				
			||||||
                () =>
 | 
					 | 
				
			||||||
                  new Promise(resolve => {
 | 
					 | 
				
			||||||
                    setTimeout(() => resolve(false), 500);
 | 
					 | 
				
			||||||
                  })
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ]);
 | 
					 | 
				
			||||||
            await page.keyboard.down("Control");
 | 
					 | 
				
			||||||
            await page.keyboard.press("v");
 | 
					 | 
				
			||||||
            await page.keyboard.up("Control");
 | 
					 | 
				
			||||||
            hasPasteEvent = await promise;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          await waitForImage(page, getEditorSelector(0));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Wait for the alt-text button to be visible.
 | 
					          // Wait for the alt-text button to be visible.
 | 
				
			||||||
          const buttonSelector = `${getEditorSelector(0)} button.altText`;
 | 
					          const buttonSelector = `${getEditorSelector(0)} button.altText`;
 | 
				
			||||||
@ -432,4 +437,142 @@ describe("Stamp Editor", () => {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe("Resize an image with the keyboard", () => {
 | 
				
			||||||
 | 
					    let pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    beforeAll(async () => {
 | 
				
			||||||
 | 
					      pages = await loadAndWait("empty.pdf", ".annotationEditorLayer", 50);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    afterAll(async () => {
 | 
				
			||||||
 | 
					      await closePages(pages);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("must check that the dimensions change", async () => {
 | 
				
			||||||
 | 
					      await Promise.all(
 | 
				
			||||||
 | 
					        pages.map(async ([browserName, page]) => {
 | 
				
			||||||
 | 
					          await page.click("#editorStamp");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          await copyImage(page, "../images/firefox_logo.png", 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const editorSelector = getEditorSelector(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          await page.click(editorSelector);
 | 
				
			||||||
 | 
					          await waitForSelectedEditor(page, editorSelector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          await page.waitForSelector(
 | 
				
			||||||
 | 
					            `${editorSelector} .resizer.topLeft[tabindex="-1"]`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const getDims = async () => {
 | 
				
			||||||
 | 
					            const [blX, blY, trX, trY] = await getFirstSerialized(
 | 
				
			||||||
 | 
					              page,
 | 
				
			||||||
 | 
					              x => x.rect
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            return [trX - blX, trY - blY];
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const [width, height] = await getDims();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Press Enter to enter in resize-with-keyboard mode.
 | 
				
			||||||
 | 
					          await page.keyboard.press("Enter");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // The resizer must become keyboard focusable.
 | 
				
			||||||
 | 
					          await page.waitForSelector(
 | 
				
			||||||
 | 
					            `${editorSelector} .resizer.topLeft[tabindex="0"]`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          let prevWidth = width;
 | 
				
			||||||
 | 
					          let prevHeight = height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const waitForDimsChange = async (w, h) => {
 | 
				
			||||||
 | 
					            await page.waitForFunction(
 | 
				
			||||||
 | 
					              (prevW, prevH) => {
 | 
				
			||||||
 | 
					                const [x1, y1, x2, y2] =
 | 
				
			||||||
 | 
					                  window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.map
 | 
				
			||||||
 | 
					                    .values()
 | 
				
			||||||
 | 
					                    .next().value.rect;
 | 
				
			||||||
 | 
					                const newWidth = x2 - x1;
 | 
				
			||||||
 | 
					                const newHeight = y2 - y1;
 | 
				
			||||||
 | 
					                return newWidth !== prevW || newHeight !== prevH;
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              {},
 | 
				
			||||||
 | 
					              w,
 | 
				
			||||||
 | 
					              h
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          for (let i = 0; i < 40; i++) {
 | 
				
			||||||
 | 
					            await page.keyboard.press("ArrowLeft");
 | 
				
			||||||
 | 
					            await waitForDimsChange(prevWidth, prevHeight);
 | 
				
			||||||
 | 
					            [prevWidth, prevHeight] = await getDims();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          let [newWidth, newHeight] = await getDims();
 | 
				
			||||||
 | 
					          expect(newWidth > width + 30)
 | 
				
			||||||
 | 
					            .withContext(`In ${browserName}`)
 | 
				
			||||||
 | 
					            .toEqual(true);
 | 
				
			||||||
 | 
					          expect(newHeight > height + 30)
 | 
				
			||||||
 | 
					            .withContext(`In ${browserName}`)
 | 
				
			||||||
 | 
					            .toEqual(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          for (let i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					            await page.keyboard.down("Control");
 | 
				
			||||||
 | 
					            await page.keyboard.press("ArrowRight");
 | 
				
			||||||
 | 
					            await page.keyboard.up("Control");
 | 
				
			||||||
 | 
					            await waitForDimsChange(prevWidth, prevHeight);
 | 
				
			||||||
 | 
					            [prevWidth, prevHeight] = await getDims();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          [newWidth, newHeight] = await getDims();
 | 
				
			||||||
 | 
					          expect(Math.abs(newWidth - width) < 2)
 | 
				
			||||||
 | 
					            .withContext(`In ${browserName}`)
 | 
				
			||||||
 | 
					            .toEqual(true);
 | 
				
			||||||
 | 
					          expect(Math.abs(newHeight - height) < 2)
 | 
				
			||||||
 | 
					            .withContext(`In ${browserName}`)
 | 
				
			||||||
 | 
					            .toEqual(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Move the focus to the next resizer.
 | 
				
			||||||
 | 
					          await page.keyboard.press("Tab");
 | 
				
			||||||
 | 
					          await page.waitForFunction(
 | 
				
			||||||
 | 
					            () => !!document.activeElement?.classList.contains("topMiddle")
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          for (let i = 0; i < 40; i++) {
 | 
				
			||||||
 | 
					            await page.keyboard.press("ArrowUp");
 | 
				
			||||||
 | 
					            await waitForDimsChange(prevWidth, prevHeight);
 | 
				
			||||||
 | 
					            [prevWidth, prevHeight] = await getDims();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          [, newHeight] = await getDims();
 | 
				
			||||||
 | 
					          expect(newHeight > height + 50)
 | 
				
			||||||
 | 
					            .withContext(`In ${browserName}`)
 | 
				
			||||||
 | 
					            .toEqual(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          for (let i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					            await page.keyboard.down("Control");
 | 
				
			||||||
 | 
					            await page.keyboard.press("ArrowDown");
 | 
				
			||||||
 | 
					            await page.keyboard.up("Control");
 | 
				
			||||||
 | 
					            await waitForDimsChange(prevWidth, prevHeight);
 | 
				
			||||||
 | 
					            [prevWidth, prevHeight] = await getDims();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          [, newHeight] = await getDims();
 | 
				
			||||||
 | 
					          expect(Math.abs(newHeight - height) < 2)
 | 
				
			||||||
 | 
					            .withContext(`In ${browserName}`)
 | 
				
			||||||
 | 
					            .toEqual(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Escape should remove the focus from the resizer.
 | 
				
			||||||
 | 
					          await page.keyboard.press("Escape");
 | 
				
			||||||
 | 
					          await page.waitForSelector(
 | 
				
			||||||
 | 
					            `${editorSelector} .resizer.topLeft[tabindex="-1"]`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          await page.waitForFunction(
 | 
				
			||||||
 | 
					            () => !document.activeElement?.classList.contains("resizer")
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -85,6 +85,14 @@ const DEFAULT_L10N_STRINGS = {
 | 
				
			|||||||
  editor_alt_text_button_label: "Alt text",
 | 
					  editor_alt_text_button_label: "Alt text",
 | 
				
			||||||
  editor_alt_text_edit_button_label: "Edit alt text",
 | 
					  editor_alt_text_edit_button_label: "Edit alt text",
 | 
				
			||||||
  editor_alt_text_decorative_tooltip: "Marked as decorative",
 | 
					  editor_alt_text_decorative_tooltip: "Marked as decorative",
 | 
				
			||||||
 | 
					  editor_resizer_label_topLeft: "Top left corner — resize",
 | 
				
			||||||
 | 
					  editor_resizer_label_topMiddle: "Top middle — resize",
 | 
				
			||||||
 | 
					  editor_resizer_label_topRight: "Top right corner — resize",
 | 
				
			||||||
 | 
					  editor_resizer_label_middleRight: "Middle right — resize",
 | 
				
			||||||
 | 
					  editor_resizer_label_bottomRight: "Bottom right corner — resize",
 | 
				
			||||||
 | 
					  editor_resizer_label_bottomMiddle: "Bottom middle — resize",
 | 
				
			||||||
 | 
					  editor_resizer_label_bottomLeft: "Bottom left corner — resize",
 | 
				
			||||||
 | 
					  editor_resizer_label_middleLeft: "Middle left — resize",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
 | 
					if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
 | 
				
			||||||
  DEFAULT_L10N_STRINGS.print_progress_percent = "{{progress}}%";
 | 
					  DEFAULT_L10N_STRINGS.print_progress_percent = "{{progress}}%";
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user