Commit c32a0a57 authored by Avi Drissman's avatar Avi Drissman Committed by Commit Bot

Use modern APIs to power speaking the whole page.

It's unclear how much the "Start Speaking" on the whole
page is used. It was implemented only three years ago and
has never worked correctly. It's not clear what it even
means to speak the entire web document for modern web
pages. However, this makes a decent attempt at doing so.

BUG=659753, 584798, 585164, 819773
TEST=as in 659753

Change-Id: I252f38c7c7879173c3c4e0afd9dc3a42c81b8a64
Reviewed-on: https://chromium-review.googlesource.com/956029Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543920}
parent f521160e
......@@ -119,7 +119,7 @@ include_rules = [
]
specific_include_rules = {
".*_browsertest[a-z_]*\.(cc|h)": [
".*_browsertest[a-z_]*\.(cc|h|mm)": [
# content -> content/shell dependency is disallowed, except browser tests.
"+content/shell/browser",
"+content/shell/common",
......
......@@ -268,7 +268,6 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
void SetAllowPauseForResizeOrRepaint(bool allow);
// RenderWidgetHostView implementation.
bool OnMessageReceived(const IPC::Message& msg) override;
void InitAsChild(gfx::NativeView parent_view) override;
void SetSize(const gfx::Size& size) override;
void SetBounds(const gfx::Rect& rect) override;
......@@ -521,6 +520,7 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
private:
friend class RenderWidgetHostViewMacTest;
FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewMacTest, GetPageTextForSpeech);
// Allocate a new FrameSinkId if this object is the platform view of a
// RenderWidgetHostViewGuest. This FrameSinkId will not be actually used in
......@@ -536,15 +536,9 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
// invoke it from the message loop.
void ShutdownHost();
// IPC message handlers.
void OnGetRenderedTextCompleted(const std::string& text);
// Send updated vsync parameters to the top level display.
void UpdateDisplayVSyncParameters();
// Dispatches a TTS session.
void SpeakText(const std::string& text);
// Adds/Removes frame observer based on state.
void UpdateNeedsBeginFramesInternal();
......@@ -555,6 +549,11 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
void OnResizeDueToAutoResizeComplete(const gfx::Size& new_size,
uint64_t sequence_number);
// Gets a textual view of the page's contents, and passes it to the callback
// provided.
using SpeechCallback = base::OnceCallback<void(const base::string16&)>;
void GetPageTextForSpeech(SpeechCallback callback);
// The associated view. This is weak and is inserted into the view hierarchy
// to own this RenderWidgetHostViewMac object. Set to nil at the start of the
// destructor.
......
......@@ -440,11 +440,10 @@ RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget,
if (GetTextInputManager())
GetTextInputManager()->AddObserver(this);
// Because of the way Mac pumps messages during resize, (see the code
// in RenderMessageFilter::OnMessageReceived), SetNeedsBeginFrame
// messages are not delayed on Mac. This leads to creation-time
// raciness where renderer sends a SetNeedsBeginFrame(true) before
// the renderer host is created to recieve it.
// Because of the way Mac pumps messages during resize, SetNeedsBeginFrame
// messages are not delayed on Mac. This leads to creation-time raciness
// where renderer sends a SetNeedsBeginFrame(true) before the renderer host is
// created to receive it.
//
// Any renderer that will produce frames needs to have begin frames sent to
// it. So unless it is never visible, start this value at true here to avoid
......@@ -516,16 +515,6 @@ RenderWidgetHostViewMac::GetTextSelection() {
///////////////////////////////////////////////////////////////////////////////
// RenderWidgetHostViewMac, RenderWidgetHostView implementation:
bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message)
IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedTextCompleted,
OnGetRenderedTextCompleted)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void RenderWidgetHostViewMac::InitAsChild(
gfx::NativeView parent_view) {
}
......@@ -1085,24 +1074,73 @@ gfx::Size RenderWidgetHostViewMac::GetRequestedRendererSize() const {
return browser_compositor_->GetRendererSize();
}
void RenderWidgetHostViewMac::SpeakSelection() {
if (![NSApp respondsToSelector:@selector(speakString:)])
return;
namespace {
const TextInputManager::TextSelection* selection = GetTextSelection();
if (!selection)
// A helper function for CombineTextNodesAndMakeCallback() below. It would
// ordinarily be a helper lambda in that class method, but it processes a tree
// and needs to be recursive, and that's crazy difficult to do with a lambda.
// TODO(avi): Move this to be a lambda when P0839R0 lands in C++.
void AddTextNodesToVector(const ui::AXNode* node,
std::vector<base::string16>* strings) {
const ui::AXNodeData& node_data = node->data();
if (node_data.role == ax::mojom::Role::kStaticText) {
if (node_data.HasStringAttribute(ax::mojom::StringAttribute::kName)) {
strings->emplace_back(
node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
}
return;
}
for (const auto* child : node->children())
AddTextNodesToVector(child, strings);
}
using SpeechCallback = base::OnceCallback<void(const base::string16&)>;
void CombineTextNodesAndMakeCallback(SpeechCallback callback,
const ui::AXTreeUpdate& update) {
std::vector<base::string16> text_node_contents;
text_node_contents.reserve(update.nodes.size());
ui::AXTree tree(update);
if (selection->selected_text().empty() && host()) {
// TODO: This will not work with OOPIFs (https://crbug.com/659753).
// If there's no selection, speak all text. Send an asynchronous IPC
// request for fetching all the text for a webcontent.
// ViewMsg_GetRenderedTextCompleted is sent back to IPC Message receiver.
host()->Send(new ViewMsg_GetRenderedText(host()->GetRoutingID()));
AddTextNodesToVector(tree.root(), &text_node_contents);
std::move(callback).Run(
base::JoinString(text_node_contents, base::ASCIIToUTF16("\n")));
}
} // namespace
void RenderWidgetHostViewMac::GetPageTextForSpeech(SpeechCallback callback) {
// Note that the WebContents::RequestAXTreeSnapshot() call has a limit on the
// number of nodes returned. For large pages, this call might hit that limit.
// This is a reasonable thing. The "Start Speaking" call dates back to the
// earliest days of the Mac, before accessibility. It was designed to show off
// the speech capabilities of the Mac, which is fine, but is mostly
// inapplicable nowadays. Is it useful to have the Mac read megabytes of text
// with zero control over positioning, with no fast-forward or rewind? What
// does it even mean to read a Web 2.0 dynamic, AJAXy page aloud from
// beginning to end?
//
// If this is an issue, please file a bug explaining the situation and how the
// limits of this feature affect you in the real world.
GetWebContents()->RequestAXTreeSnapshot(
base::BindOnce(CombineTextNodesAndMakeCallback, std::move(callback)),
ui::AXMode::kWebContents);
}
void RenderWidgetHostViewMac::SpeakSelection() {
const TextInputManager::TextSelection* selection = GetTextSelection();
if (selection && !selection->selected_text().empty()) {
ui::TextServicesContextMenu::SpeakText(selection->selected_text());
return;
}
ui::TextServicesContextMenu::SpeakText(selection->selected_text());
// With no selection, speak an approximation of the entire contents of the
// page.
GetPageTextForSpeech(base::BindOnce(ui::TextServicesContextMenu::SpeakText));
}
//
......@@ -1657,11 +1695,6 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
password_input_enabler_.reset();
}
void RenderWidgetHostViewMac::OnGetRenderedTextCompleted(
const std::string& text) {
ui::TextServicesContextMenu::SpeakText(base::UTF8ToUTF16(text));
}
void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
if (!host() || !browser_compositor_ || host()->is_hidden()) {
return;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/render_widget_host_view_mac.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
namespace content {
namespace {
class TextCallbackWaiter {
public:
TextCallbackWaiter() {}
void Wait() { run_loop_.Run(); }
const base::string16& text() const { return text_; }
void GetText(const base::string16& text) {
text_ = text;
run_loop_.Quit();
}
private:
base::string16 text_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(TextCallbackWaiter);
};
} // namespace
class RenderWidgetHostViewMacTest : public ContentBrowserTest {};
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewMacTest, GetPageTextForSpeech) {
GURL url(
"data:text/html,<span>Hello</span>"
"<span style='display:none'>Goodbye</span>"
"<span>World</span>");
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderWidgetHostView* rwhv =
shell()->web_contents()->GetMainFrame()->GetView();
RenderWidgetHostViewMac* rwhv_mac =
static_cast<RenderWidgetHostViewMac*>(rwhv);
TextCallbackWaiter waiter;
rwhv_mac->GetPageTextForSpeech(
base::BindOnce(&TextCallbackWaiter::GetText, base::Unretained(&waiter)));
waiter.Wait();
EXPECT_EQ(base::ASCIIToUTF16("Hello\nWorld"), waiter.text());
}
} // namespace content
......@@ -525,9 +525,6 @@ IPC_MESSAGE_ROUTED3(ViewMsg_ResolveTapDisambiguation,
gfx::Point /* tap_viewport_offset */,
bool /* is_long_press */)
// Fetches complete rendered content of a web page as plain text.
IPC_MESSAGE_ROUTED0(ViewMsg_GetRenderedText)
IPC_MESSAGE_ROUTED0(ViewMsg_SelectWordAroundCaret)
// Sent by the browser to ask the renderer to redraw. Robust to events that can
......@@ -762,11 +759,6 @@ IPC_MESSAGE_ROUTED3(ViewHostMsg_SelectWordAroundCaretAck,
int /* start_adjust */,
int /* end_adjust */)
#if defined(OS_MACOSX)
// Receives content of a web page as plain text.
IPC_MESSAGE_ROUTED1(ViewMsg_GetRenderedTextCompleted, std::string)
#endif
// Adding a new message? Stick to the sort order above: first platform
// independent ViewMsg, then ifdefs for platform specific ViewMsg, then platform
// independent ViewHostMsg, then ifdefs for platform specific ViewHostMsg.
......
......@@ -1030,35 +1030,6 @@ const blink::WebView* RenderViewImpl::webview() const {
return webview_;
}
#if BUILDFLAG(ENABLE_PLUGINS)
#if defined(OS_MACOSX)
void RenderViewImpl::OnGetRenderedText() {
if (!webview())
return;
if (!webview()->MainFrame()->IsWebLocalFrame())
return;
// Get rendered text from WebLocalFrame.
// TODO: Currently IPC truncates any data that has a
// size > kMaximumMessageSize. May be split the text into smaller chunks and
// send back using multiple IPC. See http://crbug.com/393444.
static const size_t kMaximumMessageSize = 8 * 1024 * 1024;
// TODO(dglazkov): Using this API is wrong. It's not OOPIF-compatible and
// sends text in the wrong order. See http://crbug.com/584798.
// TODO(dglazkov): WebFrameContentDumper should only be used for
// testing purposes. See http://crbug.com/585164.
std::string text =
WebFrameContentDumper::DumpWebViewAsText(webview(), kMaximumMessageSize)
.Utf8();
Send(new ViewMsg_GetRenderedTextCompleted(GetRoutingID(), text));
}
#endif // defined(OS_MACOSX)
#endif // ENABLE_PLUGINS
// RenderWidgetInputHandlerDelegate -----------------------------------------
bool RenderViewImpl::RenderWidgetWillHandleMouseEvent(
......@@ -1136,8 +1107,6 @@ bool RenderViewImpl::OnMessageReceived(const IPC::Message& message) {
IPC_MESSAGE_HANDLER(PageMsg_FreezePage, OnFreezePage)
#if defined(OS_MACOSX)
IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedText,
OnGetRenderedText)
IPC_MESSAGE_HANDLER(ViewMsg_Close, OnClose)
#endif
// Adding a new message? Add platform independent ones first, then put the
......
......@@ -540,9 +540,6 @@ class CONTENT_EXPORT RenderViewImpl : public RenderWidget,
void OnForceRedraw(const ui::LatencyInfo& latency_info);
void OnSelectWordAroundCaret();
void OnAudioStateChanged(bool is_audio_playing);
#if defined(OS_MACOSX)
void OnGetRenderedText();
#endif
// Page message handlers -----------------------------------------------------
void OnUpdateWindowScreenRect(gfx::Rect window_screen_rect);
......
......@@ -800,6 +800,7 @@ test("content_browsertests") {
"../browser/renderer_host/render_widget_host_browsertest.cc",
"../browser/renderer_host/render_widget_host_view_browsertest.cc",
"../browser/renderer_host/render_widget_host_view_child_frame_browsertest.cc",
"../browser/renderer_host/render_widget_host_view_mac_browsertest.mm",
"../browser/resource_loading_browsertest.cc",
"../browser/screen_orientation/screen_orientation_browsertest.cc",
"../browser/security_exploit_browsertest.cc",
......
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