Skip to content

Commit

Permalink
Bug 1828295 part 3: Add tests for GetCaretRect. r=morgan
Browse files Browse the repository at this point in the history
This required exposing GetCaretRect via XPCOM.

Differential Revision: https://phabricator.services.mozilla.com/D179346
  • Loading branch information
jcsteh committed May 31, 2023
1 parent b17144e commit 6f49fe6
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 0 deletions.
2 changes: 2 additions & 0 deletions accessible/interfaces/nsIAccessibleText.idl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ interface nsIAccessibleText : nsISupports
*/
attribute long caretOffset;

void getCaretRect(out long x, out long y, out long width, out long height);

readonly attribute long characterCount;
readonly attribute long selectionCount;

Expand Down
1 change: 1 addition & 0 deletions accessible/tests/browser/bounds/browser.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ prefs =
javascript.options.asyncstack_capture_debuggee_only=false

[browser_accessible_moved.js]
[browser_caret_rect.js]
[browser_position.js]
[browser_test_resolution.js]
skip-if = os == 'win' # bug 1372296
Expand Down
140 changes: 140 additions & 0 deletions accessible/tests/browser/bounds/browser_caret_rect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/* 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";

async function getCaretRect(browser, id) {
// The caret rect can only be queried on LocalAccessible. On Windows, we do
// send it across processes with caret events, but this currently can't be
// queried outside of the event, nor with XPCOM.
const [x, y, w, h] = await invokeContentTask(browser, [id], contentId => {
const node = content.document.getElementById(contentId);
const contentAcc = content.CommonUtils.accService.getAccessibleFor(node);
contentAcc.QueryInterface(Ci.nsIAccessibleText);
const caretX = {};
const caretY = {};
const caretW = {};
const caretH = {};
contentAcc.getCaretRect(caretX, caretY, caretW, caretH);
return [caretX.value, caretY.value, caretW.value, caretH.value];
});
info(`Caret bounds: ${x}, ${y}, ${w}, ${h}`);
return [x, y, w, h];
}

async function testCaretRect(browser, docAcc, id, offset) {
const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
is(acc.caretOffset, offset, `Caret at offset ${offset}`);
const charX = {};
const charY = {};
const charW = {};
const charH = {};
const atEnd = offset == acc.characterCount;
const empty = offset == 0 && atEnd;
const queryOffset = atEnd && !empty ? offset - 1 : offset;
acc.getCharacterExtents(
queryOffset,
charX,
charY,
charW,
charH,
COORDTYPE_SCREEN_RELATIVE
);
info(
`Character ${queryOffset} bounds: ${charX.value}, ${charY.value}, ${charW.value}, ${charH.value}`
);
const [caretX, caretY, caretW, caretH] = await getCaretRect(browser, id);
if (atEnd) {
ok(caretX > charX.value, "Caret x after last character x");
} else {
is(caretX, charX.value, "Caret x same as character x");
}
is(caretY, charY.value, "Caret y same as character y");
is(caretW, 1, "Caret width is 1");
if (!empty) {
is(caretH, charH.value, "Caret height same as character height");
}
}

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.
*/
addAccessibleTask(
`
<input id="input" value="ab">
<input id="emptyInput">
`,
async function (browser, docAcc) {
async function runTests() {
const input = findAccessibleChildByID(docAcc, "input", [
nsIAccessibleText,
]);
info("Focusing input");
let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
input.takeFocus();
await caretMoved;
await testCaretRect(browser, docAcc, "input", 0);
info("Setting caretOffset to 1");
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
input.caretOffset = 1;
await caretMoved;
await testCaretRect(browser, docAcc, "input", 1);
info("Setting caretOffset to 2");
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
input.caretOffset = 2;
await caretMoved;
await testCaretRect(browser, docAcc, "input", 2);
info("Resetting caretOffset to 0");
input.caretOffset = 0;

const emptyInput = findAccessibleChildByID(docAcc, "emptyInput", [
nsIAccessibleText,
]);
info("Focusing emptyInput");
caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, emptyInput);
emptyInput.takeFocus();
await caretMoved;
await testCaretRect(browser, docAcc, "emptyInput", 0);
}

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: ["tabsintitlebar"] },
() => !document.documentElement.hasAttribute("tabsintitlebar")
);
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 }
);
22 changes: 22 additions & 0 deletions accessible/xpcom/xpcAccessibleHyperText.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,28 @@ xpcAccessibleHyperText::SetCaretOffset(int32_t aCaretOffset) {
return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleHyperText::GetCaretRect(int32_t* aX, int32_t* aY, int32_t* aWidth,
int32_t* aHeight) {
NS_ENSURE_ARG_POINTER(aX);
NS_ENSURE_ARG_POINTER(aY);
NS_ENSURE_ARG_POINTER(aWidth);
NS_ENSURE_ARG_POINTER(aHeight);
*aX = *aY = *aWidth = *aHeight;

if (!mIntl) {
return NS_ERROR_FAILURE;
}
if (mIntl->IsRemote()) {
return NS_ERROR_NOT_IMPLEMENTED;
}

nsIWidget* widget;
LayoutDeviceIntRect rect = IntlLocal()->GetCaretRect(&widget);
rect.GetRect(aX, aY, aWidth, aHeight);
return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleHyperText::GetSelectionCount(int32_t* aSelectionCount) {
NS_ENSURE_ARG_POINTER(aSelectionCount);
Expand Down

0 comments on commit 6f49fe6

Please sign in to comment.