Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/accessible/tests/browser/bounds/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 20 kB image not shown  

Quelle  browser_caret_rect.js   Sprache: JAVA

 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


"use strict";

function getCaretRect(docAcc, id) {
  const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
  const caretX = {};
  const caretY = {};
  const caretW = {};
  const caretH = {};
  acc.getCaretRect(caretX, caretY, caretW, caretH);

  const caretBounds = [caretX.value, caretY.value, caretW.value, caretH.value];

  info(`Caret bounds: ${caretBounds}`);

  return caretBounds;
}

function getCollapsedRangeExtents(acc, offset) {
  const rangeX = {};
  const rangeY = {};
  const rangeW = {};
  const rangeH = {};
  acc.getRangeExtents(
    offset,
    offset,
    rangeX,
    rangeY,
    rangeW,
    rangeH,
    COORDTYPE_SCREEN_RELATIVE
  );

  const rangeBounds = [rangeX.value, rangeY.value, rangeW.value, rangeH.value];

  info(`Range ${offset}-${offset} bounds: ${rangeBounds}`);

  return rangeBounds;
}

async function fetchCollapsedRangeBounds(docAcc, acc) {
  const state = {};
  acc.getState(state, {});
  if (state.value & nsIAccessibleStates.STATE_FOCUSABLE) {
    // This pre-scrolls the accessible into view as a focus would do.
    let focused = waitForEvent(EVENT_FOCUS, acc);
    acc.takeFocus();
    await focused;

    // We need to blur the accessible so the caret does not interfere with
    // the bounds fetch.
    focused = waitForEvent(EVENT_FOCUS, docAcc);
    docAcc.takeFocus();
    await focused;
  }

  acc.QueryInterface(nsIAccessibleHyperText);

  // If this is a 0 length text accessible, we need to ensure that
  // characterCount is at least 1.
  const characterCount = acc.characterCount ? acc.characterCount : 1;
  const bounds = [];
  for (let offset = 0; offset < characterCount; offset++) {
    let linkIndex = acc.getLinkIndexAtOffset(offset);
    if (linkIndex != -1) {
      const link = acc
        .getLinkAt(linkIndex)
        .QueryInterface(nsIAccessibleText)
        .QueryInterface(nsIAccessible);
      bounds.push(...(await fetchCollapsedRangeBounds(docAcc, link)));
    } else {
      let rect = getCollapsedRangeExtents(acc, offset);
      bounds.push(rect);
    }
  }

  return bounds;
}

function testCaretRect(
  docAcc,
  id,
  offset,
  fetchedBounds,
  atLineEnd = false,
  isVertical = false
) {
  const acc = findAccessibleChildByID(docAcc, id, [
    nsIAccessibleText,
    nsIAccessibleHyperText,
  ]);
  is(acc.caretOffset, offset, `Caret at offset ${offset}`);
  const atEnd = offset == acc.characterCount;
  const empty = offset == 0 && atEnd;
  let queryOffset = atEnd && !empty ? offset - 1 : offset;
  const atEndInNewLine = atEnd && acc.getCharacterAtOffset(queryOffset) == "\n";

  const [rangeX, rangeY, rangeW, rangeH] =
    fetchedBounds.length > queryOffset
      ? fetchedBounds[queryOffset]
      : [0, 0, 0, 0];

  const [caretX, caretY, caretW, caretH] = getCaretRect(docAcc, id);

  // In case of an empty input `getRangeExtents()` will return the full accessible bounds.
  // When called on an embedded object, `getRangeExtents()` will return the
  // bounds for the content inside it, so it isn't a collapsed range and thus it
  // won't match the caret.
  if (!empty && acc.getLinkIndexAtOffset(offset) == -1) {
    let [currRangeX, currRangeY, currRangeW] = getCollapsedRangeExtents(
      acc,
      acc.caretOffset
    );
    Assert.deepEqual(
      [caretX, caretY, caretW],
      [currRangeX, currRangeY, currRangeW],
      "Collapsed range extents at caret position should be identical caret rect"
    );
  }

  let [caretInlinePos, caretBlockPos, rangeInlinePos, rangeBlockPos] =
    isVertical
      ? [caretY, caretX, rangeY, rangeX]
      : [caretX, caretY, rangeX, rangeY];

  // Compare inline-axis position for caret and text range
  if (atEndInNewLine) {
    Assert.lessOrEqual(
      caretInlinePos,
      rangeInlinePos,
      "Caret inline before range inline"
    );
  } else if (atEnd || atLineEnd) {
    Assert.greaterOrEqual(
      caretInlinePos,
      rangeInlinePos,
      "Caret inline after last range inline"
    );
  } else {
    // Caret inline size changes depending on device pixel ratio. In RTL
    // text that would change the x where the caret is drawn by a pixel or two.
    isWithin(
      caretInlinePos,
      rangeInlinePos,
      3,
      "Caret inline similar to range inline"
    );
  }

  // Compare block-axis position for caret and text range
  if (atEndInNewLine) {
    Assert.greater(
      caretBlockPos,
      rangeBlockPos,
      "Caret block below range block"
    );
  } else if (atLineEnd) {
    Assert.less(
      caretBlockPos,
      rangeBlockPos,
      "Caret block above start range block"
    );
  } else {
    isWithin(
      caretBlockPos,
      rangeBlockPos,
      3,
      "Caret block similar to range block"
    );
  }

  let [caretInlineSize, caretBlockSize, rangeBlockSize] = isVertical
    ? [caretH, caretW, rangeW]
    : [caretW, caretH, rangeH];

  ok(caretInlineSize, "Caret inline size is greater than 0");

  if (!empty) {
    // Depending on glyph, the range can be taller.
    isWithin(
      caretBlockSize,
      rangeBlockSize,
      2,
      "Caret block size similar to range block size"
    );
  }
}

