Bug 1515294: Only use SendSyncTextChangeEvent for live regions. r=MarcoZ
authorJames Teh <jteh@mozilla.com>
Fri, 21 Dec 2018 09:16:38 +0000
changeset 509364 db7ef78c063b2d0532056aa768f80493c8d0b739
parent 509363 d02e6cc785a9b1c53f83b5c9370cc37885c9208b
child 509365 5d16d586f9cdd00544bd5d29ac02b145e9041c48
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMarcoZ
bugs1515294, 1322532
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1515294: Only use SendSyncTextChangeEvent for live regions. r=MarcoZ In order to support IA2 live regions with e10s (bug 1322532), text change events sent from the child process to the parent process use sync IPC. That comes at a slight performance cost: the content process is blocked until the parent returns from sending and handling the event. However, there is no reason to do this if the event is not for an accessible inside a live region; current clients don't need the sync behaviour outside of live regions. Differential Revision: https://phabricator.services.mozilla.com/D15183
accessible/base/nsAccUtils.cpp
accessible/base/nsAccUtils.h
accessible/generic/Accessible.cpp
accessible/ipc/win/DocAccessibleChild.cpp
accessible/ipc/win/DocAccessibleChild.h
--- a/accessible/base/nsAccUtils.cpp
+++ b/accessible/base/nsAccUtils.cpp
@@ -434,8 +434,74 @@ bool nsAccUtils::PersistentPropertiesToA
     rv = propElem->GetValue(value);
     NS_ENSURE_SUCCESS(rv, false);
 
     aAttributes->AppendElement(Attribute(name, value));
   }
 
   return true;
 }
+
+bool nsAccUtils::IsARIALive(const Accessible* aAccessible) {
+  // Get computed aria-live property based on the closest container with the
+  // attribute. Inner nodes override outer nodes within the same
+  // document, but nodes in outer documents override nodes in inner documents.
+  // This should be the same as the container-live attribute, but we don't need
+  // the other container-* attributes, so we can't use the same function.
+  nsAutoString live;
+  nsIContent* startContent = aAccessible->GetContent();
+  while (startContent) {
+    nsIDocument* doc = startContent->GetComposedDoc();
+    if (!doc) {
+      break;
+    }
+
+    dom::Element* aTopEl = doc->GetRootElement();
+    nsIContent* ancestor = startContent;
+    while (ancestor) {
+      nsAutoString docLive;
+      const nsRoleMapEntry* role = nullptr;
+      if (ancestor->IsElement()) {
+        role = aria::GetRoleMap(ancestor->AsElement());
+      }
+      if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
+        ancestor->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live,
+                                       docLive);
+      } else if (role) {
+        GetLiveAttrValue(role->liveAttRule, docLive);
+      }
+      if (!docLive.IsEmpty()) {
+        live = docLive;
+        break;
+      }
+
+      if (ancestor == aTopEl) {
+        break;
+      }
+
+      ancestor = ancestor->GetParent();
+      if (!ancestor) {
+        ancestor = aTopEl;  // Use <body>/<frameset>
+      }
+    }
+
+    // Allow ARIA live region markup from outer documents to override.
+    nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+    if (!docShellTreeItem) {
+      break;
+    }
+
+    nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
+    docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
+    if (!sameTypeParent || sameTypeParent == docShellTreeItem) {
+      break;
+    }
+
+    nsIDocument* parentDoc = doc->GetParentDocument();
+    if (!parentDoc) {
+      break;
+    }
+
+    startContent = parentDoc->FindContentForSubDocument(doc);
+  }
+
+  return !live.IsEmpty() && !live.EqualsLiteral("off");
+}
--- a/accessible/base/nsAccUtils.h
+++ b/accessible/base/nsAccUtils.h
@@ -250,14 +250,20 @@ class nsAccUtils {
   /**
    * Return true if the given accessible can't have children. Used when exposing
    * to platform accessibility APIs, should the children be pruned off?
    */
   static bool MustPrune(Accessible* aAccessible);
 
   static bool PersistentPropertiesToArray(nsIPersistentProperties* aProps,
                                           nsTArray<Attribute>* aAttributes);
+
+  /**
+   * Return true if the given accessible is within an ARIA live region; i.e.
+   * the container-live attribute would be something other than "off" or empty.
+   */
+  static bool IsARIALive(const Accessible* aAccessible);
 };
 
 }  // namespace a11y
 }  // namespace mozilla
 
 #endif
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -841,20 +841,33 @@ nsresult Accessible::HandleAccEvent(AccE
         case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
           AccCaretMoveEvent* event = downcast_accEvent(aEvent);
           ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset());
           break;
         }
         case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
         case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
           AccTextChangeEvent* event = downcast_accEvent(aEvent);
