mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Bug 1901462: Implement UIA ITextRangeProvider::GetBoundingRectangles, r=Jamie
This revision implements GetBoundingRectangles by walking the text range line by line, adding each line rect to an output array, then returning that array to UIA clients. Since this logic was tied up in TextLeafRange::Bounds, this revision first creates function WalkLineRects which encapsulates the logic of walking a TextLeafRange line-by-line. Then, it uses that function to rewrite Bounds and implement new function LineRects, which stores all non-empty onscreen line rects in an nsTArray and returns them. The implementation of GetBoundingRectangles has been filled out; it's mostly straightforward but contains some SAFEARRAY work since UIA expects rects as doubles, rather than our internal uint32_t representation. Finally, this revision adds a test for GetBoundingRectangles. Differential Revision: https://phabricator.services.mozilla.com/D222198
This commit is contained in:
parent
82e1685960
commit
48bc589112
@ -2018,42 +2018,42 @@ bool TextLeafRange::Crop(Accessible* aContainer) {
|
||||
}
|
||||
|
||||
LayoutDeviceIntRect TextLeafRange::Bounds() const {
|
||||
if (mEnd == mStart || mEnd < mStart) {
|
||||
return LayoutDeviceIntRect();
|
||||
// Walk all the lines and union them into the result rectangle.
|
||||
LayoutDeviceIntRect result = TextLeafPoint{mStart}.CharBounds();
|
||||
const bool succeeded =
|
||||
WalkLineRects([&result](LayoutDeviceIntRect aLineRect) {
|
||||
result.UnionRect(result, aLineRect);
|
||||
});
|
||||
|
||||
if (!succeeded) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool locatedFinalLine = false;
|
||||
TextLeafPoint currPoint = mStart;
|
||||
LayoutDeviceIntRect result = currPoint.CharBounds();
|
||||
|
||||
// Union the first and last chars of each line to create a line rect. Then,
|
||||
// union the lines together.
|
||||
while (!locatedFinalLine) {
|
||||
// Fetch the last point in the current line by getting the
|
||||
// start of the next line and going back one char. We don't
|
||||
// use BOUNDARY_LINE_END here because it is equivalent to LINE_START when
|
||||
// the line doesn't end with a line feed character.
|
||||
TextLeafPoint lineStartPoint = currPoint.FindBoundary(
|
||||
nsIAccessibleText::BOUNDARY_LINE_START, eDirNext);
|
||||
TextLeafPoint lastPointInLine = lineStartPoint.FindBoundary(
|
||||
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
|
||||
// If currPoint is the end of the document, lineStartPoint will be equal
|
||||
// to currPoint and we would be in an endless loop.
|
||||
if (lineStartPoint == currPoint || mEnd <= lastPointInLine) {
|
||||
lastPointInLine = mEnd;
|
||||
locatedFinalLine = true;
|
||||
}
|
||||
|
||||
LayoutDeviceIntRect currLine = currPoint.CharBounds();
|
||||
currLine.UnionRect(currLine, lastPointInLine.CharBounds());
|
||||
result.UnionRect(result, currLine);
|
||||
|
||||
currPoint = lineStartPoint;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
nsTArray<LayoutDeviceIntRect> TextLeafRange::LineRects() const {
|
||||
// Get the bounds of the content so we can restrict our lines to just the
|
||||
// text visible within the bounds of the document.
|
||||
Maybe<LayoutDeviceIntRect> contentBounds;
|
||||
if (Accessible* doc = nsAccUtils::DocumentFor(mStart.mAcc)) {
|
||||
contentBounds.emplace(doc->Bounds());
|
||||
}
|
||||
|
||||
nsTArray<LayoutDeviceIntRect> lineRects;
|
||||
WalkLineRects([&lineRects, &contentBounds](LayoutDeviceIntRect aLineRect) {
|
||||
// Clip the bounds to the bounds of the content area.
|
||||
bool boundsVisible = true;
|
||||
if (contentBounds.isSome()) {
|
||||
boundsVisible = aLineRect.IntersectRect(aLineRect, *contentBounds);
|
||||
}
|
||||
if (boundsVisible) {
|
||||
lineRects.AppendElement(aLineRect);
|
||||
}
|
||||
});
|
||||
|
||||
return lineRects;
|
||||
}
|
||||
|
||||
TextLeafPoint TextLeafRange::TextLeafPointAtScreenPoint(int32_t aX,
|
||||
int32_t aY) const {
|
||||
// Step backwards one character to make the endPoint inclusive. This means we
|
||||
@ -2201,6 +2201,40 @@ void TextLeafRange::ScrollIntoView(uint32_t aScrollType) const {
|
||||
aScrollType);
|
||||
}
|
||||
|
||||
bool TextLeafRange::WalkLineRects(LineRectCallback aCallback) const {
|
||||
if (mEnd <= mStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool locatedFinalLine = false;
|
||||
TextLeafPoint currPoint = mStart;
|
||||
|
||||
// Union the first and last chars of each line to create a line rect.
|
||||
while (!locatedFinalLine) {
|
||||
// Fetch the last point in the current line by getting the
|
||||
// start of the next line and going back one char. We don't
|
||||
// use BOUNDARY_LINE_END here because it is equivalent to LINE_START when
|
||||
// the line doesn't end with a line feed character.
|
||||
TextLeafPoint lineStartPoint = currPoint.FindBoundary(
|
||||
nsIAccessibleText::BOUNDARY_LINE_START, eDirNext);
|
||||
TextLeafPoint lastPointInLine = lineStartPoint.FindBoundary(
|
||||
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
|
||||
// If currPoint is the end of the document, lineStartPoint will be equal
|
||||
// to currPoint and we would be in an endless loop.
|
||||
if (lineStartPoint == currPoint || mEnd <= lastPointInLine) {
|
||||
lastPointInLine = mEnd;
|
||||
locatedFinalLine = true;
|
||||
}
|
||||
|
||||
LayoutDeviceIntRect currLine = currPoint.CharBounds();
|
||||
currLine.UnionRect(currLine, lastPointInLine.CharBounds());
|
||||
aCallback(currLine);
|
||||
|
||||
currPoint = lineStartPoint;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TextLeafRange::Iterator TextLeafRange::Iterator::BeginIterator(
|
||||
const TextLeafRange& aRange) {
|
||||
Iterator result(aRange);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "AccAttributes.h"
|
||||
#include "nsDirection.h"
|
||||
#include "nsIAccessibleText.h"
|
||||
#include "mozilla/FunctionRef.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -164,8 +165,6 @@ class TextLeafPoint final {
|
||||
/**
|
||||
* Returns a rect (in dev pixels) describing position and size of
|
||||
* the character at mOffset in mAcc. This rect is screen-relative.
|
||||
* This function only works on remote accessibles, and assumes caching
|
||||
* is enabled.
|
||||
*/
|
||||
LayoutDeviceIntRect CharBounds();
|
||||
|
||||
@ -173,8 +172,7 @@ class TextLeafPoint final {
|
||||
* Returns true if the given point (in screen coords) is contained
|
||||
* in the char bounds of the current TextLeafPoint. Returns false otherwise.
|
||||
* If the current point is an empty container, we use the acc's bounds instead
|
||||
* of char bounds. Because this depends on CharBounds, this function only
|
||||
* works on remote accessibles, and assumes caching is enabled.
|
||||
* of char bounds.
|
||||
*/
|
||||
bool ContainsPoint(int32_t aX, int32_t aY);
|
||||
|
||||
@ -298,11 +296,16 @@ class TextLeafRange final {
|
||||
|
||||
/**
|
||||
* Returns a union rect (in dev pixels) of all character bounds in this range.
|
||||
* This rect is screen-relative and inclusive of mEnd. This function only
|
||||
* works on remote accessibles, and assumes caching is enabled.
|
||||
* This rect is screen-relative and inclusive of mEnd.
|
||||
*/
|
||||
LayoutDeviceIntRect Bounds() const;
|
||||
|
||||
/*
|
||||
* Returns an array of bounding rectangles, one for each visible text line in
|
||||
* this range. These rectangles are screen-relative and inclusive of mEnd.
|
||||
*/
|
||||
nsTArray<LayoutDeviceIntRect> LineRects() const;
|
||||
|
||||
/*
|
||||
* Returns a TextLeafPoint corresponding to the point in the TextLeafRange
|
||||
* containing the given screen point. The function returns a TextLeafPoint
|
||||
@ -330,6 +333,17 @@ class TextLeafRange final {
|
||||
TextLeafPoint mStart;
|
||||
TextLeafPoint mEnd;
|
||||
|
||||
/*
|
||||
* Walk all of the lines within the TextLeafRange. This function invokes the
|
||||
* given callback with each line-bounding rectangle. The bounds are inclusive
|
||||
* of all characters in each line. Each rectangle is screen-relative. The
|
||||
* function returns true if it walks any lines, and false if it could not walk
|
||||
* any rects, which could happen if the start and end points are improperly
|
||||
* positioned.
|
||||
*/
|
||||
using LineRectCallback = FunctionRef<void(LayoutDeviceIntRect)>;
|
||||
bool WalkLineRects(LineRectCallback aCallback) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* A TextLeafRange iterator will iterate through single leaf segments of the
|
||||
|
@ -1134,3 +1134,57 @@ addUiaTask(
|
||||
},
|
||||
{ uiaEnabled: true, uiaDisabled: true }
|
||||
);
|
||||
|
||||
/**
|
||||
* Test the TextRange pattern's GetBoundingRectangles method.
|
||||
*/
|
||||
addUiaTask(
|
||||
`
|
||||
<div id="test"><p id="line1">abc</p><p id="line2">d</p><p id="line3"></p></div>
|
||||
<div id="offscreen" style="position:absolute; left:200vw;">xyz</div>
|
||||
`,
|
||||
async function testTextRangeGetBoundingRectangles(browser, docAcc) {
|
||||
const line1 = findAccessibleChildByID(docAcc, "line1", [nsIAccessibleText]);
|
||||
const line2 = findAccessibleChildByID(docAcc, "line2", [nsIAccessibleText]);
|
||||
|
||||
const lineRects = await runPython(`
|
||||
global doc, docText, testAcc, range
|
||||
doc = getDocUia()
|
||||
docText = getUiaPattern(doc, "Text")
|
||||
testAcc = findUiaByDomId(doc, "test")
|
||||
range = docText.RangeFromChild(testAcc)
|
||||
return range.GetBoundingRectangles()
|
||||
`);
|
||||
|
||||
is(lineRects.length, 8, "GetBoundingRectangles returned two rectangles");
|
||||
const firstLineRect = [
|
||||
lineRects[0],
|
||||
lineRects[1],
|
||||
lineRects[2],
|
||||
lineRects[3],
|
||||
];
|
||||
const secondLineRect = [
|
||||
lineRects[4],
|
||||
lineRects[5],
|
||||
lineRects[6],
|
||||
lineRects[7],
|
||||
];
|
||||
testTextBounds(line1, 0, -1, firstLineRect, COORDTYPE_SCREEN_RELATIVE);
|
||||
testTextBounds(line2, 0, -1, secondLineRect, COORDTYPE_SCREEN_RELATIVE);
|
||||
// line3 has no rectangle - GetBoundingRectangles shouldn't return anything for empty lines.
|
||||
|
||||
// GetBoundingRectangles shouldn't return anything for offscreen lines.
|
||||
const offscreenRects = await runPython(`
|
||||
global offscreenAcc, range
|
||||
offscreenAcc = findUiaByDomId(doc, "offscreen")
|
||||
range = docText.RangeFromChild(offscreenAcc)
|
||||
return range.GetBoundingRectangles()
|
||||
`);
|
||||
is(
|
||||
offscreenRects.length,
|
||||
0,
|
||||
"GetBoundingRectangles returned no rectangles"
|
||||
);
|
||||
},
|
||||
{ uiaEnabled: true, uiaDisabled: true, chrome: true }
|
||||
);
|
||||
|
@ -13,10 +13,11 @@ Services.scriptloader.loadSubScript(
|
||||
);
|
||||
|
||||
// Loading and common.js from accessible/tests/mochitest/ for all tests, as
|
||||
// well as promisified-events.js.
|
||||
// well as promisified-events.js and layout.js.
|
||||
loadScripts(
|
||||
{ name: "common.js", dir: MOCHITESTS_DIR },
|
||||
{ name: "promisified-events.js", dir: MOCHITESTS_DIR }
|
||||
{ name: "promisified-events.js", dir: MOCHITESTS_DIR },
|
||||
{ name: "layout.js", dir: MOCHITESTS_DIR }
|
||||
);
|
||||
|
||||
let gIsUiaEnabled = false;
|
||||
|
@ -334,7 +334,58 @@ UiaTextRange::GetAttributeValue(TEXTATTRIBUTEID aAttributeId,
|
||||
|
||||
STDMETHODIMP
|
||||
UiaTextRange::GetBoundingRectangles(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
|
||||
return E_NOTIMPL;
|
||||
if (!aRetVal) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
*aRetVal = nullptr;
|
||||
TextLeafRange range = GetRange();
|
||||
if (!range) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
|
||||
// Get the rectangles for each line.
|
||||
const nsTArray<LayoutDeviceIntRect> lineRects = range.LineRects();
|
||||
|
||||
// For UIA's purposes, the rectangles of this array are four doubles arranged
|
||||
// in order {left, top, width, height}.
|
||||
SAFEARRAY* rectsVec = SafeArrayCreateVector(VT_R8, 0, lineRects.Length() * 4);
|
||||
if (!rectsVec) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
// Empty range, return an empty array.
|
||||
if (lineRects.IsEmpty()) {
|
||||
*aRetVal = rectsVec;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Get the double array out of the SAFEARRAY so we can write to it directly.
|
||||
double* safeArrayData = nullptr;
|
||||
HRESULT hr =
|
||||
SafeArrayAccessData(rectsVec, reinterpret_cast<void**>(&safeArrayData));
|
||||
if (FAILED(hr) || !safeArrayData) {
|
||||
SafeArrayDestroy(rectsVec);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Convert the int array to a double array.
|
||||
for (size_t index = 0; index < lineRects.Length(); ++index) {
|
||||
const LayoutDeviceIntRect& lineRect = lineRects[index];
|
||||
safeArrayData[index * 4 + 0] = static_cast<double>(lineRect.x);
|
||||
safeArrayData[index * 4 + 1] = static_cast<double>(lineRect.y);
|
||||
safeArrayData[index * 4 + 2] = static_cast<double>(lineRect.width);
|
||||
safeArrayData[index * 4 + 3] = static_cast<double>(lineRect.height);
|
||||
}
|
||||
|
||||
// Release the lock on the data. If that fails, bail out.
|
||||
hr = SafeArrayUnaccessData(rectsVec);
|
||||
if (FAILED(hr)) {
|
||||
SafeArrayDestroy(rectsVec);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
*aRetVal = rectsVec;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
|
Loading…
Reference in New Issue
Block a user