1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/renderer/accessibility/render_accessibility_impl.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 
10 #include <algorithm>
11 #include <set>
12 #include <string>
13 #include <utility>
14 
15 #include "base/bind.h"
16 #include "base/command_line.h"
17 #include "base/containers/queue.h"
18 #include "base/debug/crash_logging.h"
19 #include "base/location.h"
20 #include "base/memory/ptr_util.h"
21 #include "base/single_thread_task_runner.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "base/timer/elapsed_timer.h"
26 #include "build/build_config.h"
27 #include "content/public/renderer/render_thread.h"
28 #include "content/renderer/accessibility/ax_action_target_factory.h"
29 #include "content/renderer/accessibility/ax_image_annotator.h"
30 #include "content/renderer/accessibility/blink_ax_action_target.h"
31 #include "content/renderer/accessibility/render_accessibility_manager.h"
32 #include "content/renderer/render_frame_impl.h"
33 #include "content/renderer/render_frame_proxy.h"
34 #include "content/renderer/render_view_impl.h"
35 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
36 #include "services/metrics/public/cpp/mojo_ukm_recorder.h"
37 #include "services/metrics/public/cpp/ukm_builders.h"
38 #include "third_party/blink/public/platform/task_type.h"
39 #include "third_party/blink/public/web/web_disallow_transition_scope.h"
40 #include "third_party/blink/public/web/web_document.h"
41 #include "third_party/blink/public/web/web_input_element.h"
42 #include "third_party/blink/public/web/web_local_frame.h"
43 #include "third_party/blink/public/web/web_settings.h"
44 #include "third_party/blink/public/web/web_view.h"
45 #include "ui/accessibility/accessibility_switches.h"
46 #include "ui/accessibility/ax_enum_util.h"
47 #include "ui/accessibility/ax_event_intent.h"
48 #include "ui/accessibility/ax_node.h"
49 #include "ui/accessibility/ax_role_properties.h"
50 
51 using blink::WebAXContext;
52 using blink::WebAXObject;
53 using blink::WebDocument;
54 using blink::WebElement;
55 using blink::WebLocalFrame;
56 using blink::WebNode;
57 using blink::WebRect;
58 using blink::WebSettings;
59 using blink::WebView;
60 
61 namespace {
62 
63 // The minimum amount of time in milliseconds that should be spent
64 // in serializing code in order to report the elapsed time as a URL-keyed
65 // metric.
66 constexpr int kMinSerializationTimeToSendInMS = 100;
67 
68 // When URL-keyed metrics for the amount of time spent in serializing code
69 // are sent, the minimum amount of time to wait, in seconds, before
70 // sending metrics. Metrics may also be sent once per page transition.
71 constexpr int kMinUKMDelayInSeconds = 300;
72 
SetAccessibilityCrashKey(ui::AXMode mode)73 void SetAccessibilityCrashKey(ui::AXMode mode) {
74   // Add a crash key with the ax_mode, to enable searching for top crashes that
75   // occur when accessibility is turned on. This adds it for each renderer,
76   // process, and elsewhere the same key is added for the browser process.
77   // Note: in theory multiple renderers in the same process might not have the
78   // same mode. As an example, kLabelImages could be enabled for just one
79   // renderer. The presence if a mode flag means in a crash report means at
80   // least one renderer in the same process had that flag.
81   // Examples of when multiple renderers could share the same process:
82   // 1) Android, 2) When many tabs are open.
83   static auto* ax_mode_crash_key = base::debug::AllocateCrashKeyString(
84       "ax_mode", base::debug::CrashKeySize::Size64);
85   if (ax_mode_crash_key)
86     base::debug::SetCrashKeyString(ax_mode_crash_key, mode.ToString());
87 }
88 
89 }
90 
91 namespace content {
92 
93 // Cap the number of nodes returned in an accessibility
94 // tree snapshot to avoid outrageous memory or bandwidth
95 // usage.
96 const size_t kMaxSnapshotNodeCount = 5000;
97 
AXTreeSnapshotterImpl(RenderFrameImpl * render_frame)98 AXTreeSnapshotterImpl::AXTreeSnapshotterImpl(RenderFrameImpl* render_frame)
99     : render_frame_(render_frame) {
100   DCHECK(render_frame->GetWebFrame());
101   blink::WebDocument document_ = render_frame->GetWebFrame()->GetDocument();
102   context_ = std::make_unique<WebAXContext>(document_);
103 }
104 
105 AXTreeSnapshotterImpl::~AXTreeSnapshotterImpl() = default;
106 
Snapshot(ui::AXMode ax_mode,size_t max_node_count,ui::AXTreeUpdate * response)107 void AXTreeSnapshotterImpl::Snapshot(ui::AXMode ax_mode,
108                                      size_t max_node_count,
109                                      ui::AXTreeUpdate* response) {
110   // Get a snapshot of the accessibility tree as an AXNodeData.
111   ui::AXTreeUpdate content_tree;
112   SnapshotContentTree(ax_mode, max_node_count, &content_tree);
113 
114   // As a sanity check, node_id_to_clear and event_from should be uninitialized
115   // if this is a full tree snapshot. They'd only be set to something if
116   // this was indeed a partial update to the tree (which we don't want).
117   DCHECK_EQ(0, content_tree.node_id_to_clear);
118   DCHECK_EQ(ax::mojom::EventFrom::kNone, content_tree.event_from);
119 
120   // We now have a complete serialization of the accessibility tree, but it
121   // includes a few fields we don't want to export outside of content/,
122   // so copy it into a more generic ui::AXTreeUpdate instead.
123   response->root_id = content_tree.root_id;
124   response->nodes.resize(content_tree.nodes.size());
125   response->node_id_to_clear = content_tree.node_id_to_clear;
126   response->event_from = content_tree.event_from;
127   response->nodes.assign(content_tree.nodes.begin(), content_tree.nodes.end());
128 }
129 
SnapshotContentTree(ui::AXMode ax_mode,size_t max_node_count,ui::AXTreeUpdate * response)130 void AXTreeSnapshotterImpl::SnapshotContentTree(ui::AXMode ax_mode,
131                                                 size_t max_node_count,
132                                                 ui::AXTreeUpdate* response) {
133   if (!render_frame_->GetWebFrame())
134     return;
135   if (!WebAXObject::MaybeUpdateLayoutAndCheckValidity(
136           render_frame_->GetWebFrame()->GetDocument()))
137     return;
138   WebAXObject root = context_->Root();
139 
140   BlinkAXTreeSource tree_source(render_frame_, ax_mode);
141   tree_source.SetRoot(root);
142   ScopedFreezeBlinkAXTreeSource freeze(&tree_source);
143 
144   // The serializer returns an ui::AXTreeUpdate, which can store a complete
145   // or a partial accessibility tree. AXTreeSerializer is stateful, but the
146   // first time you serialize from a brand-new tree you're guaranteed to get a
147   // complete tree.
148   BlinkAXTreeSerializer serializer(&tree_source);
149   if (max_node_count)
150     serializer.set_max_node_count(max_node_count);
151   if (serializer.SerializeChanges(root, response))
152     return;
153 
154   // It's possible for the page to fail to serialize the first time due to
155   // aria-owns rearranging the page while it's being scanned. Try a second
156   // time.
157   *response = ui::AXTreeUpdate();
158   if (serializer.SerializeChanges(root, response))
159     return;
160 
161   // It failed again. Clear the response object because it might have errors.
162   *response = ui::AXTreeUpdate();
163   LOG(WARNING) << "Unable to serialize accessibility tree.";
164 }
165 
166 // static
SnapshotAccessibilityTree(RenderFrameImpl * render_frame,ui::AXTreeUpdate * response,ui::AXMode ax_mode)167 void RenderAccessibilityImpl::SnapshotAccessibilityTree(
168     RenderFrameImpl* render_frame,
169     ui::AXTreeUpdate* response,
170     ui::AXMode ax_mode) {
171   TRACE_EVENT0("accessibility",
172                "RenderAccessibilityImpl::SnapshotAccessibilityTree");
173   DCHECK(render_frame);
174   DCHECK(response);
175   if (!render_frame->GetWebFrame())
176     return;
177 
178   AXTreeSnapshotterImpl snapshotter(render_frame);
179   snapshotter.SnapshotContentTree(ax_mode, kMaxSnapshotNodeCount, response);
180 }
181 
RenderAccessibilityImpl(RenderAccessibilityManager * const render_accessibility_manager,RenderFrameImpl * const render_frame,ui::AXMode mode)182 RenderAccessibilityImpl::RenderAccessibilityImpl(
183     RenderAccessibilityManager* const render_accessibility_manager,
184     RenderFrameImpl* const render_frame,
185     ui::AXMode mode)
186     : RenderFrameObserver(render_frame),
187       render_accessibility_manager_(render_accessibility_manager),
188       render_frame_(render_frame),
189       tree_source_(std::make_unique<BlinkAXTreeSource>(render_frame, mode)),
190       serializer_(std::make_unique<BlinkAXTreeSerializer>(tree_source_.get())),
191       plugin_tree_source_(nullptr),
192       last_scroll_offset_(gfx::Size()),
193       event_schedule_status_(EventScheduleStatus::kNotWaiting),
194       reset_token_(0),
195       ukm_timer_(std::make_unique<base::ElapsedTimer>()),
196       last_ukm_source_id_(ukm::kInvalidSourceId) {
197   mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
198   content::RenderThread::Get()->BindHostReceiver(
199       recorder.InitWithNewPipeAndPassReceiver());
200   ukm_recorder_ = std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
201   WebView* web_view = render_frame_->GetRenderView()->GetWebView();
202   WebSettings* settings = web_view->GetSettings();
203 
204   SetAccessibilityCrashKey(mode);
205 #if defined(OS_ANDROID)
206   // Password values are only passed through on Android.
207   settings->SetAccessibilityPasswordValuesEnabled(true);
208 #endif
209 
210 #if !defined(OS_ANDROID)
211   // Inline text boxes can be enabled globally on all except Android.
212   // On Android they can be requested for just a specific node.
213   if (mode.has_mode(ui::AXMode::kInlineTextBoxes))
214     settings->SetInlineTextBoxAccessibilityEnabled(true);
215 #endif
216 
217 #if defined(OS_MAC)
218   // aria-modal currently prunes the accessibility tree on Mac only.
219   settings->SetAriaModalPrunesAXTree(true);
220 #endif
221 
222 #if defined(OS_CHROMEOS)
223   // Do not ignore SVG grouping (<g>) elements on ChromeOS, which is needed so
224   // Select-to-Speak can read SVG text nodes in natural reading order.
225   settings->SetAccessibilityIncludeSvgGElement(true);
226 #endif
227 
228   event_schedule_mode_ = EventScheduleMode::kDeferEvents;
229 
230   // Optionally disable AXMenuList, which makes the internal pop-up menu
231   // UI for a select element directly accessible. Disable by default on
232   // Chrome OS, but some tests may override.
233   bool disable_ax_menu_list = false;
234 #if defined(OS_CHROMEOS)
235   disable_ax_menu_list = true;
236 #endif
237   auto* command_line = base::CommandLine::ForCurrentProcess();
238   if (command_line->HasSwitch(::switches::kDisableAXMenuList)) {
239     if (command_line->GetSwitchValueASCII(::switches::kDisableAXMenuList) ==
240         "false")
241       disable_ax_menu_list = false;
242     else
243       disable_ax_menu_list = true;
244   }
245   if (disable_ax_menu_list)
246     settings->SetUseAXMenuList(false);
247 
248   const WebDocument& document = GetMainDocument();
249   if (!document.IsNull()) {
250     ax_context_ = std::make_unique<WebAXContext>(document);
251     StartOrStopLabelingImages(ui::AXMode(), mode);
252 
253     // It's possible that the webview has already loaded a webpage without
254     // accessibility being enabled. Initialize the browser's cached
255     // accessibility tree by firing a layout complete for the document.
256     // Ensure that this occurs after initial layout is actually complete.
257     ScheduleSendPendingAccessibilityEvents();
258   }
259 
260   image_annotation_debugging_ =
261       base::CommandLine::ForCurrentProcess()->HasSwitch(
262           ::switches::kEnableExperimentalAccessibilityLabelsDebugging);
263 }
264 
265 RenderAccessibilityImpl::~RenderAccessibilityImpl() = default;
266 
DidCreateNewDocument()267 void RenderAccessibilityImpl::DidCreateNewDocument() {
268   const WebDocument& document = GetMainDocument();
269   if (!document.IsNull())
270     ax_context_ = std::make_unique<WebAXContext>(document);
271 }
272 
DidCommitProvisionalLoad(ui::PageTransition transition)273 void RenderAccessibilityImpl::DidCommitProvisionalLoad(
274     ui::PageTransition transition) {
275   has_injected_stylesheet_ = false;
276 
277   // If we have events scheduled, but not sent, cancel them
278   CancelScheduledEvents();
279   // Defer events during initial page load.
280   event_schedule_mode_ = EventScheduleMode::kDeferEvents;
281 
282   MaybeSendUKM();
283   slowest_serialization_ms_ = 0;
284   ukm_timer_ = std::make_unique<base::ElapsedTimer>();
285 
286   // Remove the image annotator if the page is loading and it was added for
287   // the one-shot image annotation (i.e. AXMode for image annotation is not
288   // set).
289   if (!ax_image_annotator_ ||
290       GetAccessibilityMode().has_mode(ui::AXMode::kLabelImages)) {
291     return;
292   }
293   tree_source_->RemoveImageAnnotator();
294   ax_image_annotator_->Destroy();
295   ax_image_annotator_.reset();
296   page_language_.clear();
297 }
298 
AccessibilityModeChanged(const ui::AXMode & mode)299 void RenderAccessibilityImpl::AccessibilityModeChanged(const ui::AXMode& mode) {
300   ui::AXMode old_mode = GetAccessibilityMode();
301   if (old_mode == mode)
302     return;
303   tree_source_->SetAccessibilityMode(mode);
304 
305   SetAccessibilityCrashKey(mode);
306 
307 #if !defined(OS_ANDROID)
308   // Inline text boxes can be enabled globally on all except Android.
309   // On Android they can be requested for just a specific node.
310   RenderView* render_view = render_frame_->GetRenderView();
311   if (render_view) {
312     WebView* web_view = render_view->GetWebView();
313     if (web_view) {
314       WebSettings* settings = web_view->GetSettings();
315       if (settings) {
316         if (mode.has_mode(ui::AXMode::kInlineTextBoxes)) {
317           settings->SetInlineTextBoxAccessibilityEnabled(true);
318           tree_source_->GetRoot().MaybeUpdateLayoutAndCheckValidity();
319           tree_source_->GetRoot().LoadInlineTextBoxes();
320         } else {
321           settings->SetInlineTextBoxAccessibilityEnabled(false);
322         }
323       }
324     }
325   }
326 #endif  // !defined(OS_ANDROID)
327 
328   serializer_->Reset();
329   const WebDocument& document = GetMainDocument();
330   if (!document.IsNull()) {
331     StartOrStopLabelingImages(old_mode, mode);
332 
333     // If there are any events in flight, |HandleAXEvent| will refuse to process
334     // our new event.
335     pending_events_.clear();
336     auto root_object = WebAXObject::FromWebDocument(document, false);
337     ax::mojom::Event event = root_object.IsLoaded()
338                                  ? ax::mojom::Event::kLoadComplete
339                                  : ax::mojom::Event::kLayoutComplete;
340     HandleAXEvent(ui::AXEvent(root_object.AxID(), event));
341   }
342 }
343 
HitTest(const gfx::Point & point,ax::mojom::Event event_to_fire,int request_id,mojom::RenderAccessibility::HitTestCallback callback)344 void RenderAccessibilityImpl::HitTest(
345     const gfx::Point& point,
346     ax::mojom::Event event_to_fire,
347     int request_id,
348     mojom::RenderAccessibility::HitTestCallback callback) {
349   WebAXObject ax_object;
350   const WebDocument& document = GetMainDocument();
351   if (!document.IsNull()) {
352     auto root_obj = WebAXObject::FromWebDocument(document);
353     if (root_obj.MaybeUpdateLayoutAndCheckValidity())
354       ax_object = root_obj.HitTest(point);
355   }
356 
357   // Return if no attached accessibility object was found for the main document.
358   if (ax_object.IsDetached()) {
359     std::move(callback).Run(/*hit_test_response=*/nullptr);
360     return;
361   }
362 
363   // If the result was in the same frame, return the result.
364   ui::AXNodeData data;
365   ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
366   tree_source_->SerializeNode(ax_object, &data);
367   if (!data.HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) {
368     // Optionally fire an event, if requested to. This is a good fit for
369     // features like touch exploration on Android, Chrome OS, and
370     // possibly other platforms - if the user explore a particular point,
371     // we fire a hover event on the nearest object under the point.
372     //
373     // Avoid using this mechanism to fire a particular sentinel event
374     // and then listen for that event to associate it with the hit test
375     // request. Instead, the mojo reply should be used directly.
376     if (event_to_fire != ax::mojom::Event::kNone) {
377       const std::vector<ui::AXEventIntent> intents;
378       HandleAXEvent(ui::AXEvent(ax_object.AxID(), event_to_fire,
379                                 ax::mojom::EventFrom::kAction, intents,
380                                 request_id));
381     }
382 
383     // Reply with the result.
384     const auto& frame_token = render_frame_->GetWebFrame()->GetFrameToken();
385     std::move(callback).Run(
386         mojom::HitTestResponse::New(frame_token, point, ax_object.AxID()));
387     return;
388   }
389 
390   // The result was in a child frame. Reply so that the
391   // client can do a hit test on the child frame recursively.
392   // If it's a remote frame, transform the point into the child frame's
393   // coordinate system.
394   gfx::Point transformed_point = point;
395   blink::WebFrame* child_frame =
396       blink::WebFrame::FromFrameOwnerElement(ax_object.GetNode());
397   DCHECK(child_frame);
398 
399   if (child_frame->IsWebRemoteFrame()) {
400     // Remote frames don't have access to the information from the visual
401     // viewport regarding the visual viewport offset, so we adjust the
402     // coordinates before sending them to the remote renderer.
403     WebRect rect = ax_object.GetBoundsInFrameCoordinates();
404     // The following transformation of the input point is naive, but works
405     // fairly well. It will fail with CSS transforms that rotate or shear.
406     // https://crbug.com/981959.
407     WebView* web_view = render_frame_->GetRenderView()->GetWebView();
408     gfx::PointF viewport_offset = web_view->VisualViewportOffset();
409     transformed_point +=
410         gfx::Vector2d(viewport_offset.x(), viewport_offset.y()) -
411         gfx::Rect(rect).OffsetFromOrigin();
412   }
413 
414   std::move(callback).Run(mojom::HitTestResponse::New(
415       child_frame->GetFrameToken(), transformed_point, ax_object.AxID()));
416 }
417 
PerformAction(const ui::AXActionData & data)418 void RenderAccessibilityImpl::PerformAction(const ui::AXActionData& data) {
419   const WebDocument& document = GetMainDocument();
420   if (document.IsNull())
421     return;
422 
423   auto root = WebAXObject::FromWebDocument(document);
424   if (!root.MaybeUpdateLayoutAndCheckValidity())
425     return;
426 
427   // If an action was requested, we no longer want to defer events.
428   event_schedule_mode_ = EventScheduleMode::kProcessEventsImmediately;
429 
430   std::unique_ptr<ui::AXActionTarget> target =
431       AXActionTargetFactory::CreateFromNodeId(document, plugin_tree_source_,
432                                               data.target_node_id);
433   std::unique_ptr<ui::AXActionTarget> anchor =
434       AXActionTargetFactory::CreateFromNodeId(document, plugin_tree_source_,
435                                               data.anchor_node_id);
436   std::unique_ptr<ui::AXActionTarget> focus =
437       AXActionTargetFactory::CreateFromNodeId(document, plugin_tree_source_,
438                                               data.focus_node_id);
439 
440   switch (data.action) {
441     case ax::mojom::Action::kBlur:
442       root.Focus();
443       break;
444     case ax::mojom::Action::kClearAccessibilityFocus:
445       target->ClearAccessibilityFocus();
446       break;
447     case ax::mojom::Action::kDecrement:
448       target->Decrement();
449       break;
450     case ax::mojom::Action::kDoDefault:
451       target->Click();
452       break;
453     case ax::mojom::Action::kGetImageData:
454       OnGetImageData(target.get(), data.target_rect.size());
455       break;
456     case ax::mojom::Action::kIncrement:
457       target->Increment();
458       break;
459     case ax::mojom::Action::kScrollToMakeVisible:
460       target->ScrollToMakeVisibleWithSubFocus(
461           data.target_rect, data.horizontal_scroll_alignment,
462           data.vertical_scroll_alignment, data.scroll_behavior);
463       break;
464     case ax::mojom::Action::kScrollToPoint:
465       target->ScrollToGlobalPoint(data.target_point);
466       break;
467     case ax::mojom::Action::kLoadInlineTextBoxes:
468       OnLoadInlineTextBoxes(target.get());
469       break;
470     case ax::mojom::Action::kFocus:
471       target->Focus();
472       break;
473     case ax::mojom::Action::kSetAccessibilityFocus:
474       target->SetAccessibilityFocus();
475       break;
476     case ax::mojom::Action::kSetScrollOffset:
477       target->SetScrollOffset(data.target_point);
478       break;
479     case ax::mojom::Action::kSetSelection:
480       anchor->SetSelection(anchor.get(), data.anchor_offset, focus.get(),
481                            data.focus_offset);
482       HandleAXEvent(
483           ui::AXEvent(root.AxID(), ax::mojom::Event::kLayoutComplete));
484       break;
485     case ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint:
486       target->SetSequentialFocusNavigationStartingPoint();
487       break;
488     case ax::mojom::Action::kSetValue:
489       target->SetValue(data.value);
490       break;
491     case ax::mojom::Action::kShowContextMenu:
492       target->ShowContextMenu();
493       break;
494     case ax::mojom::Action::kScrollBackward:
495     case ax::mojom::Action::kScrollForward:
496     case ax::mojom::Action::kScrollUp:
497     case ax::mojom::Action::kScrollDown:
498     case ax::mojom::Action::kScrollLeft:
499     case ax::mojom::Action::kScrollRight:
500       Scroll(target.get(), data.action);
501       break;
502     case ax::mojom::Action::kCustomAction:
503     case ax::mojom::Action::kCollapse:
504     case ax::mojom::Action::kExpand:
505     case ax::mojom::Action::kHitTest:
506     case ax::mojom::Action::kReplaceSelectedText:
507     case ax::mojom::Action::kNone:
508       NOTREACHED();
509       break;
510     case ax::mojom::Action::kGetTextLocation:
511       break;
512     case ax::mojom::Action::kAnnotatePageImages:
513       // Ensure we aren't already labeling images, in which case this should
514       // not change.
515       if (!ax_image_annotator_) {
516         CreateAXImageAnnotator();
517         // Walk the tree to discover images, and mark them dirty so that
518         // they get added to the annotator.
519         MarkAllAXObjectsDirty(ax::mojom::Role::kImage);
520       }
521       break;
522     case ax::mojom::Action::kSignalEndOfTest:
523       // Wait for 100ms to allow pending events to come in
524       base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
525 
526       HandleAXEvent(ui::AXEvent(root.AxID(), ax::mojom::Event::kEndOfTest));
527       break;
528     case ax::mojom::Action::kShowTooltip:
529     case ax::mojom::Action::kHideTooltip:
530     case ax::mojom::Action::kInternalInvalidateTree:
531       break;
532   }
533 }
534 
Reset(int32_t reset_token)535 void RenderAccessibilityImpl::Reset(int32_t reset_token) {
536   reset_token_ = reset_token;
537   serializer_->Reset();
538   pending_events_.clear();
539 
540   const WebDocument& document = GetMainDocument();
541   if (!document.IsNull()) {
542     // Tree-only mode gets used by the automation extension API which requires a
543     // load complete event to invoke listener callbacks.
544     auto root_object = WebAXObject::FromWebDocument(document, false);
545     ax::mojom::Event event = root_object.IsLoaded()
546                                  ? ax::mojom::Event::kLoadComplete
547                                  : ax::mojom::Event::kLayoutComplete;
548     HandleAXEvent(ui::AXEvent(root_object.AxID(), event));
549   }
550 }
551 
HandleWebAccessibilityEvent(const ui::AXEvent & event)552 void RenderAccessibilityImpl::HandleWebAccessibilityEvent(
553     const ui::AXEvent& event) {
554   HandleAXEvent(event);
555 }
556 
MarkWebAXObjectDirty(const WebAXObject & obj,bool subtree)557 void RenderAccessibilityImpl::MarkWebAXObjectDirty(const WebAXObject& obj,
558                                                    bool subtree) {
559   DirtyObject dirty_object;
560   dirty_object.obj = obj;
561   dirty_object.event_from = ax::mojom::EventFrom::kAction;
562   dirty_objects_.push_back(dirty_object);
563 
564   if (subtree)
565     serializer_->InvalidateSubtree(obj);
566 
567   // If the event occurred on the focused object, process immediately.
568   if (obj.IsFocused())
569     event_schedule_mode_ = EventScheduleMode::kProcessEventsImmediately;
570 
571   ScheduleSendPendingAccessibilityEvents();
572 }
573 
HandleAXEvent(const ui::AXEvent & event)574 void RenderAccessibilityImpl::HandleAXEvent(const ui::AXEvent& event) {
575   const WebDocument& document = GetMainDocument();
576   if (document.IsNull())
577     return;
578 
579   auto obj = WebAXObject::FromWebDocumentByID(document, event.id);
580   if (obj.IsDetached())
581     return;
582 
583 #if defined(OS_ANDROID)
584   // Force the newly focused node to be re-serialized so we include its
585   // inline text boxes.
586   if (event.event_type == ax::mojom::Event::kFocus)
587     serializer_->InvalidateSubtree(obj);
588 #endif
589 
590   // If a select tag is opened or closed, all the children must be updated
591   // because their visibility may have changed.
592   if (obj.Role() == ax::mojom::Role::kMenuListPopup &&
593       event.event_type == ax::mojom::Event::kChildrenChanged) {
594     WebAXObject popup_like_object = obj.ParentObject();
595     if (!popup_like_object.IsDetached()) {
596       serializer_->InvalidateSubtree(popup_like_object);
597       HandleAXEvent(ui::AXEvent(popup_like_object.AxID(),
598                                 ax::mojom::Event::kChildrenChanged));
599     }
600   }
601 
602   // Discard duplicate accessibility events.
603   for (const ui::AXEvent& pending_event : pending_events_) {
604     if (pending_event.id == event.id &&
605         pending_event.event_type == event.event_type) {
606       return;
607     }
608   }
609   pending_events_.push_back(event);
610   if (IsImmediateProcessingRequiredForEvent(event))
611     event_schedule_mode_ = EventScheduleMode::kProcessEventsImmediately;
612 
613   ScheduleSendPendingAccessibilityEvents();
614 }
615 
IsImmediateProcessingRequiredForEvent(const ui::AXEvent & event) const616 bool RenderAccessibilityImpl::IsImmediateProcessingRequiredForEvent(
617     const ui::AXEvent& event) const {
618   if (event_schedule_mode_ == EventScheduleMode::kProcessEventsImmediately)
619     return true;  // Already scheduled for immediate mode.
620 
621   if (event.event_from == ax::mojom::EventFrom::kAction)
622     return true;  // Actions should result in an immediate response.
623 
624   switch (event.event_type) {
625     case ax::mojom::Event::kActiveDescendantChanged:
626     case ax::mojom::Event::kBlur:
627     case ax::mojom::Event::kCheckedStateChanged:
628     case ax::mojom::Event::kClicked:
629     case ax::mojom::Event::kDocumentSelectionChanged:
630     case ax::mojom::Event::kFocus:
631     case ax::mojom::Event::kHover:
632     case ax::mojom::Event::kLoadComplete:
633     case ax::mojom::Event::kTextSelectionChanged:
634     case ax::mojom::Event::kValueChanged:
635       return true;
636 
637     case ax::mojom::Event::kAriaAttributeChanged:
638     case ax::mojom::Event::kChildrenChanged:
639     case ax::mojom::Event::kDocumentTitleChanged:
640     case ax::mojom::Event::kExpandedChanged:
641     case ax::mojom::Event::kHide:
642     case ax::mojom::Event::kLayoutComplete:
643     case ax::mojom::Event::kLocationChanged:
644     case ax::mojom::Event::kMenuListValueChanged:
645     case ax::mojom::Event::kRowCollapsed:
646     case ax::mojom::Event::kRowCountChanged:
647     case ax::mojom::Event::kRowExpanded:
648     case ax::mojom::Event::kScrollPositionChanged:
649     case ax::mojom::Event::kScrolledToAnchor:
650     case ax::mojom::Event::kSelectedChildrenChanged:
651     case ax::mojom::Event::kShow:
652     case ax::mojom::Event::kTextChanged:
653       return false;
654 
655     case ax::mojom::Event::kAlert:
656     case ax::mojom::Event::kAutocorrectionOccured:
657     case ax::mojom::Event::kControlsChanged:
658     case ax::mojom::Event::kEndOfTest:
659     case ax::mojom::Event::kFocusAfterMenuClose:
660     case ax::mojom::Event::kFocusContext:
661     case ax::mojom::Event::kHitTestResult:
662     case ax::mojom::Event::kImageFrameUpdated:
663     case ax::mojom::Event::kLoadStart:
664     case ax::mojom::Event::kLiveRegionCreated:
665     case ax::mojom::Event::kLiveRegionChanged:
666     case ax::mojom::Event::kMediaStartedPlaying:
667     case ax::mojom::Event::kMediaStoppedPlaying:
668     case ax::mojom::Event::kMenuEnd:
669     case ax::mojom::Event::kMenuPopupEnd:
670     case ax::mojom::Event::kMenuPopupStart:
671     case ax::mojom::Event::kMenuStart:
672     case ax::mojom::Event::kMouseCanceled:
673     case ax::mojom::Event::kMouseDragged:
674     case ax::mojom::Event::kMouseMoved:
675     case ax::mojom::Event::kMousePressed:
676     case ax::mojom::Event::kMouseReleased:
677     case ax::mojom::Event::kNone:
678     case ax::mojom::Event::kSelection:
679     case ax::mojom::Event::kSelectionAdd:
680     case ax::mojom::Event::kSelectionRemove:
681     case ax::mojom::Event::kStateChanged:
682     case ax::mojom::Event::kTooltipClosed:
683     case ax::mojom::Event::kTooltipOpened:
684     case ax::mojom::Event::kTreeChanged:
685     case ax::mojom::Event::kWindowActivated:
686     case ax::mojom::Event::kWindowDeactivated:
687     case ax::mojom::Event::kWindowVisibilityChanged:
688       // Never fired from Blink.
689       NOTREACHED() << "Event not expected from Blink: " << event.event_type;
690       return false;
691   }
692 }
693 
ShouldSerializeNodeForEvent(const WebAXObject & obj,const ui::AXEvent & event) const694 bool RenderAccessibilityImpl::ShouldSerializeNodeForEvent(
695     const WebAXObject& obj,
696     const ui::AXEvent& event) const {
697   if (obj.IsDetached())
698     return false;
699 
700   if (event.event_type == ax::mojom::Event::kTextSelectionChanged &&
701       !obj.IsNativeTextControl()) {
702     // Selection changes on non-native text controls cause no change to the
703     // control node's data.
704     //
705     // Selection offsets exposed via kTextSelStart and kTextSelEnd are only used
706     // for plain text controls, (input of a text field type, and textarea). Rich
707     // editable areas, such as contenteditables, use AXTreeData.
708     //
709     // TODO(nektar): Remove kTextSelStart and kTextSelEnd from the renderer.
710     return false;
711   }
712 
713   return true;
714 }
715 
GetDeferredEventsDelay()716 int RenderAccessibilityImpl::GetDeferredEventsDelay() {
717   // The amount of time, in milliseconds, to wait before sending non-interactive
718   // events that are deferred before the initial page load.
719   constexpr int kDelayForDeferredUpdatesBeforePageLoad = 350;
720 
721   // The amount of time, in milliseconds, to wait before sending non-interactive
722   // events that are deferred after the initial page load.
723   // Shync with same constant in CrossPlatformAccessibilityBrowserTest.
724   constexpr int kDelayForDeferredUpdatesAfterPageLoad = 150;
725 
726   // Prefer WebDocument::IsLoaded() over WebAXObject::IsLoaded() as the
727   // latter could trigger a layout update while retrieving the root
728   // WebAXObject.
729   return GetMainDocument().IsLoaded() ? kDelayForDeferredUpdatesAfterPageLoad
730                                       : kDelayForDeferredUpdatesBeforePageLoad;
731 }
732 
ScheduleSendPendingAccessibilityEvents(bool scheduling_from_task)733 void RenderAccessibilityImpl::ScheduleSendPendingAccessibilityEvents(
734     bool scheduling_from_task) {
735   // Don't send accessibility events for frames that are not in the frame tree
736   // yet (i.e., provisional frames used for remote-to-local navigations, which
737   // haven't committed yet).  Doing so might trigger layout, which may not work
738   // correctly for those frames.  The events should be sent once such a frame
739   // commits.
740   if (!render_frame_ || !render_frame_->in_frame_tree())
741     return;
742 
743   switch (event_schedule_status_) {
744     case EventScheduleStatus::kScheduledDeferred:
745       if (event_schedule_mode_ ==
746           EventScheduleMode::kProcessEventsImmediately) {
747         // Cancel scheduled deferred events so we can schedule events to be
748         // sent immediately.
749         CancelScheduledEvents();
750         break;
751       }
752       // We have already scheduled a task to send pending events.
753       return;
754     case EventScheduleStatus::kScheduledImmediate:
755       // The send pending events task have been scheduled, but has not started.
756       return;
757     case EventScheduleStatus::kWaitingForAck:
758       // Events have been sent, wait for ack.
759       return;
760     case EventScheduleStatus::kNotWaiting:
761       // Once the events have been handled, we schedule the pending events from
762       // that task. In this case, there would be a weak ptr still in use.
763       if (!scheduling_from_task &&
764           weak_factory_for_pending_events_.HasWeakPtrs())
765         return;
766       break;
767   }
768 
769   base::TimeDelta delay = base::TimeDelta::FromMilliseconds(0);
770   switch (event_schedule_mode_) {
771     case EventScheduleMode::kDeferEvents:
772       event_schedule_status_ = EventScheduleStatus::kScheduledDeferred;
773       // Where the user is not currently navigating or typing,
774       // process changes on a delay so that they occur in larger batches,
775       // improving efficiency of repetitive mutations.
776       delay = base::TimeDelta::FromMilliseconds(GetDeferredEventsDelay());
777       break;
778     case EventScheduleMode::kProcessEventsImmediately:
779       // This set of events needed to be processed immediately because of a
780       // page load or user action.
781       event_schedule_status_ = EventScheduleStatus::kScheduledImmediate;
782       delay = base::TimeDelta::FromMilliseconds(0);
783       break;
784   }
785 
786   // When no accessibility events are in-flight post a task to send
787   // the events to the browser. We use PostTask so that we can queue
788   // up additional events.
789   render_frame_->GetTaskRunner(blink::TaskType::kInternalDefault)
790       ->PostDelayedTask(
791           FROM_HERE,
792           base::BindOnce(
793               &RenderAccessibilityImpl::SendPendingAccessibilityEvents,
794               weak_factory_for_pending_events_.GetWeakPtr()),
795           delay);
796 }
797 
GenerateAXID()798 int RenderAccessibilityImpl::GenerateAXID() {
799   WebAXObject root = tree_source_->GetRoot();
800   return root.GenerateAXID();
801 }
802 
SetPluginTreeSource(PluginAXTreeSource * plugin_tree_source)803 void RenderAccessibilityImpl::SetPluginTreeSource(
804     PluginAXTreeSource* plugin_tree_source) {
805   plugin_tree_source_ = plugin_tree_source;
806   plugin_serializer_.reset(new PluginAXTreeSerializer(plugin_tree_source_));
807 
808   OnPluginRootNodeUpdated();
809 }
810 
OnPluginRootNodeUpdated()811 void RenderAccessibilityImpl::OnPluginRootNodeUpdated() {
812   // Search the accessibility tree for plugin's root object and post a
813   // children changed notification on it to force it to update the
814   // plugin accessibility tree.
815   WebAXObject obj = GetPluginRoot();
816   if (obj.IsNull())
817     return;
818 
819   HandleAXEvent(ui::AXEvent(obj.AxID(), ax::mojom::Event::kChildrenChanged));
820 }
821 
ShowPluginContextMenu()822 void RenderAccessibilityImpl::ShowPluginContextMenu() {
823   // Search the accessibility tree for plugin's root object and invoke
824   // ShowContextMenu() on it to show context menu for plugin.
825   WebAXObject obj = GetPluginRoot();
826   if (obj.IsNull())
827     return;
828 
829   const WebDocument& document = GetMainDocument();
830   if (document.IsNull())
831     return;
832 
833   std::unique_ptr<ui::AXActionTarget> target =
834       AXActionTargetFactory::CreateFromNodeId(document, plugin_tree_source_,
835                                               obj.AxID());
836   target->ShowContextMenu();
837 }
838 
GetMainDocument()839 WebDocument RenderAccessibilityImpl::GetMainDocument() {
840   if (render_frame_ && render_frame_->GetWebFrame())
841     return render_frame_->GetWebFrame()->GetDocument();
842   return WebDocument();
843 }
844 
GetLanguage()845 std::string RenderAccessibilityImpl::GetLanguage() {
846   return page_language_;
847 }
848 
SendPendingAccessibilityEvents()849 void RenderAccessibilityImpl::SendPendingAccessibilityEvents() {
850   TRACE_EVENT0("accessibility",
851                "RenderAccessibilityImpl::SendPendingAccessibilityEvents");
852   base::ElapsedTimer timer;
853 
854   // Clear status here in case we return early.
855   event_schedule_status_ = EventScheduleStatus::kNotWaiting;
856   WebDocument document = GetMainDocument();
857   if (document.IsNull())
858     return;
859 
860   if (needs_initial_ax_tree_root_) {
861     // At the very start of accessibility for this document, push a layout
862     // complete for the entire document, in order to initialize the browser's
863     // cached accessibility tree.
864     needs_initial_ax_tree_root_ = false;
865     auto obj = WebAXObject::FromWebDocument(document);
866     pending_events_.insert(
867         pending_events_.begin(),
868         ui::AXEvent(obj.AxID(), ax::mojom::Event::kLayoutComplete));
869   }
870 
871   if (pending_events_.empty() && dirty_objects_.empty()) {
872     // By default, assume the next batch does not have interactive events, and
873     // defer so that the batch of events is larger. If any interactive events
874     // come in, the batch will be processed immediately.
875     event_schedule_mode_ = EventScheduleMode::kDeferEvents;
876     return;
877   }
878 
879   // Update layout before snapshotting the events so that live state read from
880   // the DOM during freezing (e.g. which node currently has focus) is consistent
881   // with the events and node data we're about to send up.
882   WebAXObject::UpdateLayout(document);
883 
884   // Make a copy of the events, because it's possible that
885   // actions inside this loop will cause more events to be
886   // queued up.
887   std::vector<ui::AXEvent> src_events = pending_events_;
888   pending_events_.clear();
889 
890   // The serialized list of updates and events to send to the browser.
891   std::vector<ui::AXTreeUpdate> updates;
892   std::vector<ui::AXEvent> events;
893 
894   // Keep track of nodes in the tree that need to be updated.
895   std::vector<DirtyObject> dirty_objects = dirty_objects_;
896   dirty_objects_.clear();
897 
898   // If there's a layout complete or a scroll changed message, we need to send
899   // location changes.
900   bool need_to_send_location_changes = false;
901 
902   // If there's a load complete message, we need to change the event schedule
903   // mode.
904   bool had_load_complete_messages = false;
905 
906   ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
907 
908   WebAXObject root = tree_source_->GetRoot();
909 #if DCHECK_IS_ON()
910   // Never causes a document lifecycle change during serialization,
911   // because the assumption is that layout is in a safe, stable state.
912   blink::WebDisallowTransitionScope disallow(&document);
913 #endif
914 
915   // Save the page language.
916   page_language_ = root.Language().Utf8();
917 
918   // Loop over each event and generate an updated event message.
919   for (ui::AXEvent& event : src_events) {
920     if (event.event_type == ax::mojom::Event::kLayoutComplete)
921       need_to_send_location_changes = true;
922 
923     if (event.event_type == ax::mojom::Event::kLoadComplete)
924       had_load_complete_messages = true;
925 
926     auto obj = WebAXObject::FromWebDocumentByID(document, event.id);
927 
928     // Make sure the object still exists.
929     // TODO(accessibility) Change this to CheckValidity() if there aren't crash
930     // reports of illegal lifecycle changes from WebDisallowTransitionScope.
931     if (!obj.MaybeUpdateLayoutAndCheckValidity())
932       continue;
933 
934     // Make sure it's a descendant of our root node - exceptions include the
935     // scroll area that's the parent of the main document (we ignore it), and
936     // possibly nodes attached to a different document.
937     if (!tree_source_->IsInTree(obj))
938       continue;
939 
940     // If it's ignored, find the first ancestor that's not ignored.
941     //
942     // Note that "IsDetached()" also calls "IsNull()". Additionally,
943     // "ParentObject()" always gets the first ancestor that is included in tree
944     // (ignored or unignored), so it will never return objects that are not
945     // included in the tree at all.
946     if (!obj.IsDetached() && !obj.AccessibilityIsIncludedInTree())
947       obj = obj.ParentObject();
948     for (; !obj.IsDetached() && obj.AccessibilityIsIgnored();
949          obj = obj.ParentObject()) {
950       // There are 3 states of nodes that we care about here.
951       // (x) Unignored, included in tree
952       // [x] Ignored, included in tree
953       // <x> Ignored, excluded from tree
954       //
955       // Consider the following tree :
956       // ++(0) Role::kRootWebArea
957       // ++++<1> Role::kIgnored
958       // ++++++[2] Role::kGenericContainer <body>
959       // ++++++++[3] Role::kGenericContainer with 'visibility: hidden'
960       //
961       // If we modify [3] to be 'visibility: visible', we will receive
962       // Event::kChildrenChanged here for the Ignored parent [2].
963       // We must re-serialize the Unignored parent node (0) due to this
964       // change, but we must also re-serialize [2] since its children
965       // have changed. <1> was never part of the ax tree, and therefore
966       // does not need to be serialized.
967       // Note that [3] will be serialized to (3) during :
968       // |AXTreeSerializer<>::SerializeChangedNodes| when node [2] is
969       // being serialized, since it will detect the Ignored state had
970       // changed.
971       //
972       // Similarly, during Event::kTextChanged, if any Ignored,
973       // but included in tree ancestor uses NameFrom::kContents,
974       // they must also be re-serialized in case the name changed.
975       if (ShouldSerializeNodeForEvent(obj, event)) {
976         DirtyObject dirty_object;
977         dirty_object.obj = obj;
978         dirty_object.event_from = event.event_from;
979         dirty_object.event_intents = event.event_intents;
980         dirty_objects.push_back(dirty_object);
981       }
982     }
983 
984     events.push_back(event);
985 
986     VLOG(1) << "Accessibility event: " << ui::ToString(event.event_type)
987             << " on node id " << event.id;
988 
989     // Some events don't cause any changes to their associated objects.
990     if (ShouldSerializeNodeForEvent(obj, event)) {
991       DirtyObject dirty_object;
992       dirty_object.obj = obj;
993       dirty_object.event_from = event.event_from;
994       dirty_object.event_intents = event.event_intents;
995       dirty_objects.push_back(dirty_object);
996     }
997   }
998 
999   // Popups have a document lifecycle managed separately from the main document
1000   // but we need to return a combined accessibility tree for both.
1001   // We ensured layout validity for the main document in the loop above; if a
1002   // popup is open, do the same for it.
1003   WebDocument popup_document = GetPopupDocument();
1004   if (!popup_document.IsNull()) {
1005     WebAXObject popup_root_obj = WebAXObject::FromWebDocument(popup_document);
1006     if (!popup_root_obj.MaybeUpdateLayoutAndCheckValidity()) {
1007       // If a popup is open but we can't ensure its validity, return without
1008       // sending an update bundle, the same as we would for a node in the main
1009       // document.
1010       return;
1011     }
1012   }
1013 
1014 #if DCHECK_IS_ON()
1015   // Protect against lifecycle changes in the popup document, if any.
1016   // If no popup document, use the main document -- it's harmless to protect it
1017   // twice, and some document is needed because this cannot be done in an if
1018   // statement because it's scoped.
1019   WebDocument popup_or_main_document =
1020       popup_document.IsNull() ? document : popup_document;
1021   blink::WebDisallowTransitionScope disallow2(&popup_or_main_document);
1022 #endif
1023 
1024   // Keep track of if the host node for a plugin has been invalidated,
1025   // because if so, the plugin subtree will need to be re-serialized.
1026   bool invalidate_plugin_subtree = false;
1027   if (plugin_tree_source_ && !plugin_host_node_.IsDetached()) {
1028     invalidate_plugin_subtree = !serializer_->IsInClientTree(plugin_host_node_);
1029   }
1030 
1031   // Now serialize all dirty objects. Keep track of IDs serialized
1032   // so we don't have to serialize the same node twice.
1033   std::set<int32_t> already_serialized_ids;
1034   for (size_t i = 0; i < dirty_objects.size(); i++) {
1035     auto obj = dirty_objects[i].obj;
1036     // Dirty objects can be added using MarkWebAXObjectDirty(obj) from other
1037     // parts of the code as well, so we need to ensure the object still exists.
1038     // TODO(accessibility) Change this to CheckValidity() if there aren't crash
1039     // reports of illegal lifecycle changes from WebDisallowTransitionScope.
1040     if (!obj.MaybeUpdateLayoutAndCheckValidity())
1041       continue;
1042 
1043     // If the object in question is not included in the tree, get the
1044     // nearest ancestor that is (ParentObject() will do this for us).
1045     // Otherwise this can lead to the serializer doing extra work because
1046     // the object won't be in |already_serialized_ids|.
1047     if (!obj.AccessibilityIsIncludedInTree()) {
1048       obj = obj.ParentObject();
1049       if (obj.IsDetached())
1050         continue;
1051     }
1052 
1053     if (already_serialized_ids.find(obj.AxID()) != already_serialized_ids.end())
1054       continue;
1055 
1056     ui::AXTreeUpdate update;
1057     update.event_from = dirty_objects[i].event_from;
1058     update.event_intents = dirty_objects[i].event_intents;
1059     // If there's a plugin, force the tree data to be generated in every
1060     // message so the plugin can merge its own tree data changes.
1061     if (plugin_tree_source_)
1062       update.has_tree_data = true;
1063 
1064     if (!serializer_->SerializeChanges(obj, &update)) {
1065       VLOG(1) << "Failed to serialize one accessibility event.";
1066       continue;
1067     }
1068 
1069     if (update.node_id_to_clear > 0)
1070       invalidate_plugin_subtree = true;
1071 
1072     if (plugin_tree_source_)
1073       AddPluginTreeToUpdate(&update, invalidate_plugin_subtree);
1074 
1075     for (auto& node : update.nodes)
1076       already_serialized_ids.insert(node.id);
1077 
1078     updates.push_back(update);
1079 
1080     VLOG(1) << "Accessibility tree update:\n" << update.ToString();
1081   }
1082 
1083   event_schedule_status_ = EventScheduleStatus::kWaitingForAck;
1084   render_accessibility_manager_->HandleAccessibilityEvents(
1085       updates, events, reset_token_,
1086       base::BindOnce(&RenderAccessibilityImpl::OnAccessibilityEventsHandled,
1087                      weak_factory_for_pending_events_.GetWeakPtr()));
1088   reset_token_ = 0;
1089 
1090   if (need_to_send_location_changes)
1091     SendLocationChanges();
1092 
1093   if (had_load_complete_messages) {
1094     has_injected_stylesheet_ = false;
1095   }
1096 
1097   // Now that this batch is complete, assume the next batch does not have
1098   // interactive events, and defer so that the batch of events is larger.
1099   // If any interactive events come in, the batch will be processed immediately.
1100   event_schedule_mode_ = EventScheduleMode::kDeferEvents;
1101 
1102   if (image_annotation_debugging_)
1103     AddImageAnnotationDebuggingAttributes(updates);
1104 
1105   // Measure the amount of time spent in this function. Keep track of the
1106   // maximum within a time interval so we can upload UKM.
1107   int elapsed_time_ms = timer.Elapsed().InMilliseconds();
1108   if (elapsed_time_ms > slowest_serialization_ms_) {
1109     last_ukm_source_id_ = document.GetUkmSourceId();
1110     last_ukm_url_ = document.CanonicalUrlForSharing().GetString().Utf8();
1111     slowest_serialization_ms_ = elapsed_time_ms;
1112   }
1113 
1114   if (ukm_timer_->Elapsed().InSeconds() >= kMinUKMDelayInSeconds)
1115     MaybeSendUKM();
1116 }
1117 
SendLocationChanges()1118 void RenderAccessibilityImpl::SendLocationChanges() {
1119   TRACE_EVENT0("accessibility", "RenderAccessibilityImpl::SendLocationChanges");
1120 
1121   std::vector<mojom::LocationChangesPtr> changes;
1122 
1123   // Update layout on the root of the tree.
1124   WebAXObject root = tree_source_->GetRoot();
1125 
1126   // TODO(accessibility) Change this to CheckValidity() if there aren't crash
1127   // reports of illegal lifecycle changes from WebDisallowTransitionScope.
1128   if (!root.MaybeUpdateLayoutAndCheckValidity())
1129     return;
1130 
1131   blink::WebVector<WebAXObject> changed_bounds_objects;
1132   root.GetAllObjectsWithChangedBounds(changed_bounds_objects);
1133   for (const WebAXObject& obj : changed_bounds_objects) {
1134     // See if we had a previous location. If not, this whole subtree must
1135     // be new, so no need to update.
1136     int id = obj.AxID();
1137     if (!tree_source_->HasCachedBoundingBox(id))
1138       continue;
1139 
1140     // If the location has changed, append it to the IPC message.
1141     ui::AXRelativeBounds new_location;
1142     tree_source_->PopulateAXRelativeBounds(obj, &new_location);
1143     if (new_location != tree_source_->GetCachedBoundingBox(id))
1144       changes.push_back(mojom::LocationChanges::New(id, new_location));
1145 
1146     // Save the new location.
1147     tree_source_->SetCachedBoundingBox(id, new_location);
1148   }
1149 
1150   if (changes.empty())
1151     return;
1152 
1153   // Ensure that the number of cached bounding boxes doesn't exceed the
1154   // number of nodes in the tree, that would indicate the cache could
1155   // grow without bounds. Calls from the serializer to
1156   // BlinkAXTreeSerializer::SerializerClearedNode are supposed to keep the
1157   // cache trimmed to only actual nodes in the tree.
1158   DCHECK_LE(tree_source_->GetCachedBoundingBoxCount(),
1159             serializer_->ClientTreeNodeCount());
1160 
1161   render_accessibility_manager_->HandleLocationChanges(std::move(changes));
1162 }
1163 
OnAccessibilityEventsHandled()1164 void RenderAccessibilityImpl::OnAccessibilityEventsHandled() {
1165   DCHECK_EQ(event_schedule_status_, EventScheduleStatus::kWaitingForAck);
1166   event_schedule_status_ = EventScheduleStatus::kNotWaiting;
1167   switch (event_schedule_mode_) {
1168     case EventScheduleMode::kDeferEvents:
1169       ScheduleSendPendingAccessibilityEvents(true);
1170       break;
1171     case EventScheduleMode::kProcessEventsImmediately:
1172       SendPendingAccessibilityEvents();
1173       break;
1174   }
1175 }
1176 
OnLoadInlineTextBoxes(const ui::AXActionTarget * target)1177 void RenderAccessibilityImpl::OnLoadInlineTextBoxes(
1178     const ui::AXActionTarget* target) {
1179   const BlinkAXActionTarget* blink_target =
1180       BlinkAXActionTarget::FromAXActionTarget(target);
1181   if (!blink_target)
1182     return;
1183   const WebAXObject& obj = blink_target->WebAXObject();
1184 
1185   ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
1186   if (tree_source_->ShouldLoadInlineTextBoxes(obj))
1187     return;
1188 
1189   tree_source_->SetLoadInlineTextBoxesForId(obj.AxID());
1190 
1191   const WebDocument& document = GetMainDocument();
1192   if (document.IsNull())
1193     return;
1194 
1195   // This object may not be a leaf node. Force the whole subtree to be
1196   // re-serialized.
1197   serializer_->InvalidateSubtree(obj);
1198 
1199   // Explicitly send a tree change update event now.
1200   event_schedule_mode_ = EventScheduleMode::kProcessEventsImmediately;
1201   HandleAXEvent(ui::AXEvent(obj.AxID(), ax::mojom::Event::kTreeChanged));
1202 }
1203 
OnGetImageData(const ui::AXActionTarget * target,const gfx::Size & max_size)1204 void RenderAccessibilityImpl::OnGetImageData(const ui::AXActionTarget* target,
1205                                              const gfx::Size& max_size) {
1206   const BlinkAXActionTarget* blink_target =
1207       BlinkAXActionTarget::FromAXActionTarget(target);
1208   if (!blink_target)
1209     return;
1210   const WebAXObject& obj = blink_target->WebAXObject();
1211 
1212   ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
1213   if (tree_source_->image_data_node_id() == obj.AxID())
1214     return;
1215 
1216   tree_source_->set_image_data_node_id(obj.AxID());
1217   tree_source_->set_max_image_data_size(max_size);
1218 
1219   const WebDocument& document = GetMainDocument();
1220   if (document.IsNull())
1221     return;
1222 
1223   serializer_->InvalidateSubtree(obj);
1224   event_schedule_mode_ = EventScheduleMode::kProcessEventsImmediately;
1225   HandleAXEvent(ui::AXEvent(obj.AxID(), ax::mojom::Event::kImageFrameUpdated));
1226 }
1227 
OnDestruct()1228 void RenderAccessibilityImpl::OnDestruct() {
1229   render_frame_ = nullptr;
1230   delete this;
1231 }
1232 
AddPluginTreeToUpdate(ui::AXTreeUpdate * update,bool invalidate_plugin_subtree)1233 void RenderAccessibilityImpl::AddPluginTreeToUpdate(
1234     ui::AXTreeUpdate* update,
1235     bool invalidate_plugin_subtree) {
1236   const WebDocument& document = GetMainDocument();
1237   if (invalidate_plugin_subtree)
1238     plugin_serializer_->Reset();
1239 
1240   for (size_t i = 0; i < update->nodes.size(); ++i) {
1241     if (update->nodes[i].role == ax::mojom::Role::kEmbeddedObject) {
1242       plugin_host_node_ =
1243           WebAXObject::FromWebDocumentByID(document, update->nodes[i].id);
1244 
1245       const ui::AXNode* root = plugin_tree_source_->GetRoot();
1246       update->nodes[i].child_ids.push_back(root->id());
1247 
1248       ui::AXTreeUpdate plugin_update;
1249       plugin_serializer_->SerializeChanges(root, &plugin_update);
1250 
1251       size_t old_count = update->nodes.size();
1252       size_t new_count = plugin_update.nodes.size();
1253       update->nodes.resize(old_count + new_count);
1254       for (size_t j = 0; j < new_count; ++j)
1255         update->nodes[old_count + j] = plugin_update.nodes[j];
1256       break;
1257     }
1258   }
1259 
1260   if (plugin_tree_source_->GetTreeData(&update->tree_data))
1261     update->has_tree_data = true;
1262 }
1263 
CreateAXImageAnnotator()1264 void RenderAccessibilityImpl::CreateAXImageAnnotator() {
1265   if (!render_frame_)
1266     return;
1267   mojo::PendingRemote<image_annotation::mojom::Annotator> annotator;
1268   render_frame_->GetBrowserInterfaceBroker()->GetInterface(
1269       annotator.InitWithNewPipeAndPassReceiver());
1270 
1271   ax_image_annotator_ =
1272       std::make_unique<AXImageAnnotator>(this, std::move(annotator));
1273   tree_source_->AddImageAnnotator(ax_image_annotator_.get());
1274 }
1275 
StartOrStopLabelingImages(ui::AXMode old_mode,ui::AXMode new_mode)1276 void RenderAccessibilityImpl::StartOrStopLabelingImages(ui::AXMode old_mode,
1277                                                         ui::AXMode new_mode) {
1278   if (!render_frame_)
1279     return;
1280 
1281   if (!old_mode.has_mode(ui::AXMode::kLabelImages) &&
1282       new_mode.has_mode(ui::AXMode::kLabelImages)) {
1283     CreateAXImageAnnotator();
1284   } else if (old_mode.has_mode(ui::AXMode::kLabelImages) &&
1285              !new_mode.has_mode(ui::AXMode::kLabelImages)) {
1286     tree_source_->RemoveImageAnnotator();
1287     ax_image_annotator_->Destroy();
1288     ax_image_annotator_.reset();
1289   }
1290 }
1291 
MarkAllAXObjectsDirty(ax::mojom::Role role)1292 void RenderAccessibilityImpl::MarkAllAXObjectsDirty(ax::mojom::Role role) {
1293   ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
1294   base::queue<WebAXObject> objs_to_explore;
1295   objs_to_explore.push(tree_source_->GetRoot());
1296   while (objs_to_explore.size()) {
1297     WebAXObject obj = objs_to_explore.front();
1298     objs_to_explore.pop();
1299 
1300     if (obj.Role() == role)
1301       MarkWebAXObjectDirty(obj, /* subtree */ false);
1302 
1303     std::vector<blink::WebAXObject> children;
1304     tree_source_->GetChildren(obj, &children);
1305     for (WebAXObject& child : children)
1306       objs_to_explore.push(child);
1307   }
1308 }
1309 
Scroll(const ui::AXActionTarget * target,ax::mojom::Action scroll_action)1310 void RenderAccessibilityImpl::Scroll(const ui::AXActionTarget* target,
1311                                      ax::mojom::Action scroll_action) {
1312   gfx::Rect bounds = target->GetRelativeBounds();
1313   if (bounds.IsEmpty())
1314     return;
1315 
1316   gfx::Point initial = target->GetScrollOffset();
1317   gfx::Point min = target->MinimumScrollOffset();
1318   gfx::Point max = target->MaximumScrollOffset();
1319 
1320   // TODO(anastasi): This 4/5ths came from the Android implementation, revisit
1321   // to find the appropriate modifier to keep enough context onscreen after
1322   // scrolling.
1323   int page_x = std::max((int)(bounds.width() * 4 / 5), 1);
1324   int page_y = std::max((int)(bounds.height() * 4 / 5), 1);
1325 
1326   // Forward/backward defaults to down/up unless it can only be scrolled
1327   // horizontally.
1328   if (scroll_action == ax::mojom::Action::kScrollForward)
1329     scroll_action = max.y() > min.y() ? ax::mojom::Action::kScrollDown
1330                                       : ax::mojom::Action::kScrollRight;
1331   if (scroll_action == ax::mojom::Action::kScrollBackward)
1332     scroll_action = max.y() > min.y() ? ax::mojom::Action::kScrollUp
1333                                       : ax::mojom::Action::kScrollLeft;
1334 
1335   int x = initial.x();
1336   int y = initial.y();
1337   switch (scroll_action) {
1338     case ax::mojom::Action::kScrollUp:
1339       if (initial.y() == min.y())
1340         return;
1341       y = std::max(initial.y() - page_y, min.y());
1342       break;
1343     case ax::mojom::Action::kScrollDown:
1344       if (initial.y() == max.y())
1345         return;
1346       y = std::min(initial.y() + page_y, max.y());
1347       break;
1348     case ax::mojom::Action::kScrollLeft:
1349       if (initial.x() == min.x())
1350         return;
1351       x = std::max(initial.x() - page_x, min.x());
1352       break;
1353     case ax::mojom::Action::kScrollRight:
1354       if (initial.x() == max.x())
1355         return;
1356       x = std::min(initial.x() + page_x, max.x());
1357       break;
1358     default:
1359       NOTREACHED();
1360   }
1361 
1362   target->SetScrollOffset(gfx::Point(x, y));
1363 }
1364 
AddImageAnnotationDebuggingAttributes(const std::vector<ui::AXTreeUpdate> & updates)1365 void RenderAccessibilityImpl::AddImageAnnotationDebuggingAttributes(
1366     const std::vector<ui::AXTreeUpdate>& updates) {
1367   DCHECK(image_annotation_debugging_);
1368 
1369   for (auto& update : updates) {
1370     for (auto& node : update.nodes) {
1371       if (!node.HasIntAttribute(
1372               ax::mojom::IntAttribute::kImageAnnotationStatus))
1373         continue;
1374 
1375       ax::mojom::ImageAnnotationStatus status = node.GetImageAnnotationStatus();
1376       bool should_set_attributes = false;
1377       switch (status) {
1378         case ax::mojom::ImageAnnotationStatus::kNone:
1379         case ax::mojom::ImageAnnotationStatus::kWillNotAnnotateDueToScheme:
1380         case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
1381         case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
1382         case ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation:
1383           break;
1384         case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
1385         case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
1386         case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
1387         case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
1388         case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
1389           should_set_attributes = true;
1390           break;
1391       }
1392 
1393       if (!should_set_attributes)
1394         continue;
1395 
1396       WebDocument document = GetMainDocument();
1397       if (document.IsNull())
1398         continue;
1399       WebAXObject obj = WebAXObject::FromWebDocumentByID(document, node.id);
1400       if (obj.IsDetached())
1401         continue;
1402 
1403       if (!has_injected_stylesheet_) {
1404         document.InsertStyleSheet(
1405             "[imageannotation=annotationPending] { outline: 3px solid #9ff; } "
1406             "[imageannotation=annotationSucceeded] { outline: 3px solid #3c3; "
1407             "} "
1408             "[imageannotation=annotationEmpty] { outline: 3px solid #ee6; } "
1409             "[imageannotation=annotationAdult] { outline: 3px solid #f90; } "
1410             "[imageannotation=annotationProcessFailed] { outline: 3px solid "
1411             "#c00; } ");
1412         has_injected_stylesheet_ = true;
1413       }
1414 
1415       WebNode web_node = obj.GetNode();
1416       if (web_node.IsNull() || !web_node.IsElementNode())
1417         continue;
1418 
1419       WebElement element = web_node.To<WebElement>();
1420       std::string status_str = ui::ToString(status);
1421       if (element.GetAttribute("imageannotation").Utf8() != status_str)
1422         element.SetAttribute("imageannotation",
1423                              blink::WebString::FromUTF8(status_str));
1424 
1425       std::string title = "%" + status_str;
1426       std::string annotation =
1427           node.GetStringAttribute(ax::mojom::StringAttribute::kImageAnnotation);
1428       if (!annotation.empty())
1429         title = title + ": " + annotation;
1430       if (element.GetAttribute("title").Utf8() != title) {
1431         element.SetAttribute("title", blink::WebString::FromUTF8(title));
1432       }
1433     }
1434   }
1435 }
1436 
GetPopupDocument()1437 blink::WebDocument RenderAccessibilityImpl::GetPopupDocument() {
1438   blink::WebPagePopup* popup =
1439       render_frame_->GetRenderView()->GetWebView()->GetPagePopup();
1440   if (popup)
1441     return popup->GetDocument();
1442   return WebDocument();
1443 }
1444 
GetPluginRoot()1445 WebAXObject RenderAccessibilityImpl::GetPluginRoot() {
1446   ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
1447   WebAXObject root = tree_source_->GetRoot();
1448   if (!root.MaybeUpdateLayoutAndCheckValidity())
1449     return WebAXObject();
1450 
1451   base::queue<WebAXObject> objs_to_explore;
1452   objs_to_explore.push(root);
1453   while (objs_to_explore.size()) {
1454     WebAXObject obj = objs_to_explore.front();
1455     objs_to_explore.pop();
1456 
1457     WebNode node = obj.GetNode();
1458     if (!node.IsNull() && node.IsElementNode()) {
1459       WebElement element = node.To<WebElement>();
1460       if (element.HasHTMLTagName("embed")) {
1461         return obj;
1462       }
1463     }
1464 
1465     // Explore children of this object.
1466     std::vector<WebAXObject> children;
1467     tree_source_->GetChildren(obj, &children);
1468     for (const auto& child : children)
1469       objs_to_explore.push(child);
1470   }
1471 
1472   return WebAXObject();
1473 }
1474 
CancelScheduledEvents()1475 void RenderAccessibilityImpl::CancelScheduledEvents() {
1476   switch (event_schedule_status_) {
1477     case EventScheduleStatus::kScheduledDeferred:
1478     case EventScheduleStatus::kScheduledImmediate:  // Fallthrough
1479       weak_factory_for_pending_events_.InvalidateWeakPtrs();
1480       event_schedule_status_ = EventScheduleStatus::kNotWaiting;
1481       break;
1482     case EventScheduleStatus::kWaitingForAck:
1483     case EventScheduleStatus::kNotWaiting:  // Fallthrough
1484       break;
1485   }
1486 }
1487 
MaybeSendUKM()1488 void RenderAccessibilityImpl::MaybeSendUKM() {
1489   if (slowest_serialization_ms_ < kMinSerializationTimeToSendInMS)
1490     return;
1491 
1492   ukm::builders::Accessibility_Renderer(last_ukm_source_id_)
1493       .SetCpuTime_SendPendingAccessibilityEvents(slowest_serialization_ms_)
1494       .Record(ukm_recorder_.get());
1495   ResetUKMData();
1496 }
1497 
ResetUKMData()1498 void RenderAccessibilityImpl::ResetUKMData() {
1499   slowest_serialization_ms_ = 0;
1500   ukm_timer_ = std::make_unique<base::ElapsedTimer>();
1501   last_ukm_source_id_ = ukm::kInvalidSourceId;
1502   last_ukm_url_ = "";
1503 }
1504 
1505 RenderAccessibilityImpl::DirtyObject::DirtyObject() = default;
1506 RenderAccessibilityImpl::DirtyObject::DirtyObject(const DirtyObject& other) =
1507     default;
1508 RenderAccessibilityImpl::DirtyObject::~DirtyObject() = default;
1509 
1510 }  // namespace content
1511