function getAccBounds(acc) {
  const x = {};
  const y = {};
  const w = {};
  const h = {};
  acc.getBounds(x, y, w, h);
  return [x.value, y.value, w.value, h.value];
}

/**
 * Test the caret rect in content documents.
 *
 * Padding is removed in order to make the text range bounds
 * and the caret bounds consistent for the empty text input.
 * See Bug 2038670.
 */

addAccessibleTask(
  `
<input id="input" value="ab" style="padding: 0;">
<input id="emptyInput" style="padding: 0;">
  `,
  async function (browser, docAcc) {
    async function runTests() {
      const input = findAccessibleChildByID(docAcc, "input", [
        nsIAccessibleText,
      ]);

      let fetchedBounds = await fetchCollapsedRangeBounds(docAcc, input);

      info("Focusing input");
      let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
      input.takeFocus();
      await caretMoved;
      testCaretRect(docAcc, "input", 0, fetchedBounds);
      info("Setting caretOffset to 1");
      caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
      input.caretOffset = 1;
      await caretMoved;
      testCaretRect(docAcc, "input", 1, fetchedBounds);
      info("Setting caretOffset to 2");
      caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
      input.caretOffset = 2;
      await caretMoved;
      testCaretRect(docAcc, "input", 2, fetchedBounds);
      info("Resetting caretOffset to 0");
      input.caretOffset = 0;

      const emptyInput = findAccessibleChildByID(docAcc, "emptyInput", [
        nsIAccessibleText,
      ]);

      fetchedBounds = await fetchCollapsedRangeBounds(docAcc, emptyInput);

      info("Focusing emptyInput");
      caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, emptyInput);
      emptyInput.takeFocus();
      await caretMoved;
      testCaretRect(docAcc, "emptyInput", 0, fetchedBounds);
    }

    await runTests();

    // Check that the caret rect is correct when the title bar is shown.
    if (LINUX || Services.env.get("MOZ_HEADLESS")) {
      // Disabling tabs in title bar doesn't change the bounds on Linux or in
      // headless mode.
      info("Skipping title bar tests");
      return;
    }
    const [, origDocY] = getAccBounds(docAcc);
    info("Showing title bar");
    let titleBarChanged = BrowserTestUtils.waitForMutationCondition(
      document.documentElement,
      { attributes: true, attributeFilter: ["customtitlebar"] },
      () => !document.documentElement.hasAttribute("customtitlebar")
    );
    await SpecialPowers.pushPrefEnv({
      set: [["browser.tabs.inTitlebar", false]],
    });
    await titleBarChanged;
    const [, newDocY] = getAccBounds(docAcc);
    Assert.greater(
      newDocY,
      origDocY,
      "Doc has larger y after title bar change"
    );
    await runTests();
    await SpecialPowers.popPrefEnv();
  },
  { chrome: true, topLevel: true }
);

