Bug 1688972 - Make FocusedChild work across document/process boundaries. r=Jamie
☠☠ backed out by 6b095a7cc6c6 ☠ ☠
authorEitan Isaacson <eitan@monotonous.org>
Fri, 29 Jan 2021 20:57:30 +0000
changeset 565271 4572c1ba670a3ddb159cf37ba9684fb6055f260e
parent 565270 f262a5526f6ce10fd4c23f0b2bee7eba48872432
child 565272 dd4bcc71abfede0cb0b82881f213323f5deace55
push id135334
push usereisaacson@mozilla.com
push dateFri, 29 Jan 2021 21:00:42 +0000
treeherderautoland@4572c1ba670a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJamie
bugs1688972
milestone87.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 1688972 - Make FocusedChild work across document/process boundaries. r=Jamie Differential Revision: https://phabricator.services.mozilla.com/D103102
accessible/ipc/other/DocAccessibleChild.cpp
accessible/ipc/other/DocAccessibleChild.h
accessible/ipc/other/PDocAccessible.ipdl
accessible/ipc/other/ProxyAccessible.cpp
accessible/tests/browser/fission/browser_take_focus.js
--- a/accessible/ipc/other/DocAccessibleChild.cpp
+++ b/accessible/ipc/other/DocAccessibleChild.cpp
@@ -1519,25 +1519,40 @@ mozilla::ipc::IPCResult DocAccessibleChi
   if (acc) {
     acc->TakeFocus();
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult DocAccessibleChild::RecvFocusedChild(
-    const uint64_t& aID, uint64_t* aChild, bool* aOk) {
-  *aChild = 0;
-  *aOk = false;
+    const uint64_t& aID, PDocAccessibleChild** aResultDoc,
+    uint64_t* aResultID) {
+  *aResultDoc = nullptr;
+  *aResultID = 0;
   Accessible* acc = IdToAccessible(aID);
-  if (acc) {
-    Accessible* child = acc->FocusedChild();
-    if (child) {
-      *aChild = reinterpret_cast<uint64_t>(child->UniqueID());
-      *aOk = true;
+  if (!acc) {
+    return IPC_OK();
+  }
+
+  Accessible* result = acc->FocusedChild();
+  if (result) {
+    // Accessible::FocusedChild can return an Accessible from a descendant
+    // document.
+    DocAccessibleChild* resultDoc = result->Document()->IPCDoc();
+    // We've sent the constructor for this document to the parent process.
+    // However, because the constructor is async, the parent process might
+    // get the result of this (sync) method before it runs the constructor.
+    // If we send this document in this case, the parent process will crash.
+    // Therefore, we only do this if the parent process has explicitly told
+    // us that the document has been constructed there.
+    if (resultDoc && resultDoc->IsConstructedInParentProcess()) {
+      *aResultDoc = resultDoc;
+      *aResultID =
+          result->IsDoc() ? 0 : reinterpret_cast<uint64_t>(result->UniqueID());
     }
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult DocAccessibleChild::RecvLanguage(const uint64_t& aID,
                                                          nsString* aLocale) {
   Accessible* acc = IdToAccessible(aID);
--- a/accessible/ipc/other/DocAccessibleChild.h
+++ b/accessible/ipc/other/DocAccessibleChild.h
@@ -437,19 +437,19 @@ class DocAccessibleChild : public DocAcc
   virtual mozilla::ipc::IPCResult RecvMaxValue(const uint64_t& aID,
                                                double* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvStep(const uint64_t& aID,
                                            double* aStep) override;
 
   virtual mozilla::ipc::IPCResult RecvTakeFocus(const uint64_t& aID) override;
 
-  virtual mozilla::ipc::IPCResult RecvFocusedChild(const uint64_t& aID,
-                                                   uint64_t* aChild,
-                                                   bool* aOk) override;
+  virtual mozilla::ipc::IPCResult RecvFocusedChild(
+      const uint64_t& aID, PDocAccessibleChild** aResultDoc,
+      uint64_t* aResultID) override;
 
   virtual mozilla::ipc::IPCResult RecvLanguage(const uint64_t& aID,
                                                nsString* aLocale) override;
   virtual mozilla::ipc::IPCResult RecvDocType(const uint64_t& aID,
                                               nsString* aType) override;
   virtual mozilla::ipc::IPCResult RecvTitle(const uint64_t& aID,
                                             nsString* aTitle) override;
   virtual mozilla::ipc::IPCResult RecvURL(const uint64_t& aID,
--- a/accessible/ipc/other/PDocAccessible.ipdl
+++ b/accessible/ipc/other/PDocAccessible.ipdl
@@ -305,17 +305,17 @@ child:
   nested(inside_sync) sync CurValue(uint64_t aID) returns(double aValue);
   nested(inside_sync) sync SetCurValue(uint64_t aID, double aValue) returns(bool aRetVal);
   nested(inside_sync) sync MinValue(uint64_t aID) returns(double aValue);
   nested(inside_sync) sync MaxValue(uint64_t aID) returns(double aValue);
   nested(inside_sync) sync Step(uint64_t aID) returns(double aStep);
 
   async TakeFocus(uint64_t aID);
   nested(inside_sync) sync FocusedChild(uint64_t aID)
-    returns(uint64_t aChild, bool aOk);
+    returns(nullable PDocAccessible aResultDoc, uint64_t aResultID);
 
   nested(inside_sync) sync Language(uint64_t aID) returns(nsString aLocale);
   nested(inside_sync) sync DocType(uint64_t aID) returns(nsString aType);
   nested(inside_sync) sync Title(uint64_t aID) returns(nsString aTitle);
   nested(inside_sync) sync URL(uint64_t aID) returns(nsString aURL);
   nested(inside_sync) sync MimeType(uint64_t aID) returns(nsString aMime);
   nested(inside_sync) sync URLDocTypeMimeType(uint64_t aID) returns(nsString aURL, nsString aDocType, nsString aMimeType);
 
--- a/accessible/ipc/other/ProxyAccessible.cpp
+++ b/accessible/ipc/other/ProxyAccessible.cpp
@@ -757,20 +757,47 @@ double ProxyAccessible::Step() {
   double step = UnspecifiedNaN<double>();
   Unused << mDoc->SendStep(mID, &step);
   return step;
 }
 
 void ProxyAccessible::TakeFocus() { Unused << mDoc->SendTakeFocus(mID); }
 
 ProxyAccessible* ProxyAccessible::FocusedChild() {
-  uint64_t childID = 0;
-  bool ok = false;
-  Unused << mDoc->SendFocusedChild(mID, &childID, &ok);
-  return ok ? mDoc->GetAccessible(childID) : nullptr;
+  if (mOuterDoc) {
+    // If FocusedChild was called on an outer doc, it should behave
+    // like a non-doc accessible and return its focused child, or null.
+    // If the inner doc is OOP (fission), calling FocusedChild on the outer
+    // doc would return null.
+    MOZ_ASSERT(ChildrenCount() == 1);
+    ProxyAccessible* child = FirstChild();
+    MOZ_ASSERT(child->IsDoc());
+    return (child->State() & states::FOCUSED) ? child : nullptr;
+  }
+
+  auto* doc = mDoc;
+  uint64_t id = mID;
+  if (IsDoc()) {
+    // If this is a doc we should return the focused descendant, not just the
+    // direct child. In order to do that, we get the focused parent doc,
+    // which may be an OOP iframe.
+    if (dom::BrowserParent* browser = dom::BrowserParent::GetFocused()) {
+      if (auto* focusedDoc = browser->GetTopLevelDocAccessible()) {
+        doc = focusedDoc;
+      }
+    }
+  }
+
+  PDocAccessibleParent* resultDoc = nullptr;
+  uint64_t resultID = 0;
+  Unused << doc->SendFocusedChild(id, &resultDoc, &resultID);
+
+  auto* useDoc = static_cast<DocAccessibleParent*>(resultDoc);
+  // If useDoc is null, this means there is no focused child.
+  return useDoc ? useDoc->GetAccessible(resultID) : nullptr;
 }
 
 ProxyAccessible* ProxyAccessible::ChildAtPoint(
     int32_t aX, int32_t aY, Accessible::EWhichChildAtPoint aWhichChild) {
   ProxyAccessible* target = this;
   do {
     if (target->mOuterDoc) {
       MOZ_ASSERT(target->ChildrenCount() == 1);
--- a/accessible/tests/browser/fission/browser_take_focus.js
+++ b/accessible/tests/browser/fission/browser_take_focus.js
@@ -6,21 +6,56 @@
 
 /* import-globals-from ../../mochitest/states.js */
 loadScripts(
   { name: "role.js", dir: MOCHITESTS_DIR },
   { name: "states.js", dir: MOCHITESTS_DIR }
 );
 
 addAccessibleTask(
-  `<input id="textbox" value="hello"/>`,
+  `<div role="group"><input id="textbox" value="hello"/></div>`,
   async function(browser, iframeDocAcc, contentDocAcc) {
     const textbox = findAccessibleChildByID(iframeDocAcc, "textbox");
+    const iframe = findAccessibleChildByID(contentDocAcc, "default-iframe-id");
+    const iframeDoc = findAccessibleChildByID(
+      contentDocAcc,
+      "default-iframe-body-id"
+    );
+
     testStates(textbox, STATE_FOCUSABLE, 0, STATE_FOCUSED);
 
     let onFocus = waitForEvent(EVENT_FOCUS, textbox);
     textbox.takeFocus();
     await onFocus;
 
     testStates(textbox, STATE_FOCUSABLE | STATE_FOCUSED, 0);
+
+    is(
+      getAccessibleDOMNodeID(contentDocAcc.focusedChild),
+      "textbox",
+      "correct focusedChild from top doc"
+    );
+
+    is(
+      getAccessibleDOMNodeID(iframeDocAcc.focusedChild),
+      "textbox",
+      "correct focusedChild from child doc"
+    );
+
+    ok(!iframe.focusedChild, "correct focusedChild from iframe (null)");
+
+    onFocus = waitForEvent(EVENT_FOCUS, iframeDoc);
+    iframeDoc.takeFocus();
+    await onFocus;
+
+    is(
+      getAccessibleDOMNodeID(contentDocAcc.focusedChild),
+      "default-iframe-body-id",
+      "correct focusedChild of child doc from top doc"
+    );
+    is(
+      getAccessibleDOMNodeID(iframe.focusedChild),
+      "default-iframe-body-id",
+      "correct focusedChild of child doc from iframe"
+    );
   },
   { topLevel: false, iframe: true, remoteIframe: true }
 );