-          ipcDoc->SendTextChangeEvent(
-              id, event->ModifiedText(), event->GetStartOffset(),
-              event->GetLength(), event->IsTextInserted(),
-              event->IsFromUserInput());
+          const nsString& text = event->ModifiedText();
+#if defined(XP_WIN)
+          // On Windows, events for live region updates containing embedded
+          // objects require us to dispatch synchronous events.
+          bool sync = text.Contains(L'\xfffc') &&
+                      nsAccUtils::IsARIALive(aEvent->GetAccessible());
+#endif
+          ipcDoc->SendTextChangeEvent(id, text, event->GetStartOffset(),
+                                      event->GetLength(),
+                                      event->IsTextInserted(),
+                                      event->IsFromUserInput()
+#if defined(XP_WIN)
+                                      // This parameter only exists on Windows.
+                                      ,
+                                      sync
+#endif
+          );
           break;
         }
         case nsIAccessibleEvent::EVENT_SELECTION:
         case nsIAccessibleEvent::EVENT_SELECTION_ADD:
         case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
           AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
           uint64_t widgetID =
               selEvent->Widget()->IsDoc()
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -192,19 +192,19 @@ bool DocAccessibleChild::SendCaretMoveEv
   PushDeferredEvent(
       MakeUnique<SerializedCaretMove>(this, aID, aCaretRect, aOffset));
   return true;
 }
 
 bool DocAccessibleChild::SendTextChangeEvent(
     const uint64_t& aID, const nsString& aStr, const int32_t& aStart,
     const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser,
-    const bool aDoSyncCheck) {
+    const bool aDoSync) {
   if (IsConstructedInParentProcess()) {
-    if (aDoSyncCheck && aStr.Contains(L'\xfffc')) {
+    if (aDoSync) {
       // The AT is going to need to reenter content while the event is being
       // dispatched synchronously.
       return PDocAccessibleChild::SendSyncTextChangeEvent(
           aID, aStr, aStart, aLen, aIsInsert, aFromUser);
     }
     return PDocAccessibleChild::SendTextChangeEvent(aID, aStr, aStart, aLen,
                                                     aIsInsert, aFromUser);
   }
--- a/accessible/ipc/win/DocAccessibleChild.h
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -49,17 +49,17 @@ class DocAccessibleChild : public DocAcc
                           const LayoutDeviceIntRect& aCaretRect,
                           const int32_t& aOffset);
   bool SendFocusEvent(const uint64_t& aID);
   bool SendFocusEvent(const uint64_t& aID,
                       const LayoutDeviceIntRect& aCaretRect);
   bool SendTextChangeEvent(const uint64_t& aID, const nsString& aStr,
                            const int32_t& aStart, const uint32_t& aLen,
                            const bool& aIsInsert, const bool& aFromUser,
-                           const bool aDoSyncCheck = true);
+                           const bool aDoSync = false);
   bool SendSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID,
                           const uint32_t& aType);
   bool SendRoleChangedEvent(const a11y::role& aRole);
   bool SendScrollingEvent(const uint64_t& aID, const uint64_t& aType,
                           const uint32_t& aScrollX, const uint32_t& aScrollY,
                           const uint32_t& aMaxScrollX,
                           const uint32_t& aMaxScrollY);