blob: 4d86c87583e322a36c49a07cd3a4c55fa7052e28 [file] [log] [blame]
// 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 <vector>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/input/input_router_impl.h"
#include "content/browser/renderer_host/input/synthetic_smooth_drag_gesture.h"
#include "content/browser/renderer_host/input/touch_action_filter.h"
#include "content/browser/renderer_host/input/touch_emulator.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
#include "content/common/view_messages.h"
#include "content/common/widget_messages.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/platform/web_mouse_event.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/geometry/size.h"
#include "ui/latency/latency_info.h"
namespace content {
// This test enables --site-per-porcess flag.
class RenderWidgetHostSitePerProcessTest : public ContentBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
IsolateAllSitesForTesting(command_line);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
}
protected:
WebContentsImpl* web_contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
TouchActionFilter* GetTouchActionFilterForWidget(RenderWidgetHostImpl* rwhi) {
return &static_cast<InputRouterImpl*>(rwhi->input_router())
->touch_action_filter_;
}
};
class TestInputEventObserver : public RenderWidgetHost::InputEventObserver {
public:
using EventTypeVector = std::vector<blink::WebInputEvent::Type>;
~TestInputEventObserver() override {}
void OnInputEvent(const blink::WebInputEvent& event) override {
dispatched_events_.push_back(event.GetType());
}
void OnInputEventAck(InputEventAckSource source,
InputEventAckState state,
const blink::WebInputEvent& event) override {
if (blink::WebInputEvent::IsTouchEventType(event.GetType()))
acked_touch_event_type_ = event.GetType();
}
EventTypeVector GetAndResetDispatchedEventTypes() {
EventTypeVector new_event_types;
std::swap(new_event_types, dispatched_events_);
return new_event_types;
}
blink::WebInputEvent::Type acked_touch_event_type() const {
return acked_touch_event_type_;
}
private:
EventTypeVector dispatched_events_;
blink::WebInputEvent::Type acked_touch_event_type_;
};
class RenderWidgetHostTouchEmulatorBrowserTest : public ContentBrowserTest {
public:
RenderWidgetHostTouchEmulatorBrowserTest()
: view_(nullptr),
host_(nullptr),
router_(nullptr),
last_simulated_event_time_(ui::EventTimeForNow()),
simulated_event_time_delta_(base::TimeDelta::FromMilliseconds(100)) {}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
NavigateToURL(shell(),
GURL("data:text/html,<!doctype html>"
"<body style='background-color: red;'></body>"));
view_ = static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView());
host_ = static_cast<RenderWidgetHostImpl*>(view_->GetRenderWidgetHost());
router_ = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
ASSERT_TRUE(router_);
}
base::TimeTicks GetNextSimulatedEventTime() {
last_simulated_event_time_ += simulated_event_time_delta_;
return last_simulated_event_time_;
}
void SimulateRoutedMouseEvent(blink::WebInputEvent::Type type,
int x,
int y,
int modifiers,
bool pressed) {
blink::WebMouseEvent event =
SyntheticWebMouseEventBuilder::Build(type, x, y, modifiers);
if (pressed)
event.button = blink::WebMouseEvent::Button::kLeft;
event.SetTimeStamp(GetNextSimulatedEventTime());
router_->RouteMouseEvent(view_, &event, ui::LatencyInfo());
}
RenderWidgetHostImpl* host() { return host_; }
private:
RenderWidgetHostViewBase* view_;
RenderWidgetHostImpl* host_;
RenderWidgetHostInputEventRouter* router_;
base::TimeTicks last_simulated_event_time_;
base::TimeDelta simulated_event_time_delta_;
};
// Synthetic mouse events not allowed on Android.
#if !defined(OS_ANDROID)
// This test makes sure that TouchEmulator doesn't emit a GestureScrollEnd
// without a valid unique_touch_event_id when it sees a GestureFlingStart
// terminating the underlying mouse scroll sequence. If the GestureScrollEnd is
// given a unique_touch_event_id of 0, then a crash will occur.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostTouchEmulatorBrowserTest,
TouchEmulatorPinchWithGestureFling) {
auto* touch_emulator = host()->GetTouchEmulator();
touch_emulator->Enable(TouchEmulator::Mode::kEmulatingTouchFromMouse,
ui::GestureProviderConfigType::GENERIC_MOBILE);
touch_emulator->SetPinchGestureModeForTesting(true);
TestInputEventObserver observer;
host()->AddInputEventObserver(&observer);
SyntheticSmoothDragGestureParams params;
params.start_point = gfx::PointF(10.f, 110.f);
params.gesture_source_type = SyntheticGestureParams::MOUSE_INPUT;
params.distances.push_back(gfx::Vector2d(0, -10));
params.distances.push_back(gfx::Vector2d(0, -10));
params.distances.push_back(gfx::Vector2d(0, -10));
params.distances.push_back(gfx::Vector2d(0, -10));
params.speed_in_pixels_s = 1200;
// On slow bots (e.g. ChromeOS DBG) the synthetic gesture sequence events may
// be delivered slowly/erratically-timed so that the velocity_tracker in the
// TouchEmulator's GestureDetector may either (i) drop some scroll updates
// from the velocity estimate, or (ii) create an unexpectedly low velocity
// estimate. In either case, the minimum fling start velocity may not be
// achieved, meaning the condition we're trying to test never occurs. To
// avoid that, we'll keep trying until it happens. The failure mode for the
// test is that it times out.
do {
std::unique_ptr<SyntheticSmoothDragGesture> gesture(
new SyntheticSmoothDragGesture(params));
InputEventAckWaiter scroll_end_ack_waiter(
host(), blink::WebInputEvent::kGestureScrollEnd);
base::RunLoop run_loop;
host()->QueueSyntheticGesture(
std::move(gesture),
base::BindOnce(
base::BindLambdaForTesting([&](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
run_loop.Quit();
})));
run_loop.Run();
scroll_end_ack_waiter.Wait();
// Verify that a GestureFlingStart was suppressed by the TouchEmulator, and
// that we generated a GestureScrollEnd and routed it without crashing.
TestInputEventObserver::EventTypeVector dispatched_events =
observer.GetAndResetDispatchedEventTypes();
auto it_gse = std::find(dispatched_events.begin(), dispatched_events.end(),
blink::WebInputEvent::kGestureScrollEnd);
EXPECT_NE(dispatched_events.end(), it_gse);
} while (!touch_emulator->suppress_next_fling_cancel_for_testing());
}
#endif // !defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(RenderWidgetHostTouchEmulatorBrowserTest,
TouchEmulator) {
// All touches will be immediately acked instead of sending them to the
// renderer since the test page does not have a touch handler.
host()->GetTouchEmulator()->Enable(
TouchEmulator::Mode::kEmulatingTouchFromMouse,
ui::GestureProviderConfigType::GENERIC_MOBILE);
TestInputEventObserver observer;
host()->AddInputEventObserver(&observer);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 10, 0, false);
TestInputEventObserver::EventTypeVector dispatched_events =
observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(0u, dispatched_events.size());
// Mouse press becomes touch start which in turn becomes tap.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 10, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
// Mouse drag generates touch move, cancels tap and starts scroll.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 30, 0, true);
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(4u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
// Mouse drag with shift becomes pinch.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 35,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 50,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchUpdate, dispatched_events[1]);
// Mouse drag without shift becomes scroll again.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 60, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(3u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[2]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 70, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseUp, 10, 70, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchEnd, observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchEnd, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[1]);
// Mouse move does nothing.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 80, 0, false);
dispatched_events = observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(0u, dispatched_events.size());
// Another mouse down continues scroll.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 80, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 100, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(4u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
// Another pinch.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 110,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, dispatched_events[1]);
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 120,
blink::WebInputEvent::kShiftKey, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
EXPECT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchUpdate, dispatched_events[1]);
// Turn off emulation during a pinch.
host()->GetTouchEmulator()->Disable();
EXPECT_EQ(blink::WebInputEvent::kTouchCancel,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(3u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchCancel, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[2]);
// Mouse event should pass untouched.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 10,
blink::WebInputEvent::kShiftKey, true);
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(1u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kMouseMove, dispatched_events[0]);
// Turn on emulation.
host()->GetTouchEmulator()->Enable(
TouchEmulator::Mode::kEmulatingTouchFromMouse,
ui::GestureProviderConfigType::GENERIC_MOBILE);
// Another touch.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 10, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
// Scroll.
SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 30, 0, true);
EXPECT_EQ(blink::WebInputEvent::kTouchMove,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(4u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
// Turn off emulation during a scroll.
host()->GetTouchEmulator()->Disable();
EXPECT_EQ(blink::WebInputEvent::kTouchCancel,
observer.acked_touch_event_type());
dispatched_events = observer.GetAndResetDispatchedEventTypes();
ASSERT_EQ(2u, dispatched_events.size());
EXPECT_EQ(blink::WebInputEvent::kTouchCancel, dispatched_events[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[1]);
host()->RemoveInputEventObserver(&observer);
}
// Observes the WebContents until a frame finishes loading the contents of a
// given GURL.
class DocumentLoadObserver : WebContentsObserver {
public:
DocumentLoadObserver(WebContents* contents, const GURL& url)
: WebContentsObserver(contents), document_origin_(url) {}
void Wait() {
if (loaded_)
return;
run_loop_.reset(new base::RunLoop());
run_loop_->Run();
}
private:
void DidFinishLoad(RenderFrameHost* rfh, const GURL& url) override {
loaded_ |= (url == document_origin_);
if (loaded_ && run_loop_)
run_loop_->Quit();
}
bool loaded_ = false;
const GURL document_origin_;
std::unique_ptr<base::RunLoop> run_loop_;
DISALLOW_COPY_AND_ASSIGN(DocumentLoadObserver);
};
// This test verifies that when a cross-process child frame loads, the initial
// updates for touch event handlers are sent from the renderer.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostSitePerProcessTest,
OnHasTouchEventHandlers) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL::Replacements replacement;
replacement.SetHostStr("b.com");
replacement.SetQueryStr("b()");
GURL target_child_url = main_url.ReplaceComponents(replacement);
DocumentLoadObserver child_frame_observer(shell()->web_contents(),
target_child_url);
NavigateToURL(shell(), main_url);
child_frame_observer.Wait();
auto* filter = GetTouchActionFilterForWidget(web_contents()
->GetFrameTree()
->root()
->child_at(0)
->current_frame_host()
->GetRenderWidgetHost());
EXPECT_TRUE(filter->allowed_touch_action().has_value());
}
// The plumbing that this test is verifying is not utilized on Mac/Android,
// where popup menus don't create a popup RenderWidget, but rather they trigger
// a FrameHostMsg_ShowPopup to ask the browser to build and display the actual
// popup using native controls.
#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(RenderWidgetHostSitePerProcessTest,
BrowserClosesSelectPopup) {
// Navigate to a page with a <select> element.
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/site_isolation/page-with-select.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
auto* contents = static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = contents->GetFrameTree()->root();
RenderFrameHostImpl* root_frame_host = root->current_frame_host();
RenderProcessHost* process = root_frame_host->GetProcess();
// Open the <select> menu by focusing it and sending a space key
// at the focused node. This creates a popup widget.
NativeWebKeyboardEvent event(
blink::WebKeyboardEvent::kChar, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
event.text[0] = ' ';
// A class to wait for ViewHostMsg_ShowWidget.
class WaitForShowWidgetFilter : public ObserveMessageFilter {
public:
explicit WaitForShowWidgetFilter()
: ObserveMessageFilter(ViewMsgStart, ViewHostMsg_ShowWidget::ID) {}
bool OnMessageReceived(const IPC::Message& message) override {
IPC_BEGIN_MESSAGE_MAP(WaitForShowWidgetFilter, message)
IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
IPC_END_MESSAGE_MAP()
return ObserveMessageFilter::OnMessageReceived(message);
}
int routing_id() const { return routing_id_; }
private:
~WaitForShowWidgetFilter() override = default;
void OnShowWidget(int routing_id, const gfx::Rect& initial_rect) {
routing_id_ = routing_id;
}
int routing_id_ = 0;
DISALLOW_COPY_AND_ASSIGN(WaitForShowWidgetFilter);
};
for (int i = 0; i < 2; ++i) {
bool browser_closes = i == 0;
// This focuses and opens the select box, creating a popup RenderWidget. We
// wait for the RenderWidgetHost to be shown.
auto filter = base::MakeRefCounted<WaitForShowWidgetFilter>();
process->AddFilter(filter.get());
EXPECT_TRUE(ExecuteScript(root_frame_host, "focusSelectMenu();"));
root_frame_host->GetRenderWidgetHost()->ForwardKeyboardEvent(event);
filter->Wait();
// The popup RenderWidget will get its own routing id.
int popup_routing_id = filter->routing_id();
EXPECT_TRUE(popup_routing_id);
// Grab a pointer to the popup RenderWidget.
RenderWidgetHost* popup_widget_host =
RenderWidgetHost::FromID(process->GetID(), popup_routing_id);
ASSERT_TRUE(popup_widget_host);
ASSERT_NE(popup_widget_host, root_frame_host->GetRenderWidgetHost());
// A class to wait for WidgetHostMsg_Close_ACK.
auto close_filter = base::MakeRefCounted<ObserveMessageFilter>(
WidgetMsgStart, WidgetHostMsg_Close_ACK::ID);
process->AddFilter(close_filter.get());
if (browser_closes) {
// Close the popup RenderWidget from the browser side.
auto* popup_widget_host_impl =
static_cast<RenderWidgetHostImpl*>(popup_widget_host);
popup_widget_host_impl->ShutdownAndDestroyWidget(true);
} else {
// Close the popup RenderWidget from the renderer side by removing focus.
EXPECT_TRUE(
ExecuteScript(root_frame_host, "document.activeElement.blur()"));
}
// In either case, wait until closing the popup RenderWidget is complete to
// know it worked by waiting for the WidgetHostMsg_Close_ACK.
close_filter->Wait();
// Ensure the renderer didn't explode :).
{
base::string16 title_when_done[] = {base::UTF8ToUTF16("done 0"),
base::UTF8ToUTF16("done 1")};
TitleWatcher title_watcher(shell()->web_contents(), title_when_done[i]);
EXPECT_TRUE(ExecuteScript(root_frame_host,
JsReplace("document.title='done $1'", i)));
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_done[i]);
}
}
}
#endif
} // namespace content