Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/base/NotificationController.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "NotificationController.h"
7
8
#include "DocAccessible-inl.h"
9
#include "DocAccessibleChild.h"
10
#include "nsEventShell.h"
11
#include "TextLeafAccessible.h"
12
#include "TextUpdater.h"
13
14
#include "mozilla/dom/TabChild.h"
15
#include "mozilla/dom/Element.h"
16
#include "mozilla/Telemetry.h"
17
18
using namespace mozilla;
19
using namespace mozilla::a11y;
20
using namespace mozilla::dom;
21
22
////////////////////////////////////////////////////////////////////////////////
23
// NotificationCollector
24
////////////////////////////////////////////////////////////////////////////////
25
26
NotificationController::NotificationController(DocAccessible* aDocument,
27
                                               nsIPresShell* aPresShell) :
28
  EventQueue(aDocument), mObservingState(eNotObservingRefresh),
29
  mPresShell(aPresShell), mEventGeneration(0)
30
0
{
31
#ifdef DEBUG
32
  mMoveGuardOnStack = false;
33
#endif
34
35
0
  // Schedule initial accessible tree construction.
36
0
  ScheduleProcessing();
37
0
}
38
39
NotificationController::~NotificationController()
40
0
{
41
0
  NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
42
0
  if (mDocument)
43
0
    Shutdown();
44
0
}
45
46
////////////////////////////////////////////////////////////////////////////////
47
// NotificationCollector: AddRef/Release and cycle collection
48
49
NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
50
NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
51
52
NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
53
54
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
55
0
  if (tmp->mDocument)
56
0
    tmp->Shutdown();
57
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
58
59
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
60
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
61
0
  for (auto it = tmp->mContentInsertions.ConstIter(); !it.Done(); it.Next()) {
62
0
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key");
63
0
    cb.NoteXPCOMChild(it.Key());
64
0
    nsTArray<nsCOMPtr<nsIContent>>* list = it.UserData();
65
0
    for (uint32_t i = 0; i < list->Length(); i++) {
66
0
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
67
0
                                         "mContentInsertions value item");
68
0
      cb.NoteXPCOMChild(list->ElementAt(i));
69
0
    }
70
0
  }
71
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
72
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
73
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
74
75
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
76
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
77
78
////////////////////////////////////////////////////////////////////////////////
79
// NotificationCollector: public
80
81
void
82
NotificationController::Shutdown()
83
0
{
84
0
  if (mObservingState != eNotObservingRefresh &&
85
0
      mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
86
0
    mObservingState = eNotObservingRefresh;
87
0
  }
88
0
89
0
  // Shutdown handling child documents.
90
0
  int32_t childDocCount = mHangingChildDocuments.Length();
91
0
  for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
92
0
    if (!mHangingChildDocuments[idx]->IsDefunct())
93
0
      mHangingChildDocuments[idx]->Shutdown();
94
0
  }
95
0
96
0
  mHangingChildDocuments.Clear();
97
0
98
0
  mDocument = nullptr;
99
0
  mPresShell = nullptr;
100
0
101
0
  mTextHash.Clear();
102
0
  mContentInsertions.Clear();
103
0
  mNotifications.Clear();
104
0
  mEvents.Clear();
105
0
  mRelocations.Clear();
106
0
  mEventTree.Clear();
107
0
}
108
109
EventTree*
110
NotificationController::QueueMutation(Accessible* aContainer)
111
0
{
112
0
  EventTree* tree = mEventTree.FindOrInsert(aContainer);
113
0
  if (tree) {
114
0
    ScheduleProcessing();
115
0
  }
116
0
  return tree;
117
0
}
118
119
bool
120
NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent)
121
0
{
122
0
  // We have to allow there to be a hide and then a show event for a target
123
0
  // because of targets getting moved.  However we need to coalesce a show and
124
0
  // then a hide for a target which means we need to check for that here.
125
0
  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE  &&
126
0
      aEvent->GetAccessible()->ShowEventTarget()) {
127
0
    AccTreeMutationEvent* showEvent = mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
128
0
    DropMutationEvent(showEvent);
129
0
    return false;
130
0
  }
131
0
132
0
  AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
133
0
  mEventGeneration++;
134
0
  mutEvent->SetEventGeneration(mEventGeneration);
135
0
136
0
  if (!mFirstMutationEvent) {
137
0
    mFirstMutationEvent = aEvent;
138
0
    ScheduleProcessing();
139
0
  }
140
0
141
0
  if (mLastMutationEvent) {
142
0
    NS_ASSERTION(!mLastMutationEvent->NextEvent(), "why isn't the last event the end?");
143
0
    mLastMutationEvent->SetNextEvent(aEvent);
144
0
  }
145
0
146
0
  aEvent->SetPrevEvent(mLastMutationEvent);