/**
 * Test the caret rect in multiline content.
 */

addAccessibleTask(
  `<style>
    @font-face {
      font-family: Ahem;
      src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
    }
   textarea {
      font: 10px/10px Ahem;
      width: 30px;
      height: 80px;
    }
  </style>
  <textarea id="textarea">1234
56789
</textarea>
  `,
  async function testMultiline(browser, docAcc) {
    async function moveCaret(key, keyopts = {}) {
      let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, "textarea");
      if (key) {
        EventUtils.synthesizeKey(key, keyopts);
      } else {
        // If no key is provided, just focus the textarea.
        findAccessibleChildByID(docAcc, "textarea").takeFocus();
      }

      let evt = await caretMoved;
      evt.QueryInterface(nsIAccessibleCaretMoveEvent);
      return [evt.caretOffset, evt.isAtEndOfLine];
    }

    const textarea = findAccessibleChildByID(docAcc, "textarea", [
      nsIAccessibleText,
    ]);
    let fetchedBounds = await fetchCollapsedRangeBounds(docAcc, textarea);

    info("Focusing textarea");
    let [offset, isAtLineEnd] = await moveCaret();
    is(offset, 0, "Caret at offset 0");
    is(isAtLineEnd, false, "Caret not at end of line");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Moving caret right");
    [offset, isAtLineEnd] = await moveCaret("KEY_ArrowRight");
    is(offset, 1, "Caret at offset 1");
    is(isAtLineEnd, false, "Caret not at end of line");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Moving caret right again");
    [offset, isAtLineEnd] = await moveCaret("KEY_ArrowRight");
    is(offset, 2, "Caret at offset 2");
    is(isAtLineEnd, false, "Caret not at end of line");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Moving caret right again again");
    [offset, isAtLineEnd] = await moveCaret("KEY_ArrowRight");
    is(offset, 3, "Caret at offset 3");
    is(isAtLineEnd, true, "Caret at end of line");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Moving caret right stays at same offset, but on new line");
    [offset, isAtLineEnd] = await moveCaret("KEY_ArrowRight");
    is(offset, 3, "Caret at offset 3");
    is(isAtLineEnd, false, "Caret not at end of line");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Moving caret right in second line");
    [offset, isAtLineEnd] = await moveCaret("KEY_ArrowRight");
    is(offset, 4, "Caret at offset 4");
    is(isAtLineEnd, false, "Caret not at end of line");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Move caret right to new line");
    [offset, isAtLineEnd] = await moveCaret("KEY_ArrowRight");
    is(offset, 5, "Caret at offset 5");
    is(isAtLineEnd, false, "Caret not at end of line");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Move caret to end of previous line");
    [offset, isAtLineEnd] = await moveCaret("KEY_ArrowLeft");
    is(offset, 4, "Caret at offset 4");
    is(isAtLineEnd, false, "Caret at end line break");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);

    info("Move caret to end of text");
    if (AppConstants.platform == "macosx") {
      [offset, isAtLineEnd] = await moveCaret("KEY_PageDown", {
        altKey: true,
      });
    } else {
      [offset, isAtLineEnd] = await moveCaret("KEY_End", {
        ctrlKey: true,
      });
    }
    is(offset, 11, "Caret at offset 11");
    is(isAtLineEnd, false, "Caret at end line break");
    testCaretRect(docAcc, "textarea", offset, fetchedBounds, isAtLineEnd);
  }
);

function todoIsWithin(expected, got, within, msg) {
  if (Math.abs(got - expected) <= within) {
    todo(true, `${msg} - Got ${got}`);
  } else {
    todo(
      false,
      `${msg} - Got ${got}, expected ${expected} with error of ${within}`
    );
  }
}

/**
 * Test the caret rect and collapsed range in bidi text
 */

