1 // Copyright 2016 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 "components/pdf/renderer/pdf_accessibility_tree.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/i18n/break_iterator.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/notreached.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversion_utils.h"
15 #include "components/pdf/renderer/pdf_ax_action_target.h"
16 #include "components/strings/grit/components_strings.h"
17 #include "content/public/renderer/pepper_plugin_instance.h"
18 #include "content/public/renderer/render_accessibility.h"
19 #include "content/public/renderer/render_frame.h"
20 #include "content/public/renderer/render_thread.h"
21 #include "content/public/renderer/render_view.h"
22 #include "content/public/renderer/renderer_ppapi_host.h"
23 #include "pdf/pdf_features.h"
24 #include "third_party/blink/public/strings/grit/blink_strings.h"
25 #include "ui/accessibility/ax_enums.mojom.h"
26 #include "ui/accessibility/null_ax_action_target.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/gfx/geometry/rect_conversions.h"
29 #include "ui/gfx/transform.h"
30 
31 namespace pdf {
32 
33 namespace {
34 
35 // Don't try to apply font size thresholds to automatically identify headings
36 // if the median font size is not at least this many points.
37 const float kMinimumFontSize = 5.0f;
38 
39 // Don't try to apply paragraph break thresholds to automatically identify
40 // paragraph breaks if the median line break is not at least this many points.
41 const float kMinimumLineSpacing = 5.0f;
42 
43 // Ratio between the font size of one text run and the median on the page
44 // for that text run to be considered to be a heading instead of normal text.
45 const float kHeadingFontSizeRatio = 1.2f;
46 
47 // Ratio between the line spacing between two lines and the median on the
48 // page for that line spacing to be considered a paragraph break.
49 const float kParagraphLineSpacingRatio = 1.2f;
50 
PpFloatRectToGfxRectF(const PP_FloatRect & r)51 gfx::RectF PpFloatRectToGfxRectF(const PP_FloatRect& r) {
52   return gfx::RectF(r.point.x, r.point.y, r.size.width, r.size.height);
53 }
54 
PPRectToGfxRectF(const PP_Rect & r)55 gfx::RectF PPRectToGfxRectF(const PP_Rect& r) {
56   return gfx::RectF(r.point.x, r.point.y, r.size.width, r.size.height);
57 }
58 
PpPointToVector2dF(const PP_Point & p)59 gfx::Vector2dF PpPointToVector2dF(const PP_Point& p) {
60   return gfx::Vector2dF(p.x, p.y);
61 }
62 
63 // This class is used as part of our heuristic to determine which text runs live
64 // on the same "line".  As we process runs, we keep a weighted average of the
65 // top and bottom coordinates of the line, and if a new run falls within that
66 // range (within a threshold) it is considered part of the line.
67 class LineHelper {
68  public:
LineHelper(const std::vector<ppapi::PdfAccessibilityTextRunInfo> & text_runs)69   explicit LineHelper(
70       const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs)
71       : text_runs_(text_runs) {
72     StartNewLine(0);
73   }
74 
StartNewLine(size_t current_index)75   void StartNewLine(size_t current_index) {
76     DCHECK(current_index == 0 || current_index < text_runs_.size());
77     start_index_ = current_index;
78     accumulated_weight_top_ = 0.0f;
79     accumulated_weight_bottom_ = 0.0f;
80     accumulated_width_ = 0.0f;
81   }
82 
ProcessNextRun(size_t run_index)83   void ProcessNextRun(size_t run_index) {
84     DCHECK_LT(run_index, text_runs_.size());
85     RemoveOldRunsUpTo(run_index);
86     AddRun(text_runs_[run_index].bounds);
87   }
88 
IsRunOnSameLine(size_t run_index) const89   bool IsRunOnSameLine(size_t run_index) const {
90     DCHECK_LT(run_index, text_runs_.size());
91 
92     // Calculate new top/bottom bounds for our line.
93     if (accumulated_width_ == 0.0f)
94       return false;
95 
96     float line_top = accumulated_weight_top_ / accumulated_width_;
97     float line_bottom = accumulated_weight_bottom_ / accumulated_width_;
98 
99     // Look at the next run, and determine how much it overlaps the line.
100     const auto& run_bounds = text_runs_[run_index].bounds;
101     if (run_bounds.size.height == 0.0f)
102       return false;
103 
104     float clamped_top = std::max(line_top, run_bounds.point.y);
105     float clamped_bottom =
106         std::min(line_bottom, run_bounds.point.y + run_bounds.size.height);
107     if (clamped_bottom < clamped_top)
108       return false;
109 
110     float coverage = (clamped_bottom - clamped_top) / (run_bounds.size.height);
111 
112     // See if it falls within the line (within our threshold).
113     constexpr float kLineCoverageThreshold = 0.25f;
114     return coverage > kLineCoverageThreshold;
115   }
116 
117  private:
AddRun(const PP_FloatRect & run_bounds)118   void AddRun(const PP_FloatRect& run_bounds) {
119     float run_width = fabsf(run_bounds.size.width);
120     accumulated_width_ += run_width;
121     accumulated_weight_top_ += run_bounds.point.y * run_width;
122     accumulated_weight_bottom_ +=
123         (run_bounds.point.y + run_bounds.size.height) * run_width;
124   }
125 
RemoveRun(const PP_FloatRect & run_bounds)126   void RemoveRun(const PP_FloatRect& run_bounds) {
127     float run_width = fabsf(run_bounds.size.width);
128     accumulated_width_ -= run_width;
129     accumulated_weight_top_ -= run_bounds.point.y * run_width;
130     accumulated_weight_bottom_ -=
131         (run_bounds.point.y + run_bounds.size.height) * run_width;
132   }
133 
RemoveOldRunsUpTo(size_t stop_index)134   void RemoveOldRunsUpTo(size_t stop_index) {
135     // Remove older runs from the weighted average if we've exceeded the
136     // threshold distance from them. We remove them to prevent e.g. drop-caps
137     // from unduly influencing future lines.
138     constexpr float kBoxRemoveWidthThreshold = 3.0f;
139     while (start_index_ < stop_index &&
140            accumulated_width_ > text_runs_[start_index_].bounds.size.width *
141                                     kBoxRemoveWidthThreshold) {
142       const auto& old_bounds = text_runs_[start_index_].bounds;
143       RemoveRun(old_bounds);
144       start_index_++;
145     }
146   }
147 
148   const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs_;
149   size_t start_index_;
150   float accumulated_weight_top_;
151   float accumulated_weight_bottom_;
152   float accumulated_width_;
153 
154   DISALLOW_COPY_AND_ASSIGN(LineHelper);
155 };
156 
ComputeParagraphAndHeadingThresholds(const std::vector<ppapi::PdfAccessibilityTextRunInfo> & text_runs,float * out_heading_font_size_threshold,float * out_paragraph_spacing_threshold)157 void ComputeParagraphAndHeadingThresholds(
158     const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs,
159     float* out_heading_font_size_threshold,
160     float* out_paragraph_spacing_threshold) {
161   // Scan over the font sizes and line spacing within this page and
162   // set heuristic thresholds so that text larger than the median font
163   // size can be marked as a heading, and spacing larger than the median
164   // line spacing can be a paragraph break.
165   std::vector<float> font_sizes;
166   std::vector<float> line_spacings;
167   for (size_t i = 0; i < text_runs.size(); ++i) {
168     font_sizes.push_back(text_runs[i].style.font_size);
169     if (i > 0) {
170       const auto& cur = text_runs[i].bounds;
171       const auto& prev = text_runs[i - 1].bounds;
172       if (cur.point.y > prev.point.y + prev.size.height / 2)
173         line_spacings.push_back(cur.point.y - prev.point.y);
174     }
175   }
176   if (font_sizes.size() > 2) {
177     std::sort(font_sizes.begin(), font_sizes.end());
178     float median_font_size = font_sizes[font_sizes.size() / 2];
179     if (median_font_size > kMinimumFontSize) {
180       *out_heading_font_size_threshold =
181           median_font_size * kHeadingFontSizeRatio;
182     }
183   }
184   if (line_spacings.size() > 4) {
185     std::sort(line_spacings.begin(), line_spacings.end());
186     float median_line_spacing = line_spacings[line_spacings.size() / 2];
187     if (median_line_spacing > kMinimumLineSpacing) {
188       *out_paragraph_spacing_threshold =
189           median_line_spacing * kParagraphLineSpacingRatio;
190     }
191   }
192 }
193 
FinishStaticNode(ui::AXNodeData ** static_text_node,std::string * static_text)194 void FinishStaticNode(ui::AXNodeData** static_text_node,
195                       std::string* static_text) {
196   // If we're in the middle of building a static text node, finish it before
197   // moving on to the next object.
198   if (*static_text_node) {
199     (*static_text_node)
200         ->AddStringAttribute(ax::mojom::StringAttribute::kName, (*static_text));
201     static_text->clear();
202   }
203   *static_text_node = nullptr;
204 }
205 
ConnectPreviousAndNextOnLine(ui::AXNodeData * previous_on_line_node,ui::AXNodeData * next_on_line_node)206 void ConnectPreviousAndNextOnLine(ui::AXNodeData* previous_on_line_node,
207                                   ui::AXNodeData* next_on_line_node) {
208   previous_on_line_node->AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
209                                          next_on_line_node->id);
210   next_on_line_node->AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
211                                      previous_on_line_node->id);
212 }
213 
BreakParagraph(const std::vector<ppapi::PdfAccessibilityTextRunInfo> & text_runs,uint32_t text_run_index,float paragraph_spacing_threshold)214 bool BreakParagraph(
215     const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs,
216     uint32_t text_run_index,
217     float paragraph_spacing_threshold) {
218   // Check to see if its also a new paragraph, i.e., if the distance between
219   // lines is greater than the threshold.  If there's no threshold, that
220   // means there weren't enough lines to compute an accurate median, so
221   // we compare against the line size instead.
222   float line_spacing = fabsf(text_runs[text_run_index + 1].bounds.point.y -
223                              text_runs[text_run_index].bounds.point.y);
224   return ((paragraph_spacing_threshold > 0 &&
225            line_spacing > paragraph_spacing_threshold) ||
226           (paragraph_spacing_threshold == 0 &&
227            line_spacing > kParagraphLineSpacingRatio *
228                               text_runs[text_run_index].bounds.size.height));
229 }
230 
GetStaticTextNodeFromNode(ui::AXNode * node)231 ui::AXNode* GetStaticTextNodeFromNode(ui::AXNode* node) {
232   // Returns the appropriate static text node given |node|'s type.
233   // Returns nullptr if there is no appropriate static text node.
234   if (!node)
235     return nullptr;
236   ui::AXNode* static_node = node;
237   // Get the static text from the link node.
238   if (node->data().role == ax::mojom::Role::kLink &&
239       node->children().size() == 1) {
240     static_node = node->children()[0];
241   }
242   // Get the static text from the highlight node.
243   if (node->data().role == ax::mojom::Role::kPdfActionableHighlight &&
244       !node->children().empty()) {
245     static_node = node->children()[0];
246   }
247   // If it's static text node, then it holds text.
248   if (static_node && static_node->data().role == ax::mojom::Role::kStaticText)
249     return static_node;
250   return nullptr;
251 }
252 
GetTextRunCharsAsUTF8(const ppapi::PdfAccessibilityTextRunInfo & text_run,const std::vector<PP_PrivateAccessibilityCharInfo> & chars,int char_index)253 std::string GetTextRunCharsAsUTF8(
254     const ppapi::PdfAccessibilityTextRunInfo& text_run,
255     const std::vector<PP_PrivateAccessibilityCharInfo>& chars,
256     int char_index) {
257   std::string chars_utf8;
258   for (uint32_t i = 0; i < text_run.len; ++i) {
259     base::WriteUnicodeCharacter(chars[char_index + i].unicode_character,
260                                 &chars_utf8);
261   }
262   return chars_utf8;
263 }
264 
GetTextRunCharOffsets(const ppapi::PdfAccessibilityTextRunInfo & text_run,const std::vector<PP_PrivateAccessibilityCharInfo> & chars,int char_index)265 std::vector<int32_t> GetTextRunCharOffsets(
266     const ppapi::PdfAccessibilityTextRunInfo& text_run,
267     const std::vector<PP_PrivateAccessibilityCharInfo>& chars,
268     int char_index) {
269   std::vector<int32_t> char_offsets(text_run.len);
270   double offset = 0.0;
271   for (uint32_t i = 0; i < text_run.len; ++i) {
272     offset += chars[char_index + i].char_width;
273     char_offsets[i] = floor(offset);
274   }
275   return char_offsets;
276 }
277 
278 template <typename T>
CompareTextRuns(const T & a,const T & b)279 bool CompareTextRuns(const T& a, const T& b) {
280   return a.text_run_index < b.text_run_index;
281 }
282 
283 template <typename T>
IsObjectInTextRun(const std::vector<T> & objects,uint32_t object_index,size_t text_run_index)284 bool IsObjectInTextRun(const std::vector<T>& objects,
285                        uint32_t object_index,
286                        size_t text_run_index) {
287   return (object_index < objects.size() &&
288           objects[object_index].text_run_index <= text_run_index);
289 }
290 
NormalizeTextRunIndex(uint32_t object_end_text_run_index,size_t current_text_run_index)291 size_t NormalizeTextRunIndex(uint32_t object_end_text_run_index,
292                              size_t current_text_run_index) {
293   return std::max<size_t>(
294       object_end_text_run_index,
295       current_text_run_index ? current_text_run_index - 1 : 0);
296 }
297 
IsTextRenderModeFill(const PP_TextRenderingMode & mode)298 bool IsTextRenderModeFill(const PP_TextRenderingMode& mode) {
299   switch (mode) {
300     case PP_TEXTRENDERINGMODE_FILL:
301     case PP_TEXTRENDERINGMODE_FILLSTROKE:
302     case PP_TEXTRENDERINGMODE_FILLCLIP:
303     case PP_TEXTRENDERINGMODE_FILLSTROKECLIP:
304       return true;
305     default:
306       return false;
307   }
308 }
309 
IsTextRenderModeStroke(const PP_TextRenderingMode & mode)310 bool IsTextRenderModeStroke(const PP_TextRenderingMode& mode) {
311   switch (mode) {
312     case PP_TEXTRENDERINGMODE_STROKE:
313     case PP_TEXTRENDERINGMODE_FILLSTROKE:
314     case PP_TEXTRENDERINGMODE_STROKECLIP:
315     case PP_TEXTRENDERINGMODE_FILLSTROKECLIP:
316       return true;
317     default:
318       return false;
319   }
320 }
321 
CreateNode(ax::mojom::Role role,ax::mojom::Restriction restriction,content::RenderAccessibility * render_accessibility,std::vector<std::unique_ptr<ui::AXNodeData>> * nodes)322 ui::AXNodeData* CreateNode(
323     ax::mojom::Role role,
324     ax::mojom::Restriction restriction,
325     content::RenderAccessibility* render_accessibility,
326     std::vector<std::unique_ptr<ui::AXNodeData>>* nodes) {
327   DCHECK(render_accessibility);
328 
329   auto node = std::make_unique<ui::AXNodeData>();
330   node->id = render_accessibility->GenerateAXID();
331   node->role = role;
332   node->SetRestriction(restriction);
333 
334   // All nodes other than the first one have coordinates relative to
335   // the first node.
336   if (!nodes->empty())
337     node->relative_bounds.offset_container_id = (*nodes)[0]->id;
338 
339   ui::AXNodeData* node_ptr = node.get();
340   nodes->push_back(std::move(node));
341 
342   return node_ptr;
343 }
344 
GetRoleForButtonType(PP_PrivateButtonType button_type)345 ax::mojom::Role GetRoleForButtonType(PP_PrivateButtonType button_type) {
346   switch (button_type) {
347     case PP_PrivateButtonType::PP_PRIVATEBUTTON_RADIOBUTTON:
348       return ax::mojom::Role::kRadioButton;
349     case PP_PrivateButtonType::PP_PRIVATEBUTTON_CHECKBOX:
350       return ax::mojom::Role::kCheckBox;
351     case PP_PrivateButtonType::PP_PRIVATEBUTTON_PUSHBUTTON:
352       return ax::mojom::Role::kButton;
353     default:
354       NOTREACHED();
355       return ax::mojom::Role::kNone;
356   }
357 }
358 
359 class PdfAccessibilityTreeBuilder {
360  public:
PdfAccessibilityTreeBuilder(const std::vector<ppapi::PdfAccessibilityTextRunInfo> & text_runs,const std::vector<PP_PrivateAccessibilityCharInfo> & chars,const ppapi::PdfAccessibilityPageObjects & page_objects,const gfx::RectF & page_bounds,uint32_t page_index,ui::AXNodeData * page_node,content::RenderAccessibility * render_accessibility,std::vector<std::unique_ptr<ui::AXNodeData>> * nodes,std::map<int32_t,PP_PdfPageCharacterIndex> * node_id_to_page_char_index,std::map<int32_t,PdfAccessibilityTree::AnnotationInfo> * node_id_to_annotation_info)361   explicit PdfAccessibilityTreeBuilder(
362       const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs,
363       const std::vector<PP_PrivateAccessibilityCharInfo>& chars,
364       const ppapi::PdfAccessibilityPageObjects& page_objects,
365       const gfx::RectF& page_bounds,
366       uint32_t page_index,
367       ui::AXNodeData* page_node,
368       content::RenderAccessibility* render_accessibility,
369       std::vector<std::unique_ptr<ui::AXNodeData>>* nodes,
370       std::map<int32_t, PP_PdfPageCharacterIndex>* node_id_to_page_char_index,
371       std::map<int32_t, PdfAccessibilityTree::AnnotationInfo>*
372           node_id_to_annotation_info)
373       : text_runs_(text_runs),
374         chars_(chars),
375         links_(page_objects.links),
376         images_(page_objects.images),
377         highlights_(page_objects.highlights),
378         text_fields_(page_objects.form_fields.text_fields),
379         buttons_(page_objects.form_fields.buttons),
380         choice_fields_(page_objects.form_fields.choice_fields),
381         page_bounds_(page_bounds),
382         page_index_(page_index),
383         page_node_(page_node),
384         render_accessibility_(render_accessibility),
385         nodes_(nodes),
386         node_id_to_page_char_index_(node_id_to_page_char_index),
387         node_id_to_annotation_info_(node_id_to_annotation_info) {
388     if (!text_runs.empty()) {
389       text_run_start_indices_.reserve(text_runs.size());
390       text_run_start_indices_.push_back(0);
391       for (size_t i = 0; i < text_runs.size() - 1; ++i) {
392         text_run_start_indices_.push_back(text_run_start_indices_[i] +
393                                           text_runs[i].len);
394       }
395     }
396   }
397 
BuildPageTree()398   void BuildPageTree() {
399     ComputeParagraphAndHeadingThresholds(text_runs_,
400                                          &heading_font_size_threshold_,
401                                          &paragraph_spacing_threshold_);
402 
403     ui::AXNodeData* para_node = nullptr;
404     ui::AXNodeData* static_text_node = nullptr;
405     ui::AXNodeData* previous_on_line_node = nullptr;
406     std::string static_text;
407     LineHelper line_helper(text_runs_);
408     bool pdf_forms_enabled =
409         base::FeatureList::IsEnabled(chrome_pdf::features::kAccessiblePDFForm);
410 
411     for (size_t text_run_index = 0; text_run_index < text_runs_.size();
412          ++text_run_index) {
413       // If we don't have a paragraph, create one.
414       if (!para_node) {
415         para_node =
416             CreateParagraphNode(text_runs_[text_run_index].style.font_size);
417         page_node_->child_ids.push_back(para_node->id);
418       }
419 
420       // If the |text_run_index| is less than or equal to the link's
421       // text_run_index, then push the link node in the paragraph.
422       if (IsObjectInTextRun(links_, current_link_index_, text_run_index)) {
423         FinishStaticNode(&static_text_node, &static_text);
424         const ppapi::PdfAccessibilityLinkInfo& link =
425             links_[current_link_index_++];
426         AddLinkToParaNode(link, para_node, &previous_on_line_node,
427                           &text_run_index);
428 
429         if (link.text_run_count == 0)
430           continue;
431 
432       } else if (IsObjectInTextRun(images_, current_image_index_,
433                                    text_run_index)) {
434         FinishStaticNode(&static_text_node, &static_text);
435         AddImageToParaNode(images_[current_image_index_++], para_node,
436                            &text_run_index);
437         continue;
438       } else if (IsObjectInTextRun(highlights_, current_highlight_index_,
439                                    text_run_index)) {
440         FinishStaticNode(&static_text_node, &static_text);
441         AddHighlightToParaNode(highlights_[current_highlight_index_++],
442                                para_node, &previous_on_line_node,
443                                &text_run_index);
444       } else if (IsObjectInTextRun(text_fields_, current_text_field_index_,
445                                    text_run_index) &&
446                  pdf_forms_enabled) {
447         FinishStaticNode(&static_text_node, &static_text);
448         AddTextFieldToParaNode(text_fields_[current_text_field_index_++],
449                                para_node, &text_run_index);
450         continue;
451       } else if (IsObjectInTextRun(buttons_, current_button_index_,
452                                    text_run_index) &&
453                  pdf_forms_enabled) {
454         FinishStaticNode(&static_text_node, &static_text);
455         AddButtonToParaNode(buttons_[current_button_index_++], para_node,
456                             &text_run_index);
457         continue;
458       } else if (IsObjectInTextRun(choice_fields_, current_choice_field_index_,
459                                    text_run_index) &&
460                  pdf_forms_enabled) {
461         FinishStaticNode(&static_text_node, &static_text);
462         AddChoiceFieldToParaNode(choice_fields_[current_choice_field_index_++],
463                                  para_node, &text_run_index);
464         continue;
465       } else {
466         PP_PdfPageCharacterIndex page_char_index = {
467             page_index_, text_run_start_indices_[text_run_index]};
468 
469         // This node is for the text inside the paragraph, it includes
470         // the text of all of the text runs.
471         if (!static_text_node) {
472           static_text_node = CreateStaticTextNode(page_char_index);
473           para_node->child_ids.push_back(static_text_node->id);
474         }
475 
476         const ppapi::PdfAccessibilityTextRunInfo& text_run =
477             text_runs_[text_run_index];
478         // Add this text run to the current static text node.
479         ui::AXNodeData* inline_text_box_node =
480             CreateInlineTextBoxNode(text_run, page_char_index);
481         static_text_node->child_ids.push_back(inline_text_box_node->id);
482 
483         static_text += inline_text_box_node->GetStringAttribute(
484             ax::mojom::StringAttribute::kName);
485 
486         para_node->relative_bounds.bounds.Union(
487             inline_text_box_node->relative_bounds.bounds);
488         static_text_node->relative_bounds.bounds.Union(
489             inline_text_box_node->relative_bounds.bounds);
490 
491         if (previous_on_line_node) {
492           ConnectPreviousAndNextOnLine(previous_on_line_node,
493                                        inline_text_box_node);
494         } else {
495           line_helper.StartNewLine(text_run_index);
496         }
497         line_helper.ProcessNextRun(text_run_index);
498 
499         if (text_run_index < text_runs_.size() - 1) {
500           if (line_helper.IsRunOnSameLine(text_run_index + 1)) {
501             // The next run is on the same line.
502             previous_on_line_node = inline_text_box_node;
503           } else {
504             // The next run is on a new line.
505             previous_on_line_node = nullptr;
506           }
507         }
508       }
509 
510       if (text_run_index == text_runs_.size() - 1) {
511         FinishStaticNode(&static_text_node, &static_text);
512         break;
513       }
514 
515       if (!previous_on_line_node) {
516         if (BreakParagraph(text_runs_, text_run_index,
517                            paragraph_spacing_threshold_)) {
518           FinishStaticNode(&static_text_node, &static_text);
519           para_node = nullptr;
520         }
521       }
522     }
523 
524     AddRemainingAnnotations(para_node);
525   }
526 
527  private:
AddWordStartsAndEnds(ui::AXNodeData * inline_text_box)528   void AddWordStartsAndEnds(ui::AXNodeData* inline_text_box) {
529     base::string16 text = inline_text_box->GetString16Attribute(
530         ax::mojom::StringAttribute::kName);
531     base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD);
532     if (!iter.Init())
533       return;
534 
535     std::vector<int32_t> word_starts;
536     std::vector<int32_t> word_ends;
537     while (iter.Advance()) {
538       if (iter.IsWord()) {
539         word_starts.push_back(iter.prev());
540         word_ends.push_back(iter.pos());
541       }
542     }
543     inline_text_box->AddIntListAttribute(
544         ax::mojom::IntListAttribute::kWordStarts, word_starts);
545     inline_text_box->AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
546                                          word_ends);
547   }
548 
CreateParagraphNode(float font_size)549   ui::AXNodeData* CreateParagraphNode(float font_size) {
550     ui::AXNodeData* para_node = CreateNode(ax::mojom::Role::kParagraph,
551                                            ax::mojom::Restriction::kReadOnly,
552                                            render_accessibility_, nodes_);
553     para_node->AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
554                                 true);
555 
556     // If font size exceeds the |heading_font_size_threshold_|, then classify
557     // it as a Heading.
558     if (heading_font_size_threshold_ > 0 &&
559         font_size > heading_font_size_threshold_) {
560       para_node->role = ax::mojom::Role::kHeading;
561       para_node->AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel,
562                                  2);
563       para_node->AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "h2");
564     }
565 
566     return para_node;
567   }
568 
CreateStaticTextNode(const PP_PdfPageCharacterIndex & page_char_index)569   ui::AXNodeData* CreateStaticTextNode(
570       const PP_PdfPageCharacterIndex& page_char_index) {
571     ui::AXNodeData* static_text_node = CreateNode(
572         ax::mojom::Role::kStaticText, ax::mojom::Restriction::kReadOnly,
573         render_accessibility_, nodes_);
574     static_text_node->SetNameFrom(ax::mojom::NameFrom::kContents);
575     node_id_to_page_char_index_->emplace(static_text_node->id, page_char_index);
576     return static_text_node;
577   }
578 
CreateInlineTextBoxNode(const ppapi::PdfAccessibilityTextRunInfo & text_run,const PP_PdfPageCharacterIndex & page_char_index)579   ui::AXNodeData* CreateInlineTextBoxNode(
580       const ppapi::PdfAccessibilityTextRunInfo& text_run,
581       const PP_PdfPageCharacterIndex& page_char_index) {
582     ui::AXNodeData* inline_text_box_node = CreateNode(
583         ax::mojom::Role::kInlineTextBox, ax::mojom::Restriction::kReadOnly,
584         render_accessibility_, nodes_);
585     inline_text_box_node->SetNameFrom(ax::mojom::NameFrom::kContents);
586 
587     std::string chars__utf8 =
588         GetTextRunCharsAsUTF8(text_run, chars_, page_char_index.char_index);
589     inline_text_box_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
590                                              chars__utf8);
591     inline_text_box_node->AddIntAttribute(
592         ax::mojom::IntAttribute::kTextDirection, text_run.direction);
593     inline_text_box_node->AddStringAttribute(
594         ax::mojom::StringAttribute::kFontFamily, text_run.style.font_name);
595     inline_text_box_node->AddFloatAttribute(
596         ax::mojom::FloatAttribute::kFontSize, text_run.style.font_size);
597     inline_text_box_node->AddFloatAttribute(
598         ax::mojom::FloatAttribute::kFontWeight, text_run.style.font_weight);
599     if (text_run.style.is_italic)
600       inline_text_box_node->AddTextStyle(ax::mojom::TextStyle::kItalic);
601     if (text_run.style.is_bold)
602       inline_text_box_node->AddTextStyle(ax::mojom::TextStyle::kBold);
603     if (IsTextRenderModeFill(text_run.style.render_mode)) {
604       inline_text_box_node->AddIntAttribute(ax::mojom::IntAttribute::kColor,
605                                             text_run.style.fill_color);
606     } else if (IsTextRenderModeStroke(text_run.style.render_mode)) {
607       inline_text_box_node->AddIntAttribute(ax::mojom::IntAttribute::kColor,
608                                             text_run.style.stroke_color);
609     }
610 
611     inline_text_box_node->relative_bounds.bounds =
612         PpFloatRectToGfxRectF(text_run.bounds) +
613         page_bounds_.OffsetFromOrigin();
614     std::vector<int32_t> char_offsets =
615         GetTextRunCharOffsets(text_run, chars_, page_char_index.char_index);
616     inline_text_box_node->AddIntListAttribute(
617         ax::mojom::IntListAttribute::kCharacterOffsets, char_offsets);
618     AddWordStartsAndEnds(inline_text_box_node);
619     node_id_to_page_char_index_->emplace(inline_text_box_node->id,
620                                          page_char_index);
621     return inline_text_box_node;
622   }
623 
CreateLinkNode(const ppapi::PdfAccessibilityLinkInfo & link)624   ui::AXNodeData* CreateLinkNode(const ppapi::PdfAccessibilityLinkInfo& link) {
625     ui::AXNodeData* link_node =
626         CreateNode(ax::mojom::Role::kLink, ax::mojom::Restriction::kReadOnly,
627                    render_accessibility_, nodes_);
628 
629     link_node->AddStringAttribute(ax::mojom::StringAttribute::kUrl, link.url);
630     link_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
631                                   std::string());
632     link_node->relative_bounds.bounds = PpFloatRectToGfxRectF(link.bounds);
633     node_id_to_annotation_info_->emplace(
634         link_node->id,
635         PdfAccessibilityTree::AnnotationInfo(page_index_, link.index_in_page));
636 
637     return link_node;
638   }
639 
CreateImageNode(const ppapi::PdfAccessibilityImageInfo & image)640   ui::AXNodeData* CreateImageNode(
641       const ppapi::PdfAccessibilityImageInfo& image) {
642     ui::AXNodeData* image_node =
643         CreateNode(ax::mojom::Role::kImage, ax::mojom::Restriction::kReadOnly,
644                    render_accessibility_, nodes_);
645 
646     if (image.alt_text.empty()) {
647       image_node->AddStringAttribute(
648           ax::mojom::StringAttribute::kName,
649           l10n_util::GetStringUTF8(IDS_AX_UNLABELED_IMAGE_ROLE_DESCRIPTION));
650     } else {
651       image_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
652                                      image.alt_text);
653     }
654     image_node->relative_bounds.bounds = PpFloatRectToGfxRectF(image.bounds);
655     return image_node;
656   }
657 
CreateHighlightNode(const ppapi::PdfAccessibilityHighlightInfo & highlight)658   ui::AXNodeData* CreateHighlightNode(
659       const ppapi::PdfAccessibilityHighlightInfo& highlight) {
660     ui::AXNodeData* highlight_node = CreateNode(
661         ax::mojom::Role::kPdfActionableHighlight,
662         ax::mojom::Restriction::kReadOnly, render_accessibility_, nodes_);
663 
664     highlight_node->AddStringAttribute(
665         ax::mojom::StringAttribute::kRoleDescription,
666         l10n_util::GetStringUTF8(IDS_AX_ROLE_DESCRIPTION_PDF_HIGHLIGHT));
667     highlight_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
668                                        std::string());
669     highlight_node->relative_bounds.bounds =
670         PpFloatRectToGfxRectF(highlight.bounds);
671     highlight_node->AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
672                                     highlight.color);
673 
674     return highlight_node;
675   }
676 
CreatePopupNoteNode(const ppapi::PdfAccessibilityHighlightInfo & highlight)677   ui::AXNodeData* CreatePopupNoteNode(
678       const ppapi::PdfAccessibilityHighlightInfo& highlight) {
679     ui::AXNodeData* popup_note_node =
680         CreateNode(ax::mojom::Role::kNote, ax::mojom::Restriction::kReadOnly,
681                    render_accessibility_, nodes_);
682 
683     popup_note_node->AddStringAttribute(
684         ax::mojom::StringAttribute::kRoleDescription,
685         l10n_util::GetStringUTF8(IDS_AX_ROLE_DESCRIPTION_PDF_POPUP_NOTE));
686     popup_note_node->relative_bounds.bounds =
687         PpFloatRectToGfxRectF(highlight.bounds);
688 
689     ui::AXNodeData* static_popup_note_text_node = CreateNode(
690         ax::mojom::Role::kStaticText, ax::mojom::Restriction::kReadOnly,
691         render_accessibility_, nodes_);
692 
693     static_popup_note_text_node->SetNameFrom(ax::mojom::NameFrom::kContents);
694     static_popup_note_text_node->AddStringAttribute(
695         ax::mojom::StringAttribute::kName, highlight.note_text);
696     static_popup_note_text_node->relative_bounds.bounds =
697         PpFloatRectToGfxRectF(highlight.bounds);
698 
699     popup_note_node->child_ids.push_back(static_popup_note_text_node->id);
700 
701     return popup_note_node;
702   }
703 
CreateTextFieldNode(const ppapi::PdfAccessibilityTextFieldInfo & text_field)704   ui::AXNodeData* CreateTextFieldNode(
705       const ppapi::PdfAccessibilityTextFieldInfo& text_field) {
706     ax::mojom::Restriction restriction = text_field.is_read_only
707                                              ? ax::mojom::Restriction::kReadOnly
708                                              : ax::mojom::Restriction::kNone;
709     ui::AXNodeData* text_field_node =
710         CreateNode(ax::mojom::Role::kTextField, restriction,
711                    render_accessibility_, nodes_);
712 
713     text_field_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
714                                         text_field.name);
715     text_field_node->AddStringAttribute(ax::mojom::StringAttribute::kValue,
716                                         text_field.value);
717     text_field_node->AddState(ax::mojom::State::kFocusable);
718     if (text_field.is_required)
719       text_field_node->AddState(ax::mojom::State::kRequired);
720     if (text_field.is_password)
721       text_field_node->AddState(ax::mojom::State::kProtected);
722     text_field_node->relative_bounds.bounds =
723         PpFloatRectToGfxRectF(text_field.bounds);
724     return text_field_node;
725   }
726 
CreateButtonNode(const ppapi::PdfAccessibilityButtonInfo & button)727   ui::AXNodeData* CreateButtonNode(
728       const ppapi::PdfAccessibilityButtonInfo& button) {
729     ax::mojom::Restriction restriction = button.is_read_only
730                                              ? ax::mojom::Restriction::kReadOnly
731                                              : ax::mojom::Restriction::kNone;
732     ui::AXNodeData* button_node =
733         CreateNode(GetRoleForButtonType(button.type), restriction,
734                    render_accessibility_, nodes_);
735     button_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
736                                     button.name);
737     button_node->AddState(ax::mojom::State::kFocusable);
738 
739     if (button.type == PP_PRIVATEBUTTON_RADIOBUTTON ||
740         button.type == PP_PRIVATEBUTTON_CHECKBOX) {
741       ax::mojom::CheckedState checkedState =
742           button.is_checked ? ax::mojom::CheckedState::kTrue
743                             : ax::mojom::CheckedState::kNone;
744       button_node->SetCheckedState(checkedState);
745       button_node->AddStringAttribute(ax::mojom::StringAttribute::kValue,
746                                       button.value);
747       button_node->AddIntAttribute(ax::mojom::IntAttribute::kSetSize,
748                                    button.control_count);
749       button_node->AddIntAttribute(ax::mojom::IntAttribute::kPosInSet,
750                                    button.control_index + 1);
751     }
752 
753     button_node->relative_bounds.bounds = PpFloatRectToGfxRectF(button.bounds);
754     return button_node;
755   }
756 
CreateListboxOptionNode(const ppapi::PdfAccessibilityChoiceFieldOptionInfo & choice_field_option,ax::mojom::Restriction restriction)757   ui::AXNodeData* CreateListboxOptionNode(
758       const ppapi::PdfAccessibilityChoiceFieldOptionInfo& choice_field_option,
759       ax::mojom::Restriction restriction) {
760     ui::AXNodeData* listbox_option_node =
761         CreateNode(ax::mojom::Role::kListBoxOption, restriction,
762                    render_accessibility_, nodes_);
763 
764     listbox_option_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
765                                             choice_field_option.name);
766     listbox_option_node->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected,
767                                           choice_field_option.is_selected);
768     listbox_option_node->AddState(ax::mojom::State::kFocusable);
769     return listbox_option_node;
770   }
771 
CreateListboxNode(const ppapi::PdfAccessibilityChoiceFieldInfo & choice_field,ui::AXNodeData * control_node)772   ui::AXNodeData* CreateListboxNode(
773       const ppapi::PdfAccessibilityChoiceFieldInfo& choice_field,
774       ui::AXNodeData* control_node) {
775     ax::mojom::Restriction restriction = choice_field.is_read_only
776                                              ? ax::mojom::Restriction::kReadOnly
777                                              : ax::mojom::Restriction::kNone;
778     ui::AXNodeData* listbox_node = CreateNode(
779         ax::mojom::Role::kListBox, restriction, render_accessibility_, nodes_);
780 
781     if (choice_field.type != PP_PRIVATECHOICEFIELD_COMBOBOX) {
782       listbox_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
783                                        choice_field.name);
784     }
785 
786     ui::AXNodeData* first_selected_option = nullptr;
787     for (const ppapi::PdfAccessibilityChoiceFieldOptionInfo& option :
788          choice_field.options) {
789       ui::AXNodeData* listbox_option_node =
790           CreateListboxOptionNode(option, restriction);
791       if (!first_selected_option && listbox_option_node->GetBoolAttribute(
792                                         ax::mojom::BoolAttribute::kSelected)) {
793         first_selected_option = listbox_option_node;
794       }
795       // TODO(bug 1030242): add |listbox_option_node| specific bounds here.
796       listbox_option_node->relative_bounds.bounds =
797           PpFloatRectToGfxRectF(choice_field.bounds);
798       listbox_node->child_ids.push_back(listbox_option_node->id);
799     }
800 
801     if (control_node && first_selected_option) {
802       control_node->AddIntAttribute(
803           ax::mojom::IntAttribute::kActivedescendantId,
804           first_selected_option->id);
805     }
806 
807     if (choice_field.is_multi_select)
808       listbox_node->AddState(ax::mojom::State::kMultiselectable);
809     listbox_node->AddState(ax::mojom::State::kFocusable);
810     listbox_node->relative_bounds.bounds =
811         PpFloatRectToGfxRectF(choice_field.bounds);
812     return listbox_node;
813   }
814 
CreateComboboxInputNode(const ppapi::PdfAccessibilityChoiceFieldInfo & choice_field,ax::mojom::Restriction restriction)815   ui::AXNodeData* CreateComboboxInputNode(
816       const ppapi::PdfAccessibilityChoiceFieldInfo& choice_field,
817       ax::mojom::Restriction restriction) {
818     ax::mojom::Role input_role = choice_field.has_editable_text_box
819                                      ? ax::mojom::Role::kTextFieldWithComboBox
820                                      : ax::mojom::Role::kComboBoxMenuButton;
821     ui::AXNodeData* combobox_input_node =
822         CreateNode(input_role, restriction, render_accessibility_, nodes_);
823     combobox_input_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
824                                             choice_field.name);
825     for (const ppapi::PdfAccessibilityChoiceFieldOptionInfo& option :
826          choice_field.options) {
827       if (option.is_selected) {
828         combobox_input_node->AddStringAttribute(
829             ax::mojom::StringAttribute::kValue, option.name);
830         break;
831       }
832     }
833 
834     combobox_input_node->AddState(ax::mojom::State::kFocusable);
835     combobox_input_node->relative_bounds.bounds =
836         PpFloatRectToGfxRectF(choice_field.bounds);
837     return combobox_input_node;
838   }
839 
CreateComboboxNode(const ppapi::PdfAccessibilityChoiceFieldInfo & choice_field)840   ui::AXNodeData* CreateComboboxNode(
841       const ppapi::PdfAccessibilityChoiceFieldInfo& choice_field) {
842     ax::mojom::Restriction restriction = choice_field.is_read_only
843                                              ? ax::mojom::Restriction::kReadOnly
844                                              : ax::mojom::Restriction::kNone;
845     ui::AXNodeData* combobox_node =
846         CreateNode(ax::mojom::Role::kComboBoxGrouping, restriction,
847                    render_accessibility_, nodes_);
848     ui::AXNodeData* input_element =
849         CreateComboboxInputNode(choice_field, restriction);
850     ui::AXNodeData* list_element =
851         CreateListboxNode(choice_field, input_element);
852     input_element->AddIntListAttribute(
853         ax::mojom::IntListAttribute::kControlsIds,
854         std::vector<int32_t>{list_element->id});
855     combobox_node->child_ids.push_back(input_element->id);
856     combobox_node->child_ids.push_back(list_element->id);
857     combobox_node->AddState(ax::mojom::State::kFocusable);
858     combobox_node->relative_bounds.bounds =
859         PpFloatRectToGfxRectF(choice_field.bounds);
860     return combobox_node;
861   }
862 
CreateChoiceFieldNode(const ppapi::PdfAccessibilityChoiceFieldInfo & choice_field)863   ui::AXNodeData* CreateChoiceFieldNode(
864       const ppapi::PdfAccessibilityChoiceFieldInfo& choice_field) {
865     if (choice_field.type == PP_PRIVATECHOICEFIELD_LISTBOX)
866       return CreateListboxNode(choice_field, nullptr);
867 
868     return CreateComboboxNode(choice_field);
869   }
870 
AddTextToAXNode(uint32_t start_text_run_index,uint32_t end_text_run_index,ui::AXNodeData * ax_node,ui::AXNodeData ** previous_on_line_node)871   void AddTextToAXNode(uint32_t start_text_run_index,
872                        uint32_t end_text_run_index,
873                        ui::AXNodeData* ax_node,
874                        ui::AXNodeData** previous_on_line_node) {
875     PP_PdfPageCharacterIndex page_char_index = {
876         page_index_, text_run_start_indices_[start_text_run_index]};
877     ui::AXNodeData* ax_static_text_node = CreateStaticTextNode(page_char_index);
878     ax_node->child_ids.push_back(ax_static_text_node->id);
879     // Accumulate the text of the node.
880     std::string ax_name;
881     LineHelper line_helper(text_runs_);
882 
883     for (size_t text_run_index = start_text_run_index;
884          text_run_index <= end_text_run_index; ++text_run_index) {
885       const ppapi::PdfAccessibilityTextRunInfo& text_run =
886           text_runs_[text_run_index];
887       page_char_index.char_index = text_run_start_indices_[text_run_index];
888       // Add this text run to the current static text node.
889       ui::AXNodeData* inline_text_box_node =
890           CreateInlineTextBoxNode(text_run, page_char_index);
891       ax_static_text_node->child_ids.push_back(inline_text_box_node->id);
892 
893       ax_static_text_node->relative_bounds.bounds.Union(
894           inline_text_box_node->relative_bounds.bounds);
895       ax_name += inline_text_box_node->GetStringAttribute(
896           ax::mojom::StringAttribute::kName);
897 
898       if (*previous_on_line_node) {
899         ConnectPreviousAndNextOnLine(*previous_on_line_node,
900                                      inline_text_box_node);
901       } else {
902         line_helper.StartNewLine(text_run_index);
903       }
904       line_helper.ProcessNextRun(text_run_index);
905 
906       if (text_run_index < text_runs_.size() - 1) {
907         if (line_helper.IsRunOnSameLine(text_run_index + 1)) {
908           // The next run is on the same line.
909           *previous_on_line_node = inline_text_box_node;
910         } else {
911           // The next run is on a new line.
912           *previous_on_line_node = nullptr;
913         }
914       }
915     }
916 
917     ax_node->AddStringAttribute(ax::mojom::StringAttribute::kName, ax_name);
918     ax_static_text_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
919                                             ax_name);
920   }
921 
AddTextToObjectNode(uint32_t object_text_run_index,uint32_t object_text_run_count,ui::AXNodeData * object_node,ui::AXNodeData * para_node,ui::AXNodeData ** previous_on_line_node,size_t * text_run_index)922   void AddTextToObjectNode(uint32_t object_text_run_index,
923                            uint32_t object_text_run_count,
924                            ui::AXNodeData* object_node,
925                            ui::AXNodeData* para_node,
926                            ui::AXNodeData** previous_on_line_node,
927                            size_t* text_run_index) {
928     // Annotation objects can overlap in PDF. There can be two overlapping
929     // scenarios: Partial overlap and Complete overlap.
930     // Partial overlap
931     //
932     // Link A starts      Link B starts     Link A ends            Link B ends
933     //      |a1                |b1               |a2                    |b2
934     // -----------------------------------------------------------------------
935     //                                    Text
936     //
937     // Complete overlap
938     // Link A starts      Link B starts     Link B ends            Link A ends
939     //      |a1                |b1               |b2                    |a2
940     // -----------------------------------------------------------------------
941     //                                    Text
942     //
943     // For overlapping annotations, both annotations would store the full
944     // text data and nothing will get truncated. For partial overlap, link `A`
945     // would contain text between a1 and a2 while link `B` would contain text
946     // between b1 and b2. For complete overlap as well, link `A` would contain
947     // text between a1 and a2 and link `B` would contain text between b1 and
948     // b2. The links would appear in the tree in the order of which they are
949     // present. In the tree for both overlapping scenarios, link `A` would
950     // appear first in the tree and link `B` after it.
951 
952     // If |object_text_run_count| > 0, then the object is part of the page text.
953     // Make the text runs contained by the object children of the object node.
954     size_t end_text_run_index = object_text_run_index + object_text_run_count;
955     uint32_t object_end_text_run_index =
956         std::min(end_text_run_index, text_runs_.size()) - 1;
957     AddTextToAXNode(object_text_run_index, object_end_text_run_index,
958                     object_node, previous_on_line_node);
959 
960     para_node->relative_bounds.bounds.Union(
961         object_node->relative_bounds.bounds);
962 
963     *text_run_index =
964         NormalizeTextRunIndex(object_end_text_run_index, *text_run_index);
965   }
966 
AddLinkToParaNode(const ppapi::PdfAccessibilityLinkInfo & link,ui::AXNodeData * para_node,ui::AXNodeData ** previous_on_line_node,size_t * text_run_index)967   void AddLinkToParaNode(const ppapi::PdfAccessibilityLinkInfo& link,
968                          ui::AXNodeData* para_node,
969                          ui::AXNodeData** previous_on_line_node,
970                          size_t* text_run_index) {
971     ui::AXNodeData* link_node = CreateLinkNode(link);
972     para_node->child_ids.push_back(link_node->id);
973 
974     // If |link.text_run_count| == 0, then the link is not part of the page
975     // text. Push it ahead of the current text run.
976     if (link.text_run_count == 0) {
977       --(*text_run_index);
978       return;
979     }
980 
981     // Make the text runs contained by the link children of
982     // the link node.
983     AddTextToObjectNode(link.text_run_index, link.text_run_count, link_node,
984                         para_node, previous_on_line_node, text_run_index);
985   }
986 
AddImageToParaNode(const ppapi::PdfAccessibilityImageInfo & image,ui::AXNodeData * para_node,size_t * text_run_index)987   void AddImageToParaNode(const ppapi::PdfAccessibilityImageInfo& image,
988                           ui::AXNodeData* para_node,
989                           size_t* text_run_index) {
990     // If the |text_run_index| is less than or equal to the image's text run
991     // index, then push the image ahead of the current text run.
992     ui::AXNodeData* image_node = CreateImageNode(image);
993     para_node->child_ids.push_back(image_node->id);
994     --(*text_run_index);
995   }
996 
AddHighlightToParaNode(const ppapi::PdfAccessibilityHighlightInfo & highlight,ui::AXNodeData * para_node,ui::AXNodeData ** previous_on_line_node,size_t * text_run_index)997   void AddHighlightToParaNode(
998       const ppapi::PdfAccessibilityHighlightInfo& highlight,
999       ui::AXNodeData* para_node,
1000       ui::AXNodeData** previous_on_line_node,
1001       size_t* text_run_index) {
1002     ui::AXNodeData* highlight_node = CreateHighlightNode(highlight);
1003     para_node->child_ids.push_back(highlight_node->id);
1004 
1005     // Make the text runs contained by the highlight children of
1006     // the highlight node.
1007     AddTextToObjectNode(highlight.text_run_index, highlight.text_run_count,
1008                         highlight_node, para_node, previous_on_line_node,
1009                         text_run_index);
1010 
1011     if (!highlight.note_text.empty()) {
1012       ui::AXNodeData* popup_note_node = CreatePopupNoteNode(highlight);
1013       highlight_node->child_ids.push_back(popup_note_node->id);
1014     }
1015   }
1016 
AddTextFieldToParaNode(const ppapi::PdfAccessibilityTextFieldInfo & text_field,ui::AXNodeData * para_node,size_t * text_run_index)1017   void AddTextFieldToParaNode(
1018       const ppapi::PdfAccessibilityTextFieldInfo& text_field,
1019       ui::AXNodeData* para_node,
1020       size_t* text_run_index) {
1021     // If the |text_run_index| is less than or equal to the text_field's text
1022     // run index, then push the text_field ahead of the current text run.
1023     ui::AXNodeData* text_field_node = CreateTextFieldNode(text_field);
1024     para_node->child_ids.push_back(text_field_node->id);
1025     --(*text_run_index);
1026   }
1027 
AddButtonToParaNode(const ppapi::PdfAccessibilityButtonInfo & button,ui::AXNodeData * para_node,size_t * text_run_index)1028   void AddButtonToParaNode(const ppapi::PdfAccessibilityButtonInfo& button,
1029                            ui::AXNodeData* para_node,
1030                            size_t* text_run_index) {
1031     // If the |text_run_index| is less than or equal to the button's text
1032     // run index, then push the button ahead of the current text run.
1033     ui::AXNodeData* button_node = CreateButtonNode(button);
1034     para_node->child_ids.push_back(button_node->id);
1035     --(*text_run_index);
1036   }
1037 
AddChoiceFieldToParaNode(const ppapi::PdfAccessibilityChoiceFieldInfo & choice_field,ui::AXNodeData * para_node,size_t * text_run_index)1038   void AddChoiceFieldToParaNode(
1039       const ppapi::PdfAccessibilityChoiceFieldInfo& choice_field,
1040       ui::AXNodeData* para_node,
1041       size_t* text_run_index) {
1042     // If the |text_run_index| is less than or equal to the choice_field's text
1043     // run index, then push the choice_field ahead of the current text run.
1044     ui::AXNodeData* choice_field_node = CreateChoiceFieldNode(choice_field);
1045     para_node->child_ids.push_back(choice_field_node->id);
1046     --(*text_run_index);
1047   }
1048 
AddRemainingAnnotations(ui::AXNodeData * para_node)1049   void AddRemainingAnnotations(ui::AXNodeData* para_node) {
1050     // If we don't have additional links, images or form fields to insert in the
1051     // tree, then return.
1052     if (current_link_index_ >= links_.size() &&
1053         current_image_index_ >= images_.size() &&
1054         current_text_field_index_ >= text_fields_.size() &&
1055         current_button_index_ >= buttons_.size() &&
1056         current_choice_field_index_ >= choice_fields_.size()) {
1057       return;
1058     }
1059 
1060     // If we don't have a paragraph node, create a new one.
1061     if (!para_node) {
1062       para_node = CreateNode(ax::mojom::Role::kParagraph,
1063                              ax::mojom::Restriction::kReadOnly,
1064                              render_accessibility_, nodes_);
1065       page_node_->child_ids.push_back(para_node->id);
1066     }
1067     // Push all the links not anchored to any text run to the last paragraph.
1068     for (size_t i = current_link_index_; i < links_.size(); i++) {
1069       ui::AXNodeData* link_node = CreateLinkNode(links_[i]);
1070       para_node->child_ids.push_back(link_node->id);
1071     }
1072     // Push all the images not anchored to any text run to the last paragraph.
1073     for (size_t i = current_image_index_; i < images_.size(); i++) {
1074       ui::AXNodeData* image_node = CreateImageNode(images_[i]);
1075       para_node->child_ids.push_back(image_node->id);
1076     }
1077 
1078     if (base::FeatureList::IsEnabled(
1079             chrome_pdf::features::kAccessiblePDFForm)) {
1080       // Push all the text fields not anchored to any text run to the last
1081       // paragraph.
1082       for (size_t i = current_text_field_index_; i < text_fields_.size(); i++) {
1083         ui::AXNodeData* text_field_node = CreateTextFieldNode(text_fields_[i]);
1084         para_node->child_ids.push_back(text_field_node->id);
1085       }
1086 
1087       // Push all the buttons not anchored to any text run to the last
1088       // paragraph.
1089       for (size_t i = current_button_index_; i < buttons_.size(); i++) {
1090         ui::AXNodeData* button_node = CreateButtonNode(buttons_[i]);
1091         para_node->child_ids.push_back(button_node->id);
1092       }
1093 
1094       // Push all the choice fields not anchored to any text run to the last
1095       // paragraph.
1096       for (size_t i = current_choice_field_index_; i < choice_fields_.size();
1097            i++) {
1098         ui::AXNodeData* choice_field_node =
1099             CreateChoiceFieldNode(choice_fields_[i]);
1100         para_node->child_ids.push_back(choice_field_node->id);
1101       }
1102     }
1103   }
1104 
1105   std::vector<uint32_t> text_run_start_indices_;
1106   const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs_;
1107   const std::vector<PP_PrivateAccessibilityCharInfo>& chars_;
1108   const std::vector<ppapi::PdfAccessibilityLinkInfo>& links_;
1109   uint32_t current_link_index_ = 0;
1110   const std::vector<ppapi::PdfAccessibilityImageInfo>& images_;
1111   uint32_t current_image_index_ = 0;
1112   const std::vector<ppapi::PdfAccessibilityHighlightInfo>& highlights_;
1113   uint32_t current_highlight_index_ = 0;
1114   const std::vector<ppapi::PdfAccessibilityTextFieldInfo>& text_fields_;
1115   uint32_t current_text_field_index_ = 0;
1116   const std::vector<ppapi::PdfAccessibilityButtonInfo>& buttons_;
1117   uint32_t current_button_index_ = 0;
1118   const std::vector<ppapi::PdfAccessibilityChoiceFieldInfo>& choice_fields_;
1119   uint32_t current_choice_field_index_ = 0;
1120   const gfx::RectF& page_bounds_;
1121   uint32_t page_index_;
1122   ui::AXNodeData* page_node_;
1123   content::RenderAccessibility* render_accessibility_;
1124   std::vector<std::unique_ptr<ui::AXNodeData>>* nodes_;
1125   std::map<int32_t, PP_PdfPageCharacterIndex>* node_id_to_page_char_index_;
1126   std::map<int32_t, PdfAccessibilityTree::AnnotationInfo>*
1127       node_id_to_annotation_info_;
1128   float heading_font_size_threshold_ = 0;
1129   float paragraph_spacing_threshold_ = 0;
1130 };
1131 
1132 }  // namespace
1133 
PdfAccessibilityTree(content::RendererPpapiHost * host,PP_Instance instance)1134 PdfAccessibilityTree::PdfAccessibilityTree(content::RendererPpapiHost* host,
1135                                            PP_Instance instance)
1136     : host_(host), instance_(instance) {}
1137 
~PdfAccessibilityTree()1138 PdfAccessibilityTree::~PdfAccessibilityTree() {
1139   content::RenderAccessibility* render_accessibility = GetRenderAccessibility();
1140   if (render_accessibility)
1141     render_accessibility->SetPluginTreeSource(nullptr);
1142 }
1143 
1144 // static
IsDataFromPluginValid(const std::vector<ppapi::PdfAccessibilityTextRunInfo> & text_runs,const std::vector<PP_PrivateAccessibilityCharInfo> & chars,const ppapi::PdfAccessibilityPageObjects & page_objects)1145 bool PdfAccessibilityTree::IsDataFromPluginValid(
1146     const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs,
1147     const std::vector<PP_PrivateAccessibilityCharInfo>& chars,
1148     const ppapi::PdfAccessibilityPageObjects& page_objects) {
1149   base::CheckedNumeric<uint32_t> char_length = 0;
1150   for (const ppapi::PdfAccessibilityTextRunInfo& text_run : text_runs)
1151     char_length += text_run.len;
1152 
1153   if (!char_length.IsValid() || char_length.ValueOrDie() != chars.size())
1154     return false;
1155 
1156   const std::vector<ppapi::PdfAccessibilityLinkInfo>& links =
1157       page_objects.links;
1158   if (!std::is_sorted(links.begin(), links.end(),
1159                       CompareTextRuns<ppapi::PdfAccessibilityLinkInfo>)) {
1160     return false;
1161   }
1162   // Text run index of a |link| is out of bounds if it exceeds the size of
1163   // |text_runs|. The index denotes the position of the link relative to the
1164   // text runs. The index value equal to the size of |text_runs| indicates that
1165   // the link should be after the last text run.
1166   // |index_in_page| of every |link| should be with in the range of total number
1167   // of links, which is size of |links|.
1168   for (const ppapi::PdfAccessibilityLinkInfo& link : links) {
1169     base::CheckedNumeric<uint32_t> index = link.text_run_index;
1170     index += link.text_run_count;
1171     if (!index.IsValid() || index.ValueOrDie() > text_runs.size() ||
1172         link.index_in_page >= links.size()) {
1173       return false;
1174     }
1175   }
1176 
1177   const std::vector<ppapi::PdfAccessibilityImageInfo>& images =
1178       page_objects.images;
1179   if (!std::is_sorted(images.begin(), images.end(),
1180                       CompareTextRuns<ppapi::PdfAccessibilityImageInfo>)) {
1181     return false;
1182   }
1183   // Text run index of an |image| works on the same logic as the text run index
1184   // of a |link| as mentioned above.
1185   for (const ppapi::PdfAccessibilityImageInfo& image : images) {
1186     if (image.text_run_index > text_runs.size())
1187       return false;
1188   }
1189 
1190   const std::vector<ppapi::PdfAccessibilityHighlightInfo>& highlights =
1191       page_objects.highlights;
1192   if (!std::is_sorted(highlights.begin(), highlights.end(),
1193                       CompareTextRuns<ppapi::PdfAccessibilityHighlightInfo>)) {
1194     return false;
1195   }
1196 
1197   // Since highlights also span across text runs similar to links, the
1198   // validation method is the same.
1199   // |index_in_page| of a |highlight| follows the same index validation rules
1200   // as of links.
1201   for (const auto& highlight : highlights) {
1202     base::CheckedNumeric<uint32_t> index = highlight.text_run_index;
1203     index += highlight.text_run_count;
1204     if (!index.IsValid() || index.ValueOrDie() > text_runs.size() ||
1205         highlight.index_in_page >= highlights.size()) {
1206       return false;
1207     }
1208   }
1209 
1210   const std::vector<ppapi::PdfAccessibilityTextFieldInfo>& text_fields =
1211       page_objects.form_fields.text_fields;
1212   if (!std::is_sorted(text_fields.begin(), text_fields.end(),
1213                       CompareTextRuns<ppapi::PdfAccessibilityTextFieldInfo>)) {
1214     return false;
1215   }
1216   // Text run index of an |text_field| works on the same logic as the text run
1217   // index of a |link| as mentioned above.
1218   // |index_in_page| of a |text_field| follows the same index validation rules
1219   // as of links.
1220   for (const ppapi::PdfAccessibilityTextFieldInfo& text_field : text_fields) {
1221     if (text_field.text_run_index > text_runs.size() ||
1222         text_field.index_in_page >= text_fields.size()) {
1223       return false;
1224     }
1225   }
1226 
1227   const std::vector<ppapi::PdfAccessibilityChoiceFieldInfo>& choice_fields =
1228       page_objects.form_fields.choice_fields;
1229   if (!std::is_sorted(
1230           choice_fields.begin(), choice_fields.end(),
1231           CompareTextRuns<ppapi::PdfAccessibilityChoiceFieldInfo>)) {
1232     return false;
1233   }
1234   // Text run index of an |choice_field| works on the same logic as the text run
1235   // index of a |link| as mentioned above.
1236   // |index_in_page| of a |choice_field| follows the same index validation rules
1237   // as of links.
1238   for (const auto& choice_field : choice_fields) {
1239     if (choice_field.text_run_index > text_runs.size() ||
1240         choice_field.index_in_page >= choice_fields.size()) {
1241       return false;
1242     }
1243   }
1244 
1245   const std::vector<ppapi::PdfAccessibilityButtonInfo>& buttons =
1246       page_objects.form_fields.buttons;
1247   if (!std::is_sorted(
1248           buttons.begin(), buttons.end(),
1249           CompareTextRuns<ppapi::PdfAccessibilityButtonInfo>)) {
1250     return false;
1251   }
1252   for (const ppapi::PdfAccessibilityButtonInfo& button :
1253        buttons) {
1254     // Text run index of an |button| works on the same logic as the text run
1255     // index of a |link| as mentioned above.
1256     // |index_in_page| of a |button| follows the same index validation rules as
1257     // of links.
1258     if (button.text_run_index > text_runs.size() ||
1259         button.index_in_page >= buttons.size()) {
1260       return false;
1261     }
1262     // For radio button or checkbox, value of |button.control_index| should
1263     // always be less than |button.control_count|.
1264     if ((button.type == PP_PrivateButtonType::PP_PRIVATEBUTTON_CHECKBOX ||
1265          button.type == PP_PrivateButtonType::PP_PRIVATEBUTTON_RADIOBUTTON) &&
1266         (button.control_index >= button.control_count)) {
1267       return false;
1268     }
1269   }
1270 
1271   return true;
1272 }
1273 
SetAccessibilityViewportInfo(const PP_PrivateAccessibilityViewportInfo & viewport_info)1274 void PdfAccessibilityTree::SetAccessibilityViewportInfo(
1275     const PP_PrivateAccessibilityViewportInfo& viewport_info) {
1276   zoom_ = viewport_info.zoom;
1277   scale_ = viewport_info.scale;
1278   CHECK_GT(zoom_, 0);
1279   CHECK_GT(scale_, 0);
1280   scroll_ = PpPointToVector2dF(viewport_info.scroll);
1281   offset_ = PpPointToVector2dF(viewport_info.offset);
1282 
1283   selection_start_page_index_ = viewport_info.selection_start_page_index;
1284   selection_start_char_index_ = viewport_info.selection_start_char_index;
1285   selection_end_page_index_ = viewport_info.selection_end_page_index;
1286   selection_end_char_index_ = viewport_info.selection_end_char_index;
1287 
1288   content::RenderAccessibility* render_accessibility = GetRenderAccessibility();
1289   if (render_accessibility && tree_.size() > 1) {
1290     ui::AXNode* root = tree_.root();
1291     ui::AXNodeData root_data = root->data();
1292     root_data.relative_bounds.transform = MakeTransformFromViewInfo();
1293     root->SetData(root_data);
1294     UpdateAXTreeDataFromSelection();
1295     render_accessibility->OnPluginRootNodeUpdated();
1296   }
1297 }
1298 
SetAccessibilityDocInfo(const PP_PrivateAccessibilityDocInfo & doc_info)1299 void PdfAccessibilityTree::SetAccessibilityDocInfo(
1300     const PP_PrivateAccessibilityDocInfo& doc_info) {
1301   content::RenderAccessibility* render_accessibility = GetRenderAccessibility();
1302   if (!render_accessibility)
1303     return;
1304 
1305   doc_info_ = doc_info;
1306   doc_node_ =
1307       CreateNode(ax::mojom::Role::kDocument, ax::mojom::Restriction::kReadOnly,
1308                  render_accessibility, &nodes_);
1309   doc_node_->AddStringAttribute(
1310       ax::mojom::StringAttribute::kName,
1311       l10n_util::GetPluralStringFUTF8(IDS_PDF_DOCUMENT_PAGE_COUNT,
1312                                       doc_info.page_count));
1313 
1314   // Because all of the coordinates are expressed relative to the
1315   // doc's coordinates, the origin of the doc must be (0, 0). Its
1316   // width and height will be updated as we add each page so that the
1317   // doc's bounding box surrounds all pages.
1318   doc_node_->relative_bounds.bounds = gfx::RectF(0, 0, 1, 1);
1319 }
1320 
SetAccessibilityPageInfo(const PP_PrivateAccessibilityPageInfo & page_info,const std::vector<ppapi::PdfAccessibilityTextRunInfo> & text_runs,const std::vector<PP_PrivateAccessibilityCharInfo> & chars,const ppapi::PdfAccessibilityPageObjects & page_objects)1321 void PdfAccessibilityTree::SetAccessibilityPageInfo(
1322     const PP_PrivateAccessibilityPageInfo& page_info,
1323     const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs,
1324     const std::vector<PP_PrivateAccessibilityCharInfo>& chars,
1325     const ppapi::PdfAccessibilityPageObjects& page_objects) {
1326   content::RenderAccessibility* render_accessibility = GetRenderAccessibility();
1327   if (!render_accessibility)
1328     return;
1329 
1330   // If unsanitized data is found, don't trust the PPAPI process sending it and
1331   // stop creation of the accessibility tree.
1332   if (!invalid_plugin_message_received_) {
1333     invalid_plugin_message_received_ =
1334         !IsDataFromPluginValid(text_runs, chars, page_objects);
1335   }
1336   if (invalid_plugin_message_received_)
1337     return;
1338 
1339   uint32_t page_index = page_info.page_index;
1340   CHECK_GE(page_index, 0U);
1341   CHECK_LT(page_index, doc_info_.page_count);
1342 
1343   ui::AXNodeData* page_node =
1344       CreateNode(ax::mojom::Role::kRegion, ax::mojom::Restriction::kReadOnly,
1345                  render_accessibility, &nodes_);
1346   page_node->AddStringAttribute(
1347       ax::mojom::StringAttribute::kName,
1348       l10n_util::GetPluralStringFUTF8(IDS_PDF_PAGE_INDEX, page_index + 1));
1349   page_node->AddBoolAttribute(ax::mojom::BoolAttribute::kIsPageBreakingObject,
1350                               true);
1351 
1352   gfx::RectF page_bounds = PPRectToGfxRectF(page_info.bounds);
1353   page_node->relative_bounds.bounds = page_bounds;
1354   doc_node_->relative_bounds.bounds.Union(page_node->relative_bounds.bounds);
1355   doc_node_->child_ids.push_back(page_node->id);
1356 
1357   AddPageContent(page_node, page_bounds, page_index, text_runs, chars,
1358                  page_objects, render_accessibility);
1359 
1360   if (page_index == doc_info_.page_count - 1)
1361     Finish();
1362 }
1363 
AddPageContent(ui::AXNodeData * page_node,const gfx::RectF & page_bounds,uint32_t page_index,const std::vector<ppapi::PdfAccessibilityTextRunInfo> & text_runs,const std::vector<PP_PrivateAccessibilityCharInfo> & chars,const ppapi::PdfAccessibilityPageObjects & page_objects,content::RenderAccessibility * render_accessibility)1364 void PdfAccessibilityTree::AddPageContent(
1365     ui::AXNodeData* page_node,
1366     const gfx::RectF& page_bounds,
1367     uint32_t page_index,
1368     const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs,
1369     const std::vector<PP_PrivateAccessibilityCharInfo>& chars,
1370     const ppapi::PdfAccessibilityPageObjects& page_objects,
1371     content::RenderAccessibility* render_accessibility) {
1372   DCHECK(page_node);
1373   PdfAccessibilityTreeBuilder tree_builder(
1374       text_runs, chars, page_objects, page_bounds, page_index, page_node,
1375       render_accessibility, &nodes_, &node_id_to_page_char_index_,
1376       &node_id_to_annotation_info_);
1377   tree_builder.BuildPageTree();
1378 }
1379 
Finish()1380 void PdfAccessibilityTree::Finish() {
1381   doc_node_->relative_bounds.transform = MakeTransformFromViewInfo();
1382 
1383   ui::AXTreeUpdate update;
1384   update.root_id = doc_node_->id;
1385   for (const auto& node : nodes_)
1386     update.nodes.push_back(*node);
1387 
1388   if (!tree_.Unserialize(update))
1389     LOG(FATAL) << tree_.error();
1390 
1391   UpdateAXTreeDataFromSelection();
1392 
1393   content::RenderAccessibility* render_accessibility = GetRenderAccessibility();
1394   if (render_accessibility)
1395     render_accessibility->SetPluginTreeSource(this);
1396 }
1397 
UpdateAXTreeDataFromSelection()1398 void PdfAccessibilityTree::UpdateAXTreeDataFromSelection() {
1399   tree_data_.sel_is_backward = false;
1400   if (selection_start_page_index_ > selection_end_page_index_) {
1401     tree_data_.sel_is_backward = true;
1402   } else if (selection_start_page_index_ == selection_end_page_index_ &&
1403              selection_start_char_index_ > selection_end_char_index_) {
1404     tree_data_.sel_is_backward = true;
1405   }
1406 
1407   FindNodeOffset(selection_start_page_index_, selection_start_char_index_,
1408                  &tree_data_.sel_anchor_object_id,
1409                  &tree_data_.sel_anchor_offset);
1410   FindNodeOffset(selection_end_page_index_, selection_end_char_index_,
1411                  &tree_data_.sel_focus_object_id, &tree_data_.sel_focus_offset);
1412 }
1413 
FindNodeOffset(uint32_t page_index,uint32_t page_char_index,int32_t * out_node_id,int32_t * out_node_char_index) const1414 void PdfAccessibilityTree::FindNodeOffset(uint32_t page_index,
1415                                           uint32_t page_char_index,
1416                                           int32_t* out_node_id,
1417                                           int32_t* out_node_char_index) const {
1418   *out_node_id = -1;
1419   *out_node_char_index = 0;
1420   ui::AXNode* root = tree_.root();
1421   if (page_index >= root->children().size())
1422     return;
1423   ui::AXNode* page = root->children()[page_index];
1424 
1425   // Iterate over all paragraphs within this given page, and static text nodes
1426   // within each paragraph.
1427   for (ui::AXNode* para : page->children()) {
1428     for (ui::AXNode* child_node : para->children()) {
1429       ui::AXNode* static_text = GetStaticTextNodeFromNode(child_node);
1430       if (!static_text)
1431         continue;
1432       // Look up the page-relative character index for static nodes from a map
1433       // we built while the document was initially built.
1434       auto iter = node_id_to_page_char_index_.find(static_text->id());
1435       uint32_t char_index = iter->second.char_index;
1436       uint32_t len = static_text->data()
1437                          .GetStringAttribute(ax::mojom::StringAttribute::kName)
1438                          .size();
1439 
1440       // If the character index we're looking for falls within the range
1441       // of this node, return the node ID and index within this node's text.
1442       if (page_char_index <= char_index + len) {
1443         *out_node_id = static_text->id();
1444         *out_node_char_index = page_char_index - char_index;
1445         return;
1446       }
1447     }
1448   }
1449 }
1450 
FindCharacterOffset(const ui::AXNode & node,uint32_t char_offset_in_node,PP_PdfPageCharacterIndex * page_char_index) const1451 bool PdfAccessibilityTree::FindCharacterOffset(
1452     const ui::AXNode& node,
1453     uint32_t char_offset_in_node,
1454     PP_PdfPageCharacterIndex* page_char_index) const {
1455   auto iter = node_id_to_page_char_index_.find(GetId(&node));
1456   if (iter == node_id_to_page_char_index_.end())
1457     return false;
1458   page_char_index->char_index = iter->second.char_index + char_offset_in_node;
1459   page_char_index->page_index = iter->second.page_index;
1460   return true;
1461 }
1462 
GetRenderAccessibility()1463 content::RenderAccessibility* PdfAccessibilityTree::GetRenderAccessibility() {
1464   content::RenderFrame* render_frame =
1465       host_->GetRenderFrameForInstance(instance_);
1466   if (!render_frame)
1467     return nullptr;
1468   content::RenderAccessibility* render_accessibility =
1469       render_frame->GetRenderAccessibility();
1470   if (!render_accessibility)
1471     return nullptr;
1472 
1473   // If RenderAccessibility is unable to generate valid positive IDs,
1474   // we shouldn't use it. This can happen if Blink accessibility is disabled
1475   // after we started generating the accessible PDF.
1476   if (render_accessibility->GenerateAXID() <= 0)
1477     return nullptr;
1478 
1479   return render_accessibility;
1480 }
1481 
1482 std::unique_ptr<gfx::Transform>
MakeTransformFromViewInfo() const1483 PdfAccessibilityTree::MakeTransformFromViewInfo() const {
1484   double applicable_scale_factor =
1485       content::RenderThread::Get()->IsUseZoomForDSF() ? scale_ : 1;
1486   auto transform = std::make_unique<gfx::Transform>();
1487   // |scroll_| represents the offset from which PDF content starts. It is the
1488   // height of the PDF toolbar and the width of sidenav in pixels if it is open.
1489   // Sizes of PDF toolbar and sidenav do not change with zoom.
1490   transform->Scale(applicable_scale_factor, applicable_scale_factor);
1491   transform->Translate(-scroll_);
1492   transform->Scale(zoom_, zoom_);
1493   transform->Translate(offset_);
1494   return transform;
1495 }
1496 
AnnotationInfo(uint32_t page_index,uint32_t annotation_index)1497 PdfAccessibilityTree::AnnotationInfo::AnnotationInfo(uint32_t page_index,
1498                                                      uint32_t annotation_index)
1499     : page_index(page_index), annotation_index(annotation_index) {}
1500 
1501 PdfAccessibilityTree::AnnotationInfo::AnnotationInfo(
1502     const AnnotationInfo& other) = default;
1503 
1504 PdfAccessibilityTree::AnnotationInfo::~AnnotationInfo() = default;
1505 
1506 //
1507 // AXTreeSource implementation.
1508 //
1509 
GetTreeData(ui::AXTreeData * tree_data) const1510 bool PdfAccessibilityTree::GetTreeData(ui::AXTreeData* tree_data) const {
1511   tree_data->sel_is_backward = tree_data_.sel_is_backward;
1512   tree_data->sel_anchor_object_id = tree_data_.sel_anchor_object_id;
1513   tree_data->sel_anchor_offset = tree_data_.sel_anchor_offset;
1514   tree_data->sel_focus_object_id = tree_data_.sel_focus_object_id;
1515   tree_data->sel_focus_offset = tree_data_.sel_focus_offset;
1516   return true;
1517 }
1518 
GetRoot() const1519 ui::AXNode* PdfAccessibilityTree::GetRoot() const {
1520   return tree_.root();
1521 }
1522 
GetFromId(int32_t id) const1523 ui::AXNode* PdfAccessibilityTree::GetFromId(int32_t id) const {
1524   return tree_.GetFromId(id);
1525 }
1526 
GetId(const ui::AXNode * node) const1527 int32_t PdfAccessibilityTree::GetId(const ui::AXNode* node) const {
1528   return node->id();
1529 }
1530 
GetChildren(const ui::AXNode * node,std::vector<const ui::AXNode * > * out_children) const1531 void PdfAccessibilityTree::GetChildren(
1532     const ui::AXNode* node,
1533     std::vector<const ui::AXNode*>* out_children) const {
1534   *out_children = std::vector<const ui::AXNode*>(node->children().cbegin(),
1535                                                  node->children().cend());
1536 }
1537 
GetParent(const ui::AXNode * node) const1538 ui::AXNode* PdfAccessibilityTree::GetParent(const ui::AXNode* node) const {
1539   return node->parent();
1540 }
1541 
IsIgnored(const ui::AXNode * node) const1542 bool PdfAccessibilityTree::IsIgnored(const ui::AXNode* node) const {
1543   return node->IsIgnored();
1544 }
1545 
IsValid(const ui::AXNode * node) const1546 bool PdfAccessibilityTree::IsValid(const ui::AXNode* node) const {
1547   return node != nullptr;
1548 }
1549 
IsEqual(const ui::AXNode * node1,const ui::AXNode * node2) const1550 bool PdfAccessibilityTree::IsEqual(const ui::AXNode* node1,
1551                                    const ui::AXNode* node2) const {
1552   return node1 == node2;
1553 }
1554 
GetNull() const1555 const ui::AXNode* PdfAccessibilityTree::GetNull() const {
1556   return nullptr;
1557 }
1558 
SerializeNode(const ui::AXNode * node,ui::AXNodeData * out_data) const1559 void PdfAccessibilityTree::SerializeNode(
1560     const ui::AXNode* node, ui::AXNodeData* out_data) const {
1561   *out_data = node->data();
1562 }
1563 
CreateActionTarget(const ui::AXNode & target_node)1564 std::unique_ptr<ui::AXActionTarget> PdfAccessibilityTree::CreateActionTarget(
1565     const ui::AXNode& target_node) {
1566   return std::make_unique<PdfAXActionTarget>(target_node, this);
1567 }
1568 
ShowContextMenu()1569 bool PdfAccessibilityTree::ShowContextMenu() {
1570   content::RenderAccessibility* render_accessibility = GetRenderAccessibility();
1571   if (!render_accessibility)
1572     return false;
1573 
1574   render_accessibility->ShowPluginContextMenu();
1575   return true;
1576 }
1577 
HandleAction(const PP_PdfAccessibilityActionData & action_data)1578 void PdfAccessibilityTree::HandleAction(
1579     const PP_PdfAccessibilityActionData& action_data) {
1580   content::PepperPluginInstance* plugin_instance =
1581       host_->GetPluginInstance(instance_);
1582   if (plugin_instance) {
1583     plugin_instance->HandleAccessibilityAction(action_data);
1584   }
1585 }
1586 
1587 base::Optional<PdfAccessibilityTree::AnnotationInfo>
GetPdfAnnotationInfoFromAXNode(int32_t ax_node_id) const1588 PdfAccessibilityTree::GetPdfAnnotationInfoFromAXNode(int32_t ax_node_id) const {
1589   auto iter = node_id_to_annotation_info_.find(ax_node_id);
1590   if (iter == node_id_to_annotation_info_.end())
1591     return base::nullopt;
1592 
1593   return AnnotationInfo(iter->second.page_index, iter->second.annotation_index);
1594 }
1595 
1596 }  // namespace pdf
1597