147
0
  mLastMutationEvent = aEvent;
148
0
  mMutationMap.PutEvent(aEvent);
149
0
150
0
  // Because we could be hiding the target of a show event we need to get rid
151
0
  // of any such events.  It may be possible to do less than coallesce all
152
0
  // events, however that is easiest.
153
0
  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
154
0
    CoalesceMutationEvents();
155
0
156
0
    // mLastMutationEvent will point to something other than aEvent if and only
157
0
    // if aEvent was just coalesced away.  In that case a parent accessible
158
0
    // must already have the required reorder and text change events so we are
159
0
    // done here.
160
0
    if (mLastMutationEvent != aEvent) {
161
0
      return false;
162
0
    }
163
0
  }
164
0
165
0
  // We need to fire a reorder event after all of the events targeted at shown or
166
0
  // hidden children of a container.  So either queue a new one, or move an
167
0
  // existing one to the end of the queue if the container already has a
168
0
  // reorder event.
169
0
  Accessible* target = aEvent->GetAccessible();
170
0
  Accessible* container = aEvent->GetAccessible()->Parent();
171
0
  RefPtr<AccReorderEvent> reorder;
172
0
  if (!container->ReorderEventTarget()) {
173
0
    reorder = new AccReorderEvent(container);
174
0
    container->SetReorderEventTarget(true);
175
0
    mMutationMap.PutEvent(reorder);
176
0
177
0
    // Since this is the first child of container that is changing, the name of
178
0
    // container may be changing.
179
0
    QueueNameChange(target);
180
0
  } else {
181
0
    AccReorderEvent* event = downcast_accEvent(mMutationMap.GetEvent(container, EventMap::ReorderEvent));
182
0
    reorder = event;
183
0
    if (mFirstMutationEvent == event) {
184
0
      mFirstMutationEvent = event->NextEvent();
185
0
    } else {
186
0
      event->PrevEvent()->SetNextEvent(event->NextEvent());
187
0
    }
188
0
189
0
      event->NextEvent()->SetPrevEvent(event->PrevEvent());
190
0
      event->SetNextEvent(nullptr);
191
0
  }
192
0
193
0
  reorder->SetEventGeneration(mEventGeneration);
194
0
  reorder->SetPrevEvent(mLastMutationEvent);
195
0
  mLastMutationEvent->SetNextEvent(reorder);
196
0
  mLastMutationEvent = reorder;
197
0
198
0
  // It is not possible to have a text change event for something other than a
199
0
  // hyper text accessible.
200
0
  if (!container->IsHyperText()) {
201
0
    return true;
202
0
  }
203
0
204
0
  MOZ_ASSERT(mutEvent);
205
0
206
0
  nsString text;
207
0
  aEvent->GetAccessible()->AppendTextTo(text);
208
0
  if (text.IsEmpty()) {
209
0
    return true;
210
0
  }
211
0
212
0
  int32_t offset = container->AsHyperText()->GetChildOffset(target);
213
0
  AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
214
0
  while (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
215
0
    prevEvent = prevEvent->PrevEvent();
216
0
  }
217
0
218
0
  if (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
219
0
      mutEvent->IsHide()) {
220
0
    AccHideEvent* prevHide = downcast_accEvent(prevEvent);
221
0
    AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent;
222
0
    if (prevTextChange && prevHide->Parent() == mutEvent->Parent()) {
223
0
      if (prevHide->mNextSibling == target) {
224
0
        target->AppendTextTo(prevTextChange->mModifiedText);
225
0
        prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
226
0
      } else if (prevHide->mPrevSibling == target) {
227
0
        nsString temp;
228
0
        target->AppendTextTo(temp);
229
0
230
0
        uint32_t extraLen = temp.Length();
231
0
        temp += prevTextChange->mModifiedText;;
232
0
        prevTextChange->mModifiedText = temp;
233
0
        prevTextChange->mStart -= extraLen;
234
0
        prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
235
0
      }
236
0
    }
237
0
  } else if (prevEvent && mutEvent->IsShow() &&
238
0
             prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
239
0
    AccShowEvent* prevShow = downcast_accEvent(prevEvent);
240
0
    AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent;
241
0
    if (prevTextChange && prevShow->Parent() == target->Parent()) {
242
0
      int32_t index = target->IndexInParent();
243
0
      int32_t prevIndex = prevShow->GetAccessible()->IndexInParent();
244
0
      if (prevIndex + 1 == index) {
245
0
        target->AppendTextTo(prevTextChange->mModifiedText);
246
0
        prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
247
0
      } else if (index + 1 == prevIndex) {
248
0
        nsString temp;
249
0
        target->AppendTextTo(temp);
250
0
        prevTextChange->mStart -= temp.Length();
251
0
        temp += prevTextChange->mModifiedText;
252
0
        prevTextChange->mModifiedText = temp;
253
0
        prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
254
0
      }
255
0
    }
256
0
  }
