Bug 1515294: Only use SendSyncTextChangeEvent for live regions. r=MarcoZ
authorJames Teh <jteh@mozilla.com>
Fri, 21 Dec 2018 09:16:38 +0000
changeset 508753 415f827cb9ce211c48b0c6625e917582bc81fd3a
parent 508752 114c94d2677cac6b77071b302233bb93e2573fb5
child 508754 4f4436e2493a153ccff73f2a470e8964f1c33519
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);