diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/pdf | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/pdf')
5 files changed, 939 insertions, 548 deletions
diff --git a/chromium/components/pdf/renderer/pdf_accessibility_tree.cc b/chromium/components/pdf/renderer/pdf_accessibility_tree.cc index 01a1ba0397f..5dd34743dde 100644 --- a/chromium/components/pdf/renderer/pdf_accessibility_tree.cc +++ b/chromium/components/pdf/renderer/pdf_accessibility_tree.cc @@ -230,14 +230,19 @@ bool BreakParagraph( ui::AXNode* GetStaticTextNodeFromNode(ui::AXNode* node) { // Returns the appropriate static text node given |node|'s type. // Returns nullptr if there is no appropriate static text node. + if (!node) + return nullptr; ui::AXNode* static_node = node; // Get the static text from the link node. - if (node && - (node->data().role == ax::mojom::Role::kLink || - node->data().role == ax::mojom::Role::kPdfActionableHighlight) && + if (node->data().role == ax::mojom::Role::kLink && node->children().size() == 1) { static_node = node->children()[0]; } + // Get the static text from the highlight node. + if (node->data().role == ax::mojom::Role::kPdfActionableHighlight && + !node->children().empty()) { + static_node = node->children()[0]; + } // If it's static text node, then it holds text. if (static_node && static_node->data().role == ax::mojom::Role::kStaticText) return static_node; @@ -313,6 +318,595 @@ bool IsTextRenderModeStroke(const PP_TextRenderingMode& mode) { } } +ui::AXNodeData* CreateNode( + ax::mojom::Role role, + ax::mojom::Restriction restriction, + content::RenderAccessibility* render_accessibility, + std::vector<std::unique_ptr<ui::AXNodeData>>* nodes) { + DCHECK(render_accessibility); + + auto node = std::make_unique<ui::AXNodeData>(); + node->id = render_accessibility->GenerateAXID(); + node->role = role; + node->SetRestriction(restriction); + + // All nodes other than the first one have coordinates relative to + // the first node. + if (!nodes->empty()) + node->relative_bounds.offset_container_id = (*nodes)[0]->id; + + ui::AXNodeData* node_ptr = node.get(); + nodes->push_back(std::move(node)); + + return node_ptr; +} + +class PdfAccessibilityTreeBuilder { + public: + explicit 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) + : text_runs_(text_runs), + chars_(chars), + links_(page_objects.links), + images_(page_objects.images), + highlights_(page_objects.highlights), + text_fields_(page_objects.form_fields.text_fields), + page_bounds_(page_bounds), + page_index_(page_index), + page_node_(page_node), + render_accessibility_(render_accessibility), + nodes_(nodes), + node_id_to_page_char_index_(node_id_to_page_char_index), + node_id_to_annotation_info_(node_id_to_annotation_info) { + if (!text_runs.empty()) { + text_run_start_indices_.reserve(text_runs.size()); + text_run_start_indices_.push_back(0); + for (size_t i = 0; i < text_runs.size() - 1; ++i) { + text_run_start_indices_.push_back(text_run_start_indices_[i] + + text_runs[i].len); + } + } + } + + void BuildPageTree() { + ComputeParagraphAndHeadingThresholds(text_runs_, + &heading_font_size_threshold_, + ¶graph_spacing_threshold_); + + ui::AXNodeData* para_node = nullptr; + ui::AXNodeData* static_text_node = nullptr; + ui::AXNodeData* previous_on_line_node = nullptr; + std::string static_text; + uint32_t current_link_index = 0; + uint32_t current_image_index = 0; + uint32_t current_highlight_index = 0; + uint32_t current_text_field_index = 0; + LineHelper line_helper(text_runs_); + + for (size_t text_run_index = 0; text_run_index < text_runs_.size(); + ++text_run_index) { + // If we don't have a paragraph, create one. + if (!para_node) { + para_node = + CreateParagraphNode(text_runs_[text_run_index].style.font_size); + page_node_->child_ids.push_back(para_node->id); + } + + // If the |text_run_index| is less than or equal to the link's + // text_run_index, then push the link node in the paragraph. + if (IsObjectInTextRun(links_, current_link_index, text_run_index)) { + FinishStaticNode(&static_text_node, &static_text); + const ppapi::PdfAccessibilityLinkInfo& link = + links_[current_link_index++]; + AddLinkToParaNode(link, para_node, &previous_on_line_node, + &text_run_index); + + if (link.text_run_count == 0) + continue; + + } else if (IsObjectInTextRun(images_, current_image_index, + text_run_index)) { + FinishStaticNode(&static_text_node, &static_text); + AddImageToParaNode(images_[current_image_index++], para_node, + &text_run_index); + continue; + } else if (IsObjectInTextRun(highlights_, current_highlight_index, + text_run_index) && + base::FeatureList::IsEnabled( + chrome_pdf::features::kAccessiblePDFHighlight)) { + FinishStaticNode(&static_text_node, &static_text); + AddHighlightToParaNode(highlights_[current_highlight_index++], + para_node, &previous_on_line_node, + &text_run_index); + } else if (IsObjectInTextRun(text_fields_, current_text_field_index, + text_run_index) && + base::FeatureList::IsEnabled( + chrome_pdf::features::kAccessiblePDFForm)) { + FinishStaticNode(&static_text_node, &static_text); + AddTextFieldToParaNode(text_fields_[current_text_field_index++], + para_node, &text_run_index); + continue; + } else { + PP_PdfPageCharacterIndex page_char_index = { + page_index_, text_run_start_indices_[text_run_index]}; + + // This node is for the text inside the paragraph, it includes + // the text of all of the text runs. + if (!static_text_node) { + static_text_node = CreateStaticTextNode(page_char_index); + para_node->child_ids.push_back(static_text_node->id); + } + + const ppapi::PdfAccessibilityTextRunInfo& text_run = + text_runs_[text_run_index]; + // Add this text run to the current static text node. + ui::AXNodeData* inline_text_box_node = + CreateInlineTextBoxNode(text_run, page_char_index); + static_text_node->child_ids.push_back(inline_text_box_node->id); + + static_text += inline_text_box_node->GetStringAttribute( + ax::mojom::StringAttribute::kName); + + para_node->relative_bounds.bounds.Union( + inline_text_box_node->relative_bounds.bounds); + static_text_node->relative_bounds.bounds.Union( + inline_text_box_node->relative_bounds.bounds); + + if (previous_on_line_node) { + ConnectPreviousAndNextOnLine(previous_on_line_node, + inline_text_box_node); + } else { + line_helper.StartNewLine(text_run_index); + } + line_helper.ProcessNextRun(text_run_index); + + if (text_run_index < text_runs_.size() - 1) { + if (line_helper.IsRunOnSameLine(text_run_index + 1)) { + // The next run is on the same line. + previous_on_line_node = inline_text_box_node; + } else { + // The next run is on a new line. + previous_on_line_node = nullptr; + } + } + } + + if (text_run_index == text_runs_.size() - 1) { + FinishStaticNode(&static_text_node, &static_text); + break; + } + + if (!previous_on_line_node) { + if (BreakParagraph(text_runs_, text_run_index, + paragraph_spacing_threshold_)) { + FinishStaticNode(&static_text_node, &static_text); + para_node = nullptr; + } + } + } + + base::span<const ppapi::PdfAccessibilityLinkInfo> remaining_links = + base::make_span(links_).subspan(current_link_index); + base::span<const ppapi::PdfAccessibilityImageInfo> remaining_images = + base::make_span(images_).subspan(current_image_index); + base::span<const ppapi::PdfAccessibilityTextFieldInfo> + remaining_text_fields = + base::make_span(text_fields_).subspan(current_text_field_index); + AddRemainingAnnotations(remaining_links, remaining_images, + remaining_text_fields, para_node); + } + + private: + void AddWordStartsAndEnds(ui::AXNodeData* inline_text_box) { + base::string16 text = inline_text_box->GetString16Attribute( + ax::mojom::StringAttribute::kName); + base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD); + if (!iter.Init()) + return; + + std::vector<int32_t> word_starts; + std::vector<int32_t> word_ends; + while (iter.Advance()) { + if (iter.IsWord()) { + word_starts.push_back(iter.prev()); + word_ends.push_back(iter.pos()); + } + } + inline_text_box->AddIntListAttribute( + ax::mojom::IntListAttribute::kWordStarts, word_starts); + inline_text_box->AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, + word_ends); + } + + ui::AXNodeData* CreateParagraphNode(float font_size) { + ui::AXNodeData* para_node = CreateNode(ax::mojom::Role::kParagraph, + ax::mojom::Restriction::kReadOnly, + render_accessibility_, nodes_); + para_node->AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, + true); + + // If font size exceeds the |heading_font_size_threshold_|, then classify + // it as a Heading. + if (heading_font_size_threshold_ > 0 && + font_size > heading_font_size_threshold_) { + para_node->role = ax::mojom::Role::kHeading; + para_node->AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, + 2); + para_node->AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "h2"); + } + + return para_node; + } + + ui::AXNodeData* CreateStaticTextNode( + const PP_PdfPageCharacterIndex& page_char_index) { + ui::AXNodeData* static_text_node = CreateNode( + ax::mojom::Role::kStaticText, ax::mojom::Restriction::kReadOnly, + render_accessibility_, nodes_); + node_id_to_page_char_index_->emplace(static_text_node->id, page_char_index); + return static_text_node; + } + + ui::AXNodeData* CreateInlineTextBoxNode( + const ppapi::PdfAccessibilityTextRunInfo& text_run, + const PP_PdfPageCharacterIndex& page_char_index) { + ui::AXNodeData* inline_text_box_node = CreateNode( + ax::mojom::Role::kInlineTextBox, ax::mojom::Restriction::kReadOnly, + render_accessibility_, nodes_); + + std::string chars__utf8 = + GetTextRunCharsAsUTF8(text_run, chars_, page_char_index.char_index); + inline_text_box_node->AddStringAttribute(ax::mojom::StringAttribute::kName, + chars__utf8); + inline_text_box_node->AddIntAttribute( + ax::mojom::IntAttribute::kTextDirection, text_run.direction); + inline_text_box_node->AddStringAttribute( + ax::mojom::StringAttribute::kFontFamily, text_run.style.font_name); + inline_text_box_node->AddFloatAttribute( + ax::mojom::FloatAttribute::kFontSize, text_run.style.font_size); + inline_text_box_node->AddFloatAttribute( + ax::mojom::FloatAttribute::kFontWeight, text_run.style.font_weight); + if (text_run.style.is_italic) + inline_text_box_node->AddTextStyle(ax::mojom::TextStyle::kItalic); + if (text_run.style.is_bold) + inline_text_box_node->AddTextStyle(ax::mojom::TextStyle::kBold); + if (IsTextRenderModeFill(text_run.style.render_mode)) { + inline_text_box_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, + text_run.style.fill_color); + } else if (IsTextRenderModeStroke(text_run.style.render_mode)) { + inline_text_box_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, + text_run.style.stroke_color); + } + + inline_text_box_node->relative_bounds.bounds = + PpFloatRectToGfxRectF(text_run.bounds) + + page_bounds_.OffsetFromOrigin(); + std::vector<int32_t> char_offsets = + GetTextRunCharOffsets(text_run, chars_, page_char_index.char_index); + inline_text_box_node->AddIntListAttribute( + ax::mojom::IntListAttribute::kCharacterOffsets, char_offsets); + AddWordStartsAndEnds(inline_text_box_node); + node_id_to_page_char_index_->emplace(inline_text_box_node->id, + page_char_index); + return inline_text_box_node; + } + + ui::AXNodeData* CreateLinkNode(const ppapi::PdfAccessibilityLinkInfo& link) { + ui::AXNodeData* link_node = + CreateNode(ax::mojom::Role::kLink, ax::mojom::Restriction::kReadOnly, + render_accessibility_, nodes_); + + link_node->AddStringAttribute(ax::mojom::StringAttribute::kUrl, link.url); + link_node->AddStringAttribute(ax::mojom::StringAttribute::kName, + std::string()); + link_node->relative_bounds.bounds = PpFloatRectToGfxRectF(link.bounds); + node_id_to_annotation_info_->emplace( + link_node->id, + PdfAccessibilityTree::AnnotationInfo(page_index_, link.index_in_page)); + + return link_node; + } + + ui::AXNodeData* CreateImageNode( + const ppapi::PdfAccessibilityImageInfo& image) { + ui::AXNodeData* image_node = + CreateNode(ax::mojom::Role::kImage, ax::mojom::Restriction::kReadOnly, + render_accessibility_, nodes_); + + if (image.alt_text.empty()) { + image_node->AddStringAttribute( + ax::mojom::StringAttribute::kName, + l10n_util::GetStringUTF8(IDS_AX_UNLABELED_IMAGE_ROLE_DESCRIPTION)); + } else { + image_node->AddStringAttribute(ax::mojom::StringAttribute::kName, + image.alt_text); + } + image_node->relative_bounds.bounds = PpFloatRectToGfxRectF(image.bounds); + return image_node; + } + + ui::AXNodeData* CreateHighlightNode( + const ppapi::PdfAccessibilityHighlightInfo& highlight) { + ui::AXNodeData* highlight_node = CreateNode( + ax::mojom::Role::kPdfActionableHighlight, + ax::mojom::Restriction::kReadOnly, render_accessibility_, nodes_); + + highlight_node->AddStringAttribute( + ax::mojom::StringAttribute::kRoleDescription, + l10n_util::GetStringUTF8(IDS_AX_ROLE_DESCRIPTION_PDF_HIGHLIGHT)); + highlight_node->AddStringAttribute(ax::mojom::StringAttribute::kName, + std::string()); + highlight_node->relative_bounds.bounds = + PpFloatRectToGfxRectF(highlight.bounds); + highlight_node->AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, + highlight.color); + + return highlight_node; + } + + ui::AXNodeData* CreatePopupNoteNode( + const ppapi::PdfAccessibilityHighlightInfo& highlight) { + ui::AXNodeData* popup_note_node = + CreateNode(ax::mojom::Role::kNote, ax::mojom::Restriction::kReadOnly, + render_accessibility_, nodes_); + + popup_note_node->AddStringAttribute( + ax::mojom::StringAttribute::kRoleDescription, + l10n_util::GetStringUTF8(IDS_AX_ROLE_DESCRIPTION_PDF_POPUP_NOTE)); + popup_note_node->AddStringAttribute(ax::mojom::StringAttribute::kName, + highlight.note_text); + popup_note_node->relative_bounds.bounds = + PpFloatRectToGfxRectF(highlight.bounds); + + return popup_note_node; + } + + ui::AXNodeData* CreateTextFieldNode( + const ppapi::PdfAccessibilityTextFieldInfo& text_field) { + ax::mojom::Restriction restriction = text_field.is_read_only + ? ax::mojom::Restriction::kReadOnly + : ax::mojom::Restriction::kNone; + ui::AXNodeData* text_field_node = + CreateNode(ax::mojom::Role::kTextField, restriction, + render_accessibility_, nodes_); + + text_field_node->AddStringAttribute(ax::mojom::StringAttribute::kName, + text_field.name); + text_field_node->AddStringAttribute(ax::mojom::StringAttribute::kValue, + text_field.value); + text_field_node->AddState(ax::mojom::State::kFocusable); + if (text_field.is_required) + text_field_node->AddState(ax::mojom::State::kRequired); + if (text_field.is_password) + text_field_node->AddState(ax::mojom::State::kProtected); + text_field_node->relative_bounds.bounds = + PpFloatRectToGfxRectF(text_field.bounds); + return text_field_node; + } + + void AddTextToAXNode(uint32_t start_text_run_index, + uint32_t end_text_run_index, + ui::AXNodeData* ax_node, + ui::AXNodeData** previous_on_line_node) { + PP_PdfPageCharacterIndex page_char_index = { + page_index_, text_run_start_indices_[start_text_run_index]}; + ui::AXNodeData* ax_static_text_node = CreateStaticTextNode(page_char_index); + ax_node->child_ids.push_back(ax_static_text_node->id); + // Accumulate the text of the node. + std::string ax_name; + LineHelper line_helper(text_runs_); + + for (size_t text_run_index = start_text_run_index; + text_run_index <= end_text_run_index; ++text_run_index) { + const ppapi::PdfAccessibilityTextRunInfo& text_run = + text_runs_[text_run_index]; + page_char_index.char_index = text_run_start_indices_[text_run_index]; + // Add this text run to the current static text node. + ui::AXNodeData* inline_text_box_node = + CreateInlineTextBoxNode(text_run, page_char_index); + ax_static_text_node->child_ids.push_back(inline_text_box_node->id); + + ax_static_text_node->relative_bounds.bounds.Union( + inline_text_box_node->relative_bounds.bounds); + ax_name += inline_text_box_node->GetStringAttribute( + ax::mojom::StringAttribute::kName); + + if (*previous_on_line_node) { + ConnectPreviousAndNextOnLine(*previous_on_line_node, + inline_text_box_node); + } else { + line_helper.StartNewLine(text_run_index); + } + line_helper.ProcessNextRun(text_run_index); + + if (text_run_index < text_runs_.size() - 1) { + if (line_helper.IsRunOnSameLine(text_run_index + 1)) { + // The next run is on the same line. + *previous_on_line_node = inline_text_box_node; + } else { + // The next run is on a new line. + *previous_on_line_node = nullptr; + } + } + } + + ax_node->AddStringAttribute(ax::mojom::StringAttribute::kName, ax_name); + ax_static_text_node->AddStringAttribute(ax::mojom::StringAttribute::kName, + ax_name); + } + + void 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) { + // Annotation objects can overlap in PDF. There can be two overlapping + // scenarios: Partial overlap and Complete overlap. + // Partial overlap + // + // Link A starts Link B starts Link A ends Link B ends + // |a1 |b1 |a2 |b2 + // ----------------------------------------------------------------------- + // Text + // + // Complete overlap + // Link A starts Link B starts Link B ends Link A ends + // |a1 |b1 |b2 |a2 + // ----------------------------------------------------------------------- + // Text + // + // For overlapping annotations, both annotations would store the full + // text data and nothing will get truncated. For partial overlap, link `A` + // would contain text between a1 and a2 while link `B` would contain text + // between b1 and b2. For complete overlap as well, link `A` would contain + // text between a1 and a2 and link `B` would contain text between b1 and + // b2. The links would appear in the tree in the order of which they are + // present. In the tree for both overlapping scenarios, link `A` would + // appear first in the tree and link `B` after it. + + // If |object_text_run_count| > 0, then the object is part of the page text. + // Make the text runs contained by the object children of the object node. + size_t end_text_run_index = object_text_run_index + object_text_run_count; + uint32_t object_end_text_run_index = + std::min(end_text_run_index, text_runs_.size()) - 1; + AddTextToAXNode(object_text_run_index, object_end_text_run_index, + object_node, previous_on_line_node); + + para_node->relative_bounds.bounds.Union( + object_node->relative_bounds.bounds); + + *text_run_index = + NormalizeTextRunIndex(object_end_text_run_index, *text_run_index); + } + + void AddLinkToParaNode(const ppapi::PdfAccessibilityLinkInfo& link, + ui::AXNodeData* para_node, + ui::AXNodeData** previous_on_line_node, + size_t* text_run_index) { + ui::AXNodeData* link_node = CreateLinkNode(link); + para_node->child_ids.push_back(link_node->id); + + // If |link.text_run_count| == 0, then the link is not part of the page + // text. Push it ahead of the current text run. + if (link.text_run_count == 0) { + --(*text_run_index); + return; + } + + // Make the text runs contained by the link children of + // the link node. + AddTextToObjectNode(link.text_run_index, link.text_run_count, link_node, + para_node, previous_on_line_node, text_run_index); + } + + void AddImageToParaNode(const ppapi::PdfAccessibilityImageInfo& image, + ui::AXNodeData* para_node, + size_t* text_run_index) { + // If the |text_run_index| is less than or equal to the image's text run + // index, then push the image ahead of the current text run. + ui::AXNodeData* image_node = CreateImageNode(image); + para_node->child_ids.push_back(image_node->id); + --(*text_run_index); + } + + void AddHighlightToParaNode( + const ppapi::PdfAccessibilityHighlightInfo& highlight, + ui::AXNodeData* para_node, + ui::AXNodeData** previous_on_line_node, + size_t* text_run_index) { + ui::AXNodeData* highlight_node = CreateHighlightNode(highlight); + para_node->child_ids.push_back(highlight_node->id); + + // Make the text runs contained by the highlight children of + // the highlight node. + AddTextToObjectNode(highlight.text_run_index, highlight.text_run_count, + highlight_node, para_node, previous_on_line_node, + text_run_index); + + if (!highlight.note_text.empty()) { + ui::AXNodeData* popup_note_node = CreatePopupNoteNode(highlight); + highlight_node->child_ids.push_back(popup_note_node->id); + } + } + + void AddTextFieldToParaNode( + const ppapi::PdfAccessibilityTextFieldInfo& text_field, + ui::AXNodeData* para_node, + size_t* text_run_index) { + // If the |text_run_index| is less than or equal to the text_field's text + // run index, then push the text_field ahead of the current text run. + ui::AXNodeData* text_field_node = CreateTextFieldNode(text_field); + para_node->child_ids.push_back(text_field_node->id); + --(*text_run_index); + } + + void AddRemainingAnnotations( + base::span<const ppapi::PdfAccessibilityLinkInfo> links, + base::span<const ppapi::PdfAccessibilityImageInfo> images, + base::span<const ppapi::PdfAccessibilityTextFieldInfo> text_fields, + ui::AXNodeData* para_node) { + // If we have additional links, images or text fields to insert in the tree, + // and we don't have a paragraph node, create a new one. + if (!para_node && + (!links.empty() || !images.empty() || !text_fields.empty())) { + para_node = CreateNode(ax::mojom::Role::kParagraph, + ax::mojom::Restriction::kReadOnly, + render_accessibility_, nodes_); + page_node_->child_ids.push_back(para_node->id); + } + // Push all the links not anchored to any text run to the last paragraph. + for (const ppapi::PdfAccessibilityLinkInfo& link : links) { + ui::AXNodeData* link_node = CreateLinkNode(link); + para_node->child_ids.push_back(link_node->id); + } + // Push all the images not anchored to any text run to the last paragraph. + for (const ppapi::PdfAccessibilityImageInfo& image : images) { + ui::AXNodeData* image_node = CreateImageNode(image); + para_node->child_ids.push_back(image_node->id); + } + + if (base::FeatureList::IsEnabled( + chrome_pdf::features::kAccessiblePDFForm)) { + // Push all the text fields not anchored to any text run to the last + // paragraph. + for (const ppapi::PdfAccessibilityTextFieldInfo& text_field : + text_fields) { + ui::AXNodeData* text_field_node = CreateTextFieldNode(text_field); + para_node->child_ids.push_back(text_field_node->id); + } + } + } + + std::vector<uint32_t> text_run_start_indices_; + const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs_; + const std::vector<PP_PrivateAccessibilityCharInfo>& chars_; + const std::vector<ppapi::PdfAccessibilityLinkInfo>& links_; + const std::vector<ppapi::PdfAccessibilityImageInfo>& images_; + const std::vector<ppapi::PdfAccessibilityHighlightInfo>& highlights_; + const std::vector<ppapi::PdfAccessibilityTextFieldInfo>& text_fields_; + 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_; + float heading_font_size_threshold_ = 0; + float paragraph_spacing_threshold_ = 0; +}; + } // namespace PdfAccessibilityTree::PdfAccessibilityTree(content::RendererPpapiHost* host, @@ -383,6 +977,56 @@ bool PdfAccessibilityTree::IsDataFromPluginValid( return false; } + const std::vector<ppapi::PdfAccessibilityTextFieldInfo>& text_fields = + page_objects.form_fields.text_fields; + if (!std::is_sorted(text_fields.begin(), text_fields.end(), + CompareTextRuns<ppapi::PdfAccessibilityTextFieldInfo>)) { + return false; + } + // Text run index of an |text_field| works on the same logic as the text run + // index of a |link| as mentioned above. + for (const ppapi::PdfAccessibilityTextFieldInfo& text_field : text_fields) { + if (text_field.text_run_index > text_runs.size()) + return false; + } + + const std::vector<ppapi::PdfAccessibilityChoiceFieldInfo>& choice_fields = + page_objects.form_fields.choice_fields; + if (!std::is_sorted( + choice_fields.begin(), choice_fields.end(), + CompareTextRuns<ppapi::PdfAccessibilityChoiceFieldInfo>)) { + return false; + } + // Text run index of an |choice_field| works on the same logic as the text run + // index of a |link| as mentioned above. + for (const auto& choice_field : choice_fields) { + if (choice_field.text_run_index > text_runs.size()) + return false; + } + + const std::vector<ppapi::PdfAccessibilityButtonInfo>& buttons = + page_objects.form_fields.buttons; + if (!std::is_sorted( + buttons.begin(), buttons.end(), + CompareTextRuns<ppapi::PdfAccessibilityButtonInfo>)) { + return false; + } + for (const ppapi::PdfAccessibilityButtonInfo& button : + buttons) { + // Text run index of an |button| works on the same logic as the text run + // index of a |link| as mentioned above. + if (button.text_run_index > text_runs.size()) + return false; + + // For radio button or checkbox, value of |button.control_index| should + // always be less than |button.control_count|. + if ((button.type == PP_PrivateButtonType::PP_PRIVATEBUTTON_CHECKBOX || + button.type == PP_PrivateButtonType::PP_PRIVATEBUTTON_RADIOBUTTON) && + (button.control_index >= button.control_count)) { + return false; + } + } + return true; } @@ -413,12 +1057,14 @@ void PdfAccessibilityTree::SetAccessibilityViewportInfo( void PdfAccessibilityTree::SetAccessibilityDocInfo( const PP_PrivateAccessibilityDocInfo& doc_info) { - if (!GetRenderAccessibility()) + content::RenderAccessibility* render_accessibility = GetRenderAccessibility(); + if (!render_accessibility) return; doc_info_ = doc_info; doc_node_ = - CreateNode(ax::mojom::Role::kDocument, ax::mojom::Restriction::kReadOnly); + CreateNode(ax::mojom::Role::kDocument, ax::mojom::Restriction::kReadOnly, + render_accessibility, &nodes_); doc_node_->AddStringAttribute( ax::mojom::StringAttribute::kName, l10n_util::GetPluralStringFUTF8(IDS_PDF_DOCUMENT_PAGE_COUNT, @@ -454,7 +1100,8 @@ void PdfAccessibilityTree::SetAccessibilityPageInfo( CHECK_LT(page_index, doc_info_.page_count); ui::AXNodeData* page_node = - CreateNode(ax::mojom::Role::kRegion, ax::mojom::Restriction::kReadOnly); + CreateNode(ax::mojom::Role::kRegion, ax::mojom::Restriction::kReadOnly, + render_accessibility, &nodes_); page_node->AddStringAttribute( ax::mojom::StringAttribute::kName, l10n_util::GetPluralStringFUTF8(IDS_PDF_PAGE_INDEX, page_index + 1)); @@ -467,7 +1114,7 @@ void PdfAccessibilityTree::SetAccessibilityPageInfo( doc_node_->child_ids.push_back(page_node->id); AddPageContent(page_node, page_bounds, page_index, text_runs, chars, - page_objects); + page_objects, render_accessibility); if (page_index == doc_info_.page_count - 1) Finish(); @@ -479,256 +1126,14 @@ void PdfAccessibilityTree::AddPageContent( uint32_t page_index, const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs, const std::vector<PP_PrivateAccessibilityCharInfo>& chars, - const ppapi::PdfAccessibilityPageObjects& page_objects) { + const ppapi::PdfAccessibilityPageObjects& page_objects, + content::RenderAccessibility* render_accessibility) { DCHECK(page_node); - float heading_font_size_threshold = 0; - float paragraph_spacing_threshold = 0; - ComputeParagraphAndHeadingThresholds(text_runs, &heading_font_size_threshold, - ¶graph_spacing_threshold); - - std::vector<uint32_t> text_run_start_indices; - if (!text_runs.empty()) { - text_run_start_indices.reserve(text_runs.size()); - text_run_start_indices.push_back(0); - for (size_t i = 0; i < text_runs.size() - 1; ++i) { - text_run_start_indices.push_back(text_run_start_indices[i] + - text_runs[i].len); - } - } - ui::AXNodeData* para_node = nullptr; - ui::AXNodeData* static_text_node = nullptr; - ui::AXNodeData* previous_on_line_node = nullptr; - std::string static_text; - uint32_t current_link_index = 0; - uint32_t current_image_index = 0; - uint32_t current_highlight_index = 0; - uint32_t current_text_field_index = 0; - LineHelper line_helper(text_runs); - const std::vector<ppapi::PdfAccessibilityLinkInfo>& links = - page_objects.links; - const std::vector<ppapi::PdfAccessibilityImageInfo>& images = - page_objects.images; - const std::vector<ppapi::PdfAccessibilityHighlightInfo>& highlights = - page_objects.highlights; - const std::vector<ppapi::PdfAccessibilityTextFieldInfo>& text_fields = - page_objects.text_fields; - - for (size_t text_run_index = 0; text_run_index < text_runs.size(); - ++text_run_index) { - // If we don't have a paragraph, create one. - if (!para_node) { - para_node = CreateParagraphNode(text_runs[text_run_index].style.font_size, - heading_font_size_threshold); - page_node->child_ids.push_back(para_node->id); - } - - // If the |text_run_index| is less than or equal to the link's - // text_run_index, then push the link node in the paragraph. - if (IsObjectInTextRun(links, current_link_index, text_run_index)) { - FinishStaticNode(&static_text_node, &static_text); - const ppapi::PdfAccessibilityLinkInfo& link = links[current_link_index++]; - ui::AXNodeData* link_node = CreateLinkNode(link, page_index); - para_node->child_ids.push_back(link_node->id); - - // If |link.text_run_count| == 0, then the link is not part of the page - // text. Push it ahead of the current text run. - if (link.text_run_count == 0) { - --text_run_index; - continue; - } - - // Annotations can overlap in PDF. There can be two overlapping scenarios: - // Partial overlap and Complete overlap. - // Partial overlap - // - // Link A starts Link B starts Link A ends Link B ends - // |a1 |b1 |a2 |b2 - // ----------------------------------------------------------------------- - // Text - // - // Complete overlap - // Link A starts Link B starts Link B ends Link A ends - // |a1 |b1 |b2 |a2 - // ----------------------------------------------------------------------- - // Text - // - // For overlapping annotations, both annotations would store the full - // text data and nothing will get truncated. For partial overlap, link `A` - // would contain text between a1 and a2 while link `B` would contain text - // between b1 and b2. For complete overlap as well, link `A` would contain - // text between a1 and a2 and link `B` would contain text between b1 and - // b2. The links would appear in the tree in the order of which they are - // present. In the tree for both overlapping scenarios, link `A` would - // appear first in the tree and link `B` after it. - - // If |link.text_run_count| > 0, then the link is part of the page text. - // Make the text runs contained by the link children of the link node. - size_t end_text_run_index = link.text_run_index + link.text_run_count; - uint32_t link_end_text_run_index = - std::min(end_text_run_index, text_runs.size()) - 1; - AddTextToAXNode(link.text_run_index, link_end_text_run_index, text_runs, - chars, page_bounds, page_index, text_run_start_indices, - link_node, &previous_on_line_node); - - para_node->relative_bounds.bounds.Union( - link_node->relative_bounds.bounds); - - text_run_index = - NormalizeTextRunIndex(link_end_text_run_index, text_run_index); - } else if (IsObjectInTextRun(images, current_image_index, text_run_index)) { - FinishStaticNode(&static_text_node, &static_text); - // If the |text_run_index| is less than or equal to the image's text run - // index, then push the image ahead of the current text run. - ui::AXNodeData* image_node = CreateImageNode(images[current_image_index]); - para_node->child_ids.push_back(image_node->id); - ++current_image_index; - --text_run_index; - continue; - } else if (IsObjectInTextRun(highlights, current_highlight_index, - text_run_index) && - base::FeatureList::IsEnabled( - chrome_pdf::features::kAccessiblePDFHighlight)) { - FinishStaticNode(&static_text_node, &static_text); - - const ppapi::PdfAccessibilityHighlightInfo& highlight = - highlights[current_highlight_index++]; - - ui::AXNodeData* highlight_node = CreateHighlightNode(highlight); - para_node->child_ids.push_back(highlight_node->id); - - // Make the text runs contained by the highlight children of - // the highlight node. - size_t end_text_run_index = - highlight.text_run_index + highlight.text_run_count; - uint32_t highlight_end_text_run_index = - std::min(end_text_run_index, text_runs.size()) - 1; - AddTextToAXNode(highlight.text_run_index, highlight_end_text_run_index, - text_runs, chars, page_bounds, page_index, - text_run_start_indices, highlight_node, - &previous_on_line_node); - - para_node->relative_bounds.bounds.Union( - highlight_node->relative_bounds.bounds); - - text_run_index = - NormalizeTextRunIndex(highlight_end_text_run_index, text_run_index); - } else if (IsObjectInTextRun(text_fields, current_text_field_index, - text_run_index) && - base::FeatureList::IsEnabled( - chrome_pdf::features::kAccessiblePDFForm)) { - FinishStaticNode(&static_text_node, &static_text); - // If the |text_run_index| is less than or equal to the text_field's text - // run index, then push the text_field ahead of the current text run. - ui::AXNodeData* text_field_node = - CreateTextFieldNode(text_fields[current_text_field_index]); - para_node->child_ids.push_back(text_field_node->id); - ++current_text_field_index; - --text_run_index; - continue; - } else { - PP_PdfPageCharacterIndex page_char_index = { - page_index, text_run_start_indices[text_run_index]}; - - // This node is for the text inside the paragraph, it includes - // the text of all of the text runs. - if (!static_text_node) { - static_text_node = CreateStaticTextNode(page_char_index); - para_node->child_ids.push_back(static_text_node->id); - } - - const ppapi::PdfAccessibilityTextRunInfo& text_run = - text_runs[text_run_index]; - // Add this text run to the current static text node. - ui::AXNodeData* inline_text_box_node = CreateInlineTextBoxNode( - text_run, chars, page_char_index, page_bounds); - static_text_node->child_ids.push_back(inline_text_box_node->id); - - static_text += inline_text_box_node->GetStringAttribute( - ax::mojom::StringAttribute::kName); - - para_node->relative_bounds.bounds.Union( - inline_text_box_node->relative_bounds.bounds); - static_text_node->relative_bounds.bounds.Union( - inline_text_box_node->relative_bounds.bounds); - - if (previous_on_line_node) { - ConnectPreviousAndNextOnLine(previous_on_line_node, - inline_text_box_node); - } else { - line_helper.StartNewLine(text_run_index); - } - line_helper.ProcessNextRun(text_run_index); - - if (text_run_index < text_runs.size() - 1) { - if (line_helper.IsRunOnSameLine(text_run_index + 1)) { - // The next run is on the same line. - previous_on_line_node = inline_text_box_node; - } else { - // The next run is on a new line. - previous_on_line_node = nullptr; - } - } - } - - if (text_run_index == text_runs.size() - 1) { - FinishStaticNode(&static_text_node, &static_text); - break; - } - - if (!previous_on_line_node) { - if (BreakParagraph(text_runs, text_run_index, - paragraph_spacing_threshold)) { - FinishStaticNode(&static_text_node, &static_text); - para_node = nullptr; - } - } - } - - base::span<const ppapi::PdfAccessibilityLinkInfo> remaining_links = - base::make_span(links).subspan(current_link_index); - base::span<const ppapi::PdfAccessibilityImageInfo> remaining_images = - base::make_span(images).subspan(current_image_index); - base::span<const ppapi::PdfAccessibilityTextFieldInfo> remaining_text_fields = - base::make_span(text_fields).subspan(current_text_field_index); - AddRemainingAnnotations(page_node, page_bounds, page_index, remaining_links, - remaining_images, remaining_text_fields, para_node); -} - -void PdfAccessibilityTree::AddRemainingAnnotations( - ui::AXNodeData* page_node, - const gfx::RectF& page_bounds, - uint32_t page_index, - base::span<const ppapi::PdfAccessibilityLinkInfo> links, - base::span<const ppapi::PdfAccessibilityImageInfo> images, - base::span<const ppapi::PdfAccessibilityTextFieldInfo> text_fields, - ui::AXNodeData* para_node) { - // If we have additional links, images or text fields to insert in the tree, - // and we don't have a paragraph node, create a new one. - if (!para_node && - (!links.empty() || !images.empty() || !text_fields.empty())) { - para_node = CreateNode(ax::mojom::Role::kParagraph, - ax::mojom::Restriction::kReadOnly); - page_node->child_ids.push_back(para_node->id); - } - // Push all the links not anchored to any text run to the last paragraph. - for (const ppapi::PdfAccessibilityLinkInfo& link : links) { - ui::AXNodeData* link_node = CreateLinkNode(link, page_index); - para_node->child_ids.push_back(link_node->id); - } - // Push all the images not anchored to any text run to the last paragraph. - for (const ppapi::PdfAccessibilityImageInfo& image : images) { - ui::AXNodeData* image_node = CreateImageNode(image); - para_node->child_ids.push_back(image_node->id); - } - - if (base::FeatureList::IsEnabled(chrome_pdf::features::kAccessiblePDFForm)) { - // Push all the text fields not anchored to any text run to the last - // paragraph. - for (const ppapi::PdfAccessibilityTextFieldInfo& text_field : text_fields) { - ui::AXNodeData* text_field_node = CreateTextFieldNode(text_field); - para_node->child_ids.push_back(text_field_node->id); - } - } + PdfAccessibilityTreeBuilder tree_builder( + text_runs, chars, page_objects, page_bounds, page_index, page_node, + render_accessibility, &nodes_, &node_id_to_page_char_index_, + &node_id_to_annotation_info_); + tree_builder.BuildPageTree(); } void PdfAccessibilityTree::Finish() { @@ -814,231 +1219,6 @@ bool PdfAccessibilityTree::FindCharacterOffset( return true; } -ui::AXNodeData* PdfAccessibilityTree::CreateNode( - ax::mojom::Role role, - ax::mojom::Restriction restriction) { - content::RenderAccessibility* render_accessibility = GetRenderAccessibility(); - DCHECK(render_accessibility); - - auto node = std::make_unique<ui::AXNodeData>(); - node->id = render_accessibility->GenerateAXID(); - node->role = role; - node->SetRestriction(restriction); - - // All nodes other than the first one have coordinates relative to - // the first node. - if (!nodes_.empty()) - node->relative_bounds.offset_container_id = nodes_[0]->id; - - ui::AXNodeData* node_ptr = node.get(); - nodes_.push_back(std::move(node)); - - return node_ptr; -} - -ui::AXNodeData* PdfAccessibilityTree::CreateParagraphNode( - float font_size, - float heading_font_size_threshold) { - ui::AXNodeData* para_node = CreateNode(ax::mojom::Role::kParagraph, - ax::mojom::Restriction::kReadOnly); - para_node->AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, - true); - - // If font size exceeds the |heading_font_size_threshold|, then classify - // it as a Heading. - if (heading_font_size_threshold > 0 && - font_size > heading_font_size_threshold) { - para_node->role = ax::mojom::Role::kHeading; - para_node->AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, 2); - para_node->AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "h2"); - } - - return para_node; -} - -ui::AXNodeData* PdfAccessibilityTree::CreateStaticTextNode( - const PP_PdfPageCharacterIndex& page_char_index) { - ui::AXNodeData* static_text_node = CreateNode( - ax::mojom::Role::kStaticText, ax::mojom::Restriction::kReadOnly); - node_id_to_page_char_index_.emplace(static_text_node->id, page_char_index); - return static_text_node; -} - -ui::AXNodeData* PdfAccessibilityTree::CreateInlineTextBoxNode( - const ppapi::PdfAccessibilityTextRunInfo& text_run, - const std::vector<PP_PrivateAccessibilityCharInfo>& chars, - const PP_PdfPageCharacterIndex& page_char_index, - const gfx::RectF& page_bounds) { - ui::AXNodeData* inline_text_box_node = CreateNode( - ax::mojom::Role::kInlineTextBox, ax::mojom::Restriction::kReadOnly); - - std::string chars_utf8 = - GetTextRunCharsAsUTF8(text_run, chars, page_char_index.char_index); - inline_text_box_node->AddStringAttribute(ax::mojom::StringAttribute::kName, - chars_utf8); - inline_text_box_node->AddIntAttribute(ax::mojom::IntAttribute::kTextDirection, - text_run.direction); - inline_text_box_node->AddStringAttribute( - ax::mojom::StringAttribute::kFontFamily, text_run.style.font_name); - inline_text_box_node->AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, - text_run.style.font_size); - inline_text_box_node->AddFloatAttribute( - ax::mojom::FloatAttribute::kFontWeight, text_run.style.font_weight); - if (text_run.style.is_italic) - inline_text_box_node->AddTextStyle(ax::mojom::TextStyle::kItalic); - if (text_run.style.is_bold) - inline_text_box_node->AddTextStyle(ax::mojom::TextStyle::kBold); - if (IsTextRenderModeFill(text_run.style.render_mode)) { - inline_text_box_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, - text_run.style.fill_color); - } else if (IsTextRenderModeStroke(text_run.style.render_mode)) { - inline_text_box_node->AddIntAttribute(ax::mojom::IntAttribute::kColor, - text_run.style.stroke_color); - } - - inline_text_box_node->relative_bounds.bounds = - PpFloatRectToGfxRectF(text_run.bounds) + page_bounds.OffsetFromOrigin(); - std::vector<int32_t> char_offsets = - GetTextRunCharOffsets(text_run, chars, page_char_index.char_index); - inline_text_box_node->AddIntListAttribute( - ax::mojom::IntListAttribute::kCharacterOffsets, char_offsets); - AddWordStartsAndEnds(inline_text_box_node); - node_id_to_page_char_index_.emplace(inline_text_box_node->id, - page_char_index); - return inline_text_box_node; -} - -ui::AXNodeData* PdfAccessibilityTree::CreateLinkNode( - const ppapi::PdfAccessibilityLinkInfo& link, - uint32_t page_index) { - ui::AXNodeData* link_node = - CreateNode(ax::mojom::Role::kLink, ax::mojom::Restriction::kReadOnly); - - link_node->AddStringAttribute(ax::mojom::StringAttribute::kUrl, link.url); - link_node->AddStringAttribute(ax::mojom::StringAttribute::kName, - std::string()); - link_node->relative_bounds.bounds = PpFloatRectToGfxRectF(link.bounds); - node_id_to_annotation_info_.emplace( - link_node->id, AnnotationInfo(page_index, link.index_in_page)); - - return link_node; -} - -ui::AXNodeData* PdfAccessibilityTree::CreateImageNode( - const ppapi::PdfAccessibilityImageInfo& image) { - ui::AXNodeData* image_node = - CreateNode(ax::mojom::Role::kImage, ax::mojom::Restriction::kReadOnly); - - if (image.alt_text.empty()) { - image_node->AddStringAttribute( - ax::mojom::StringAttribute::kName, - l10n_util::GetStringUTF8(IDS_AX_UNLABELED_IMAGE_ROLE_DESCRIPTION)); - } else { - image_node->AddStringAttribute(ax::mojom::StringAttribute::kName, - image.alt_text); - } - image_node->relative_bounds.bounds = PpFloatRectToGfxRectF(image.bounds); - return image_node; -} - -ui::AXNodeData* PdfAccessibilityTree::CreateHighlightNode( - const ppapi::PdfAccessibilityHighlightInfo& highlight) { - ui::AXNodeData* highlight_node = - CreateNode(ax::mojom::Role::kPdfActionableHighlight, - ax::mojom::Restriction::kReadOnly); - - highlight_node->AddStringAttribute( - ax::mojom::StringAttribute::kRoleDescription, - l10n_util::GetStringUTF8(IDS_AX_ROLE_DESCRIPTION_PDF_HIGHLIGHT)); - highlight_node->AddStringAttribute(ax::mojom::StringAttribute::kName, - std::string()); - highlight_node->relative_bounds.bounds = - PpFloatRectToGfxRectF(highlight.bounds); - highlight_node->AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, - highlight.color); - - return highlight_node; -} - -ui::AXNodeData* PdfAccessibilityTree::CreateTextFieldNode( - const ppapi::PdfAccessibilityTextFieldInfo& text_field) { - ax::mojom::Restriction restriction = text_field.is_read_only - ? ax::mojom::Restriction::kReadOnly - : ax::mojom::Restriction::kNone; - ui::AXNodeData* text_field_node = - CreateNode(ax::mojom::Role::kTextField, restriction); - - text_field_node->AddStringAttribute(ax::mojom::StringAttribute::kName, - text_field.name); - text_field_node->AddStringAttribute(ax::mojom::StringAttribute::kValue, - text_field.value); - text_field_node->AddState(ax::mojom::State::kFocusable); - if (text_field.is_required) - text_field_node->AddState(ax::mojom::State::kRequired); - if (text_field.is_password) - text_field_node->AddState(ax::mojom::State::kProtected); - text_field_node->relative_bounds.bounds = - PpFloatRectToGfxRectF(text_field.bounds); - return text_field_node; -} - -void PdfAccessibilityTree::AddTextToAXNode( - uint32_t start_text_run_index, - uint32_t end_text_run_index, - const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs, - const std::vector<PP_PrivateAccessibilityCharInfo>& chars, - const gfx::RectF& page_bounds, - uint32_t page_index, - const std::vector<uint32_t>& text_run_start_indices, - ui::AXNodeData* ax_node, - ui::AXNodeData** previous_on_line_node) { - PP_PdfPageCharacterIndex page_char_index = { - page_index, text_run_start_indices[start_text_run_index]}; - ui::AXNodeData* ax_static_text_node = CreateStaticTextNode(page_char_index); - ax_node->child_ids.push_back(ax_static_text_node->id); - // Accumulate the text of the node. - std::string ax_name; - LineHelper line_helper(text_runs); - - for (size_t text_run_index = start_text_run_index; - text_run_index <= end_text_run_index; ++text_run_index) { - const ppapi::PdfAccessibilityTextRunInfo& text_run = - text_runs[text_run_index]; - page_char_index.char_index = text_run_start_indices[text_run_index]; - // Add this text run to the current static text node. - ui::AXNodeData* inline_text_box_node = - CreateInlineTextBoxNode(text_run, chars, page_char_index, page_bounds); - ax_static_text_node->child_ids.push_back(inline_text_box_node->id); - - ax_static_text_node->relative_bounds.bounds.Union( - inline_text_box_node->relative_bounds.bounds); - ax_name += inline_text_box_node->GetStringAttribute( - ax::mojom::StringAttribute::kName); - - if (*previous_on_line_node) { - ConnectPreviousAndNextOnLine(*previous_on_line_node, - inline_text_box_node); - } else { - line_helper.StartNewLine(text_run_index); - } - line_helper.ProcessNextRun(text_run_index); - - if (text_run_index < text_runs.size() - 1) { - if (line_helper.IsRunOnSameLine(text_run_index + 1)) { - // The next run is on the same line. - *previous_on_line_node = inline_text_box_node; - } else { - // The next run is on a new line. - *previous_on_line_node = nullptr; - } - } - } - - ax_node->AddStringAttribute(ax::mojom::StringAttribute::kName, ax_name); - ax_static_text_node->AddStringAttribute(ax::mojom::StringAttribute::kName, - ax_name); -} - content::RenderAccessibility* PdfAccessibilityTree::GetRenderAccessibility() { content::RenderFrame* render_frame = host_->GetRenderFrameForInstance(instance_); @@ -1073,28 +1253,6 @@ PdfAccessibilityTree::MakeTransformFromViewInfo() const { return transform; } -void PdfAccessibilityTree::AddWordStartsAndEnds( - ui::AXNodeData* inline_text_box) { - base::string16 text = - inline_text_box->GetString16Attribute(ax::mojom::StringAttribute::kName); - base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD); - if (!iter.Init()) - return; - - std::vector<int32_t> word_starts; - std::vector<int32_t> word_ends; - while (iter.Advance()) { - if (iter.IsWord()) { - word_starts.push_back(iter.prev()); - word_ends.push_back(iter.pos()); - } - } - inline_text_box->AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts, - word_starts); - inline_text_box->AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, - word_ends); -} - PdfAccessibilityTree::AnnotationInfo::AnnotationInfo(uint32_t page_index, uint32_t annotation_index) : page_index(page_index), annotation_index(annotation_index) {} @@ -1167,6 +1325,15 @@ std::unique_ptr<ui::AXActionTarget> PdfAccessibilityTree::CreateActionTarget( return std::make_unique<PdfAXActionTarget>(target_node, this); } +bool PdfAccessibilityTree::ShowContextMenu() { + content::RenderAccessibility* render_accessibility = GetRenderAccessibility(); + if (!render_accessibility) + return false; + + render_accessibility->ShowPluginContextMenu(); + return true; +} + void PdfAccessibilityTree::HandleAction( const PP_PdfAccessibilityActionData& action_data) { content::PepperPluginInstance* plugin_instance = diff --git a/chromium/components/pdf/renderer/pdf_accessibility_tree.h b/chromium/components/pdf/renderer/pdf_accessibility_tree.h index f467ec768f2..8ca15eeca31 100644 --- a/chromium/components/pdf/renderer/pdf_accessibility_tree.h +++ b/chromium/components/pdf/renderer/pdf_accessibility_tree.h @@ -92,6 +92,8 @@ class PdfAccessibilityTree : public content::PluginAXTreeSource { std::unique_ptr<ui::AXActionTarget> CreateActionTarget( const ui::AXNode& target_node) override; + bool ShowContextMenu(); + private: // Update the AXTreeData when the selected range changed. void UpdateAXTreeDataFromSelection(); @@ -116,48 +118,11 @@ class PdfAccessibilityTree : public content::PluginAXTreeSource { uint32_t page_index, const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs, const std::vector<PP_PrivateAccessibilityCharInfo>& chars, - const ppapi::PdfAccessibilityPageObjects& page_objects); - void AddRemainingAnnotations( - ui::AXNodeData* page_node, - const gfx::RectF& page_bounds, - uint32_t page_index, - base::span<const ppapi::PdfAccessibilityLinkInfo> links, - base::span<const ppapi::PdfAccessibilityImageInfo> images, - base::span<const ppapi::PdfAccessibilityTextFieldInfo> text_fields, - ui::AXNodeData* para_node); - - ui::AXNodeData* CreateNode(ax::mojom::Role role, - ax::mojom::Restriction restriction); - ui::AXNodeData* CreateParagraphNode(float font_size, - float heading_font_size_threshold); - ui::AXNodeData* CreateStaticTextNode( - const PP_PdfPageCharacterIndex& page_char_index); - ui::AXNodeData* CreateInlineTextBoxNode( - const ppapi::PdfAccessibilityTextRunInfo& text_run, - const std::vector<PP_PrivateAccessibilityCharInfo>& chars, - const PP_PdfPageCharacterIndex& page_char_index, - const gfx::RectF& page_bounds); - ui::AXNodeData* CreateLinkNode(const ppapi::PdfAccessibilityLinkInfo& link, - uint32_t page_index); - ui::AXNodeData* CreateImageNode( - const ppapi::PdfAccessibilityImageInfo& image); - ui::AXNodeData* CreateHighlightNode( - const ppapi::PdfAccessibilityHighlightInfo& highlight); - ui::AXNodeData* CreateTextFieldNode( - const ppapi::PdfAccessibilityTextFieldInfo& text_field); - void AddTextToAXNode( - uint32_t start_text_run_index, - uint32_t end_text_run_index, - const std::vector<ppapi::PdfAccessibilityTextRunInfo>& text_runs, - const std::vector<PP_PrivateAccessibilityCharInfo>& chars, - const gfx::RectF& page_bounds, - uint32_t page_index, - const std::vector<uint32_t>& text_run_start_indices, - ui::AXNodeData* ax_node, - ui::AXNodeData** previous_on_line_node); + const ppapi::PdfAccessibilityPageObjects& page_objects, + content::RenderAccessibility* render_accessibility); + content::RenderAccessibility* GetRenderAccessibility(); std::unique_ptr<gfx::Transform> MakeTransformFromViewInfo() const; - void AddWordStartsAndEnds(ui::AXNodeData* inline_text_box); ui::AXTreeData tree_data_; ui::AXTree tree_; diff --git a/chromium/components/pdf/renderer/pdf_accessibility_tree_browsertest.cc b/chromium/components/pdf/renderer/pdf_accessibility_tree_browsertest.cc index 9192ec5da51..83ec2533223 100644 --- a/chromium/components/pdf/renderer/pdf_accessibility_tree_browsertest.cc +++ b/chromium/components/pdf/renderer/pdf_accessibility_tree_browsertest.cc @@ -448,6 +448,7 @@ TEST_F(PdfAccessibilityTreeTest, TestHighlightCreation) { chrome_pdf::features::kAccessiblePDFHighlight); constexpr uint32_t kHighlightWhiteColor = MakeARGB(255, 255, 255, 255); + const char kPopupNoteText[] = "Text Note"; text_runs_.emplace_back(kFirstTextRun); text_runs_.emplace_back(kSecondTextRun); @@ -460,6 +461,7 @@ TEST_F(PdfAccessibilityTreeTest, TestHighlightCreation) { highlight.text_run_index = 0; highlight.text_run_count = 2; highlight.color = kHighlightWhiteColor; + highlight.note_text = kPopupNoteText; page_objects_.highlights.push_back(std::move(highlight)); } @@ -486,6 +488,7 @@ TEST_F(PdfAccessibilityTreeTest, TestHighlightCreation) { * ++++ Paragraph * ++++++ Highlight * ++++++++ Static Text + * ++++++++ Note */ ui::AXNode* root_node = pdf_accessibility_tree.GetRoot(); @@ -517,12 +520,23 @@ TEST_F(PdfAccessibilityTreeTest, TestHighlightCreation) { EXPECT_EQ(kHighlightWhiteColor, static_cast<uint32_t>(highlight_node->GetIntAttribute( ax::mojom::IntAttribute::kBackgroundColor))); - ASSERT_EQ(1u, highlight_node->children().size()); + ASSERT_EQ(2u, highlight_node->children().size()); ui::AXNode* static_text_node = highlight_node->children()[0]; ASSERT_TRUE(static_text_node); EXPECT_EQ(ax::mojom::Role::kStaticText, static_text_node->data().role); ASSERT_EQ(2u, static_text_node->children().size()); + + ui::AXNode* popup_note_node = highlight_node->children()[1]; + ASSERT_TRUE(popup_note_node); + EXPECT_EQ(ax::mojom::Role::kNote, popup_note_node->data().role); + EXPECT_EQ(kPopupNoteText, popup_note_node->GetStringAttribute( + ax::mojom::StringAttribute::kName)); + EXPECT_EQ(l10n_util::GetStringUTF8(IDS_AX_ROLE_DESCRIPTION_PDF_POPUP_NOTE), + popup_note_node->GetStringAttribute( + ax::mojom::StringAttribute::kRoleDescription)); + EXPECT_EQ(gfx::RectF(1.0f, 1.0f, 5.0f, 6.0f), + popup_note_node->data().relative_bounds.bounds); } TEST_F(PdfAccessibilityTreeTest, TestTextFieldNodeCreation) { @@ -545,7 +559,7 @@ TEST_F(PdfAccessibilityTreeTest, TestTextFieldNodeCreation) { text_field.is_read_only = false; text_field.is_required = false; text_field.is_password = false; - page_objects_.text_fields.push_back(std::move(text_field)); + page_objects_.form_fields.text_fields.push_back(std::move(text_field)); } { @@ -558,7 +572,7 @@ TEST_F(PdfAccessibilityTreeTest, TestTextFieldNodeCreation) { text_field.is_read_only = true; text_field.is_required = true; text_field.is_password = true; - page_objects_.text_fields.push_back(std::move(text_field)); + page_objects_.form_fields.text_fields.push_back(std::move(text_field)); } page_info_.text_run_count = text_runs_.size(); @@ -1262,7 +1276,6 @@ TEST_F(PdfAccessibilityTreeTest, TestEmptyPdfAxActions) { EXPECT_FALSE(pdf_action_target->SetSelected(false)); EXPECT_FALSE(pdf_action_target->SetSequentialFocusNavigationStartingPoint()); EXPECT_FALSE(pdf_action_target->SetValue("test")); - EXPECT_FALSE(pdf_action_target->ShowContextMenu()); EXPECT_FALSE(pdf_action_target->ScrollToMakeVisible()); } @@ -1422,4 +1435,36 @@ TEST_F(PdfAccessibilityTreeTest, TestSelectionActionDataConversion) { pdf_anchor_action_target.get(), 1, pdf_focus_action_target.get(), 5)); } +TEST_F(PdfAccessibilityTreeTest, TestShowContextMenuAction) { + text_runs_.emplace_back(kFirstTextRun); + text_runs_.emplace_back(kSecondTextRun); + chars_.insert(chars_.end(), std::begin(kDummyCharsData), + std::end(kDummyCharsData)); + + page_info_.text_run_count = text_runs_.size(); + page_info_.char_count = chars_.size(); + + content::RenderFrame* render_frame = view_->GetMainRenderFrame(); + ASSERT_TRUE(render_frame); + render_frame->SetAccessibilityModeForTest(ui::AXMode::kWebContents); + ASSERT_TRUE(render_frame->GetRenderAccessibility()); + + ActionHandlingFakePepperPluginInstance fake_pepper_instance; + FakeRendererPpapiHost host(view_->GetMainRenderFrame(), + &fake_pepper_instance); + PP_Instance instance = 0; + PdfAccessibilityTree pdf_accessibility_tree(&host, instance); + pdf_accessibility_tree.SetAccessibilityViewportInfo(viewport_info_); + pdf_accessibility_tree.SetAccessibilityDocInfo(doc_info_); + pdf_accessibility_tree.SetAccessibilityPageInfo(page_info_, text_runs_, + chars_, page_objects_); + ui::AXNode* root_node = pdf_accessibility_tree.GetRoot(); + ASSERT_TRUE(root_node); + + std::unique_ptr<ui::AXActionTarget> pdf_action_target = + pdf_accessibility_tree.CreateActionTarget(*root_node); + ASSERT_EQ(ui::AXActionTarget::Type::kPdf, pdf_action_target->GetType()); + EXPECT_TRUE(pdf_action_target->ShowContextMenu()); +} + } // namespace pdf diff --git a/chromium/components/pdf/renderer/pdf_accessibility_tree_unittest.cc b/chromium/components/pdf/renderer/pdf_accessibility_tree_unittest.cc index 5149a579b55..9c6bd747685 100644 --- a/chromium/components/pdf/renderer/pdf_accessibility_tree_unittest.cc +++ b/chromium/components/pdf/renderer/pdf_accessibility_tree_unittest.cc @@ -204,4 +204,218 @@ TEST(PdfAccessibilityTreeUnitTest, OutOfBoundHighlight) { page_objects)); } +TEST(PdfAccessibilityTreeUnitTest, UnsortedTextFieldVector) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + // Add first text field in the vector. + ppapi::PdfAccessibilityTextFieldInfo text_field; + text_field.text_run_index = 2; + page_objects.form_fields.text_fields.push_back(std::move(text_field)); + } + + { + // Add second text field in the vector. + ppapi::PdfAccessibilityTextFieldInfo text_field; + text_field.text_run_index = 0; + page_objects.form_fields.text_fields.push_back(std::move(text_field)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + +TEST(PdfAccessibilityTreeUnitTest, OutOfBoundTextField) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + ppapi::PdfAccessibilityTextFieldInfo text_field; + text_field.text_run_index = 3; + page_objects.form_fields.text_fields.push_back(std::move(text_field)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + +TEST(PdfAccessibilityTreeUnitTest, UnsortedChoiceFieldVector) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + // Add first choice field in the vector. + ppapi::PdfAccessibilityChoiceFieldInfo choice_field; + choice_field.text_run_index = 2; + page_objects.form_fields.choice_fields.push_back(std::move(choice_field)); + } + + { + // Add second choice field in the vector. + ppapi::PdfAccessibilityChoiceFieldInfo choice_field; + choice_field.text_run_index = 0; + page_objects.form_fields.choice_fields.push_back(std::move(choice_field)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + +TEST(PdfAccessibilityTreeUnitTest, OutOfBoundChoiceField) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + ppapi::PdfAccessibilityChoiceFieldInfo choice_field; + choice_field.text_run_index = 3; + page_objects.form_fields.choice_fields.push_back(std::move(choice_field)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + +TEST(PdfAccessibilityTreeUnitTest, UnsortedButtonVector) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + // Add first button in the vector. + ppapi::PdfAccessibilityButtonInfo button; + button.text_run_index = 2; + page_objects.form_fields.buttons.push_back(std::move(button)); + } + + { + // Add second button in the vector. + ppapi::PdfAccessibilityButtonInfo button; + button.text_run_index = 0; + page_objects.form_fields.buttons.push_back(std::move(button)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + +TEST(PdfAccessibilityTreeUnitTest, OutOfBoundButton) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + ppapi::PdfAccessibilityButtonInfo button; + button.text_run_index = 3; + page_objects.form_fields.buttons.push_back(std::move(button)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + +TEST(PdfAccessibilityTreeUnitTest, OutOfBoundRadioButton) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + ppapi::PdfAccessibilityButtonInfo button; + button.type = PP_PrivateButtonType::PP_PRIVATEBUTTON_RADIOBUTTON; + button.text_run_index = 0; + button.control_index = 1; + button.control_count = 2; + page_objects.form_fields.buttons.push_back(std::move(button)); + } + + EXPECT_TRUE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); + + { + ppapi::PdfAccessibilityButtonInfo button; + button.type = PP_PrivateButtonType::PP_PRIVATEBUTTON_RADIOBUTTON; + button.text_run_index = 0; + button.control_index = 3; + button.control_count = 2; + page_objects.form_fields.buttons.push_back(std::move(button)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + +TEST(PdfAccessibilityTreeUnitTest, OutOfBoundCheckBox) { + std::vector<ppapi::PdfAccessibilityTextRunInfo> text_runs; + text_runs.emplace_back(kFirstTextRun); + text_runs.emplace_back(kSecondTextRun); + + std::vector<PP_PrivateAccessibilityCharInfo> chars( + std::begin(kDummyCharsData), std::end(kDummyCharsData)); + + ppapi::PdfAccessibilityPageObjects page_objects; + + { + ppapi::PdfAccessibilityButtonInfo button; + button.type = PP_PrivateButtonType::PP_PRIVATEBUTTON_CHECKBOX; + button.text_run_index = 0; + button.control_index = 1; + button.control_count = 2; + page_objects.form_fields.buttons.push_back(std::move(button)); + } + + EXPECT_TRUE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); + + { + ppapi::PdfAccessibilityButtonInfo button; + button.type = PP_PrivateButtonType::PP_PRIVATEBUTTON_CHECKBOX; + button.text_run_index = 0; + button.control_index = 3; + button.control_count = 2; + page_objects.form_fields.buttons.push_back(std::move(button)); + } + + EXPECT_FALSE(PdfAccessibilityTree::IsDataFromPluginValid(text_runs, chars, + page_objects)); +} + } // namespace pdf diff --git a/chromium/components/pdf/renderer/pdf_ax_action_target.cc b/chromium/components/pdf/renderer/pdf_ax_action_target.cc index 6ca7c61c9f3..2bea222fab5 100644 --- a/chromium/components/pdf/renderer/pdf_ax_action_target.cc +++ b/chromium/components/pdf/renderer/pdf_ax_action_target.cc @@ -157,7 +157,7 @@ bool PdfAXActionTarget::SetValue(const std::string& value) const { } bool PdfAXActionTarget::ShowContextMenu() const { - return false; + return pdf_accessibility_tree_source_->ShowContextMenu(); } bool PdfAXActionTarget::ScrollToMakeVisible() const { |