257
0
258
0
  if (!mutEvent->mTextChangeEvent) {
259
0
    mutEvent->mTextChangeEvent =
260
0
      new AccTextChangeEvent(container, offset, text, mutEvent->IsShow(),
261
0
                             aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
262
0
  }
263
0
264
0
  return true;
265
0
}
266
267
void
268
NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent)
269
0
{
270
0
  // unset the event bits since the event isn't being fired any more.
271
0
  if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
272
0
    aEvent->GetAccessible()->SetReorderEventTarget(false);
273
0
  } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
274
0
    aEvent->GetAccessible()->SetShowEventTarget(false);
275
0
  } else {
276
0
    aEvent->GetAccessible()->SetHideEventTarget(false);
277
0
278
0
    AccHideEvent* hideEvent = downcast_accEvent(aEvent);
279
0
    MOZ_ASSERT(hideEvent);
280
0
281
0
    if (hideEvent->NeedsShutdown()) {
282
0
      mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
283
0
    }
284
0
  }
285
0
286
0
  // Do the work to splice the event out of the list.
287
0
  if (mFirstMutationEvent == aEvent) {
288
0
    mFirstMutationEvent = aEvent->NextEvent();
289
0
  } else {
290
0
    aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
291
0
  }
292
0
293
0
  if (mLastMutationEvent == aEvent) {
294
0
    mLastMutationEvent = aEvent->PrevEvent();
295
0
  } else {
296
0
    aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent());
297
0
  }
298
0
299
0
  aEvent->SetPrevEvent(nullptr);
300
0
  aEvent->SetNextEvent(nullptr);
301
0
  mMutationMap.RemoveEvent(aEvent);
302
0
}
303
304
void
305
NotificationController::CoalesceMutationEvents()
306
0
{
307
0
  AccTreeMutationEvent* event = mFirstMutationEvent;
308
0
  while (event) {
309
0
    AccTreeMutationEvent* nextEvent = event->NextEvent();
310
0
    uint32_t eventType = event->GetEventType();
311
0
    if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
312
0
      Accessible* acc = event->GetAccessible();
313
0
      while (acc) {
314
0
        if (acc->IsDoc()) {
315
0
          break;
316
0
        }
317
0
318
0
        // if a parent of the reorder event's target is being hidden that
319
0
        // hide event's target must have a parent that is also a reorder event
320
0
        // target.  That means we don't need this reorder event.
321
0
        if (acc->HideEventTarget()) {
322
0
          DropMutationEvent(event);
323
0
          break;
324
0
        }
325
0
326
0
        Accessible* parent = acc->Parent();
327
0
        if (parent->ReorderEventTarget()) {
328
0
          AccReorderEvent* reorder = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
329
0
330
0
          // We want to make sure that a reorder event comes after any show or
331
0
          // hide events targeted at the children of its target.  We keep the
332
0
          // invariant that event generation goes up as you are farther in the
333
0
          // queue, so we want to use the spot of the event with the higher
334
0
          // generation number, and keep that generation number.
335
0
          if (reorder && reorder->EventGeneration() < event->EventGeneration()) {
336
0
            reorder->SetEventGeneration(event->EventGeneration());
337
0
338
0
            // It may be true that reorder was before event, and we coalesced
339
0
            // away all the show / hide events between them.  In that case
340
0
            // event is already immediately after reorder in the queue and we
341
0
            // do not need to rearrange the list of events.
342
0
            if (event != reorder->NextEvent()) {
343
0
              // There really should be a show or hide event before the first
344
0
              // reorder event.
345
0
              if (reorder->PrevEvent()) {
346
0
                reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
347
0
              } else {
348
0
                mFirstMutationEvent = reorder->NextEvent();
349
0
              }
350
0
351
0
              reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
352
0
              event->PrevEvent()->SetNextEvent(reorder);
353
0
              reorder->SetPrevEvent(event->PrevEvent());
354
0
              event->SetPrevEvent(reorder);
355
0
              reorder->SetNextEvent(event);
356
0
            }
357
0
          }
358
0
          DropMutationEvent(event);
359
0
          break;
360
0
        }
361
0
362
0
        acc = parent;
363
0
      }
364
0
    } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
365
0
      Accessible* parent = event->GetAccessible()->Parent();
366
0
      while (parent) {
367
0
        if (parent->IsDoc()) {
368
0
          break;
369
0
        }
370
0
371
0
        // if the parent of a show event is being either shown or hidden then
372
0
        // we don't need to fire a show event for a subtree of that change.
373
0
        if (parent->ShowEventTarget() || parent->HideEventTarget()) {
374
0
          DropMutationEvent(event);
375
0
          break;
376
0
        }
377
0
378
0
        parent = parent->Parent();
379
0
      }
380
0
    } else {
381
0
      MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE, "mutation event list has an invalid event");
