Commit c2d7ac72 authored by Mansi Awasthi's avatar Mansi Awasthi Committed by Commit Bot

Reland: Supporting context menu on PDF via AT clients

Original CL: http://crrev.com/c/2172854
Revert: http://crrev.com/c/2204841

The original CL got reverted because following browser test was failing
on the Mac10.12 tests bot -
PDFExtensionTest.PdfAccessibilityContextMenuAction

Added missing ContextMenuWaiter in the test to fix the issue.

Original description:
This CL adds support for invoking context menu on PDF via AT clients. To
show context menu for PDF plugin, the context menu action can be invoked
on respective EMBED node. This internally takes care of showing plugin
related context menu option(See ContextMenuController::ShowContextMenu()
for reference).

Implemented PdfAXActionTarget::ShowContextMenu() that forwards the call
to RenderAccessibilityImpl::ShowPluginContextMenu() to find the EMBED
object in accessibility tree and invoke ShowContextMenu() on EMBED node.

This CL also includes browser test to validate E2E scenario.

CQ_INCLUDE_TRYBOTS=luci.chromium.try:mac_chromium_dbg_ng

Bug: 1023283
Change-Id: Ie710fd24232b8dfeb3ce4bc6c0b56250ec9fbf0d

Tbr: dmazzoni@chromium.org,jochen@chromium.org
Change-Id: Ie710fd24232b8dfeb3ce4bc6c0b56250ec9fbf0d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2210080
Commit-Queue: Mansi Awasthi <maawas@microsoft.com>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: Ankit Kumar 🌪️ <ankk@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#771188}
parent ead15db2
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "base/path_service.h" #include "base/path_service.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/pattern.h" #include "base/strings/pattern.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
...@@ -89,6 +90,7 @@ ...@@ -89,6 +90,7 @@
#include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/embedded_test_server.h"
#include "pdf/pdf_features.h" #include "pdf/pdf_features.h"
#include "services/network/public/cpp/features.h" #include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/context_menu_data/media_type.h"
#include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enum_util.h" #include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_enums.mojom.h"
...@@ -1349,6 +1351,67 @@ IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilitySelection) { ...@@ -1349,6 +1351,67 @@ IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilitySelection) {
EXPECT_EQ(ax::mojom::Role::kRegion, region->data().role); EXPECT_EQ(ax::mojom::Role::kRegion, region->data().role);
} }
IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilityContextMenuAction) {
// Validate the context menu arguments for PDF selection when context menu is
// invoked via accessibility tree.
const char kExepectedPDFSelection[] =
"1 First Section\n"
"This is the first section.\n"
"1\n"
"1.1 First Subsection\n"
"This is the first subsection.\n"
"2\n"
"2 Second Section\n"
"3";
GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf"));
WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
ASSERT_TRUE(guest_contents);
CHECK(content::ExecuteScript(
GetActiveWebContents(),
"document.getElementsByTagName('embed')[0].postMessage("
"{type: 'selectAll'});"));
EnableAccessibilityForWebContents(guest_contents);
WaitForAccessibilityTreeToContainNodeWithName(guest_contents,
"1 First Section\r\n");
// Find PDF document node in the accessibility tree.
content::FindAccessibilityNodeCriteria find_criteria;
find_criteria.role = ax::mojom::Role::kEmbeddedObject;
ui::AXPlatformNodeDelegate* pdf_root =
content::FindAccessibilityNode(guest_contents, find_criteria);
ASSERT_TRUE(pdf_root);
find_criteria.role = ax::mojom::Role::kDocument;
ui::AXPlatformNodeDelegate* pdf_doc_node =
FindAccessibilityNodeInSubtree(pdf_root, find_criteria);
ASSERT_TRUE(pdf_doc_node);
content::RenderProcessHost* guest_process_host =
guest_contents->GetMainFrame()->GetProcess();
auto context_menu_filter = base::MakeRefCounted<content::ContextMenuFilter>();
guest_process_host->AddFilter(context_menu_filter.get());
ContextMenuWaiter menu_waiter;
// Invoke kShowContextMenu accessibility action on PDF document node.
ui::AXActionData data;
data.action = ax::mojom::Action::kShowContextMenu;
pdf_doc_node->AccessibilityPerformAction(data);
menu_waiter.WaitForMenuOpenAndClose();
context_menu_filter->Wait();
content::UntrustworthyContextMenuParams params =
context_menu_filter->get_params();
// Validate the context menu params for selection.
EXPECT_EQ(blink::ContextMenuDataMediaType::kPlugin, params.media_type);
std::string selected_text = base::UTF16ToUTF8(params.selection_text);
base::ReplaceChars(selected_text, "\r", "", &selected_text);
EXPECT_EQ(kExepectedPDFSelection, selected_text);
}
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Test a particular PDF encountered in the wild that triggered a crash // Test a particular PDF encountered in the wild that triggered a crash
// when accessibility is enabled. (http://crbug.com/668724) // when accessibility is enabled. (http://crbug.com/668724)
......
...@@ -1167,6 +1167,15 @@ std::unique_ptr<ui::AXActionTarget> PdfAccessibilityTree::CreateActionTarget( ...@@ -1167,6 +1167,15 @@ std::unique_ptr<ui::AXActionTarget> PdfAccessibilityTree::CreateActionTarget(
return std::make_unique<PdfAXActionTarget>(target_node, this); 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( void PdfAccessibilityTree::HandleAction(
const PP_PdfAccessibilityActionData& action_data) { const PP_PdfAccessibilityActionData& action_data) {
content::PepperPluginInstance* plugin_instance = content::PepperPluginInstance* plugin_instance =
......
...@@ -92,6 +92,8 @@ class PdfAccessibilityTree : public content::PluginAXTreeSource { ...@@ -92,6 +92,8 @@ class PdfAccessibilityTree : public content::PluginAXTreeSource {
std::unique_ptr<ui::AXActionTarget> CreateActionTarget( std::unique_ptr<ui::AXActionTarget> CreateActionTarget(
const ui::AXNode& target_node) override; const ui::AXNode& target_node) override;
bool ShowContextMenu();
private: private:
// Update the AXTreeData when the selected range changed. // Update the AXTreeData when the selected range changed.
void UpdateAXTreeDataFromSelection(); void UpdateAXTreeDataFromSelection();
......
...@@ -1262,7 +1262,6 @@ TEST_F(PdfAccessibilityTreeTest, TestEmptyPdfAxActions) { ...@@ -1262,7 +1262,6 @@ TEST_F(PdfAccessibilityTreeTest, TestEmptyPdfAxActions) {
EXPECT_FALSE(pdf_action_target->SetSelected(false)); EXPECT_FALSE(pdf_action_target->SetSelected(false));
EXPECT_FALSE(pdf_action_target->SetSequentialFocusNavigationStartingPoint()); EXPECT_FALSE(pdf_action_target->SetSequentialFocusNavigationStartingPoint());
EXPECT_FALSE(pdf_action_target->SetValue("test")); EXPECT_FALSE(pdf_action_target->SetValue("test"));
EXPECT_FALSE(pdf_action_target->ShowContextMenu());
EXPECT_FALSE(pdf_action_target->ScrollToMakeVisible()); EXPECT_FALSE(pdf_action_target->ScrollToMakeVisible());
} }
...@@ -1422,4 +1421,36 @@ TEST_F(PdfAccessibilityTreeTest, TestSelectionActionDataConversion) { ...@@ -1422,4 +1421,36 @@ TEST_F(PdfAccessibilityTreeTest, TestSelectionActionDataConversion) {
pdf_anchor_action_target.get(), 1, pdf_focus_action_target.get(), 5)); 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 } // namespace pdf
...@@ -157,7 +157,7 @@ bool PdfAXActionTarget::SetValue(const std::string& value) const { ...@@ -157,7 +157,7 @@ bool PdfAXActionTarget::SetValue(const std::string& value) const {
} }
bool PdfAXActionTarget::ShowContextMenu() const { bool PdfAXActionTarget::ShowContextMenu() const {
return false; return pdf_accessibility_tree_source_->ShowContextMenu();
} }
bool PdfAXActionTarget::ScrollToMakeVisible() const { bool PdfAXActionTarget::ScrollToMakeVisible() const {
......
...@@ -22,6 +22,7 @@ class CONTENT_EXPORT RenderAccessibility { ...@@ -22,6 +22,7 @@ class CONTENT_EXPORT RenderAccessibility {
// PluginAXTreeSource, into the page's accessibility tree. // PluginAXTreeSource, into the page's accessibility tree.
virtual void SetPluginTreeSource(PluginAXTreeSource* source) = 0; virtual void SetPluginTreeSource(PluginAXTreeSource* source) = 0;
virtual void OnPluginRootNodeUpdated() = 0; virtual void OnPluginRootNodeUpdated() = 0;
virtual void ShowPluginContextMenu() = 0;
protected: protected:
~RenderAccessibility() {} ~RenderAccessibility() {}
......
...@@ -592,37 +592,31 @@ void RenderAccessibilityImpl::SetPluginTreeSource( ...@@ -592,37 +592,31 @@ void RenderAccessibilityImpl::SetPluginTreeSource(
} }
void RenderAccessibilityImpl::OnPluginRootNodeUpdated() { void RenderAccessibilityImpl::OnPluginRootNodeUpdated() {
// Search the accessibility tree for an EMBED element and post a // Search the accessibility tree for plugin's root object and post a
// children changed notification on it to force it to update the // children changed notification on it to force it to update the
// plugin accessibility tree. // plugin accessibility tree.
WebAXObject obj = GetPluginRoot();
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_); if (obj.IsNull())
WebAXObject root = tree_source_.GetRoot();
if (!root.UpdateLayoutAndCheckValidity())
return; return;
base::queue<WebAXObject> objs_to_explore; HandleAXEvent(ui::AXEvent(obj.AxID(), ax::mojom::Event::kChildrenChanged));
objs_to_explore.push(root); }
while (objs_to_explore.size()) {
WebAXObject obj = objs_to_explore.front();
objs_to_explore.pop();
WebNode node = obj.GetNode(); void RenderAccessibilityImpl::ShowPluginContextMenu() {
if (!node.IsNull() && node.IsElementNode()) { // Search the accessibility tree for plugin's root object and invoke
WebElement element = node.To<WebElement>(); // ShowContextMenu() on it to show context menu for plugin.
if (element.HasHTMLTagName("embed")) { WebAXObject obj = GetPluginRoot();
HandleAXEvent( if (obj.IsNull())
ui::AXEvent(obj.AxID(), ax::mojom::Event::kChildrenChanged)); return;
break;
}
}
// Explore children of this object. const WebDocument& document = GetMainDocument();
std::vector<WebAXObject> children; if (document.IsNull())
tree_source_.GetChildren(obj, &children); return;
for (size_t i = 0; i < children.size(); ++i)
objs_to_explore.push(children[i]); std::unique_ptr<ui::AXActionTarget> target =
} AXActionTargetFactory::CreateFromNodeId(document, plugin_tree_source_,
obj.AxID());
target->ShowContextMenu();
} }
WebDocument RenderAccessibilityImpl::GetMainDocument() { WebDocument RenderAccessibilityImpl::GetMainDocument() {
...@@ -1216,6 +1210,36 @@ blink::WebDocument RenderAccessibilityImpl::GetPopupDocument() { ...@@ -1216,6 +1210,36 @@ blink::WebDocument RenderAccessibilityImpl::GetPopupDocument() {
return WebDocument(); return WebDocument();
} }
WebAXObject RenderAccessibilityImpl::GetPluginRoot() {
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
WebAXObject root = tree_source_.GetRoot();
if (!root.UpdateLayoutAndCheckValidity())
return WebAXObject();
base::queue<WebAXObject> objs_to_explore;
objs_to_explore.push(root);
while (objs_to_explore.size()) {
WebAXObject obj = objs_to_explore.front();
objs_to_explore.pop();
WebNode node = obj.GetNode();
if (!node.IsNull() && node.IsElementNode()) {
WebElement element = node.To<WebElement>();
if (element.HasHTMLTagName("embed")) {
return obj;
}
}
// Explore children of this object.
std::vector<WebAXObject> children;
tree_source_.GetChildren(obj, &children);
for (const auto& child : children)
objs_to_explore.push(child);
}
return WebAXObject();
}
RenderAccessibilityImpl::DirtyObject::DirtyObject() = default; RenderAccessibilityImpl::DirtyObject::DirtyObject() = default;
RenderAccessibilityImpl::DirtyObject::DirtyObject(const DirtyObject& other) = RenderAccessibilityImpl::DirtyObject::DirtyObject(const DirtyObject& other) =
default; default;
......
...@@ -115,6 +115,7 @@ class CONTENT_EXPORT RenderAccessibilityImpl : public RenderAccessibility, ...@@ -115,6 +115,7 @@ class CONTENT_EXPORT RenderAccessibilityImpl : public RenderAccessibility,
int GenerateAXID() override; int GenerateAXID() override;
void SetPluginTreeSource(PluginAXTreeSource* source) override; void SetPluginTreeSource(PluginAXTreeSource* source) override;
void OnPluginRootNodeUpdated() override; void OnPluginRootNodeUpdated() override;
void ShowPluginContextMenu() override;
// RenderFrameObserver implementation. // RenderFrameObserver implementation.
void DidCreateNewDocument() override; void DidCreateNewDocument() override;
...@@ -195,6 +196,10 @@ class CONTENT_EXPORT RenderAccessibilityImpl : public RenderAccessibility, ...@@ -195,6 +196,10 @@ class CONTENT_EXPORT RenderAccessibilityImpl : public RenderAccessibility,
// Returns the document for the active popup if any. // Returns the document for the active popup if any.
blink::WebDocument GetPopupDocument(); blink::WebDocument GetPopupDocument();
// Searches the accessibility tree for plugin's root object and returns it.
// Returns an empty WebAXObject if no root object is present.
blink::WebAXObject GetPluginRoot();
// The RenderAccessibilityManager that owns us. // The RenderAccessibilityManager that owns us.
RenderAccessibilityManager* render_accessibility_manager_; RenderAccessibilityManager* render_accessibility_manager_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment