1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsCaretAccessible.h"
39 :
40 : #include "nsAccessibilityService.h"
41 : #include "nsAccUtils.h"
42 : #include "nsCoreUtils.h"
43 : #include "nsIAccessibleEvent.h"
44 :
45 : #include "nsCaret.h"
46 : #include "nsIDOMDocument.h"
47 : #include "nsIDOMHTMLAnchorElement.h"
48 : #include "nsIDOMHTMLInputElement.h"
49 : #include "nsIDOMHTMLTextAreaElement.h"
50 : #include "nsIFrame.h"
51 : #include "nsIPresShell.h"
52 : #include "nsRootAccessible.h"
53 : #include "nsISelectionPrivate.h"
54 : #include "nsServiceManagerUtils.h"
55 :
56 : class nsIWidget;
57 :
58 0 : NS_IMPL_ISUPPORTS1(nsCaretAccessible, nsISelectionListener)
59 :
60 0 : nsCaretAccessible::nsCaretAccessible( nsRootAccessible *aRootAccessible):
61 0 : mLastCaretOffset(-1), mRootAccessible(aRootAccessible)
62 : {
63 0 : }
64 :
65 0 : nsCaretAccessible::~nsCaretAccessible()
66 : {
67 0 : }
68 :
69 0 : void nsCaretAccessible::Shutdown()
70 : {
71 : // The caret accessible isn't shut down until the nsRootAccessible owning it is shut down
72 : // Each nsDocAccessible, including the nsRootAccessible, is responsible for clearing the
73 : // doc selection listeners they registered in this nsCaretAccessible
74 :
75 0 : ClearControlSelectionListener(); // Clear the selection listener for the currently focused control
76 0 : mLastTextAccessible = nsnull;
77 0 : mLastUsedSelection = nsnull;
78 0 : mRootAccessible = nsnull;
79 0 : }
80 :
81 0 : nsresult nsCaretAccessible::ClearControlSelectionListener()
82 : {
83 : nsCOMPtr<nsISelectionController> controller =
84 0 : GetSelectionControllerForNode(mCurrentControl);
85 :
86 0 : mCurrentControl = nsnull;
87 :
88 0 : if (!controller)
89 0 : return NS_OK;
90 :
91 : // Remove 'this' registered as selection listener for the normal selection.
92 0 : nsCOMPtr<nsISelection> normalSel;
93 0 : controller->GetSelection(nsISelectionController::SELECTION_NORMAL,
94 0 : getter_AddRefs(normalSel));
95 0 : nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(normalSel));
96 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
97 :
98 0 : nsresult rv = selPrivate->RemoveSelectionListener(this);
99 0 : NS_ENSURE_SUCCESS(rv, rv);
100 :
101 : // Remove 'this' registered as selection listener for the spellcheck
102 : // selection.
103 0 : nsCOMPtr<nsISelection> spellcheckSel;
104 0 : controller->GetSelection(nsISelectionController::SELECTION_SPELLCHECK,
105 0 : getter_AddRefs(spellcheckSel));
106 0 : selPrivate = do_QueryInterface(spellcheckSel);
107 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
108 :
109 0 : return selPrivate->RemoveSelectionListener(this);
110 : }
111 :
112 : nsresult
113 0 : nsCaretAccessible::SetControlSelectionListener(nsIContent *aCurrentNode)
114 : {
115 0 : NS_ENSURE_TRUE(mRootAccessible, NS_ERROR_FAILURE);
116 :
117 0 : ClearControlSelectionListener();
118 :
119 0 : mCurrentControl = aCurrentNode;
120 0 : mLastTextAccessible = nsnull;
121 :
122 : // When focus moves such that the caret is part of a new frame selection
123 : // this removes the old selection listener and attaches a new one for
124 : // the current focus.
125 :
126 : nsCOMPtr<nsISelectionController> controller =
127 0 : GetSelectionControllerForNode(mCurrentControl);
128 : #ifdef DEBUG
129 0 : NS_ASSERTION(controller || aCurrentNode->IsNodeOfType(nsINode::eDOCUMENT),
130 : "No selection controller for non document node!");
131 : #endif
132 0 : if (!controller)
133 0 : return NS_OK;
134 :
135 : // Register 'this' as selection listener for the normal selection.
136 0 : nsCOMPtr<nsISelection> normalSel;
137 0 : controller->GetSelection(nsISelectionController::SELECTION_NORMAL,
138 0 : getter_AddRefs(normalSel));
139 0 : nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(normalSel));
140 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
141 :
142 0 : nsresult rv = selPrivate->AddSelectionListener(this);
143 0 : NS_ENSURE_SUCCESS(rv, rv);
144 :
145 : // Register 'this' as selection listener for the spellcheck selection.
146 0 : nsCOMPtr<nsISelection> spellcheckSel;
147 0 : controller->GetSelection(nsISelectionController::SELECTION_SPELLCHECK,
148 0 : getter_AddRefs(spellcheckSel));
149 0 : selPrivate = do_QueryInterface(spellcheckSel);
150 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
151 :
152 0 : return selPrivate->AddSelectionListener(this);
153 : }
154 :
155 : nsresult
156 0 : nsCaretAccessible::AddDocSelectionListener(nsIPresShell *aShell)
157 : {
158 0 : NS_ENSURE_TRUE(mRootAccessible, NS_ERROR_FAILURE);
159 :
160 0 : nsCOMPtr<nsISelectionController> selCon = do_QueryInterface(aShell);
161 0 : NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
162 :
163 0 : nsCOMPtr<nsISelection> domSel;
164 0 : selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(domSel));
165 0 : nsCOMPtr<nsISelectionPrivate> selPrivate = do_QueryInterface(domSel);
166 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
167 :
168 0 : nsresult rv = selPrivate->AddSelectionListener(this);
169 0 : NS_ENSURE_SUCCESS(rv, rv);
170 :
171 0 : nsCOMPtr<nsISelection> spellcheckSel;
172 0 : selCon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK,
173 0 : getter_AddRefs(spellcheckSel));
174 0 : selPrivate = do_QueryInterface(spellcheckSel);
175 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
176 :
177 0 : return selPrivate->AddSelectionListener(this);
178 : }
179 :
180 : nsresult
181 0 : nsCaretAccessible::RemoveDocSelectionListener(nsIPresShell *aShell)
182 : {
183 0 : nsCOMPtr<nsISelectionController> selCon = do_QueryInterface(aShell);
184 0 : NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
185 :
186 0 : nsCOMPtr<nsISelection> domSel;
187 0 : selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(domSel));
188 0 : nsCOMPtr<nsISelectionPrivate> selPrivate = do_QueryInterface(domSel);
189 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
190 :
191 0 : selPrivate->RemoveSelectionListener(this);
192 :
193 0 : nsCOMPtr<nsISelection> spellcheckSel;
194 0 : selCon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK,
195 0 : getter_AddRefs(spellcheckSel));
196 0 : selPrivate = do_QueryInterface(spellcheckSel);
197 0 : NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE);
198 :
199 0 : return selPrivate->RemoveSelectionListener(this);
200 : }
201 :
202 : NS_IMETHODIMP
203 0 : nsCaretAccessible::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
204 : nsISelection* aSelection,
205 : PRInt16 aReason)
206 : {
207 0 : NS_ENSURE_ARG(aDOMDocument);
208 0 : NS_ENSURE_STATE(mRootAccessible);
209 :
210 0 : nsCOMPtr<nsIDocument> documentNode(do_QueryInterface(aDOMDocument));
211 0 : nsDocAccessible* document = GetAccService()->GetDocAccessible(documentNode);
212 :
213 : #ifdef DEBUG_NOTIFICATIONS
214 : nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection));
215 :
216 : PRInt16 type = 0;
217 : privSel->GetType(&type);
218 :
219 : if (type == nsISelectionController::SELECTION_NORMAL ||
220 : type == nsISelectionController::SELECTION_SPELLCHECK) {
221 :
222 : bool isNormalSelection =
223 : (type == nsISelectionController::SELECTION_NORMAL);
224 :
225 : bool isIgnored = !document || !document->IsContentLoaded();
226 : printf("\nSelection changed, selection type: %s, notification %s\n",
227 : (isNormalSelection ? "normal" : "spellcheck"),
228 : (isIgnored ? "ignored" : "pending"));
229 : } else {
230 : bool isIgnored = !document || !document->IsContentLoaded();
231 : printf("\nSelection changed, selection type: unknown, notification %s\n",
232 : (isIgnored ? "ignored" : "pending"));
233 : }
234 : #endif
235 :
236 : // Don't fire events until document is loaded.
237 0 : if (document && document->IsContentLoaded()) {
238 : // The caret accessible has the same lifetime as the root accessible, and
239 : // this outlives all its descendant document accessibles, so that we are
240 : // guaranteed that the notification is processed before the caret accessible
241 : // is destroyed.
242 : document->HandleNotification<nsCaretAccessible, nsISelection>
243 0 : (this, &nsCaretAccessible::ProcessSelectionChanged, aSelection);
244 : }
245 :
246 0 : return NS_OK;
247 : }
248 :
249 : void
250 0 : nsCaretAccessible::ProcessSelectionChanged(nsISelection* aSelection)
251 : {
252 0 : nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection));
253 :
254 0 : PRInt16 type = 0;
255 0 : privSel->GetType(&type);
256 :
257 0 : if (type == nsISelectionController::SELECTION_NORMAL)
258 0 : NormalSelectionChanged(aSelection);
259 :
260 0 : else if (type == nsISelectionController::SELECTION_SPELLCHECK)
261 0 : SpellcheckSelectionChanged(aSelection);
262 0 : }
263 :
264 : void
265 0 : nsCaretAccessible::NormalSelectionChanged(nsISelection* aSelection)
266 : {
267 0 : mLastUsedSelection = do_GetWeakReference(aSelection);
268 :
269 0 : PRInt32 rangeCount = 0;
270 0 : aSelection->GetRangeCount(&rangeCount);
271 0 : if (rangeCount == 0) {
272 0 : mLastTextAccessible = nsnull;
273 0 : return; // No selection
274 : }
275 :
276 : nsHyperTextAccessible* textAcc =
277 0 : nsAccUtils::GetTextAccessibleFromSelection(aSelection);
278 0 : if (!textAcc)
279 0 : return;
280 :
281 0 : PRInt32 caretOffset = -1;
282 0 : nsresult rv = textAcc->GetCaretOffset(&caretOffset);
283 0 : if (NS_FAILED(rv))
284 0 : return;
285 :
286 0 : if (textAcc == mLastTextAccessible && caretOffset == mLastCaretOffset) {
287 0 : PRInt32 selectionCount = 0;
288 0 : textAcc->GetSelectionCount(&selectionCount); // Don't swallow similar events when selecting text
289 0 : if (!selectionCount)
290 0 : return; // Swallow duplicate caret event
291 : }
292 :
293 0 : mLastCaretOffset = caretOffset;
294 0 : mLastTextAccessible = textAcc;
295 :
296 : nsRefPtr<AccEvent> event =
297 0 : new AccCaretMoveEvent(mLastTextAccessible->GetNode());
298 0 : if (event)
299 0 : mLastTextAccessible->Document()->FireDelayedAccessibleEvent(event);
300 : }
301 :
302 : void
303 0 : nsCaretAccessible::SpellcheckSelectionChanged(nsISelection* aSelection)
304 : {
305 : // XXX: fire an event for accessible of focus node of the selection. If
306 : // spellchecking is enabled then we will fire the number of events for
307 : // the same accessible for newly appended range of the selection (for every
308 : // misspelled word). If spellchecking is disabled (for example,
309 : // @spellcheck="false" on html:body) then we won't fire any event.
310 :
311 : nsHyperTextAccessible* textAcc =
312 0 : nsAccUtils::GetTextAccessibleFromSelection(aSelection);
313 0 : if (!textAcc)
314 0 : return;
315 :
316 : nsRefPtr<AccEvent> event =
317 0 : new AccEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, textAcc);
318 0 : if (event)
319 0 : textAcc->Document()->FireDelayedAccessibleEvent(event);
320 : }
321 :
322 : nsIntRect
323 0 : nsCaretAccessible::GetCaretRect(nsIWidget **aOutWidget)
324 : {
325 0 : nsIntRect caretRect;
326 0 : NS_ENSURE_TRUE(aOutWidget, caretRect);
327 0 : *aOutWidget = nsnull;
328 0 : NS_ENSURE_TRUE(mRootAccessible, caretRect);
329 :
330 0 : if (!mLastTextAccessible) {
331 0 : return caretRect; // Return empty rect
332 : }
333 :
334 0 : nsINode *lastNodeWithCaret = mLastTextAccessible->GetNode();
335 0 : NS_ENSURE_TRUE(lastNodeWithCaret, caretRect);
336 :
337 0 : nsIPresShell *presShell = nsCoreUtils::GetPresShellFor(lastNodeWithCaret);
338 0 : NS_ENSURE_TRUE(presShell, caretRect);
339 :
340 0 : nsRefPtr<nsCaret> caret = presShell->GetCaret();
341 0 : NS_ENSURE_TRUE(caret, caretRect);
342 :
343 0 : nsCOMPtr<nsISelection> caretSelection(do_QueryReferent(mLastUsedSelection));
344 0 : NS_ENSURE_TRUE(caretSelection, caretRect);
345 :
346 : bool isVisible;
347 0 : caret->GetCaretVisible(&isVisible);
348 0 : if (!isVisible) {
349 0 : return nsIntRect(); // Return empty rect
350 : }
351 :
352 0 : nsRect rect;
353 0 : nsIFrame* frame = caret->GetGeometry(caretSelection, &rect);
354 0 : if (!frame || rect.IsEmpty()) {
355 0 : return nsIntRect(); // Return empty rect
356 : }
357 :
358 0 : nsPoint offset;
359 : // Offset from widget origin to the frame origin, which includes chrome
360 : // on the widget.
361 0 : *aOutWidget = frame->GetNearestWidget(offset);
362 0 : NS_ENSURE_TRUE(*aOutWidget, nsIntRect());
363 0 : rect.MoveBy(offset);
364 :
365 0 : caretRect = rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel());
366 : // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
367 0 : caretRect.MoveBy((*aOutWidget)->WidgetToScreenOffset() - (*aOutWidget)->GetClientOffset());
368 :
369 : // Correct for character size, so that caret always matches the size of the character
370 : // This is important for font size transitions, and is necessary because the Gecko caret uses the
371 : // previous character's size as the user moves forward in the text by character.
372 : PRInt32 charX, charY, charWidth, charHeight;
373 0 : if (NS_SUCCEEDED(mLastTextAccessible->GetCharacterExtents(mLastCaretOffset, &charX, &charY,
374 : &charWidth, &charHeight,
375 : nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE))) {
376 0 : caretRect.height -= charY - caretRect.y;
377 0 : caretRect.y = charY;
378 : }
379 :
380 0 : return caretRect;
381 : }
382 :
383 : already_AddRefed<nsISelectionController>
384 0 : nsCaretAccessible::GetSelectionControllerForNode(nsIContent *aContent)
385 : {
386 0 : if (!aContent)
387 0 : return nsnull;
388 :
389 0 : nsIPresShell *presShell = aContent->OwnerDoc()->GetShell();
390 0 : if (!presShell)
391 0 : return nsnull;
392 :
393 0 : nsIFrame *frame = aContent->GetPrimaryFrame();
394 0 : if (!frame)
395 0 : return nsnull;
396 :
397 0 : nsPresContext *presContext = presShell->GetPresContext();
398 0 : if (!presContext)
399 0 : return nsnull;
400 :
401 0 : nsISelectionController *controller = nsnull;
402 0 : frame->GetSelectionController(presContext, &controller);
403 0 : return controller;
404 : }
405 :
|