addAccessibleTask(
  `
<input id="input1" value="hello שלום world">
<input id="input2" dir="rtl" value="שלום hello עולם">
<textarea id="textarea" dir="rtl">שלום
עולם</textarea>
  `,
  async function testRTL(browser, docAcc) {
    const input1 = findAccessibleChildByID(docAcc, "input1", [
      nsIAccessibleText,
    ]);

    let fetchedBounds = await fetchCollapsedRangeBounds(docAcc, input1);

    info("Focusing input1");
    let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input1);
    input1.takeFocus();
    await caretMoved;
    testCaretRect(docAcc, "input1", 0, fetchedBounds);

    info("Setting caretOffset to 1");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input1);
    input1.caretOffset = 1;
    await caretMoved;
    testCaretRect(docAcc, "input1", 1, fetchedBounds);

    info("Setting caretOffset to 6 (first in embedded RTL)");
    // Retrieving rangeX before caret goes there
    let [rangeX] = getCollapsedRangeExtents(input1, 6);
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input1);
    input1.caretOffset = 6;
    await caretMoved;
    let [caretX] = getCaretRect(docAcc, "input1");
    todoIsWithin(caretX, rangeX, 2, "Caret x similar to range x");

    info("Setting caretOffset to 7 (in embedded RTL)");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input1);
    input1.caretOffset = 7;
    await caretMoved;
    testCaretRect(docAcc, "input1", 7, fetchedBounds);

    info("Resetting caretOffset to 0");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input1);
    input1.caretOffset = 0;
    await caretMoved;

    const input2 = findAccessibleChildByID(docAcc, "input2", [
      nsIAccessibleText,
    ]);

    fetchedBounds = await fetchCollapsedRangeBounds(docAcc, input2);

    info("Focusing input2");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input2);
    input2.takeFocus();
    await caretMoved;
    testCaretRect(docAcc, "input2", 0, fetchedBounds);

    info("Setting caretOffset to 1");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input2);
    input2.caretOffset = 1;
    await caretMoved;
    testCaretRect(docAcc, "input2", 1, fetchedBounds);

    info("Setting caretOffset to 5 (first in embedded LTR)");
    // Retrieving rangeX before caret goes there
    [rangeX] = getCollapsedRangeExtents(input2, 5);
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input2);
    input2.caretOffset = 5;
    await caretMoved;
    [caretX] = getCaretRect(docAcc, "input1");
    todoIsWithin(caretX, rangeX, 2, "Caret x similar to range x");

    info("Setting caretOffset to 7 (in embedded LTR)");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input2);
    input2.caretOffset = 7;
    await caretMoved;
    testCaretRect(docAcc, "input2", 7, fetchedBounds);

    const textarea = findAccessibleChildByID(docAcc, "textarea", [
      nsIAccessibleText,
    ]);

    fetchedBounds = await fetchCollapsedRangeBounds(docAcc, textarea);

    info("Focusing textarea");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    textarea.takeFocus();
    await caretMoved;
    testCaretRect(docAcc, "textarea", 0, fetchedBounds);

    info("Setting caretOffset to 1");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    textarea.caretOffset = 1;
    await caretMoved;
    testCaretRect(docAcc, "textarea", 1, fetchedBounds);

    info("Setting caretOffset to 4 (before newline)");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    textarea.caretOffset = 4;
    await caretMoved;
    testCaretRect(docAcc, "textarea", 4, fetchedBounds);

    info("Setting caretOffset to 5 (after newline)");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    textarea.caretOffset = 5;
    await caretMoved;
    testCaretRect(docAcc, "textarea", 5, fetchedBounds);
  },
  { chrome: true, topLevel: true }
);

/**
 * Test the caret rect in vertical text.
 *
 * Padding is removed in order to make the text range bounds
 * and the caret bounds consistent for the empty text input.
 * See Bug 2038670.
 */

