/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=2:
*/
/* 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/. */
#include "ia2Accessible.h"
#include "ia2AccessibleHypertext.h"
#include "ia2AccessibleText.h"
#include "AccessibleText_i.c"
#include "mozilla/a11y/HyperTextAccessibleBase.h"
#include "mozilla/ClearOnShutdown.h"
using namespace mozilla::a11y;
HyperTextAccessibleBase* ia2AccessibleText::sLastTextChangeAcc = nullptr;
mozilla::StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString;
uint32_t ia2AccessibleText::sLastTextChangeStart = 0;
uint32_t ia2AccessibleText::sLastTextChangeEnd = 0;
bool ia2AccessibleText::sLastTextChangeWasInsert =
false;
HyperTextAccessibleBase* ia2AccessibleText::TextAcc() {
auto hyp =
static_cast<ia2AccessibleHypertext*>(
this);
Accessible* acc = hyp->Acc();
return acc ? acc->AsHyperTextBase() : nullptr;
}
// IAccessibleText
STDMETHODIMP
ia2AccessibleText::addSelection(
long aStartOffset,
long aEndOffset) {
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
return textAcc->AddToSelection(aStartOffset, aEndOffset) ? S_OK
: E_INVALIDARG;
}
STDMETHODIMP
ia2AccessibleText::get_attributes(
long aOffset,
long* aStartOffset,
long* aEndOffset, BSTR* aTextAttributes) {
if (!aStartOffset || !aEndOffset || !aTextAttributes)
return E_INVALIDARG;
*aStartOffset = 0;
*aEndOffset = 0;
*aTextAttributes = nullptr;
int32_t startOffset = 0, endOffset = 0;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
RefPtr<AccAttributes> attributes =
textAcc->TextAttributes(
true, aOffset, &startOffset, &endOffset);
HRESULT hr =
ia2Accessible::ConvertToIA2Attributes(attributes, aTextAttributes);
if (FAILED(hr))
return hr;
*aStartOffset = startOffset;
*aEndOffset = endOffset;
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::get_caretOffset(
long* aOffset) {
if (!aOffset)
return E_INVALIDARG;
*aOffset = -1;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
*aOffset = textAcc->CaretOffset();
return *aOffset != -1 ? S_OK : S_FALSE;
}
STDMETHODIMP
ia2AccessibleText::get_characterExtents(
long aOffset,
enum IA2CoordinateType aCoordType,
long* aX,
long* aY,
long* aWidth,
long* aHeight) {
if (!aX || !aY || !aWidth || !aHeight)
return E_INVALIDARG;
*aX = *aY = *aWidth = *aHeight = 0;
uint32_t geckoCoordType =
(aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE)
? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
: nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
LayoutDeviceIntRect rect;
auto textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
rect = textAcc->CharBounds(aOffset, geckoCoordType);
// Can't use GetRect() because of long vs. int32_t mismatch
*aX = rect.X();
*aY = rect.Y();
*aWidth = rect.Width();
*aHeight = rect.Height();
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::get_nSelections(
long* aNSelections) {
if (!aNSelections)
return E_INVALIDARG;
*aNSelections = 0;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
*aNSelections = textAcc->SelectionCount();
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::get_offsetAtPoint(
long aX,
long aY,
enum IA2CoordinateType aCoordType,
long* aOffset) {
if (!aOffset)
return E_INVALIDARG;
*aOffset = 0;
uint32_t geckoCoordType =
(aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE)
? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
: nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
*aOffset = textAcc->OffsetAtPoint(aX, aY, geckoCoordType);
return *aOffset == -1 ? S_FALSE : S_OK;
}
STDMETHODIMP
ia2AccessibleText::get_selection(
long aSelectionIndex,
long* aStartOffset,
long* aEndOffset) {
if (!aStartOffset || !aEndOffset)
return E_INVALIDARG;
*aStartOffset = *aEndOffset = 0;
int32_t startOffset = 0, endOffset = 0;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->SelectionBoundsAt(aSelectionIndex, &startOffset, &endOffset)) {
return E_INVALIDARG;
}
*aStartOffset = startOffset;
*aEndOffset = endOffset;
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::get_text(
long aStartOffset,
long aEndOffset, BSTR* aText) {
if (!aText)
return E_INVALIDARG;
*aText = nullptr;
nsAutoString text;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) {
return E_INVALIDARG;
}
textAcc->TextSubstring(aStartOffset, aEndOffset, text);
if (text.IsEmpty())
return S_FALSE;
*aText = ::SysAllocStringLen(text.get(), text.Length());
return *aText ? S_OK : E_OUTOFMEMORY;
}
STDMETHODIMP
ia2AccessibleText::get_textBeforeOffset(
long aOffset,
enum IA2TextBoundaryType aBoundaryType,
long* aStartOffset,
long* aEndOffset,
BSTR* aText) {
if (!aStartOffset || !aEndOffset || !aText)
return E_INVALIDARG;
*aStartOffset = *aEndOffset = 0;
*aText = nullptr;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->IsValidOffset(aOffset))
return E_INVALIDARG;
nsAutoString text;
int32_t startOffset = 0, endOffset = 0;
if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
startOffset = 0;
endOffset = textAcc->CharacterCount();
textAcc->TextSubstring(startOffset, endOffset, text);
}
else {
AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
if (boundaryType == -1)
return S_FALSE;
textAcc->TextBeforeOffset(aOffset, boundaryType, &startOffset, &endOffset,
text);
}
*aStartOffset = startOffset;
*aEndOffset = endOffset;
if (text.IsEmpty())
return S_FALSE;
*aText = ::SysAllocStringLen(text.get(), text.Length());
return *aText ? S_OK : E_OUTOFMEMORY;
}
STDMETHODIMP
ia2AccessibleText::get_textAfterOffset(
long aOffset,
enum IA2TextBoundaryType aBoundaryType,
long* aStartOffset,
long* aEndOffset,
BSTR* aText) {
if (!aStartOffset || !aEndOffset || !aText)
return E_INVALIDARG;
*aStartOffset = 0;
*aEndOffset = 0;
*aText = nullptr;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->IsValidOffset(aOffset))
return E_INVALIDARG;
nsAutoString text;
int32_t startOffset = 0, endOffset = 0;
if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
startOffset = 0;
endOffset = textAcc->CharacterCount();
textAcc->TextSubstring(startOffset, endOffset, text);
}
else {
AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
if (boundaryType == -1)
return S_FALSE;
textAcc->TextAfterOffset(aOffset, boundaryType, &startOffset, &endOffset,
text);
}
*aStartOffset = startOffset;
*aEndOffset = endOffset;
if (text.IsEmpty())
return S_FALSE;
*aText = ::SysAllocStringLen(text.get(), text.Length());
return *aText ? S_OK : E_OUTOFMEMORY;
}
STDMETHODIMP
ia2AccessibleText::get_textAtOffset(
long aOffset,
enum IA2TextBoundaryType aBoundaryType,
long* aStartOffset,
long* aEndOffset,
BSTR* aText) {
if (!aStartOffset || !aEndOffset || !aText)
return E_INVALIDARG;
*aStartOffset = *aEndOffset = 0;
*aText = nullptr;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc)
return CO_E_OBJNOTCONNECTED;
if (!textAcc->IsValidOffset(aOffset))
return E_INVALIDARG;
nsAutoString text;
int32_t startOffset = 0, endOffset = 0;
if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
startOffset = 0;
endOffset = textAcc->CharacterCount();
textAcc->TextSubstring(startOffset, endOffset, text);
}
else {
AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
if (boundaryType == -1)
return S_FALSE;
textAcc->TextAtOffset(aOffset, boundaryType, &startOffset, &endOffset,
text);
}
*aStartOffset = startOffset;
*aEndOffset = endOffset;
if (text.IsEmpty())
return S_FALSE;
*aText = ::SysAllocStringLen(text.get(), text.Length());
return *aText ? S_OK : E_OUTOFMEMORY;
}
STDMETHODIMP
ia2AccessibleText::removeSelection(
long aSelectionIndex) {
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
return textAcc->RemoveFromSelection(aSelectionIndex) ? S_OK : E_INVALIDARG;
}
STDMETHODIMP
ia2AccessibleText::setCaretOffset(
long aOffset) {
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->IsValidOffset(aOffset))
return E_INVALIDARG;
textAcc->SetCaretOffset(aOffset);
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::setSelection(
long aSelectionIndex,
long aStartOffset,
long aEndOffset) {
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
return textAcc->SetSelectionBoundsAt(aSelectionIndex, aStartOffset,
aEndOffset)
? S_OK
: E_INVALIDARG;
}
STDMETHODIMP
ia2AccessibleText::get_nCharacters(
long* aNCharacters) {
if (!aNCharacters)
return E_INVALIDARG;
*aNCharacters = 0;
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc)
return CO_E_OBJNOTCONNECTED;
*aNCharacters = textAcc->CharacterCount();
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::scrollSubstringTo(
long aStartIndex,
long aEndIndex,
enum IA2ScrollType aScrollType) {
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->IsValidRange(aStartIndex, aEndIndex))
return E_INVALIDARG;
textAcc->ScrollSubstringTo(aStartIndex, aEndIndex, aScrollType);
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::scrollSubstringToPoint(
long aStartIndex,
long aEndIndex,
enum IA2CoordinateType aCoordType,
long aX,
long aY) {
HyperTextAccessibleBase* textAcc = TextAcc();
if (!textAcc) {
return CO_E_OBJNOTCONNECTED;
}
if (!textAcc->IsValidRange(aStartIndex, aEndIndex)) {
return E_INVALIDARG;
}
uint32_t geckoCoordType =
(aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE)
? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
: nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
textAcc->ScrollSubstringToPoint(aStartIndex, aEndIndex, geckoCoordType, aX,
aY);
return S_OK;
}
STDMETHODIMP
ia2AccessibleText::get_newText(IA2TextSegment* aNewText) {
return GetModifiedText(
true, aNewText);
}
STDMETHODIMP
ia2AccessibleText::get_oldText(IA2TextSegment* aOldText) {
return GetModifiedText(
false, aOldText);
}
// ia2AccessibleText
HRESULT
ia2AccessibleText::GetModifiedText(
bool aGetInsertedText,
IA2TextSegment* aText) {
if (!aText)
return E_INVALIDARG;
if (!sLastTextChangeAcc)
return S_OK;
if (aGetInsertedText != sLastTextChangeWasInsert)
return S_OK;
if (sLastTextChangeAcc != TextAcc())
return S_OK;
aText->start = sLastTextChangeStart;
aText->end = sLastTextChangeEnd;
if (sLastTextChangeString->IsEmpty())
return S_FALSE;
aText->text = ::SysAllocStringLen(sLastTextChangeString->get(),
sLastTextChangeString->Length());
return aText->text ? S_OK : E_OUTOFMEMORY;
}
AccessibleTextBoundary ia2AccessibleText::GetGeckoTextBoundary(
enum IA2TextBoundaryType aBoundaryType) {
switch (aBoundaryType) {
case IA2_TEXT_BOUNDARY_CHAR:
return nsIAccessibleText::BOUNDARY_CLUSTER;
case IA2_TEXT_BOUNDARY_WORD:
return nsIAccessibleText::BOUNDARY_WORD_START;
case IA2_TEXT_BOUNDARY_LINE:
return nsIAccessibleText::BOUNDARY_LINE_START;
case IA2_TEXT_BOUNDARY_PARAGRAPH:
return nsIAccessibleText::BOUNDARY_PARAGRAPH;
// case IA2_TEXT_BOUNDARY_SENTENCE:
// XXX: not implemented
default:
return -1;
}
}
void ia2AccessibleText::InitTextChangeData() {
ClearOnShutdown(&sLastTextChangeString);
}
void ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleBase* aAcc,
bool aInsert,
const nsAString& aStr,
int32_t aStart, uint32_t aLen) {
if (!sLastTextChangeString) sLastTextChangeString =
new nsString();
sLastTextChangeAcc = aAcc;
sLastTextChangeStart = aStart;
sLastTextChangeEnd = aStart + aLen;
sLastTextChangeWasInsert = aInsert;
*sLastTextChangeString = aStr;
}