382
0
383
0
      AccHideEvent* hideEvent = downcast_accEvent(event);
384
0
      Accessible* parent = hideEvent->Parent();
385
0
      while (parent) {
386
0
        if (parent->IsDoc()) {
387
0
          break;
388
0
        }
389
0
390
0
        if (parent->HideEventTarget()) {
391
0
          DropMutationEvent(event);
392
0
          break;
393
0
        }
394
0
395
0
        if (parent->ShowEventTarget()) {
396
0
          AccShowEvent* showEvent = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ShowEvent));
397
0
          if (showEvent->EventGeneration() < hideEvent->EventGeneration()) {
398
0
            DropMutationEvent(hideEvent);
399
0
            break;
400
0
          }
401
0
        }
402
0
403
0
        parent = parent->Parent();
404
0
      }
405
0
    }
406
0
407
0
    event = nextEvent;
408
0
  }
409
0
}
410
411
void
412
NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
413
0
{
414
0
  // Schedule child document binding to the tree.
415
0
  mHangingChildDocuments.AppendElement(aDocument);
416
0
  ScheduleProcessing();
417
0
}
418
419
void
420
NotificationController::ScheduleContentInsertion(Accessible* aContainer,
421
                                                 nsIContent* aStartChildNode,
422
                                                 nsIContent* aEndChildNode)
423
0
{
424
0
  nsTArray<nsCOMPtr<nsIContent>> list;
425
0
426
0
  bool needsProcessing = false;
427
0
  nsIContent* node = aStartChildNode;
428
0
  while (node != aEndChildNode) {
429
0
    // Notification triggers for content insertion even if no content was
430
0
    // actually inserted, check if the given content has a frame to discard
431
0
    // this case early.
432
0
    if (node->GetPrimaryFrame()) {
433
0
      if (list.AppendElement(node))
434
0
        needsProcessing = true;
435
0
    }
436
0
    node = node->GetNextSibling();
437
0
  }
438
0
439
0
  if (needsProcessing) {
440
0
    mContentInsertions.LookupOrAdd(aContainer)->AppendElements(list);
441
0
    ScheduleProcessing();
442
0
  }
443
0
}
444
445
void
446
NotificationController::ScheduleProcessing()
447
0
{
448
0
  // If notification flush isn't planed yet start notification flush
449
0
  // asynchronously (after style and layout).
450
0
  if (mObservingState == eNotObservingRefresh) {
451
0
    if (mPresShell->AddRefreshObserver(this, FlushType::Display))
452
0
      mObservingState = eRefreshObserving;
453
0
  }
454
0
}
455
456
////////////////////////////////////////////////////////////////////////////////
457
// NotificationCollector: protected
458
459
bool
460
NotificationController::IsUpdatePending()
461
0
{
462
0
  return mPresShell->IsLayoutFlushObserver() ||
463
0
    mObservingState == eRefreshProcessingForUpdate ||
464
0
    WaitingForParent() ||
465
0
    mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
466
0
    mTextHash.Count() != 0 ||
467
0
    !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
468
0
}
469
470
bool
471
NotificationController::WaitingForParent()
472
0
{
473
0
  DocAccessible* parentdoc = mDocument->ParentDocument();
474
0
  if (!parentdoc) {
475
0
    return false;
476
0
  }
477
0
478
0
  NotificationController* parent = parentdoc->mNotificationController;
479
0
  if (!parent || parent == this) {
480
0
    // Do not wait for nothing or ourselves
481
0
    return false;
482
0
  }
483
0
484
0
  // Wait for parent's notifications processing
485
0
  return parent->mContentInsertions.Count() != 0 ||
486
0
         parent->mNotifications.Length() != 0;
487
0
}
488
489
void
490
NotificationController::ProcessMutationEvents()
491
0
{
492
0
  // there is no reason to fire a hide event for a child of a show event
493
0
  // target.  That can happen if something is inserted into the tree and
494
0
  // removed before the next refresh driver tick, but it should not be
495
0
  // observable outside gecko so it should be safe to coalesce away any such
496
0
  // events.  This means that it should be fine to fire all of the hide events
497
0
  // first, and then deal with any shown subtrees.
498
0
  for (AccTreeMutationEvent* event = mFirstMutationEvent;
499
0
       event; event = event->NextEvent()) {
500
0
    if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) {
501
0
      continue;
502
0
    }
503
0
504
0
    nsEventShell::FireEvent(event);
505
0
    if (!mDocument) {
506
0
      return;
507
0
    }
508
0
509
0
    AccMutationEvent* mutEvent = downcast_accEvent(event);
510
0
    if (mutEvent->mTextChangeEvent) {
511
0
      nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
512
0
      if (!mDocument) {
513
0
        return;
514
0
      }
515
0
    }
516
0
517
0
    // Fire menupopup end event before a hide event if a menu goes away.
518
0
519
0
    // XXX: We don't look into children of hidden subtree to find hiding
520
0
    // menupopup (as we did prior bug 570275) because we don't do that when
521
0
    // menu is showing (and that's impossible until bug 606924 is fixed).
522
0
    // Nevertheless we should do this at least because layout coalesces
523
0
    // the changes before our processing and we may miss some menupopup
524
0
    // events. Now we just want to be consistent in content insertion/removal
525
0
    // handling.
526
0
    if (event->mAccessible->ARIARole() == roles::MENUPOPUP) {
527
0
      nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
528
0
                              event->mAccessible);
529
0
      if (!mDocument) {
530
0
        return;
531
0
      }
532
0
    }