addAccessibleTask(
  `
<input id="input" value="ab" style="writing-mode: vertical-lr; padding: 0;">
<input id="emptyInput" style="writing-mode: vertical-lr; padding: 0;">
  `,
  async function testVerticalInputs(browser, docAcc) {
    const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]);
    let fetchedBounds = await fetchCollapsedRangeBounds(docAcc, input);

    info("Focusing input");
    let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
    input.takeFocus();
    await caretMoved;
    testCaretRect(docAcc, "input", 0, fetchedBounds, false, true);
    info("Setting caretOffset to 1");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
    input.caretOffset = 1;
    await caretMoved;
    testCaretRect(docAcc, "input", 1, fetchedBounds, false, true);
    info("Setting caretOffset to 2");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
    input.caretOffset = 2;
    await caretMoved;
    testCaretRect(docAcc, "input", 2, fetchedBounds, false, true);
    info("Resetting caretOffset to 0");
    input.caretOffset = 0;

    const emptyInput = findAccessibleChildByID(docAcc, "emptyInput", [
      nsIAccessibleText,
    ]);
    fetchedBounds = await fetchCollapsedRangeBounds(docAcc, emptyInput);

    info("Focusing emptyInput");
    caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, emptyInput);
    emptyInput.takeFocus();
    await caretMoved;
    testCaretRect(docAcc, "emptyInput", 0, fetchedBounds, false, true);
  }
);

/**
 * Test contenteditable caret
 */

addAccessibleTask(
  `
<div contenteditable="true" id="input" role="textbox">one <strong>two</strong> <i>three</i></div>
  `,
  async function testRichTextEdit(browser, docAcc) {
    const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]);
    let fetchedBounds = await fetchCollapsedRangeBounds(docAcc, input);

    let focused = waitForEvent(EVENT_FOCUS, input);
    input.takeFocus();
    info("Focusing input");
    await focused;
    testCaretRect(docAcc, "input", 0, fetchedBounds);

    for (let offset = 1; offset < fetchedBounds.length - 1; offset++) {
      info("Pressing ArrowRight to move caret to index " + offset);
      let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED);
      EventUtils.synthesizeKey("KEY_ArrowRight");

      let evt = (await caretMoved).QueryInterface(nsIAccessibleCaretMoveEvent);

      let acc = evt.accessible.QueryInterface(nsIAccessibleHyperText);
      let caretOffset = evt.caretOffset;
      let linkIndex = acc.getLinkIndexAtOffset(evt.caretOffset);
      if (linkIndex != -1) {
        acc = acc
          .getLinkAt(linkIndex)
          .QueryInterface(nsIAccessibleText)
          .QueryInterface(nsIAccessible);
        caretOffset = 0;
      }

      const [rangeX, rangeY, rangeW, rangeH] =
        fetchedBounds.length > offset ? fetchedBounds[offset] : [0, 0, 0, 0];
      const [caretX, caretY, caretW, caretH] = getCollapsedRangeExtents(
        acc,
        caretOffset
      );

      info(
        `${offset}: Caret rect: ${caretX}, ${caretY}, ${caretW}, ${caretH}, Prefetched rect: ${rangeX}, ${rangeY}, ${rangeW}, ${rangeH}`
      );

      isWithin(rangeX, caretX, 2, "Caret x similar to range x");
      isWithin(rangeY, caretY, 2, "Caret y similar to range y");
      isWithin(rangeW, caretW, 2, "Caret width similar to range width");
    }
  }
);

/**
 * Test a contentEditable with a paragraph containing a line break.
 */

addAccessibleTask(
  `
<style>
  @font-face {
    font-family: Ahem;
    src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
  }
  #editable {
    font: 10px/10px Ahem;
  }
</style>
<div id="editable" contenteditable>a<p><br></p></div>
  `,
  async function testEditablePBr(browser, docAcc) {
    const editable = findAccessibleChildByID(docAcc, "editable", [
      nsIAccessibleText,
    ]);
    const fetchedBounds = await fetchCollapsedRangeBounds(docAcc, editable);

    info("Focusing editable");
    let moved = waitForEvent(EVENT_FOCUS, editable);
    editable.takeFocus();
    await moved;
    testCaretRect(docAcc, "editable", 0, fetchedBounds);

    info("Pressing ArrowRight");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED);
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await moved;
    testCaretRect(docAcc, "editable", 1, fetchedBounds, true);
  },
  { chrome: true, topLevel: true }
);

Messung V0.5 in Prozent
C=95 H=100 G=97

¤ Dauer der Verarbeitung: 0.11 Sekunden  (vorverarbeitet am  2026-09-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.