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 "OuterDocAccessible.h"
7 
8 #include "LocalAccessible-inl.h"
9 #include "nsAccUtils.h"
10 #include "DocAccessible-inl.h"
11 #include "mozilla/a11y/DocAccessibleChild.h"
12 #include "mozilla/a11y/DocAccessibleParent.h"
13 #if defined(XP_WIN)
14 #  include "mozilla/a11y/ProxyWrappers.h"
15 #endif
16 #include "mozilla/dom/BrowserBridgeChild.h"
17 #include "mozilla/dom/BrowserParent.h"
18 #include "Role.h"
19 #include "States.h"
20 
21 #ifdef A11Y_LOG
22 #  include "Logging.h"
23 #endif
24 
25 using namespace mozilla;
26 using namespace mozilla::a11y;
27 
28 ////////////////////////////////////////////////////////////////////////////////
29 // OuterDocAccessible
30 ////////////////////////////////////////////////////////////////////////////////
31 
OuterDocAccessible(nsIContent * aContent,DocAccessible * aDoc)32 OuterDocAccessible::OuterDocAccessible(nsIContent* aContent,
33                                        DocAccessible* aDoc)
34     : AccessibleWrap(aContent, aDoc) {
35   mType = eOuterDocType;
36 
37 #ifdef XP_WIN
38   if (DocAccessibleParent* remoteDoc = RemoteChildDoc()) {
39     remoteDoc->SendParentCOMProxy(this);
40   }
41 #endif
42 
43   if (IPCAccessibilityActive()) {
44     auto bridge = dom::BrowserBridgeChild::GetFrom(aContent);
45     if (bridge) {
46       // This is an iframe which will be rendered in another process.
47       SendEmbedderAccessible(bridge);
48     }
49   }
50 
51   // Request document accessible for the content document to make sure it's
52   // created. It will appended to outerdoc accessible children asynchronously.
53   dom::Document* outerDoc = mContent->GetUncomposedDoc();
54   if (outerDoc) {
55     dom::Document* innerDoc = outerDoc->GetSubDocumentFor(mContent);
56     if (innerDoc) GetAccService()->GetDocAccessible(innerDoc);
57   }
58 }
59 
~OuterDocAccessible()60 OuterDocAccessible::~OuterDocAccessible() {}
61 
SendEmbedderAccessible(dom::BrowserBridgeChild * aBridge)62 void OuterDocAccessible::SendEmbedderAccessible(
63     dom::BrowserBridgeChild* aBridge) {
64   MOZ_ASSERT(mDoc);
65   DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
66   if (ipcDoc) {
67     uint64_t id = reinterpret_cast<uintptr_t>(UniqueID());
68 #if defined(XP_WIN)
69     ipcDoc->SetEmbedderOnBridge(aBridge, id);
70 #else
71     aBridge->SendSetEmbedderAccessible(ipcDoc, id);
72 #endif
73   }
74 }
75 
76 ////////////////////////////////////////////////////////////////////////////////
77 // LocalAccessible public (DON'T add methods here)
78 
NativeRole() const79 role OuterDocAccessible::NativeRole() const { return roles::INTERNAL_FRAME; }
80 
LocalChildAtPoint(int32_t aX,int32_t aY,EWhichChildAtPoint aWhichChild)81 LocalAccessible* OuterDocAccessible::LocalChildAtPoint(
82     int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) {
83   nsIntRect docRect = Bounds();
84   if (!docRect.Contains(aX, aY)) return nullptr;
85 
86   // Always return the inner doc as direct child accessible unless bounds
87   // outside of it.
88   LocalAccessible* child = LocalChildAt(0);
89   NS_ENSURE_TRUE(child, nullptr);
90 
91   if (aWhichChild == Accessible::EWhichChildAtPoint::DeepestChild) {
92 #if defined(XP_WIN)
93     // On Windows, OuterDocAccessible::GetChildAt can return a proxy wrapper
94     // for a remote document. These aren't real Accessibles and
95     // shouldn't be returned except to the Windows a11y code (which doesn't use
96     // eDeepestChild). Calling ChildAtPoint on these will crash!
97     return nullptr;
98 #else
99     return child->LocalChildAtPoint(
100         aX, aY, Accessible::EWhichChildAtPoint::DeepestChild);
101 #endif  // defined(XP_WIN)
102   }
103   return child;
104 }
105 
106 ////////////////////////////////////////////////////////////////////////////////
107 // LocalAccessible public
108 
Shutdown()109 void OuterDocAccessible::Shutdown() {
110 #ifdef A11Y_LOG
111   if (logging::IsEnabled(logging::eDocDestroy)) logging::OuterDocDestroy(this);
112 #endif
113 
114   LocalAccessible* child = mChildren.SafeElementAt(0, nullptr);
115   if (child) {
116 #ifdef A11Y_LOG
117     if (logging::IsEnabled(logging::eDocDestroy)) {
118       logging::DocDestroy("outerdoc's child document rebind is scheduled",
119                           child->AsDoc()->DocumentNode());
120     }
121 #endif
122     RemoveChild(child);
123 
124     // XXX: sometimes outerdoc accessible is shutdown because of layout style
125     // change however the presshell of underlying document isn't destroyed and
126     // the document doesn't get pagehide events. Schedule a document rebind
127     // to its parent document. Otherwise a document accessible may be lost if
128     // its outerdoc has being recreated (see bug 862863 for details).
129     if (!mDoc->IsDefunct()) {
130       MOZ_ASSERT(!child->IsDefunct(),
131                  "Attempt to reattach shutdown document accessible");
132       if (!child->IsDefunct()) {
133         mDoc->BindChildDocument(child->AsDoc());
134       }
135     }
136   }
137 
138   AccessibleWrap::Shutdown();
139 }
140 
InsertChildAt(uint32_t aIdx,LocalAccessible * aAccessible)141 bool OuterDocAccessible::InsertChildAt(uint32_t aIdx,
142                                        LocalAccessible* aAccessible) {
143   MOZ_RELEASE_ASSERT(aAccessible->IsDoc(),
144                      "OuterDocAccessible can have a document child only!");
145 
146   // We keep showing the old document for a bit after creating the new one,
147   // and while building the new DOM and frame tree. That's done on purpose
148   // to avoid weird flashes of default background color.
149   // The old viewer will be destroyed after the new one is created.
150   // For a11y, it should be safe to shut down the old document now.
151   if (mChildren.Length()) mChildren[0]->Shutdown();
152 
153   if (!AccessibleWrap::InsertChildAt(0, aAccessible)) return false;
154 
155 #ifdef A11Y_LOG
156   if (logging::IsEnabled(logging::eDocCreate)) {
157     logging::DocCreate("append document to outerdoc",
158                        aAccessible->AsDoc()->DocumentNode());
159     logging::Address("outerdoc", this);
160   }
161 #endif
162 
163   return true;
164 }
165 
RemoveChild(LocalAccessible * aAccessible)166 bool OuterDocAccessible::RemoveChild(LocalAccessible* aAccessible) {
167   LocalAccessible* child = mChildren.SafeElementAt(0, nullptr);
168   MOZ_ASSERT(child == aAccessible, "Wrong child to remove!");
169   if (child != aAccessible) {
170     return false;
171   }
172 
173 #ifdef A11Y_LOG
174   if (logging::IsEnabled(logging::eDocDestroy)) {
175     logging::DocDestroy("remove document from outerdoc",
176                         child->AsDoc()->DocumentNode(), child->AsDoc());
177     logging::Address("outerdoc", this);
178   }
179 #endif
180 
181   bool wasRemoved = AccessibleWrap::RemoveChild(child);
182 
183   NS_ASSERTION(!mChildren.Length(),
184                "This child document of outerdoc accessible wasn't removed!");
185 
186   return wasRemoved;
187 }
188 
IsAcceptableChild(nsIContent * aEl) const189 bool OuterDocAccessible::IsAcceptableChild(nsIContent* aEl) const {
190   // outer document accessible doesn't not participate in ordinal tree
191   // mutations.
192   return false;
193 }
194 
195 #if defined(XP_WIN)
196 
RemoteChildDocAccessible() const197 LocalAccessible* OuterDocAccessible::RemoteChildDocAccessible() const {
198   RemoteAccessible* docProxy = RemoteChildDoc();
199   if (docProxy) {
200     // We're in the parent process, but we're embedding a remote document.
201     return WrapperFor(docProxy);
202   }
203 
204   if (IPCAccessibilityActive()) {
205     auto bridge = dom::BrowserBridgeChild::GetFrom(mContent);
206     if (bridge) {
207       // We're an iframe in a content process and we're embedding a remote
208       // document (in another content process). The COM proxy for the embedded
209       // document accessible was sent to us from the parent via PBrowserBridge.
210       return bridge->GetEmbeddedDocAccessible();
211     }
212   }
213 
214   return nullptr;
215 }
216 
217 // On Windows e10s, since we don't cache in the chrome process, LocalChildAt
218 // must be implemented so that we properly cross the chrome-to-content
219 // boundary when traversing.
220 
LocalChildAt(uint32_t aIndex) const221 LocalAccessible* OuterDocAccessible::LocalChildAt(uint32_t aIndex) const {
222   LocalAccessible* result = AccessibleWrap::LocalChildAt(aIndex);
223   if (result || aIndex) {
224     return result;
225   }
226   // If we are asking for child 0 and GetChildAt doesn't return anything, try
227   // to get the remote child doc and return that instead.
228   return RemoteChildDocAccessible();
229 }
230 
231 #endif  // defined(XP_WIN)
232 
233 // Accessible
234 
ChildCount() const235 uint32_t OuterDocAccessible::ChildCount() const {
236   uint32_t result = mChildren.Length();
237   if (!result &&
238 #if defined(XP_WIN)
239       ((StaticPrefs::accessibility_cache_enabled_AtStartup() &&
240         RemoteChildDoc()) ||
241        // On Windows with the cache disabled, as well as returning 1 for a
242        // remote document in the parent process, we also need to return 1 in a
243        // content process for an OOP iframe.
244        RemoteChildDocAccessible())
245 #else
246       RemoteChildDoc()
247 #endif
248   ) {
249     result = 1;
250   }
251   return result;
252 }
253 
ChildAt(uint32_t aIndex) const254 Accessible* OuterDocAccessible::ChildAt(uint32_t aIndex) const {
255   // We deliberately bypass OuterDocAccessible::LocalChildAt on Windows because
256   // it will return a RemoteAccessibleWrap for a remote document.
257   LocalAccessible* result = AccessibleWrap::LocalChildAt(aIndex);
258   if (result || aIndex) {
259     return result;
260   }
261 
262   return RemoteChildDoc();
263 }
264 
ChildAtPoint(int32_t aX,int32_t aY,EWhichChildAtPoint aWhichChild)265 Accessible* OuterDocAccessible::ChildAtPoint(int32_t aX, int32_t aY,
266                                              EWhichChildAtPoint aWhichChild) {
267   nsIntRect docRect = Bounds();
268   if (!docRect.Contains(aX, aY)) return nullptr;
269 
270   // Always return the inner doc as direct child accessible unless bounds
271   // outside of it.
272   Accessible* child = ChildAt(0);
273   NS_ENSURE_TRUE(child, nullptr);
274 
275   if (aWhichChild == EWhichChildAtPoint::DeepestChild) {
276     return child->ChildAtPoint(aX, aY, EWhichChildAtPoint::DeepestChild);
277   }
278   return child;
279 }
280 
RemoteChildDoc() const281 DocAccessibleParent* OuterDocAccessible::RemoteChildDoc() const {
282   dom::BrowserParent* tab = dom::BrowserParent::GetFrom(GetContent());
283   if (!tab) {
284     return nullptr;
285   }
286 
287   return tab->GetTopLevelDocAccessible();
288 }
289