533
0
534
0
    AccHideEvent* hideEvent = downcast_accEvent(event);
535
0
    if (hideEvent->NeedsShutdown()) {
536
0
      mDocument->ShutdownChildrenInSubtree(event->mAccessible);
537
0
    }
538
0
  }
539
0
540
0
  // Group the show events by the parent of their target.
541
0
  nsDataHashtable<nsPtrHashKey<Accessible>, nsTArray<AccTreeMutationEvent*>> showEvents;
542
0
  for (AccTreeMutationEvent* event = mFirstMutationEvent;
543
0
       event; event = event->NextEvent()) {
544
0
    if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
545
0
      continue;
546
0
    }
547
0
548
0
    Accessible* parent = event->GetAccessible()->Parent();
549
0
    showEvents.GetOrInsert(parent).AppendElement(event);
550
0
  }
551
0
552
0
  // We need to fire show events for the children of an accessible in the order
553
0
  // of their indices at this point.  So sort each set of events for the same
554
0
  // container by the index of their target.
555
0
  for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
556
0
    struct AccIdxComparator {
557
0
      bool LessThan(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
558
0
      {
559
0
        int32_t aIdx = a->GetAccessible()->IndexInParent();
560
0
        int32_t bIdx = b->GetAccessible()->IndexInParent();
561
0
        MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
562
0
        return aIdx < bIdx;
563
0
      }
564
0
      bool Equals(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
565
0
      {
566
0
        DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent();
567
0
        DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent();
568
0
        MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
569
0
        return false;
570
0
      }
571
0
    };
572
0
573
0
    nsTArray<AccTreeMutationEvent*>& events = iter.Data();
574
0
    events.Sort(AccIdxComparator());
575
0
    for (AccTreeMutationEvent* event: events) {
576
0
      nsEventShell::FireEvent(event);
577
0
      if (!mDocument) {
578
0
        return;
579
0
      }
580
0
581
0
      AccMutationEvent* mutEvent = downcast_accEvent(event);
582
0
      if (mutEvent->mTextChangeEvent) {
583
0
        nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
584
0
        if (!mDocument) {
585
0
          return;
586
0
        }
587
0
      }
588
0
    }
589
0
  }
590
0
591
0
  // Now we can fire the reorder events after all the show and hide events.
592
0
  for (AccTreeMutationEvent* event = mFirstMutationEvent;
593
0
       event; event = event->NextEvent()) {
594
0
    if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) {
595
0
      continue;
596
0
    }
597
0
598
0
    nsEventShell::FireEvent(event);
599
0
    if (!mDocument) {
600
0
      return;
601
0
    }
602
0
603
0
    Accessible* target = event->GetAccessible();
604
0
    target->Document()->MaybeNotifyOfValueChange(target);
605
0
    if (!mDocument) {
606
0
      return;
607
0
    }
608
0
  }
