1 : /* -*- Mode: C++; tab-width: 2; 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 : * Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2010
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Alexander Surkov <surkov.alexander@gmail.com> (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "NotificationController.h"
40 :
41 : #include "nsAccessibilityService.h"
42 : #include "nsAccUtils.h"
43 : #include "nsCoreUtils.h"
44 : #include "nsDocAccessible.h"
45 : #include "nsEventShell.h"
46 : #include "nsTextAccessible.h"
47 : #include "FocusManager.h"
48 : #include "Role.h"
49 : #include "TextUpdater.h"
50 :
51 : #include "mozilla/dom/Element.h"
52 :
53 : using namespace mozilla::a11y;
54 :
55 : // Defines the number of selection add/remove events in the queue when they
56 : // aren't packed into single selection within event.
57 : const unsigned int kSelChangeCountToPack = 5;
58 :
59 : ////////////////////////////////////////////////////////////////////////////////
60 : // NotificationCollector
61 : ////////////////////////////////////////////////////////////////////////////////
62 :
63 0 : NotificationController::NotificationController(nsDocAccessible* aDocument,
64 : nsIPresShell* aPresShell) :
65 : mObservingState(eNotObservingRefresh), mDocument(aDocument),
66 0 : mPresShell(aPresShell)
67 : {
68 0 : mTextHash.Init();
69 :
70 : // Schedule initial accessible tree construction.
71 0 : ScheduleProcessing();
72 0 : }
73 :
74 0 : NotificationController::~NotificationController()
75 : {
76 0 : NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
77 0 : if (mDocument)
78 0 : Shutdown();
79 0 : }
80 :
81 : ////////////////////////////////////////////////////////////////////////////////
82 : // NotificationCollector: AddRef/Release and cycle collection
83 :
84 0 : NS_IMPL_ADDREF(NotificationController)
85 0 : NS_IMPL_RELEASE(NotificationController)
86 :
87 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
88 :
89 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController)
90 0 : if (tmp->mDocument)
91 0 : tmp->Shutdown();
92 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
93 :
94 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(NotificationController)
95 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocument");
96 0 : cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mDocument.get()));
97 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mHangingChildDocuments,
98 : nsDocAccessible)
99 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mContentInsertions,
100 : ContentInsertion)
101 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mEvents, AccEvent)
102 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
103 :
104 0 : NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
105 0 : NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
106 :
107 : ////////////////////////////////////////////////////////////////////////////////
108 : // NotificationCollector: public
109 :
110 : void
111 0 : NotificationController::Shutdown()
112 : {
113 0 : if (mObservingState != eNotObservingRefresh &&
114 0 : mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
115 0 : mObservingState = eNotObservingRefresh;
116 : }
117 :
118 : // Shutdown handling child documents.
119 0 : PRInt32 childDocCount = mHangingChildDocuments.Length();
120 0 : for (PRInt32 idx = childDocCount - 1; idx >= 0; idx--) {
121 0 : if (!mHangingChildDocuments[idx]->IsDefunct())
122 0 : mHangingChildDocuments[idx]->Shutdown();
123 : }
124 :
125 0 : mHangingChildDocuments.Clear();
126 :
127 0 : mDocument = nsnull;
128 0 : mPresShell = nsnull;
129 :
130 0 : mTextHash.Clear();
131 0 : mContentInsertions.Clear();
132 0 : mNotifications.Clear();
133 0 : mEvents.Clear();
134 0 : }
135 :
136 : void
137 0 : NotificationController::QueueEvent(AccEvent* aEvent)
138 : {
139 0 : if (!mEvents.AppendElement(aEvent))
140 0 : return;
141 :
142 : // Filter events.
143 0 : CoalesceEvents();
144 :
145 : // Associate text change with hide event if it wasn't stolen from hiding
146 : // siblings during coalescence.
147 0 : AccMutationEvent* showOrHideEvent = downcast_accEvent(aEvent);
148 0 : if (showOrHideEvent && !showOrHideEvent->mTextChangeEvent)
149 0 : CreateTextChangeEventFor(showOrHideEvent);
150 :
151 0 : ScheduleProcessing();
152 : }
153 :
154 : void
155 0 : NotificationController::ScheduleChildDocBinding(nsDocAccessible* aDocument)
156 : {
157 : // Schedule child document binding to the tree.
158 0 : mHangingChildDocuments.AppendElement(aDocument);
159 0 : ScheduleProcessing();
160 0 : }
161 :
162 : void
163 0 : NotificationController::ScheduleContentInsertion(nsAccessible* aContainer,
164 : nsIContent* aStartChildNode,
165 : nsIContent* aEndChildNode)
166 : {
167 : nsRefPtr<ContentInsertion> insertion = new ContentInsertion(mDocument,
168 0 : aContainer);
169 0 : if (insertion && insertion->InitChildList(aStartChildNode, aEndChildNode) &&
170 0 : mContentInsertions.AppendElement(insertion)) {
171 0 : ScheduleProcessing();
172 : }
173 0 : }
174 :
175 : ////////////////////////////////////////////////////////////////////////////////
176 : // NotificationCollector: protected
177 :
178 : void
179 0 : NotificationController::ScheduleProcessing()
180 : {
181 : // If notification flush isn't planed yet start notification flush
182 : // asynchronously (after style and layout).
183 0 : if (mObservingState == eNotObservingRefresh) {
184 0 : if (mPresShell->AddRefreshObserver(this, Flush_Display))
185 0 : mObservingState = eRefreshObserving;
186 : }
187 0 : }
188 :
189 : bool
190 0 : NotificationController::IsUpdatePending()
191 : {
192 0 : return mPresShell->IsLayoutFlushObserver() ||
193 : mObservingState == eRefreshProcessingForUpdate ||
194 0 : mContentInsertions.Length() != 0 || mNotifications.Length() != 0 ||
195 0 : mTextHash.Count() != 0 ||
196 0 : !mDocument->HasLoadState(nsDocAccessible::eTreeConstructed);
197 : }
198 :
199 : ////////////////////////////////////////////////////////////////////////////////
200 : // NotificationCollector: private
201 :
202 : void
203 0 : NotificationController::WillRefresh(mozilla::TimeStamp aTime)
204 : {
205 : // If the document accessible that notification collector was created for is
206 : // now shut down, don't process notifications anymore.
207 0 : NS_ASSERTION(mDocument,
208 : "The document was shut down while refresh observer is attached!");
209 0 : if (!mDocument)
210 0 : return;
211 :
212 : // Any generic notifications should be queued if we're processing content
213 : // insertions or generic notifications.
214 0 : mObservingState = eRefreshProcessingForUpdate;
215 :
216 : // Initial accessible tree construction.
217 0 : if (!mDocument->HasLoadState(nsDocAccessible::eTreeConstructed)) {
218 : // If document is not bound to parent at this point then the document is not
219 : // ready yet (process notifications later).
220 0 : if (!mDocument->IsBoundToParent()) {
221 0 : mObservingState = eRefreshObserving;
222 0 : return;
223 : }
224 :
225 : #ifdef DEBUG_NOTIFICATIONS
226 : printf("\ninitial tree created, document: %p, document node: %p\n",
227 : mDocument.get(), mDocument->GetDocumentNode());
228 : #endif
229 :
230 0 : mDocument->DoInitialUpdate();
231 :
232 0 : NS_ASSERTION(mContentInsertions.Length() == 0,
233 : "Pending content insertions while initial accessible tree isn't created!");
234 : }
235 :
236 : // Process content inserted notifications to update the tree. Process other
237 : // notifications like DOM events and then flush event queue. If any new
238 : // notifications are queued during this processing then they will be processed
239 : // on next refresh. If notification processing queues up new events then they
240 : // are processed in this refresh. If events processing queues up new events
241 : // then new events are processed on next refresh.
242 : // Note: notification processing or event handling may shut down the owning
243 : // document accessible.
244 :
245 : // Process only currently queued content inserted notifications.
246 0 : nsTArray<nsRefPtr<ContentInsertion> > contentInsertions;
247 0 : contentInsertions.SwapElements(mContentInsertions);
248 :
249 0 : PRUint32 insertionCount = contentInsertions.Length();
250 0 : for (PRUint32 idx = 0; idx < insertionCount; idx++) {
251 0 : contentInsertions[idx]->Process();
252 0 : if (!mDocument)
253 : return;
254 : }
255 :
256 : // Process rendered text change notifications.
257 0 : mTextHash.EnumerateEntries(TextEnumerator, mDocument);
258 0 : mTextHash.Clear();
259 :
260 : // Bind hanging child documents.
261 0 : PRUint32 hangingDocCnt = mHangingChildDocuments.Length();
262 0 : for (PRUint32 idx = 0; idx < hangingDocCnt; idx++) {
263 0 : nsDocAccessible* childDoc = mHangingChildDocuments[idx];
264 0 : if (childDoc->IsDefunct())
265 0 : continue;
266 :
267 0 : nsIContent* ownerContent = mDocument->GetDocumentNode()->
268 0 : FindContentForSubDocument(childDoc->GetDocumentNode());
269 0 : if (ownerContent) {
270 0 : nsAccessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
271 0 : if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
272 0 : if (mDocument->AppendChildDocument(childDoc))
273 0 : continue;
274 :
275 0 : outerDocAcc->RemoveChild(childDoc);
276 : }
277 :
278 : // Failed to bind the child document, destroy it.
279 0 : childDoc->Shutdown();
280 : }
281 : }
282 0 : mHangingChildDocuments.Clear();
283 :
284 : // If the document is ready and all its subdocuments are completely loaded
285 : // then process the document load.
286 0 : if (mDocument->HasLoadState(nsDocAccessible::eReady) &&
287 0 : !mDocument->HasLoadState(nsDocAccessible::eCompletelyLoaded) &&
288 : hangingDocCnt == 0) {
289 0 : PRUint32 childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
290 0 : for (; childDocIdx < childDocCnt; childDocIdx++) {
291 0 : nsDocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
292 0 : if (!childDoc->HasLoadState(nsDocAccessible::eCompletelyLoaded))
293 0 : break;
294 : }
295 :
296 0 : if (childDocIdx == childDocCnt) {
297 0 : mDocument->ProcessLoad();
298 0 : if (!mDocument)
299 : return;
300 : }
301 : }
302 :
303 : // Process only currently queued generic notifications.
304 0 : nsTArray < nsRefPtr<Notification> > notifications;
305 0 : notifications.SwapElements(mNotifications);
306 :
307 0 : PRUint32 notificationCount = notifications.Length();
308 0 : for (PRUint32 idx = 0; idx < notificationCount; idx++) {
309 0 : notifications[idx]->Process();
310 0 : if (!mDocument)
311 : return;
312 : }
313 :
314 : // Process invalidation list of the document after all accessible tree
315 : // modification are done.
316 0 : mDocument->ProcessInvalidationList();
317 :
318 : // If a generic notification occurs after this point then we may be allowed to
319 : // process it synchronously.
320 0 : mObservingState = eRefreshObserving;
321 :
322 : // Process only currently queued events.
323 0 : nsTArray<nsRefPtr<AccEvent> > events;
324 0 : events.SwapElements(mEvents);
325 :
326 0 : PRUint32 eventCount = events.Length();
327 0 : for (PRUint32 idx = 0; idx < eventCount; idx++) {
328 0 : AccEvent* accEvent = events[idx];
329 0 : if (accEvent->mEventRule != AccEvent::eDoNotEmit) {
330 0 : nsAccessible* target = accEvent->GetAccessible();
331 0 : if (!target || target->IsDefunct())
332 0 : continue;
333 :
334 : // Dispatch the focus event if target is still focused.
335 0 : if (accEvent->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
336 0 : FocusMgr()->ProcessFocusEvent(accEvent);
337 0 : continue;
338 : }
339 :
340 0 : mDocument->ProcessPendingEvent(accEvent);
341 :
342 : // Fire text change event caused by tree mutation.
343 0 : AccMutationEvent* showOrHideEvent = downcast_accEvent(accEvent);
344 0 : if (showOrHideEvent) {
345 0 : if (showOrHideEvent->mTextChangeEvent)
346 0 : mDocument->ProcessPendingEvent(showOrHideEvent->mTextChangeEvent);
347 : }
348 : }
349 0 : if (!mDocument)
350 : return;
351 : }
352 :
353 : // Stop further processing if there are no new notifications of any kind or
354 : // events and document load is processed.
355 0 : if (mContentInsertions.Length() == 0 && mNotifications.Length() == 0 &&
356 0 : mEvents.Length() == 0 && mTextHash.Count() == 0 &&
357 0 : mHangingChildDocuments.Length() == 0 &&
358 0 : mDocument->HasLoadState(nsDocAccessible::eCompletelyLoaded) &&
359 0 : mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
360 0 : mObservingState = eNotObservingRefresh;
361 : }
362 : }
363 :
364 : ////////////////////////////////////////////////////////////////////////////////
365 : // NotificationController: event queue
366 :
367 : void
368 0 : NotificationController::CoalesceEvents()
369 : {
370 0 : PRUint32 numQueuedEvents = mEvents.Length();
371 0 : PRInt32 tail = numQueuedEvents - 1;
372 0 : AccEvent* tailEvent = mEvents[tail];
373 :
374 0 : switch(tailEvent->mEventRule) {
375 : case AccEvent::eCoalesceFromSameSubtree:
376 : {
377 : // No node means this is application accessible (which is a subject of
378 : // reorder events), we do not coalesce events for it currently.
379 0 : if (!tailEvent->mNode)
380 0 : return;
381 :
382 0 : for (PRInt32 index = tail - 1; index >= 0; index--) {
383 0 : AccEvent* thisEvent = mEvents[index];
384 :
385 0 : if (thisEvent->mEventType != tailEvent->mEventType)
386 0 : continue; // Different type
387 :
388 : // Skip event for application accessible since no coalescence for it
389 : // is supported. Ignore events from different documents since we don't
390 : // coalesce them.
391 0 : if (!thisEvent->mNode ||
392 0 : thisEvent->mNode->OwnerDoc() != tailEvent->mNode->OwnerDoc())
393 0 : continue;
394 :
395 : // Coalesce earlier event for the same target.
396 0 : if (thisEvent->mNode == tailEvent->mNode) {
397 0 : thisEvent->mEventRule = AccEvent::eDoNotEmit;
398 0 : return;
399 : }
400 :
401 : // If event queue contains an event of the same type and having target
402 : // that is sibling of target of newly appended event then apply its
403 : // event rule to the newly appended event.
404 :
405 : // Coalesce hide and show events for sibling targets.
406 0 : if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE) {
407 0 : AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent);
408 0 : AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent);
409 0 : if (thisHideEvent->mParent == tailHideEvent->mParent) {
410 0 : tailEvent->mEventRule = thisEvent->mEventRule;
411 :
412 : // Coalesce text change events for hide events.
413 0 : if (tailEvent->mEventRule != AccEvent::eDoNotEmit)
414 0 : CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
415 :
416 0 : return;
417 : }
418 0 : } else if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_SHOW) {
419 0 : if (thisEvent->mAccessible->Parent() ==
420 0 : tailEvent->mAccessible->Parent()) {
421 0 : tailEvent->mEventRule = thisEvent->mEventRule;
422 :
423 : // Coalesce text change events for show events.
424 0 : if (tailEvent->mEventRule != AccEvent::eDoNotEmit) {
425 0 : AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent);
426 0 : AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent);
427 0 : CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent);
428 : }
429 :
430 0 : return;
431 : }
432 : }
433 :
434 : // Ignore events unattached from DOM since we don't coalesce them.
435 0 : if (!thisEvent->mNode->IsInDoc())
436 0 : continue;
437 :
438 : // Coalesce events by sibling targets (this is a case for reorder
439 : // events).
440 0 : if (thisEvent->mNode->GetNodeParent() ==
441 0 : tailEvent->mNode->GetNodeParent()) {
442 0 : tailEvent->mEventRule = thisEvent->mEventRule;
443 0 : return;
444 : }
445 :
446 : // This and tail events can be anywhere in the tree, make assumptions
447 : // for mutation events.
448 :
449 : // Coalesce tail event if tail node is descendant of this node. Stop
450 : // processing if tail event is coalesced since all possible descendants
451 : // of this node was coalesced before.
452 : // Note: more older hide event target (thisNode) can't contain recent
453 : // hide event target (tailNode), i.e. be ancestor of tailNode. Skip
454 : // this check for hide events.
455 0 : if (tailEvent->mEventType != nsIAccessibleEvent::EVENT_HIDE &&
456 0 : nsCoreUtils::IsAncestorOf(thisEvent->mNode, tailEvent->mNode)) {
457 0 : tailEvent->mEventRule = AccEvent::eDoNotEmit;
458 0 : return;
459 : }
460 :
461 : // If this node is a descendant of tail node then coalesce this event,
462 : // check other events in the queue. Do not emit thisEvent, also apply
463 : // this result to sibling nodes of thisNode.
464 0 : if (nsCoreUtils::IsAncestorOf(tailEvent->mNode, thisEvent->mNode)) {
465 0 : thisEvent->mEventRule = AccEvent::eDoNotEmit;
466 : ApplyToSiblings(0, index, thisEvent->mEventType,
467 0 : thisEvent->mNode, AccEvent::eDoNotEmit);
468 0 : continue;
469 : }
470 :
471 : } // for (index)
472 :
473 0 : } break; // case eCoalesceFromSameSubtree
474 :
475 : case AccEvent::eCoalesceOfSameType:
476 : {
477 : // Coalesce old events by newer event.
478 0 : for (PRInt32 index = tail - 1; index >= 0; index--) {
479 0 : AccEvent* accEvent = mEvents[index];
480 0 : if (accEvent->mEventType == tailEvent->mEventType &&
481 : accEvent->mEventRule == tailEvent->mEventRule) {
482 0 : accEvent->mEventRule = AccEvent::eDoNotEmit;
483 0 : return;
484 : }
485 : }
486 0 : } break; // case eCoalesceOfSameType
487 :
488 : case AccEvent::eRemoveDupes:
489 : {
490 : // Check for repeat events, coalesce newly appended event by more older
491 : // event.
492 0 : for (PRInt32 index = tail - 1; index >= 0; index--) {
493 0 : AccEvent* accEvent = mEvents[index];
494 0 : if (accEvent->mEventType == tailEvent->mEventType &&
495 : accEvent->mEventRule == tailEvent->mEventRule &&
496 0 : accEvent->mNode == tailEvent->mNode) {
497 0 : tailEvent->mEventRule = AccEvent::eDoNotEmit;
498 0 : return;
499 : }
500 : }
501 0 : } break; // case eRemoveDupes
502 :
503 : case AccEvent::eCoalesceSelectionChange:
504 : {
505 0 : AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
506 0 : PRInt32 index = tail - 1;
507 0 : for (; index >= 0; index--) {
508 0 : AccEvent* thisEvent = mEvents[index];
509 0 : if (thisEvent->mEventRule == tailEvent->mEventRule) {
510 : AccSelChangeEvent* thisSelChangeEvent =
511 0 : downcast_accEvent(thisEvent);
512 :
513 : // Coalesce selection change events within same control.
514 0 : if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
515 0 : CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
516 0 : return;
517 : }
518 : }
519 : }
520 :
521 0 : } break; // eCoalesceSelectionChange
522 :
523 : default:
524 0 : break; // case eAllowDupes, eDoNotEmit
525 : } // switch
526 : }
527 :
528 : void
529 0 : NotificationController::ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd,
530 : PRUint32 aEventType, nsINode* aNode,
531 : AccEvent::EEventRule aEventRule)
532 : {
533 0 : for (PRUint32 index = aStart; index < aEnd; index ++) {
534 0 : AccEvent* accEvent = mEvents[index];
535 0 : if (accEvent->mEventType == aEventType &&
536 0 : accEvent->mEventRule != AccEvent::eDoNotEmit && accEvent->mNode &&
537 0 : accEvent->mNode->GetNodeParent() == aNode->GetNodeParent()) {
538 0 : accEvent->mEventRule = aEventRule;
539 : }
540 : }
541 0 : }
542 :
543 : void
544 0 : NotificationController::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
545 : AccSelChangeEvent* aThisEvent,
546 : PRInt32 aThisIndex)
547 : {
548 0 : aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
549 :
550 : // Pack all preceding events into single selection within event
551 : // when we receive too much selection add/remove events.
552 0 : if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
553 0 : aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
554 0 : aTailEvent->mAccessible = aTailEvent->mWidget;
555 0 : aThisEvent->mEventRule = AccEvent::eDoNotEmit;
556 :
557 : // Do not emit any preceding selection events for same widget if they
558 : // weren't coalesced yet.
559 0 : if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
560 0 : for (PRInt32 jdx = aThisIndex - 1; jdx >= 0; jdx--) {
561 0 : AccEvent* prevEvent = mEvents[jdx];
562 0 : if (prevEvent->mEventRule == aTailEvent->mEventRule) {
563 : AccSelChangeEvent* prevSelChangeEvent =
564 0 : downcast_accEvent(prevEvent);
565 0 : if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
566 0 : prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
567 : }
568 : }
569 : }
570 0 : return;
571 : }
572 :
573 : // Pack sequential selection remove and selection add events into
574 : // single selection change event.
575 0 : if (aTailEvent->mPreceedingCount == 1 &&
576 0 : aTailEvent->mItem != aThisEvent->mItem) {
577 0 : if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
578 : aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
579 0 : aThisEvent->mEventRule = AccEvent::eDoNotEmit;
580 0 : aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
581 0 : aTailEvent->mPackedEvent = aThisEvent;
582 0 : return;
583 : }
584 :
585 0 : if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
586 : aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
587 0 : aTailEvent->mEventRule = AccEvent::eDoNotEmit;
588 0 : aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
589 0 : aThisEvent->mPackedEvent = aThisEvent;
590 0 : return;
591 : }
592 : }
593 :
594 : // Unpack the packed selection change event because we've got one
595 : // more selection add/remove.
596 0 : if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
597 0 : if (aThisEvent->mPackedEvent) {
598 : aThisEvent->mPackedEvent->mEventType =
599 : aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
600 : nsIAccessibleEvent::EVENT_SELECTION_ADD :
601 0 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
602 :
603 : aThisEvent->mPackedEvent->mEventRule =
604 0 : AccEvent::eCoalesceSelectionChange;
605 :
606 0 : aThisEvent->mPackedEvent = nsnull;
607 : }
608 :
609 : aThisEvent->mEventType =
610 : aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
611 : nsIAccessibleEvent::EVENT_SELECTION_ADD :
612 0 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
613 :
614 0 : return;
615 : }
616 :
617 : // Convert into selection add since control has single selection but other
618 : // selection events for this control are queued.
619 0 : if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
620 0 : aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
621 : }
622 :
623 : void
624 0 : NotificationController::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
625 : AccHideEvent* aThisEvent)
626 : {
627 : // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
628 : // affect the text within the hypertext.
629 :
630 0 : AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
631 0 : if (!textEvent)
632 0 : return;
633 :
634 0 : if (aThisEvent->mNextSibling == aTailEvent->mAccessible) {
635 0 : aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
636 :
637 0 : } else if (aThisEvent->mPrevSibling == aTailEvent->mAccessible) {
638 0 : PRUint32 oldLen = textEvent->GetLength();
639 0 : aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
640 0 : textEvent->mStart -= textEvent->GetLength() - oldLen;
641 : }
642 :
643 0 : aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent);
644 : }
645 :
646 : void
647 0 : NotificationController::CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent,
648 : AccShowEvent* aThisEvent)
649 : {
650 0 : AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
651 0 : if (!textEvent)
652 0 : return;
653 :
654 0 : if (aTailEvent->mAccessible->IndexInParent() ==
655 0 : aThisEvent->mAccessible->IndexInParent() + 1) {
656 : // If tail target was inserted after this target, i.e. tail target is next
657 : // sibling of this target.
658 0 : aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
659 :
660 0 : } else if (aTailEvent->mAccessible->IndexInParent() ==
661 0 : aThisEvent->mAccessible->IndexInParent() -1) {
662 : // If tail target was inserted before this target, i.e. tail target is
663 : // previous sibling of this target.
664 0 : nsAutoString startText;
665 0 : aTailEvent->mAccessible->AppendTextTo(startText);
666 0 : textEvent->mModifiedText = startText + textEvent->mModifiedText;
667 0 : textEvent->mStart -= startText.Length();
668 : }
669 :
670 0 : aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent);
671 : }
672 :
673 : void
674 0 : NotificationController::CreateTextChangeEventFor(AccMutationEvent* aEvent)
675 : {
676 0 : nsDocAccessible* document = aEvent->GetDocAccessible();
677 0 : nsAccessible* container = document->GetContainerAccessible(aEvent->mNode);
678 0 : if (!container)
679 0 : return;
680 :
681 0 : nsHyperTextAccessible* textAccessible = container->AsHyperText();
682 0 : if (!textAccessible)
683 0 : return;
684 :
685 : // Don't fire event for the first html:br in an editor.
686 0 : if (aEvent->mAccessible->Role() == roles::WHITESPACE) {
687 0 : nsCOMPtr<nsIEditor> editor = textAccessible->GetEditor();
688 0 : if (editor) {
689 0 : bool isEmpty = false;
690 0 : editor->GetDocumentIsEmpty(&isEmpty);
691 0 : if (isEmpty)
692 : return;
693 : }
694 : }
695 :
696 0 : PRInt32 offset = textAccessible->GetChildOffset(aEvent->mAccessible);
697 :
698 0 : nsAutoString text;
699 0 : aEvent->mAccessible->AppendTextTo(text);
700 0 : if (text.IsEmpty())
701 : return;
702 :
703 : aEvent->mTextChangeEvent =
704 0 : new AccTextChangeEvent(textAccessible, offset, text, aEvent->IsShow(),
705 0 : aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
706 : }
707 :
708 : ////////////////////////////////////////////////////////////////////////////////
709 : // Notification controller: text leaf accessible text update
710 :
711 : PLDHashOperator
712 0 : NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
713 : void* aUserArg)
714 : {
715 0 : nsDocAccessible* document = static_cast<nsDocAccessible*>(aUserArg);
716 0 : nsIContent* textNode = aEntry->GetKey();
717 0 : nsAccessible* textAcc = document->GetAccessible(textNode);
718 :
719 : // If the text node is not in tree or doesn't have frame then this case should
720 : // have been handled already by content removal notifications.
721 0 : nsINode* containerNode = textNode->GetNodeParent();
722 0 : if (!containerNode) {
723 0 : NS_ASSERTION(!textAcc,
724 : "Text node was removed but accessible is kept alive!");
725 0 : return PL_DHASH_NEXT;
726 : }
727 :
728 0 : nsIFrame* textFrame = textNode->GetPrimaryFrame();
729 0 : if (!textFrame) {
730 0 : NS_ASSERTION(!textAcc,
731 : "Text node isn't rendered but accessible is kept alive!");
732 0 : return PL_DHASH_NEXT;
733 : }
734 :
735 0 : nsIContent* containerElm = containerNode->IsElement() ?
736 0 : containerNode->AsElement() : nsnull;
737 :
738 0 : nsAutoString text;
739 0 : textFrame->GetRenderedText(&text);
740 :
741 : // Remove text accessible if rendered text is empty.
742 0 : if (textAcc) {
743 0 : if (text.IsEmpty()) {
744 : #ifdef DEBUG_NOTIFICATIONS
745 : PRUint32 index = containerNode->IndexOf(textNode);
746 :
747 : nsCAutoString tag;
748 : nsCAutoString id;
749 : if (containerElm) {
750 : containerElm->Tag()->ToUTF8String(tag);
751 : nsIAtom* atomid = containerElm->GetID();
752 : if (atomid)
753 : atomid->ToUTF8String(id);
754 : }
755 :
756 : printf("\npending text node removal: container: %s@id='%s', index in container: %d\n\n",
757 : tag.get(), id.get(), index);
758 : #endif
759 :
760 0 : document->ContentRemoved(containerElm, textNode);
761 0 : return PL_DHASH_NEXT;
762 : }
763 :
764 : // Update text of the accessible and fire text change events.
765 : #ifdef DEBUG_TEXTCHANGE
766 : PRUint32 index = containerNode->IndexOf(textNode);
767 :
768 : nsCAutoString tag;
769 : nsCAutoString id;
770 : if (containerElm) {
771 : containerElm->Tag()->ToUTF8String(tag);
772 : nsIAtom* atomid = containerElm->GetID();
773 : if (atomid)
774 : atomid->ToUTF8String(id);
775 : }
776 :
777 : printf("\ntext may be changed: container: %s@id='%s', index in container: %d, old text '%s', new text: '%s'\n\n",
778 : tag.get(), id.get(), index,
779 : NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get(),
780 : NS_ConvertUTF16toUTF8(text).get());
781 : #endif
782 :
783 0 : TextUpdater::Run(document, textAcc->AsTextLeaf(), text);
784 0 : return PL_DHASH_NEXT;
785 : }
786 :
787 : // Append an accessible if rendered text is not empty.
788 0 : if (!text.IsEmpty()) {
789 : #ifdef DEBUG_NOTIFICATIONS
790 : PRUint32 index = containerNode->IndexOf(textNode);
791 :
792 : nsCAutoString tag;
793 : nsCAutoString id;
794 : if (containerElm) {
795 : containerElm->Tag()->ToUTF8String(tag);
796 : nsIAtom* atomid = containerElm->GetID();
797 : if (atomid)
798 : atomid->ToUTF8String(id);
799 : }
800 :
801 : printf("\npending text node insertion: container: %s@id='%s', index in container: %d\n\n",
802 : tag.get(), id.get(), index);
803 : #endif
804 :
805 : // Make sure the text node is in accessible document still.
806 0 : nsAccessible* container = document->GetAccessibleOrContainer(containerNode);
807 0 : NS_ASSERTION(container,
808 : "Text node having rendered text hasn't accessible document!");
809 0 : if (container) {
810 0 : nsTArray<nsCOMPtr<nsIContent> > insertedContents;
811 0 : insertedContents.AppendElement(textNode);
812 0 : document->ProcessContentInserted(container, &insertedContents);
813 : }
814 : }
815 :
816 0 : return PL_DHASH_NEXT;
817 : }
818 :
819 :
820 : ////////////////////////////////////////////////////////////////////////////////
821 : // NotificationController: content inserted notification
822 :
823 0 : NotificationController::ContentInsertion::
824 : ContentInsertion(nsDocAccessible* aDocument, nsAccessible* aContainer) :
825 0 : mDocument(aDocument), mContainer(aContainer)
826 : {
827 0 : }
828 :
829 : bool
830 0 : NotificationController::ContentInsertion::
831 : InitChildList(nsIContent* aStartChildNode, nsIContent* aEndChildNode)
832 : {
833 0 : bool haveToUpdate = false;
834 :
835 0 : nsIContent* node = aStartChildNode;
836 0 : while (node != aEndChildNode) {
837 : // Notification triggers for content insertion even if no content was
838 : // actually inserted, check if the given content has a frame to discard
839 : // this case early.
840 0 : if (node->GetPrimaryFrame()) {
841 0 : if (mInsertedContent.AppendElement(node))
842 0 : haveToUpdate = true;
843 : }
844 :
845 0 : node = node->GetNextSibling();
846 : }
847 :
848 0 : return haveToUpdate;
849 : }
850 :
851 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController::ContentInsertion)
852 :
853 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController::ContentInsertion)
854 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContainer)
855 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
856 :
857 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(NotificationController::ContentInsertion)
858 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContainer");
859 0 : cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mContainer.get()));
860 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
861 :
862 0 : NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController::ContentInsertion,
863 : AddRef)
864 0 : NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController::ContentInsertion,
865 : Release)
866 :
867 : void
868 0 : NotificationController::ContentInsertion::Process()
869 : {
870 : #ifdef DEBUG_NOTIFICATIONS
871 : nsIContent* firstChildNode = mInsertedContent[0];
872 :
873 : nsCAutoString tag;
874 : firstChildNode->Tag()->ToUTF8String(tag);
875 :
876 : nsIAtom* atomid = firstChildNode->GetID();
877 : nsCAutoString id;
878 : if (atomid)
879 : atomid->ToUTF8String(id);
880 :
881 : nsCAutoString ctag;
882 : nsCAutoString cid;
883 : nsIAtom* catomid = nsnull;
884 : if (mContainer->IsContent()) {
885 : mContainer->GetContent()->Tag()->ToUTF8String(ctag);
886 : catomid = mContainer->GetContent()->GetID();
887 : if (catomid)
888 : catomid->ToUTF8String(cid);
889 : }
890 :
891 : printf("\npending content insertion: %s@id='%s', container: %s@id='%s', inserted content amount: %d\n\n",
892 : tag.get(), id.get(), ctag.get(), cid.get(), mInsertedContent.Length());
893 : #endif
894 :
895 0 : mDocument->ProcessContentInserted(mContainer, &mInsertedContent);
896 :
897 0 : mDocument = nsnull;
898 0 : mContainer = nsnull;
899 0 : mInsertedContent.Clear();
900 4392 : }
901 :
|