1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "ia2AccessibleText.h"
9 
10 #include "Accessible2.h"
11 #include "AccessibleText_i.c"
12 
13 #include "HyperTextAccessibleWrap.h"
14 #include "HyperTextAccessible-inl.h"
15 #include "ProxyWrappers.h"
16 #include "mozilla/ClearOnShutdown.h"
17 
18 using namespace mozilla::a11y;
19 
20 StaticRefPtr<HyperTextAccessibleWrap> ia2AccessibleText::sLastTextChangeAcc;
21 StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString;
22 uint32_t ia2AccessibleText::sLastTextChangeStart = 0;
23 uint32_t ia2AccessibleText::sLastTextChangeEnd = 0;
24 bool ia2AccessibleText::sLastTextChangeWasInsert = false;
25 
26 // IAccessibleText
27 
28 STDMETHODIMP
addSelection(long aStartOffset,long aEndOffset)29 ia2AccessibleText::addSelection(long aStartOffset, long aEndOffset)
30 {
31   A11Y_TRYBLOCK_BEGIN
32 
33   MOZ_ASSERT(!HyperTextProxyFor(this));
34 
35   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
36   if (textAcc->IsDefunct())
37     return CO_E_OBJNOTCONNECTED;
38 
39   return textAcc->AddToSelection(aStartOffset, aEndOffset) ?
40     S_OK : E_INVALIDARG;
41 
42   A11Y_TRYBLOCK_END
43 }
44 
45 STDMETHODIMP
get_attributes(long aOffset,long * aStartOffset,long * aEndOffset,BSTR * aTextAttributes)46 ia2AccessibleText::get_attributes(long aOffset, long *aStartOffset,
47                                   long *aEndOffset, BSTR *aTextAttributes)
48 {
49   A11Y_TRYBLOCK_BEGIN
50 
51   if (!aStartOffset || !aEndOffset || !aTextAttributes)
52     return E_INVALIDARG;
53 
54   *aStartOffset = 0;
55   *aEndOffset = 0;
56   *aTextAttributes = nullptr;
57 
58   int32_t startOffset = 0, endOffset = 0;
59   HRESULT hr;
60   MOZ_ASSERT(!HyperTextProxyFor(this));
61   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
62   if (textAcc->IsDefunct()) {
63     return CO_E_OBJNOTCONNECTED;
64   }
65 
66   nsCOMPtr<nsIPersistentProperties> attributes =
67     textAcc->TextAttributes(true, aOffset, &startOffset, &endOffset);
68 
69   hr = AccessibleWrap::ConvertToIA2Attributes(attributes, aTextAttributes);
70   if (FAILED(hr))
71     return hr;
72 
73   *aStartOffset = startOffset;
74   *aEndOffset = endOffset;
75 
76   return S_OK;
77 
78   A11Y_TRYBLOCK_END
79 }
80 
81 STDMETHODIMP
get_caretOffset(long * aOffset)82 ia2AccessibleText::get_caretOffset(long *aOffset)
83 {
84   A11Y_TRYBLOCK_BEGIN
85 
86   if (!aOffset)
87     return E_INVALIDARG;
88 
89   *aOffset = -1;
90 
91   MOZ_ASSERT(!HyperTextProxyFor(this));
92   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
93   if (textAcc->IsDefunct()) {
94     return CO_E_OBJNOTCONNECTED;
95   }
96 
97   *aOffset = textAcc->CaretOffset();
98 
99   return *aOffset != -1 ? S_OK : S_FALSE;
100 
101   A11Y_TRYBLOCK_END
102 }
103 
104 STDMETHODIMP
get_characterExtents(long aOffset,enum IA2CoordinateType aCoordType,long * aX,long * aY,long * aWidth,long * aHeight)105 ia2AccessibleText::get_characterExtents(long aOffset,
106                                         enum IA2CoordinateType aCoordType,
107                                         long* aX, long* aY,
108                                         long* aWidth, long* aHeight)
109 {
110   A11Y_TRYBLOCK_BEGIN
111 
112   if (!aX || !aY || !aWidth || !aHeight)
113     return E_INVALIDARG;
114   *aX = *aY = *aWidth = *aHeight = 0;
115 
116   uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
117     nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
118     nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
119   nsIntRect rect;
120   MOZ_ASSERT(!HyperTextProxyFor(this));
121   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
122   if (textAcc->IsDefunct())
123     return CO_E_OBJNOTCONNECTED;
124 
125   rect = textAcc->CharBounds(aOffset, geckoCoordType);
126 
127   *aX = rect.x;
128   *aY = rect.y;
129   *aWidth = rect.width;
130   *aHeight = rect.height;
131   return S_OK;
132 
133   A11Y_TRYBLOCK_END
134 }
135 
136 STDMETHODIMP
get_nSelections(long * aNSelections)137 ia2AccessibleText::get_nSelections(long* aNSelections)
138 {
139   A11Y_TRYBLOCK_BEGIN
140 
141   if (!aNSelections)
142     return E_INVALIDARG;
143   *aNSelections = 0;
144 
145   MOZ_ASSERT(!HyperTextProxyFor(this));
146   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
147   if (textAcc->IsDefunct()) {
148     return CO_E_OBJNOTCONNECTED;
149   }
150 
151   *aNSelections = textAcc->SelectionCount();
152 
153   return S_OK;
154 
155   A11Y_TRYBLOCK_END
156 }
157 
158 STDMETHODIMP
get_offsetAtPoint(long aX,long aY,enum IA2CoordinateType aCoordType,long * aOffset)159 ia2AccessibleText::get_offsetAtPoint(long aX, long aY,
160                                      enum IA2CoordinateType aCoordType,
161                                      long* aOffset)
162 {
163   A11Y_TRYBLOCK_BEGIN
164 
165   if (!aOffset)
166     return E_INVALIDARG;
167   *aOffset = 0;
168 
169   uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
170     nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
171     nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
172 
173   MOZ_ASSERT(!HyperTextProxyFor(this));
174   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
175   if (textAcc->IsDefunct()) {
176     return CO_E_OBJNOTCONNECTED;
177   }
178 
179   *aOffset = textAcc->OffsetAtPoint(aX, aY, geckoCoordType);
180 
181   return *aOffset == -1 ? S_FALSE : S_OK;
182 
183   A11Y_TRYBLOCK_END
184 }
185 
186 STDMETHODIMP
get_selection(long aSelectionIndex,long * aStartOffset,long * aEndOffset)187 ia2AccessibleText::get_selection(long aSelectionIndex, long* aStartOffset,
188                                  long* aEndOffset)
189 {
190   A11Y_TRYBLOCK_BEGIN
191 
192   if (!aStartOffset || !aEndOffset)
193     return E_INVALIDARG;
194   *aStartOffset = *aEndOffset = 0;
195 
196   int32_t startOffset = 0, endOffset = 0;
197   MOZ_ASSERT(!HyperTextProxyFor(this));
198   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
199   if (textAcc->IsDefunct()) {
200     return CO_E_OBJNOTCONNECTED;
201   }
202 
203   if (!textAcc->SelectionBoundsAt(aSelectionIndex, &startOffset, &endOffset)) {
204     return E_INVALIDARG;
205   }
206 
207   *aStartOffset = startOffset;
208   *aEndOffset = endOffset;
209   return S_OK;
210 
211   A11Y_TRYBLOCK_END
212 }
213 
214 STDMETHODIMP
get_text(long aStartOffset,long aEndOffset,BSTR * aText)215 ia2AccessibleText::get_text(long aStartOffset, long aEndOffset, BSTR* aText)
216 {
217   A11Y_TRYBLOCK_BEGIN
218 
219   if (!aText)
220     return E_INVALIDARG;
221 
222   *aText = nullptr;
223 
224   nsAutoString text;
225   MOZ_ASSERT(!HyperTextProxyFor(this));
226   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
227   if (textAcc->IsDefunct()) {
228     return CO_E_OBJNOTCONNECTED;
229   }
230 
231   if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) {
232     return E_INVALIDARG;
233   }
234 
235   textAcc->TextSubstring(aStartOffset, aEndOffset, text);
236 
237   if (text.IsEmpty())
238     return S_FALSE;
239 
240   *aText = ::SysAllocStringLen(text.get(), text.Length());
241   return *aText ? S_OK : E_OUTOFMEMORY;
242 
243   A11Y_TRYBLOCK_END
244 }
245 
246 STDMETHODIMP
get_textBeforeOffset(long aOffset,enum IA2TextBoundaryType aBoundaryType,long * aStartOffset,long * aEndOffset,BSTR * aText)247 ia2AccessibleText::get_textBeforeOffset(long aOffset,
248                                         enum IA2TextBoundaryType aBoundaryType,
249                                         long* aStartOffset, long* aEndOffset,
250                                         BSTR* aText)
251 {
252   A11Y_TRYBLOCK_BEGIN
253 
254   if (!aStartOffset || !aEndOffset || !aText)
255     return E_INVALIDARG;
256 
257   *aStartOffset = *aEndOffset = 0;
258   *aText = nullptr;
259 
260   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
261   if (textAcc->IsDefunct())
262     return CO_E_OBJNOTCONNECTED;
263 
264   if (!textAcc->IsValidOffset(aOffset))
265     return E_INVALIDARG;
266 
267   nsAutoString text;
268   int32_t startOffset = 0, endOffset = 0;
269 
270   if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
271     startOffset = 0;
272     endOffset = textAcc->CharacterCount();
273     textAcc->TextSubstring(startOffset, endOffset, text);
274   } else {
275     AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
276     if (boundaryType == -1)
277       return S_FALSE;
278 
279     textAcc->TextBeforeOffset(aOffset, boundaryType, &startOffset, &endOffset, text);
280   }
281 
282   *aStartOffset = startOffset;
283   *aEndOffset = endOffset;
284 
285   if (text.IsEmpty())
286     return S_FALSE;
287 
288   *aText = ::SysAllocStringLen(text.get(), text.Length());
289   return *aText ? S_OK : E_OUTOFMEMORY;
290 
291   A11Y_TRYBLOCK_END
292 }
293 
294 STDMETHODIMP
get_textAfterOffset(long aOffset,enum IA2TextBoundaryType aBoundaryType,long * aStartOffset,long * aEndOffset,BSTR * aText)295 ia2AccessibleText::get_textAfterOffset(long aOffset,
296                                        enum IA2TextBoundaryType aBoundaryType,
297                                        long* aStartOffset, long* aEndOffset,
298                                        BSTR* aText)
299 {
300   A11Y_TRYBLOCK_BEGIN
301 
302   if (!aStartOffset || !aEndOffset || !aText)
303     return E_INVALIDARG;
304 
305   *aStartOffset = 0;
306   *aEndOffset = 0;
307   *aText = nullptr;
308 
309   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
310   if (textAcc->IsDefunct())
311     return CO_E_OBJNOTCONNECTED;
312 
313   if (!textAcc->IsValidOffset(aOffset))
314     return E_INVALIDARG;
315 
316   nsAutoString text;
317   int32_t startOffset = 0, endOffset = 0;
318 
319   if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
320     startOffset = 0;
321     endOffset = textAcc->CharacterCount();
322     textAcc->TextSubstring(startOffset, endOffset, text);
323   } else {
324     AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
325     if (boundaryType == -1)
326       return S_FALSE;
327     textAcc->TextAfterOffset(aOffset, boundaryType, &startOffset, &endOffset, text);
328   }
329 
330   *aStartOffset = startOffset;
331   *aEndOffset = endOffset;
332 
333   if (text.IsEmpty())
334     return S_FALSE;
335 
336   *aText = ::SysAllocStringLen(text.get(), text.Length());
337   return *aText ? S_OK : E_OUTOFMEMORY;
338 
339   A11Y_TRYBLOCK_END
340 }
341 
342 STDMETHODIMP
get_textAtOffset(long aOffset,enum IA2TextBoundaryType aBoundaryType,long * aStartOffset,long * aEndOffset,BSTR * aText)343 ia2AccessibleText::get_textAtOffset(long aOffset,
344                                     enum IA2TextBoundaryType aBoundaryType,
345                                     long* aStartOffset, long* aEndOffset,
346                                     BSTR* aText)
347 {
348   A11Y_TRYBLOCK_BEGIN
349 
350   if (!aStartOffset || !aEndOffset || !aText)
351     return E_INVALIDARG;
352 
353   *aStartOffset = *aEndOffset = 0;
354   *aText = nullptr;
355 
356   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
357   if (textAcc->IsDefunct())
358     return CO_E_OBJNOTCONNECTED;
359 
360   if (!textAcc->IsValidOffset(aOffset))
361     return E_INVALIDARG;
362 
363   nsAutoString text;
364   int32_t startOffset = 0, endOffset = 0;
365   if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
366     startOffset = 0;
367     endOffset = textAcc->CharacterCount();
368     textAcc->TextSubstring(startOffset, endOffset, text);
369   } else {
370     AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
371     if (boundaryType == -1)
372       return S_FALSE;
373     textAcc->TextAtOffset(aOffset, boundaryType, &startOffset, &endOffset, text);
374   }
375 
376   *aStartOffset = startOffset;
377   *aEndOffset = endOffset;
378 
379   if (text.IsEmpty())
380     return S_FALSE;
381 
382   *aText = ::SysAllocStringLen(text.get(), text.Length());
383   return *aText ? S_OK : E_OUTOFMEMORY;
384 
385   A11Y_TRYBLOCK_END
386 }
387 
388 STDMETHODIMP
removeSelection(long aSelectionIndex)389 ia2AccessibleText::removeSelection(long aSelectionIndex)
390 {
391   A11Y_TRYBLOCK_BEGIN
392 
393   MOZ_ASSERT(!HyperTextProxyFor(this));
394 
395   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
396   if (textAcc->IsDefunct())
397     return CO_E_OBJNOTCONNECTED;
398 
399   return textAcc->RemoveFromSelection(aSelectionIndex) ?
400     S_OK : E_INVALIDARG;
401 
402   A11Y_TRYBLOCK_END
403 }
404 
405 STDMETHODIMP
setCaretOffset(long aOffset)406 ia2AccessibleText::setCaretOffset(long aOffset)
407 {
408   A11Y_TRYBLOCK_BEGIN
409 
410   MOZ_ASSERT(!HyperTextProxyFor(this));
411 
412   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
413   if (textAcc->IsDefunct())
414     return CO_E_OBJNOTCONNECTED;
415 
416   if (!textAcc->IsValidOffset(aOffset))
417     return E_INVALIDARG;
418 
419   textAcc->SetCaretOffset(aOffset);
420   return S_OK;
421 
422   A11Y_TRYBLOCK_END
423 }
424 
425 STDMETHODIMP
setSelection(long aSelectionIndex,long aStartOffset,long aEndOffset)426 ia2AccessibleText::setSelection(long aSelectionIndex, long aStartOffset,
427                                 long aEndOffset)
428 {
429   A11Y_TRYBLOCK_BEGIN
430 
431   MOZ_ASSERT(!HyperTextProxyFor(this));
432 
433   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
434   if (textAcc->IsDefunct())
435     return CO_E_OBJNOTCONNECTED;
436 
437   return textAcc->SetSelectionBoundsAt(aSelectionIndex, aStartOffset, aEndOffset) ?
438     S_OK : E_INVALIDARG;
439 
440   A11Y_TRYBLOCK_END
441 }
442 
443 STDMETHODIMP
get_nCharacters(long * aNCharacters)444 ia2AccessibleText::get_nCharacters(long* aNCharacters)
445 {
446   A11Y_TRYBLOCK_BEGIN
447 
448   if (!aNCharacters)
449     return E_INVALIDARG;
450   *aNCharacters = 0;
451 
452   MOZ_ASSERT(!HyperTextProxyFor(this));
453 
454   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
455   if (textAcc->IsDefunct())
456     return CO_E_OBJNOTCONNECTED;
457 
458   *aNCharacters  = textAcc->CharacterCount();
459   return S_OK;
460 
461   A11Y_TRYBLOCK_END
462 }
463 
464 STDMETHODIMP
scrollSubstringTo(long aStartIndex,long aEndIndex,enum IA2ScrollType aScrollType)465 ia2AccessibleText::scrollSubstringTo(long aStartIndex, long aEndIndex,
466                                      enum IA2ScrollType aScrollType)
467 {
468   A11Y_TRYBLOCK_BEGIN
469 
470   MOZ_ASSERT(!HyperTextProxyFor(this));
471 
472   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
473   if (textAcc->IsDefunct())
474     return CO_E_OBJNOTCONNECTED;
475 
476   if (!textAcc->IsValidRange(aStartIndex, aEndIndex))
477     return E_INVALIDARG;
478 
479   textAcc->ScrollSubstringTo(aStartIndex, aEndIndex, aScrollType);
480   return S_OK;
481 
482   A11Y_TRYBLOCK_END
483 }
484 
485 STDMETHODIMP
scrollSubstringToPoint(long aStartIndex,long aEndIndex,enum IA2CoordinateType aCoordType,long aX,long aY)486 ia2AccessibleText::scrollSubstringToPoint(long aStartIndex, long aEndIndex,
487                                           enum IA2CoordinateType aCoordType,
488                                           long aX, long aY)
489 {
490   A11Y_TRYBLOCK_BEGIN
491 
492   uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
493     nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
494     nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
495 
496   MOZ_ASSERT(!HyperTextProxyFor(this));
497 
498   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
499   if (textAcc->IsDefunct())
500     return CO_E_OBJNOTCONNECTED;
501 
502   if (!textAcc->IsValidRange(aStartIndex, aEndIndex))
503     return E_INVALIDARG;
504 
505   textAcc->ScrollSubstringToPoint(aStartIndex, aEndIndex,
506                                   geckoCoordType, aX, aY);
507   return S_OK;
508 
509   A11Y_TRYBLOCK_END
510 }
511 
512 STDMETHODIMP
get_newText(IA2TextSegment * aNewText)513 ia2AccessibleText::get_newText(IA2TextSegment *aNewText)
514 {
515   A11Y_TRYBLOCK_BEGIN
516 
517   return GetModifiedText(true, aNewText);
518 
519   A11Y_TRYBLOCK_END
520 }
521 
522 STDMETHODIMP
get_oldText(IA2TextSegment * aOldText)523 ia2AccessibleText::get_oldText(IA2TextSegment *aOldText)
524 {
525   A11Y_TRYBLOCK_BEGIN
526 
527   return GetModifiedText(false, aOldText);
528 
529   A11Y_TRYBLOCK_END
530 }
531 
532 // ia2AccessibleText
533 
534 HRESULT
GetModifiedText(bool aGetInsertedText,IA2TextSegment * aText)535 ia2AccessibleText::GetModifiedText(bool aGetInsertedText,
536                                    IA2TextSegment *aText)
537 {
538   if (!aText)
539     return E_INVALIDARG;
540 
541   if (!sLastTextChangeAcc)
542     return S_OK;
543 
544   if (aGetInsertedText != sLastTextChangeWasInsert)
545     return S_OK;
546 
547   if (sLastTextChangeAcc != this)
548     return S_OK;
549 
550   aText->start = sLastTextChangeStart;
551   aText->end = sLastTextChangeEnd;
552 
553   if (sLastTextChangeString->IsEmpty())
554     return S_FALSE;
555 
556   aText->text = ::SysAllocStringLen(sLastTextChangeString->get(), sLastTextChangeString->Length());
557   return aText->text ? S_OK : E_OUTOFMEMORY;
558 }
559 
560 AccessibleTextBoundary
GetGeckoTextBoundary(enum IA2TextBoundaryType aBoundaryType)561 ia2AccessibleText::GetGeckoTextBoundary(enum IA2TextBoundaryType aBoundaryType)
562 {
563   switch (aBoundaryType) {
564     case IA2_TEXT_BOUNDARY_CHAR:
565       return nsIAccessibleText::BOUNDARY_CHAR;
566     case IA2_TEXT_BOUNDARY_WORD:
567       return nsIAccessibleText::BOUNDARY_WORD_START;
568     case IA2_TEXT_BOUNDARY_LINE:
569       return nsIAccessibleText::BOUNDARY_LINE_START;
570     //case IA2_TEXT_BOUNDARY_SENTENCE:
571     //case IA2_TEXT_BOUNDARY_PARAGRAPH:
572       // XXX: not implemented
573     default:
574       return -1;
575   }
576 }
577 
578 void
InitTextChangeData()579 ia2AccessibleText::InitTextChangeData()
580 {
581   ClearOnShutdown(&sLastTextChangeAcc);
582   ClearOnShutdown(&sLastTextChangeString);
583 }
584 
585 void
UpdateTextChangeData(HyperTextAccessibleWrap * aAcc,bool aInsert,const nsString & aStr,int32_t aStart,uint32_t aLen)586 ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleWrap* aAcc,
587                                         bool aInsert, const nsString& aStr,
588                                         int32_t aStart, uint32_t aLen)
589 {
590   if (!sLastTextChangeString)
591     sLastTextChangeString = new nsString();
592 
593   sLastTextChangeAcc = aAcc;
594   sLastTextChangeStart = aStart;
595   sLastTextChangeEnd = aStart + aLen;
596   sLastTextChangeWasInsert = aInsert;
597   *sLastTextChangeString = aStr;
598 }
599