609
0
}
610
611
////////////////////////////////////////////////////////////////////////////////
612
// NotificationCollector: private
613
614
void
615
NotificationController::WillRefresh(mozilla::TimeStamp aTime)
616
0
{
617
0
  Telemetry::AutoTimer<Telemetry::A11Y_TREE_UPDATE_TIMING_MS> timer;
618
0
619
0
  AUTO_PROFILER_LABEL("NotificationController::WillRefresh", OTHER);
620
0
621
0
  // If the document accessible that notification collector was created for is
622
0
  // now shut down, don't process notifications anymore.
623
0
  NS_ASSERTION(mDocument,
624
0
               "The document was shut down while refresh observer is attached!");
625
0
  if (!mDocument)
626
0
    return;
627
0
628
0
  // Wait until an update, we have started, or an interruptible reflow is
629
0
  // finished.
630
0
  if (mObservingState == eRefreshProcessing ||
631
0
      mObservingState == eRefreshProcessingForUpdate ||
632
0
      mPresShell->IsReflowInterrupted()) {
633
0
    return;
634
0
  }
635
0
636
0
  // Process parent's notifications before ours, to get proper ordering between
637
0
  // e.g. tab event and content event.
638
0
  if (WaitingForParent()) {
639
0
    mDocument->ParentDocument()->mNotificationController->WillRefresh(aTime);
640
0
    if (!mDocument) {
641
0
      return;
642
0
    }
643
0
  }
644
0
645
0
  // Any generic notifications should be queued if we're processing content
646
0
  // insertions or generic notifications.
647
0
  mObservingState = eRefreshProcessingForUpdate;
648
0
649
0
  // Initial accessible tree construction.
650
0
  if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
651
0
    // If document is not bound to parent at this point then the document is not
652
0
    // ready yet (process notifications later).
653
0
    if (!mDocument->IsBoundToParent()) {
654
0
      mObservingState = eRefreshObserving;
655
0
      return;
656
0
    }
657
0
658
0
#ifdef A11Y_LOG
659
0
    if (logging::IsEnabled(logging::eTree)) {
660
0
      logging::MsgBegin("TREE", "initial tree created");
661
0
      logging::Address("document", mDocument);
662
0
      logging::MsgEnd();
663
0
    }
664
0
#endif
665
0
666
0
    mDocument->DoInitialUpdate();
667
0
668
0
    NS_ASSERTION(mContentInsertions.Count() == 0,
669
0
                 "Pending content insertions while initial accessible tree isn't created!");
670
0
  }
671
0
672
0
  // Initialize scroll support if needed.
673
0
  if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
674
0
    mDocument->AddScrollListener();
675
0
676
0
  // Process rendered text change notifications.
677
0
  for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
678
0
    nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
679
0
    nsIContent* textNode = entry->GetKey();
680
0
    Accessible* textAcc = mDocument->GetAccessible(textNode);
681
0
682
0
    // If the text node is not in tree or doesn't have a frame, or placed in
683
0
    // another document, then this case should have been handled already by
684
0
    // content removal notifications.
685
0
    nsINode* containerNode = textNode->GetFlattenedTreeParentNode();
686
0
    if (!containerNode || textNode->OwnerDoc() != mDocument->DocumentNode()) {
687
0
      MOZ_ASSERT(!textAcc,
688
0
                 "Text node was removed but accessible is kept alive!");
689
0
      continue;
690
0
    }
691
0
692
0
    nsIFrame* textFrame = textNode->GetPrimaryFrame();
693
0
    if (!textFrame) {
694
0
      MOZ_ASSERT(!textAcc,
695
0
                 "Text node isn't rendered but accessible is kept alive!");
696
0
      continue;
697
0
    }
698
0
699
0
  #ifdef A11Y_LOG
700
0
    nsIContent* containerElm = containerNode->IsElement() ?
701
0
      containerNode->AsElement() : nullptr;
702
0
  #endif
703
0
704
0
    nsIFrame::RenderedText text = textFrame->GetRenderedText(0,
705
0
        UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
706
0
        nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
707
0
708
0
    // Remove text accessible if rendered text is empty.
709
0
    if (textAcc) {
710
0
      if (text.mString.IsEmpty()) {
711
0
  #ifdef A11Y_LOG
712
0
        if (logging::IsEnabled(logging::eTree | logging::eText)) {
713
0
          logging::MsgBegin("TREE", "text node lost its content; doc: %p", mDocument);
714
0
          logging::Node("container", containerElm);
715
0
          logging::Node("content", textNode);
716
0
          logging::MsgEnd();
717
0
        }
718
0
  #endif
719
0
720
0
        mDocument->ContentRemoved(textAcc);
721
0
        continue;
722
0
      }
723
0
724
0
      // Update text of the accessible and fire text change events.
725
0
  #ifdef A11Y_LOG
726
0
      if (logging::IsEnabled(logging::eText)) {
727
0
        logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
728
0
        logging::Node("container", containerElm);
729
0
        logging::Node("content", textNode);
730
0
        logging::MsgEntry("old text '%s'",
731
0
                          NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
732
0
        logging::MsgEntry("new text: '%s'",
733
0
                          NS_ConvertUTF16toUTF8(text.mString).get());
734
0
        logging::MsgEnd();
735
0
      }
736
0
  #endif
737
0
738
0
      TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
739
0
      continue;
740
0
    }
741
0
742
0
    // Append an accessible if rendered text is not empty.
743
0
    if (!text.mString.IsEmpty()) {
744
0
  #ifdef A11Y_LOG
745
0
      if (logging::IsEnabled(logging::eTree | logging::eText)) {
746
0
        logging::MsgBegin("TREE", "text node gains new content; doc: %p", mDocument);
747
0
        logging::Node("container", containerElm);
748
0
        logging::Node("content", textNode);
749
0
        logging::MsgEnd();
750
0
      }
751
0
  #endif
752
0
753
0
      MOZ_ASSERT(mDocument->AccessibleOrTrueContainer(containerNode),
754
0
                 "Text node having rendered text hasn't accessible document!");
755
0
756
0
      Accessible* container = mDocument->AccessibleOrTrueContainer(
757
0
        containerNode, DocAccessible::eNoContainerIfARIAHidden);
758
0
      if (container) {
759
0
        nsTArray<nsCOMPtr<nsIContent>>* list =
760
0
          mContentInsertions.LookupOrAdd(container);
761
0
        list->AppendElement(textNode);
762
0
      }
763
0
    }
764
0
  }
765
0
  mTextHash.Clear();
766
0
767
0
  // Process content inserted notifications to update the tree.
768
0
  for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
769
0
    mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
770
0
    if (!mDocument) {
771
0
      return;
772
0
    }
773
0
  }
774
0
  mContentInsertions.Clear();
775
0
776
0
  // Bind hanging child documents unless we are using IPC and the
777
0
  // document has no IPC actor.  If we fail to bind the child doc then
778
0
  // shut it down.
779
0
  uint32_t hangingDocCnt = mHangingChildDocuments.Length();
780
0
  nsTArray<RefPtr<DocAccessible>> newChildDocs;
781
0
  for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
782
0
    DocAccessible* childDoc = mHangingChildDocuments[idx];
783
0
    if (childDoc->IsDefunct())
784
0
      continue;
785
0
786
0
    if (IPCAccessibilityActive() && !mDocument->IPCDoc()) {
787
0
      childDoc->Shutdown();
788
0
      continue;
789
0
    }
790
0
791
0
    nsIContent* ownerContent = mDocument->DocumentNode()->
792
0
      FindContentForSubDocument(childDoc->DocumentNode());
793
0
    if (ownerContent) {
794
0
      Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
795
0
      if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
796
0
        if (mDocument->AppendChildDocument(childDoc)) {
797
0
          newChildDocs.AppendElement(std::move(mHangingChildDocuments[idx]));
798
0
          continue;
799
0
        }
800
0
801
0
        outerDocAcc->RemoveChild(childDoc);
802
0
      }
803
0
804
0
      // Failed to bind the child document, destroy it.
805
0
      childDoc->Shutdown();
806
0
    }
807
0
  }
808
0
809
0
  // Clear the hanging documents list, even if we didn't bind them.
810
0
  mHangingChildDocuments.Clear();
811
0
  MOZ_ASSERT(mDocument, "Illicit document shutdown");
812
0
  if (!mDocument) {
813
0
    return;
814
0
  }
815
0
816
0
  // If the document is ready and all its subdocuments are completely loaded
817
0
  // then process the document load.
818
0
  if (mDocument->HasLoadState(DocAccessible::eReady) &&
819
0
      !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
820
0
      hangingDocCnt == 0) {
821
0
    uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
822
0
    for (; childDocIdx < childDocCnt; childDocIdx++) {
823
0
      DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
824
0
      if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded))
825
0
        break;
826
0
    }
827
0
828
0
    if (childDocIdx == childDocCnt) {
829
0
      mDocument->ProcessLoad();
830
0
      if (!mDocument)
831
0
        return;
832
0
    }
833
0
  }
834
0
835
0
  // Process only currently queued generic notifications.
836
0
  nsTArray < RefPtr<Notification> > notifications;
837
0
  notifications.SwapElements(mNotifications);
838
0
839
0
  uint32_t notificationCount = notifications.Length();
840
0
  for (uint32_t idx = 0; idx < notificationCount; idx++) {
841
0
    notifications[idx]->Process();
842
0
    if (!mDocument)
843
0
      return;
844
0
  }
845
0
846
0
  // Process invalidation list of the document after all accessible tree
847
0
  // modification are done.
848
0
  mDocument->ProcessInvalidationList();
849
0
850
0
  // Process relocation list.
851
0
  for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
852
0
    // owner should be in a document and have na associated DOM node (docs sometimes don't)
853
0
    if (mRelocations[idx]->IsInDocument() && mRelocations[idx]->HasOwnContent()) {
854
0
      mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
855
0
    }
856
0
  }
857
0
  mRelocations.Clear();
858
0
859
0
  // If a generic notification occurs after this point then we may be allowed to
860
0
  // process it synchronously.  However we do not want to reenter if fireing
861
0
  // events causes script to run.
862
0
  mObservingState = eRefreshProcessing;
863
0
864
0
  CoalesceMutationEvents();
865
0
  ProcessMutationEvents();
866
0
  mEventGeneration = 0;
867
0
868
0
  // Now that we are done with them get rid of the events we fired.
869
0
  RefPtr<AccTreeMutationEvent> mutEvent = std::move(mFirstMutationEvent);
870
0
  mLastMutationEvent = nullptr;
871
0
  mFirstMutationEvent = nullptr;
872
0
  while (mutEvent) {
873
0
    RefPtr<AccTreeMutationEvent> nextEvent = mutEvent->NextEvent();
874
0
    Accessible* target = mutEvent->GetAccessible();
875
0
876
0
    // We need to be careful here, while it may seem that we can simply 0 all
877
0
    // the pending event bits that is not true.  Because accessibles may be
878
0
    // reparented they may be the target of both a hide event and a show event
879
0
    // at the same time.
880
0
    if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
881
0
      target->SetShowEventTarget(false);
882
0
    }
883
0
884
0
    if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
885
0
      target->SetHideEventTarget(false);
886
0
    }
887
0
888
0
    // However it is not possible for a reorder event target to also be the
889
0
    // target of a show or hide, so we can just zero that.
890
0
    target->SetReorderEventTarget(false);
891
0
892
0
    mutEvent->SetPrevEvent(nullptr);
893
0
    mutEvent->SetNextEvent(nullptr);
894
0
    mMutationMap.RemoveEvent(mutEvent);
895
0
    mutEvent = nextEvent;
896
0
  }
897
0
898
0
  ProcessEventQueue();
899
0
900
0
  if (IPCAccessibilityActive()) {
901
0
    size_t newDocCount = newChildDocs.Length();
902
0
    for (size_t i = 0; i < newDocCount; i++) {
903
0
      DocAccessible* childDoc = newChildDocs[i];
904
0
      if (childDoc->IsDefunct()) {
905
0
        continue;
906
0
      }
907
0
908
0
      Accessible* parent = childDoc->Parent();
909
0
      DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
910
0
      MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc);
911
0
      uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
912
0
      MOZ_DIAGNOSTIC_ASSERT(id);
913
0
      DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
914
0
      if (ipcDoc) {
915
0
        parentIPCDoc->SendBindChildDoc(ipcDoc, id);
916
0
        continue;
917
0
      }
918
0
919
0
      ipcDoc = new DocAccessibleChild(childDoc, parentIPCDoc->Manager());
920
0
      childDoc->SetIPCDoc(ipcDoc);
921
0
922
#if defined(XP_WIN)
923
      parentIPCDoc->ConstructChildDocInParentProcess(ipcDoc, id,
924
                                                     AccessibleWrap::GetChildIDFor(childDoc));
925
#else
926
      nsCOMPtr<nsITabChild> tabChild =
927
0
        do_GetInterface(mDocument->DocumentNode()->GetDocShell());
928
0
      if (tabChild) {
929
0
        static_cast<TabChild*>(tabChild.get())->
930
0
          SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
931
0
      }
932
0
#endif
933
0
    }
934
0
  }
935
0
936
0
  mObservingState = eRefreshObserving;
937
0
  if (!mDocument)
938
0
    return;
939
0
940
0
  // Stop further processing if there are no new notifications of any kind or
941
0
  // events and document load is processed.
942
0
  if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() &&
943
0
      mEvents.IsEmpty() && mTextHash.Count() == 0 &&
944
0
      mHangingChildDocuments.IsEmpty() &&
945
0
      mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
946
0
      mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
947
0
    mObservingState = eNotObservingRefresh;
948
0
  }
949
0
}
950
951
void
952
NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent)
953
0
{
954
0
  EventType type = GetEventType(aEvent);
955
0
  uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
956
0
  MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
957
0
  addr |= type;
958
0
  mTable.Put(addr, aEvent);
959
0
}
960
961
AccTreeMutationEvent*
962
NotificationController::EventMap::GetEvent(Accessible* aTarget, EventType aType)
963
0
{
964
0
  uint64_t addr = reinterpret_cast<uintptr_t>(aTarget);
965
0
  MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned");
966
0
967
0
  addr |= aType;
968
0
  return mTable.GetWeak(addr);
969
0
}
970
971
void
972
NotificationController::EventMap::RemoveEvent(AccTreeMutationEvent* aEvent)
973
0
{
974
0
  EventType type = GetEventType(aEvent);
975
0
  uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
976
0
  MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
977
0
  addr |= type;
978
0
979
0
  MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
980
0
  mTable.Remove(addr);
981
0
}
982
983
  NotificationController::EventMap::EventType
984
NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent)
985
0
{
986
0
  switch(aEvent->GetEventType())
987
0
  {
988
0
    case nsIAccessibleEvent::EVENT_SHOW:
989
0
      return ShowEvent;
990
0
    case nsIAccessibleEvent::EVENT_HIDE:
991
0
      return HideEvent;
992
0
    case nsIAccessibleEvent::EVENT_REORDER:
993
0
      return ReorderEvent;
994
0
    default:
995
0
      MOZ_ASSERT_UNREACHABLE("event has invalid type");
996
0
      return ShowEvent;
997
0
  }
998
0
}