blob: b5c7288c96daa0c54ab103a7cb7714fcdf4403e2 [file] [log] [blame]
// Copyright 2013 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 <stddef.h>
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/download/public/common/download_file_factory.h"
#include "components/download/public/common/download_file_impl.h"
#include "components/download/public/common/download_task_runner.h"
#include "content/browser/devtools/protocol/devtools_download_manager_delegate.h"
#include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/download/download_manager_impl.h"
#include "content/browser/host_zoom_map_impl.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/tracing_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/slow_download_http_response.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/shell/browser/shell_download_manager_delegate.h"
#include "net/base/features.h"
#include "net/dns/dns_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/dns/public/secure_dns_mode.h"
#include "net/dns/public/util.h"
#include "net/test/ssl_test_util.h"
#include "net/test/test_doh_server.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/boringssl/src/include/openssl/nid.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/layout.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/display/screen.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/snapshot/snapshot.h"
#if BUILDFLAG(IS_POSIX)
#include "base/task/deferred_sequenced_task_runner.h"
#include "base/tracing/perfetto_task_runner.h"
#include "services/tracing/perfetto/system_test_utils.h"
#endif
#define EXPECT_SIZE_EQ(expected, actual) \
do { \
EXPECT_EQ((expected).width(), (actual).width()); \
EXPECT_EQ((expected).height(), (actual).height()); \
} while (false)
using testing::ElementsAre;
using testing::Eq;
namespace content {
namespace {
class TestJavaScriptDialogManager : public JavaScriptDialogManager,
public WebContentsDelegate {
public:
TestJavaScriptDialogManager() {}
TestJavaScriptDialogManager(const TestJavaScriptDialogManager&) = delete;
TestJavaScriptDialogManager& operator=(const TestJavaScriptDialogManager&) =
delete;
~TestJavaScriptDialogManager() override {}
void Handle() {
if (!callback_.is_null()) {
std::move(callback_).Run(true, std::u16string());
} else {
handle_ = true;
}
}
// WebContentsDelegate
JavaScriptDialogManager* GetJavaScriptDialogManager(
WebContents* source) override {
return this;
}
// JavaScriptDialogManager
void RunJavaScriptDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
JavaScriptDialogType dialog_type,
const std::u16string& message_text,
const std::u16string& default_prompt_text,
DialogClosedCallback callback,
bool* did_suppress_message) override {
if (handle_) {
handle_ = false;
std::move(callback).Run(true, std::u16string());
} else {
callback_ = std::move(callback);
}
}
void RunBeforeUnloadDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
bool is_reload,
DialogClosedCallback callback) override {}
bool HandleJavaScriptDialog(WebContents* web_contents,
bool accept,
const std::u16string* prompt_override) override {
is_handled_ = true;
return true;
}
void CancelDialogs(WebContents* web_contents,
bool reset_state) override {}
bool is_handled() { return is_handled_; }
private:
DialogClosedCallback callback_;
bool handle_ = false;
bool is_handled_ = false;
};
} // namespace
class SitePerProcessDevToolsProtocolTest : public DevToolsProtocolTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
DevToolsProtocolTest::SetUpCommandLine(command_line);
IsolateAllSitesForTesting(command_line);
}
};
class SyntheticKeyEventTest : public DevToolsProtocolTest {
protected:
void SendKeyEvent(const std::string& type,
int modifier,
int windowsKeyCode,
int nativeKeyCode,
const std::string& key,
bool wait) {
base::Value::Dict params;
params.Set("type", type);
params.Set("modifiers", modifier);
params.Set("windowsVirtualKeyCode", windowsKeyCode);
params.Set("nativeVirtualKeyCode", nativeKeyCode);
params.Set("key", key);
SendCommand("Input.dispatchKeyEvent", std::move(params), wait);
}
};
class PrerenderDevToolsProtocolTest : public DevToolsProtocolTest {
public:
PrerenderDevToolsProtocolTest() {
prerender_helper_ = std::make_unique<test::PrerenderTestHelper>(
base::BindRepeating(&PrerenderDevToolsProtocolTest::web_contents,
base::Unretained(this)));
}
GURL GetUrl(const std::string& path) {
return embedded_test_server()->GetURL("a.test", path);
}
bool HasHostForUrl(const GURL& url) {
int host_id = prerender_helper_->GetHostForUrl(url);
return host_id != RenderFrameHost::kNoFrameTreeNodeId;
}
int AddPrerender(const GURL& prerendering_url) {
return prerender_helper_->AddPrerender(prerendering_url);
}
RenderFrameHostImpl* GetPrerenderedMainFrameHost(int host_id) {
return static_cast<RenderFrameHostImpl*>(
prerender_helper_->GetPrerenderedMainFrameHost(host_id));
}
private:
WebContents* web_contents() const { return shell()->web_contents(); }
std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
};
class SyntheticMouseEventTest : public DevToolsProtocolTest {
public:
SyntheticMouseEventTest() {
// On Android, zoom level is set to 0 in
// WebContentsImpl::GetPendingPageZoomLevel unless the kAccessibilityPageZoom
// feature is enabled. We enable it to be able to test mouse events across all
// platforms.
#if BUILDFLAG(IS_ANDROID)
feature_list_.InitAndEnableFeature(features::kAccessibilityPageZoom);
#endif
}
protected:
void SendMouseEvent(const std::string& type,
int x,
int y,
const std::string& button,
bool wait) {
base::Value::Dict params;
params.Set("type", type);
params.Set("x", x);
params.Set("y", y);
if (!button.empty()) {
params.Set("button", button);
params.Set("clickCount", 1);
}
SendCommand("Input.dispatchMouseEvent", std::move(params), wait);
}
void InitMouseDownLog() {
ASSERT_TRUE(
content::ExecJs(shell()->web_contents(),
"logs = []; window.addEventListener('mousedown', e => "
"logs.push(`${e.type},${e.clientX},${e.clientY}`));"));
}
std::string GetMouseDownLog() {
return content::EvalJs(shell()->web_contents(), "window.logs.join(';')")
.ExtractString();
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SyntheticKeyEventTest, KeyEventSynthesizeKey) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
ASSERT_TRUE(content::ExecJs(
shell()->web_contents(),
"function handleKeyEvent(event) {"
"domAutomationController.send(event.key);"
"}"
"document.body.addEventListener('keydown', handleKeyEvent);"
"document.body.addEventListener('keyup', handleKeyEvent);"));
DOMMessageQueue dom_message_queue(shell()->web_contents());
// Send enter (keycode 13).
SendKeyEvent("rawKeyDown", 0, 13, 13, "Enter", true);
SendKeyEvent("keyUp", 0, 13, 13, "Enter", true);
std::string key;
ASSERT_TRUE(dom_message_queue.WaitForMessage(&key));
EXPECT_EQ("\"Enter\"", key);
ASSERT_TRUE(dom_message_queue.WaitForMessage(&key));
EXPECT_EQ("\"Enter\"", key);
// Send escape (keycode 27).
SendKeyEvent("rawKeyDown", 0, 27, 27, "Escape", true);
SendKeyEvent("keyUp", 0, 27, 27, "Escape", true);
ASSERT_TRUE(dom_message_queue.WaitForMessage(&key));
EXPECT_EQ("\"Escape\"", key);
ASSERT_TRUE(dom_message_queue.WaitForMessage(&key));
EXPECT_EQ("\"Escape\"", key);
}
// Flaky: https://crbug.com/889878
IN_PROC_BROWSER_TEST_F(SyntheticKeyEventTest, DISABLED_KeyboardEventAck) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
ASSERT_TRUE(content::ExecJs(
shell()->web_contents(),
"document.body.addEventListener('keydown', () => {debugger;});"));
auto filter = std::make_unique<InputMsgWatcher>(
RenderWidgetHostImpl::From(shell()
->web_contents()
->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetWidget()),
blink::WebInputEvent::Type::kRawKeyDown);
SendCommandSync("Debugger.enable");
SendKeyEvent("rawKeyDown", 0, 13, 13, "Enter", false);
// We expect that the debugger message event arrives *before* the input
// event ack, and the subsequent command response for Input.dispatchKeyEvent.
WaitForNotification("Debugger.paused");
EXPECT_FALSE(filter->HasReceivedAck());
EXPECT_EQ(1, received_responses_count());
SendCommandSync("Debugger.resume");
filter->WaitForAck();
EXPECT_EQ(3, received_responses_count());
}
// Flaky: https://crbug.com/1263461
IN_PROC_BROWSER_TEST_F(SyntheticMouseEventTest, DISABLED_MouseEventAck) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
ASSERT_TRUE(content::ExecJs(
shell()->web_contents(),
"document.body.addEventListener('mousedown', () => {debugger;});"));
auto filter = std::make_unique<InputMsgWatcher>(
RenderWidgetHostImpl::From(shell()
->web_contents()
->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetWidget()),
blink::WebInputEvent::Type::kMouseDown);
SendCommandSync("Debugger.enable");
SendMouseEvent("mousePressed", 15, 15, "left", false);
// We expect that the debugger message event arrives *before* the input
// event ack, and the subsequent command response for
// Input.dispatchMouseEvent.
WaitForNotification("Debugger.paused");
EXPECT_FALSE(filter->HasReceivedAck());
EXPECT_EQ(1, received_responses_count());
SendCommandSync("Debugger.resume");
filter->WaitForAck();
EXPECT_EQ(3, received_responses_count());
}
IN_PROC_BROWSER_TEST_F(SyntheticMouseEventTest, MouseEventCoordinates) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/zoom.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
InitMouseDownLog();
// In about 1 out of 1000 runs, the event gets lost on the way to the
// renderer. We repeat the event dispatch until it succeeds since we want to
// test event coordinates.
while (GetMouseDownLog() == "") {
SendMouseEvent("mousePressed", 15, 15, "left", true);
}
ASSERT_EQ("mousedown,15,15", GetMouseDownLog());
}
IN_PROC_BROWSER_TEST_F(SyntheticMouseEventTest, MouseEventCoordinatesWithZoom) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/zoom.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
SendCommandSync("Page.enable");
InitMouseDownLog();
HostZoomMap* host_zoom_map =
HostZoomMap::GetForWebContents(shell()->web_contents());
host_zoom_map->SetZoomLevelForHost(test_url.host(),
blink::PageZoomFactorToZoomLevel(2.5));
WaitForNotification("Page.frameResized", true);
// In about 1 out of 1000 runs, the event gets lost on the way to the
// renderer. We repeat the event dispatch until it succeeds since we want to
// test event coordinates.
while (GetMouseDownLog() == "") {
SendMouseEvent("mousePressed", 15, 15, "left", true);
}
ASSERT_EQ("mousedown,15,15", GetMouseDownLog());
}
namespace {
bool DecodePNG(std::string base64_data, SkBitmap* bitmap) {
std::string png_data;
if (!base::Base64Decode(base64_data, &png_data))
return false;
return gfx::PNGCodec::Decode(
reinterpret_cast<unsigned const char*>(png_data.data()), png_data.size(),
bitmap);
}
std::unique_ptr<SkBitmap> DecodeJPEG(std::string base64_data) {
std::string jpeg_data;
if (!base::Base64Decode(base64_data, &jpeg_data))
return nullptr;
return gfx::JPEGCodec::Decode(
reinterpret_cast<unsigned const char*>(jpeg_data.data()),
jpeg_data.size());
}
int ColorsSquareDiff(SkColor color1, SkColor color2) {
auto a_diff = static_cast<int>(SkColorGetA(color1)) -
static_cast<int>(SkColorGetA(color2));
auto r_diff = static_cast<int>(SkColorGetR(color1)) -
static_cast<int>(SkColorGetR(color2));
auto g_diff = static_cast<int>(SkColorGetG(color1)) -
static_cast<int>(SkColorGetG(color2));
auto b_diff = static_cast<int>(SkColorGetB(color1)) -
static_cast<int>(SkColorGetB(color2));
return a_diff * a_diff + r_diff * r_diff + g_diff * g_diff +
b_diff * b_diff;
}
bool ColorsMatchWithinLimit(SkColor color1, SkColor color2, int max_collor_diff) {
return ColorsSquareDiff(color1, color2) <= max_collor_diff * max_collor_diff;
}
// Adapted from cc::ExactPixelComparator.
bool MatchesBitmap(const SkBitmap& expected_bmp,
const SkBitmap& actual_bmp,
const gfx::Rect& matching_mask,
float device_scale_factor,
int max_collor_diff) {
// Number of pixels with an error
int error_pixels_count = 0;
gfx::Rect error_bounding_rect = gfx::Rect();
// Scale expectations along with the mask.
device_scale_factor = device_scale_factor ? device_scale_factor : 1;
// Check that bitmaps have identical dimensions.
int expected_width = round(expected_bmp.width() * device_scale_factor);
int expected_height = round(expected_bmp.height() * device_scale_factor);
EXPECT_EQ(expected_width, actual_bmp.width());
EXPECT_EQ(expected_height, actual_bmp.height());
if (expected_width != actual_bmp.width() ||
expected_height != actual_bmp.height()) {
return false;
}
DCHECK(gfx::SkIRectToRect(actual_bmp.bounds()).Contains(matching_mask));
for (int x = matching_mask.x(); x < matching_mask.right(); ++x) {
for (int y = matching_mask.y(); y < matching_mask.bottom(); ++y) {
SkColor actual_color =
actual_bmp.getColor(x * device_scale_factor, y * device_scale_factor);
SkColor expected_color = expected_bmp.getColor(x, y);
if (!ColorsMatchWithinLimit(actual_color, expected_color, max_collor_diff)) {
if (error_pixels_count < 10) {
LOG(ERROR) << "Pixel (" << x << "," << y
<< "). Expected: " << std::hex << expected_color
<< ", actual: " << actual_color << std::dec
<< ", square diff: " << ColorsSquareDiff(expected_color, actual_color);
}
error_pixels_count++;
error_bounding_rect.Union(gfx::Rect(x, y, 1, 1));
}
}
}
if (error_pixels_count != 0) {
LOG(ERROR) << "Number of pixel with an error: " << error_pixels_count;
LOG(ERROR) << "Error Bounding Box : " << error_bounding_rect.ToString();
return false;
}
return true;
}
} // namespace
enum class ScreenshotEncoding { PNG, JPEG, WEBP };
std::string EncodingEnumToString(ScreenshotEncoding encoding) {
switch (encoding) {
case ScreenshotEncoding::PNG:
return "png";
case ScreenshotEncoding::JPEG:
return "jpeg";
case ScreenshotEncoding::WEBP:
return "webp";
default:
return "";
}
}
class CaptureScreenshotTest : public DevToolsProtocolTest {
protected:
std::unique_ptr<SkBitmap> CaptureScreenshot(
ScreenshotEncoding encoding,
bool from_surface,
const gfx::RectF& clip = gfx::RectF(),
float clip_scale = 0,
bool capture_beyond_viewport = false,
bool expect_error = false) {
base::Value::Dict params;
params.Set("format", EncodingEnumToString(encoding));
params.Set("quality", 100);
params.Set("fromSurface", from_surface);
if (capture_beyond_viewport) {
params.Set("captureBeyondViewport", true);
}
if (clip_scale) {
base::Value::Dict clip_value;
clip_value.Set("x", clip.x());
clip_value.Set("y", clip.y());
clip_value.Set("width", clip.width());
clip_value.Set("height", clip.height());
clip_value.Set("scale", clip_scale);
params.Set("clip", std::move(clip_value));
}
SendCommandSync("Page.captureScreenshot", std::move(params));
std::unique_ptr<SkBitmap> result_bitmap;
if (expect_error && error()) {
EXPECT_THAT(error()->FindInt("code"),
testing::Optional(
static_cast<int>(crdtp::DispatchCode::SERVER_ERROR)));
} else {
const std::string* base64 = result()->FindString("data");
if (encoding == ScreenshotEncoding::PNG) {
result_bitmap = std::make_unique<SkBitmap>();
EXPECT_TRUE(DecodePNG(*base64, result_bitmap.get()));
} else if (encoding == ScreenshotEncoding::JPEG) {
result_bitmap = DecodeJPEG(*base64);
} else {
// Decode not implemented.
}
EXPECT_TRUE(result_bitmap);
}
return result_bitmap;
}
void CaptureScreenshotAndCompareTo(const SkBitmap& expected_bitmap,
ScreenshotEncoding encoding,
bool from_surface,
float device_scale_factor = 0,
const gfx::RectF& clip = gfx::RectF(),
float clip_scale = 0,
bool capture_beyond_viewport = false) {
std::unique_ptr<SkBitmap> result_bitmap = CaptureScreenshot(
encoding, from_surface, clip, clip_scale, capture_beyond_viewport);
gfx::Rect matching_mask(gfx::SkIRectToRect(expected_bitmap.bounds()));
#if BUILDFLAG(IS_MAC)
// Mask out the corners, which may be drawn differently on Mac because of
// rounded corners.
matching_mask.Inset(4);
#endif
// A color profile can be installed on the host that could affect
// pixel colors. Also JPEG compression could further distort the color.
// Allow some error between actual and expected pixel values.
// That assumes there is no shift in pixel positions, so it only works
// reliably if all pixels have equal values.
int max_collor_diff = 20;
EXPECT_TRUE(MatchesBitmap(expected_bitmap, *result_bitmap, matching_mask,
device_scale_factor, max_collor_diff));
}
// Takes a screenshot of a colored box that is positioned inside the frame.
void PlaceAndCaptureBox(const gfx::Size& frame_size,
const gfx::Size& box_size,
float screenshot_scale,
float device_scale_factor) {
static const int kBoxOffsetHeight = 100;
const gfx::Size scaled_box_size =
ScaleToFlooredSize(box_size, screenshot_scale);
base::Value::Dict params;
VLOG(1) << "Testing screenshot of box with size " << box_size.width() << "x"
<< box_size.height() << "px at scale " << screenshot_scale
<< " ...";
// Draw a blue box of provided size in the horizontal center of the page.
EXPECT_TRUE(content::ExecJs(
shell()->web_contents(),
base::StringPrintf(
"var style = document.body.style; "
"style.overflow = 'hidden'; "
"style.minHeight = '%dpx'; "
"style.backgroundImage = 'linear-gradient(#0000ff, #0000ff)'; "
"style.backgroundSize = '%dpx %dpx'; "
"style.backgroundPosition = '50%% %dpx'; "
"style.backgroundRepeat = 'no-repeat'; ",
box_size.height() + kBoxOffsetHeight, box_size.width(),
box_size.height(), kBoxOffsetHeight)));
// Force frame size: The offset of the blue box within the frame shouldn't
// change during screenshotting. This verifies that the page doesn't observe
// a change in frame size as a side effect of screenshotting.
params = base::Value::Dict();
params.Set("width", frame_size.width());
params.Set("height", frame_size.height());
params.Set("deviceScaleFactor", device_scale_factor);
params.Set("mobile", false);
SendCommandSync("Emulation.setDeviceMetricsOverride", std::move(params));
// Resize frame to scaled blue box size.
gfx::RectF clip;
clip.set_width(box_size.width());
clip.set_height(box_size.height());
clip.set_x((frame_size.width() - box_size.width()) / 2.);
clip.set_y(kBoxOffsetHeight);
// Capture screenshot and verify that it is indeed blue.
SkBitmap expected_bitmap;
expected_bitmap.allocN32Pixels(scaled_box_size.width(),
scaled_box_size.height());
expected_bitmap.eraseColor(SkColorSetRGB(0x00, 0x00, 0xff));
// If the device scale factor is 0,
// get the original device scale factor to compare with
if (!device_scale_factor) {
device_scale_factor = display::Screen::GetScreen()
->GetPrimaryDisplay()
.device_scale_factor();
}
CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG,
true, device_scale_factor, clip,
screenshot_scale);
// Reset for next screenshot.
SendCommandSync("Emulation.clearDeviceMetricsOverride");
}
bool IsTrusted() override { return is_trusted_; }
bool is_trusted_ = true;
private:
#if !BUILDFLAG(IS_ANDROID)
void SetUp() override {
EnablePixelOutput();
DevToolsProtocolTest::SetUp();
}
#endif
};
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest,
CaptureScreenshotBeyondViewport_OutOfView) {
// TODO(crbug.com/653637) This test fails consistently on low-end Android
// devices.
if (base::SysInfo::IsLowEndDevice())
return;
// Load dummy page before getting the window size.
shell()->LoadURL(GURL("data:text/html,"));
gfx::Size window_size =
static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView())
->GetCompositorViewportPixelSize();
// Make a page a bit bigger than the view to force scrollbars to be shown.
float content_height = window_size.height() + 10;
float content_width = window_size.width() + 10;
shell()->LoadURL(
GURL("data:text/html,<body "
"style='background:%23123456;height:" +
base::NumberToString(content_height) +
"px;width:" + base::NumberToString(content_width) + "px'></body>"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
// Generate expected screenshot without any scrollbars.
SkBitmap expected_bitmap;
expected_bitmap.allocN32Pixels(content_width, content_height);
expected_bitmap.eraseColor(SkColorSetRGB(0x12, 0x34, 0x56));
float device_scale_factor =
display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
// Verify there are no scrollbars on the screenshot.
CaptureScreenshotAndCompareTo(
expected_bitmap, ScreenshotEncoding::PNG, true, device_scale_factor,
gfx::RectF(0, 0, content_width, content_height), 1, true);
}
// ChromeOS and Android has fading out scrollbars, which makes the test flacky.
// TODO(crbug.com/1150059) Android has a problem with changing scale.
// TODO(crbug.com/1147911) Android Lollipop has a problem with capturing
// screenshot.
// TODO(crbug.com/1156767) Flaky on linux-lacros-tester-rel
// TODO(crbug.com/1286261): Failing on MacOS.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) || \
BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_MAC)
#define MAYBE_CaptureScreenshotBeyondViewport_InnerScrollbarsAreShown \
DISABLED_CaptureScreenshotBeyondViewport_InnerScrollbarsAreShown
#else
#define MAYBE_CaptureScreenshotBeyondViewport_InnerScrollbarsAreShown \
CaptureScreenshotBeyondViewport_InnerScrollbarsAreShown
#endif
IN_PROC_BROWSER_TEST_F(
CaptureScreenshotTest,
MAYBE_CaptureScreenshotBeyondViewport_InnerScrollbarsAreShown) {
// TODO(crbug.com/653637) This test fails consistently on low-end Android
// devices.
if (base::SysInfo::IsLowEndDevice())
return;
shell()->LoadURL(GURL(
"data:text/html,<body><div style='width: 50px; height: 50px; overflow: "
"scroll;'><h3>test</h3><h3>test</h3><h3>test</h3></div></body>"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
// We compare against the actual physical backing size rather than the
// view size, because the view size is stored adjusted for DPI and only in
// integer precision.
gfx::Size view_size = static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView())
->GetCompositorViewportPixelSize();
// Capture a screenshot not "form surface", meaning without emulation and
// without changing preferences, as-is.
std::unique_ptr<SkBitmap> expected_bitmap =
CaptureScreenshot(ScreenshotEncoding::PNG, false);
float device_scale_factor =
display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
// Compare the captured screenshot with one made "from_surface", where actual
// scrollbar magic happened, and verify it looks the same, meaning the
// internal scrollbars are rendered.
CaptureScreenshotAndCompareTo(
*expected_bitmap, ScreenshotEncoding::PNG, true, device_scale_factor,
gfx::RectF(0, 0, view_size.width(), view_size.height()), 1, true);
}
// ChromeOS and Android don't support software compositing.
#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
class NoGPUCaptureScreenshotTest : public CaptureScreenshotTest {
void SetUpCommandLine(base::CommandLine* command_line) override {
CaptureScreenshotTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kDisableGpuCompositing);
}
};
// Tests that large screenshots are composited fine with software compositor.
// Regression test for https://crbug.com/1137291.
// Flaky on Linux. http://crbug.com/1301176
#if BUILDFLAG(IS_LINUX)
#define MAYBE_LargeScreenshot DISABLED_LargeScreenshot
#else
#define MAYBE_LargeScreenshot LargeScreenshot
#endif
IN_PROC_BROWSER_TEST_F(NoGPUCaptureScreenshotTest, MAYBE_LargeScreenshot) {
// This test fails consistently on low-end Android devices.
// See crbug.com/653637.
// TODO(eseckler): Reenable with error limit if necessary.
if (base::SysInfo::IsLowEndDevice())
return;
// If disabling software compositing is disabled by the test caller,
// we're out of luck.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableSoftwareCompositingFallback)) {
return;
}
shell()->LoadURL(
GURL("data:text/html,"
"<style>body,html { padding: 0; margin: 0; }</style>"
"<div style='width: 1250px; height: 8440px; "
" background: linear-gradient(red, blue)'></div>"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
auto params = base::Value::Dict();
params.Set("width", 1280);
params.Set("height", 8440);
params.Set("deviceScaleFactor", 1);
params.Set("mobile", false);
SendCommandSync("Emulation.setDeviceMetricsOverride", std::move(params));
auto bitmap = CaptureScreenshot(ScreenshotEncoding::PNG, true,
gfx::RectF(0, 0, 1280, 8440), 1);
SendCommandSync("Emulation.clearDeviceMetricsOverride");
EXPECT_EQ(1280, bitmap->width());
EXPECT_EQ(8440, bitmap->height());
// Top-left is red-ish.
SkColor top_left = bitmap->getColor(0, 0);
EXPECT_GT(static_cast<int>(SkColorGetR(top_left)), 128);
EXPECT_LT(static_cast<int>(SkColorGetB(top_left)), 128);
// Bottom-left is blue-ish.
SkColor bottom_left = bitmap->getColor(0, 8339);
EXPECT_LT(static_cast<int>(SkColorGetR(bottom_left)), 128);
EXPECT_GT(static_cast<int>(SkColorGetB(bottom_left)), 128);
}
#endif // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
// Setting frame size (through RWHV) is not supported on Android.
// This test seems to be very flaky on all platforms: https://crbug.com/801173
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, DISABLED_CaptureScreenshotArea) {
static const gfx::Size kFrameSize(800, 600);
shell()->LoadURL(GURL("about:blank"));
Attach();
// Test capturing a subarea inside the emulated frame at different scales.
PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 1.0, 1.);
PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 2.0, 1.);
PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 0.5, 1.);
// Check non-1 device scale factor.
PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 1.0, 2.);
// Ensure not emulating device scale factor works.
PlaceAndCaptureBox(kFrameSize, gfx::Size(100, 200), 1.0, 0.);
}
// Verifies that setDefaultBackgroundColorOverride changes the background color
// of a page that does not specify one.
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest,
SetDefaultBackgroundColorOverride) {
if (base::SysInfo::IsLowEndDevice())
return;
shell()->LoadURL(GURL("about:blank"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
// Override background to blue.
base::Value::Dict color;
color.Set("r", 0x00);
color.Set("g", 0x00);
color.Set("b", 0xff);
color.Set("a", 1.0);
base::Value::Dict params;
params.Set("color", std::move(color));
SendCommandSync("Emulation.setDefaultBackgroundColorOverride",
std::move(params));
SkBitmap expected_bitmap;
// We compare against the actual physical backing size rather than the
// view size, because the view size is stored adjusted for DPI and only in
// integer precision.
gfx::Size view_size = static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView())
->GetCompositorViewportPixelSize();
expected_bitmap.allocN32Pixels(view_size.width(), view_size.height());
expected_bitmap.eraseColor(SkColorSetRGB(0x00, 0x00, 0xff));
CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG, true);
// Tests that resetting Emulation.setDefaultBackgroundColorOverride
// clears the background color override.
SendCommandSync("Emulation.setDefaultBackgroundColorOverride");
expected_bitmap.eraseColor(SK_ColorWHITE);
CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG, true);
}
// Verifies that setDefaultBackgroundColor and captureScreenshot support a fully
// and semi-transparent background, and that setDeviceMetricsOverride doesn't
// affect it.
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, TransparentScreenshots) {
if (base::SysInfo::IsLowEndDevice())
return;
shell()->LoadURL(
GURL("data:text/html,<body style='background:transparent'></body>"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
auto params = base::Value::Dict();
{
// Override background to fully transparent.
base::Value::Dict color;
color.Set("r", 0);
color.Set("g", 0);
color.Set("b", 0);
color.Set("a", 0);
params.Set("color", std::move(color));
}
SendCommandSync("Emulation.setDefaultBackgroundColorOverride",
std::move(params));
SkBitmap expected_bitmap;
// We compare against the actual physical backing size rather than the
// view size, because the view size is stored adjusted for DPI and only in
// integer precision.
gfx::Size view_size = static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView())
->GetCompositorViewportPixelSize();
expected_bitmap.allocN32Pixels(view_size.width(), view_size.height());
expected_bitmap.eraseColor(SK_ColorTRANSPARENT);
CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG, true);
#if !BUILDFLAG(IS_ANDROID)
float device_scale_factor =
display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
// Check that device emulation does not affect the transparency.
params = base::Value::Dict();
params.Set("width", view_size.width());
params.Set("height", view_size.height());
params.Set("deviceScaleFactor", 0);
params.Set("mobile", false);
params.Set("fitWindow", false);
SendCommandSync("Emulation.setDeviceMetricsOverride", std::move(params));
CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG, true,
device_scale_factor);
SendCommandSync("Emulation.clearDeviceMetricsOverride");
#endif // !BUILDFLAG(IS_ANDROID)
{
// Override background to a semi-transparent color.
base::Value::Dict color;
color.Set("r", 255);
color.Set("g", 0);
color.Set("b", 0);
color.Set("a", 1.0 / 255 * 16);
params = base::Value::Dict();
params.Set("color", std::move(color));
}
SendCommandSync("Emulation.setDefaultBackgroundColorOverride",
std::move(params));
expected_bitmap.eraseColor(SkColorSetARGB(16, 255, 0, 0));
CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG, true);
#if !BUILDFLAG(IS_ANDROID)
// Check that device emulation does not affect the transparency.
params = base::Value::Dict();
params.Set("width", view_size.width());
params.Set("height", view_size.height());
params.Set("deviceScaleFactor", 0);
params.Set("mobile", false);
params.Set("fitWindow", false);
SendCommandSync("Emulation.setDeviceMetricsOverride", std::move(params));
CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG, true,
device_scale_factor);
SendCommandSync("Emulation.clearDeviceMetricsOverride");
#endif // !BUILDFLAG(IS_ANDROID)
}
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest,
OnlyScreenshotsFromSurfaceWhenUnsafeNotAllowed) {
is_trusted_ = false;
shell()->LoadURL(GURL("about:blank"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
CaptureScreenshot(ScreenshotEncoding::PNG, false, gfx::RectF(), 0, true,
true);
}
#if BUILDFLAG(IS_ANDROID)
// Disabled, see http://crbug.com/469947.
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_SynthesizePinchGesture) {
GURL test_url = GetTestUrl("devtools", "synthetic_gesture_tests.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
int old_width = EvalJs(shell(), "window.innerWidth").ExtractInt();
int old_height = EvalJs(shell(), "window.innerHeight").ExtractInt();
base::Value::Dict params;
params.Set("x", old_width / 2);
params.Set("y", old_height / 2);
params.Set("scaleFactor", 2.0);
SendCommandSync("Input.synthesizePinchGesture", std::move(params));
int new_width = EvalJs(shell(), "window.innerWidth").ExtractInt();
ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_width) / new_width);
int new_height = EvalJs(shell(), "window.innerHeight").ExtractInt();
ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_height) / new_height);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_SynthesizeScrollGesture) {
GURL test_url = GetTestUrl("devtools", "synthetic_gesture_tests.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
ASSERT_EQ(0, EvalJs(shell(), "document.body.scrollTop"));
base::Value::Dict params;
params.Set("x", 0);
params.Set("y", 0);
params.Set("xDistance", 0);
params.Set("yDistance", -100);
SendCommandSync("Input.synthesizeScrollGesture", std::move(params));
ASSERT_EQ(100, EvalJs(shell(), "document.body.scrollTop"));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_SynthesizeTapGesture) {
GURL test_url = GetTestUrl("devtools", "synthetic_gesture_tests.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
ASSERT_EQ(0, EvalJs(shell(), "document.body.scrollTop"));
base::Value::Dict params;
params.Set("x", 16);
params.Set("y", 16);
params.Set("gestureSourceType", "touch");
SendCommandSync("Input.synthesizeTapGesture", std::move(params));
// The link that we just tapped should take us to the bottom of the page. The
// new value of |document.body.scrollTop| will depend on the screen dimensions
// of the device that we're testing on, but in any case it should be greater
// than 0.
ASSERT_GT(EvalJs(shell(), "document.body.scrollTop").ExtractInt(), 0);
}
#endif // BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/1303155): Flaky on multiple bots.
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_PageCrash) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
base::Value::Dict command_params;
command_params.Set("discover", true);
SendCommandSync("Target.setDiscoverTargets", std::move(command_params));
base::Value::Dict params = WaitForNotification("Target.targetCreated", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.type"), Eq("page"));
std::string target_id = *params.FindStringByDottedPath("targetInfo.targetId");
ClearNotifications();
{
content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
SendCommandAsync("Page.crash");
params = WaitForNotification("Target.targetCrashed", true);
}
EXPECT_EQ(*params.FindString("targetId"), target_id);
ClearNotifications();
shell()->LoadURL(test_url);
WaitForNotification("Inspector.targetReloadedAfterCrash", true);
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PageCrashInFrame DISABLED_PageCrashInFrame
#else
#define MAYBE_PageCrashInFrame PageCrashInFrame
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessDevToolsProtocolTest,
MAYBE_PageCrashInFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url =
embedded_test_server()->GetURL("/devtools/page-with-oopif.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
base::Value::Dict command_params;
command_params.Set("discover", true);
SendCommandSync("Target.setDiscoverTargets", std::move(command_params));
base::Value::Dict params;
std::string frame_target_id;
for (int targetCount = 1; true; targetCount++) {
params = WaitForNotification("Target.targetCreated", true);
if (*params.FindStringByDottedPath("targetInfo.type") == "iframe") {
frame_target_id = *params.FindStringByDottedPath("targetInfo.targetId");
break;
}
ASSERT_LT(targetCount, 2);
}
command_params = base::Value::Dict();
command_params.Set("targetId", frame_target_id);
command_params.Set("flatten", true);
const base::Value::Dict* result =
SendCommandSync("Target.attachToTarget", std::move(command_params));
ASSERT_TRUE(result);
const std::string* session_id = result->FindString("sessionId");
ASSERT_TRUE(session_id);
ClearNotifications();
{
content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
SendSessionCommand("Page.crash", base::Value::Dict(), *session_id, false);
params = WaitForNotification("Target.targetCrashed", true);
}
EXPECT_EQ(frame_target_id, *params.FindString("targetId"));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, PageCrashClearsPendingCommands) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
base::Value::Dict command_params;
command_params.Set("discover", true);
SendCommandSync("Target.setDiscoverTargets", std::move(command_params));
base::Value::Dict params = WaitForNotification("Target.targetCreated", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.type"), Eq("page"));
std::string target_id = *params.FindStringByDottedPath("targetInfo.targetId");
SendCommandSync("Debugger.enable");
command_params = base::Value::Dict();
command_params.Set("expression", "console.log('first page'); debugger");
SendCommandAsync("Runtime.evaluate", std::move(command_params));
WaitForNotification("Debugger.paused");
{
content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
shell()->LoadURL(GURL(blink::kChromeUICrashURL));
params = WaitForNotification("Target.targetCrashed", true);
}
ClearNotifications();
SendCommandAsync("Page.reload");
WaitForNotification("Inspector.targetReloadedAfterCrash", true);
command_params = base::Value::Dict();
command_params.Set("expression", "console.log('second page')");
SendCommandSync("Runtime.evaluate", std::move(command_params));
EXPECT_THAT(console_messages_, ElementsAre("first page", "second page"));
}
// TODO(crbug.com/1280531): Disabled due to flakiness. Flaky on mac and linux
// la-cros
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
DISABLED_NavigationPreservesMessages) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
SendCommandAsync("Page.enable");
base::Value::Dict params;
test_url = GetTestUrl("devtools", "navigation.html");
params.Set("url", test_url.spec());
TestNavigationObserver navigation_observer(shell()->web_contents());
SendCommandSync("Page.navigate", std::move(params));
navigation_observer.Wait();
EXPECT_GE(received_responses_count(), 2);
EXPECT_TRUE(HasExistingNotification("Page.frameStartedLoading"));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
NavigationToFileUrlRequiresFileAccess) {
Attach();
base::Value::Dict params;
GURL test_url = GetTestUrl("devtools", "navigation.html");
params.Set("url", test_url.spec());
ASSERT_TRUE(SendCommandSync("Page.navigate", params.Clone()));
Detach();
SetMayReadLocalFiles(false);
Attach();
ASSERT_FALSE(SendCommandSync("Page.navigate", params.Clone()));
EXPECT_THAT(
error()->FindInt("code"),
testing::Optional(static_cast<int>(crdtp::DispatchCode::SERVER_ERROR)));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteNoDetach) {
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url1 = embedded_test_server()->GetURL(
"A.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1);
Attach();
GURL test_url2 = embedded_test_server()->GetURL(
"B.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2, 1);
EXPECT_FALSE(HasExistingNotification());
}
// TODO(crbug.com/1280746): Flaky on MacOS.
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_CrossSiteNavigation) {
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url1 =
embedded_test_server()->GetURL("A.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1);
Attach();
SendCommandAsync("Page.enable");
GURL test_url2 =
embedded_test_server()->GetURL("B.com", "/devtools/navigation.html");
base::Value::Dict params;
params.Set("url", test_url2.spec());
const base::Value::Dict* result =
SendCommandSync("Page.navigate", std::move(params));
const std::string* frame_id = result->FindString("frameId");
base::Value::Dict frame_stopped =
WaitForNotification("Page.frameStoppedLoading", true);
EXPECT_EQ(*frame_stopped.FindString("frameId"), *frame_id);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteCrash) {
set_agent_host_can_close();
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url1 =
embedded_test_server()->GetURL("A.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1);
Attach();
CrashTab(shell()->web_contents());
GURL test_url2 =
embedded_test_server()->GetURL("B.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2, 1);
// Should not crash at this point.
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, InspectorTargetCrashedNavigate) {
set_agent_host_can_close();
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a = embedded_test_server()->GetURL("a.com", "/title1.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), url_a, 1);
Attach();
SendCommandSync("Inspector.enable");
{
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(shell());
shell()->LoadURL(GURL(blink::kChromeUICrashURL));
WaitForNotification("Inspector.targetCrashed");
}
ClearNotifications();
shell()->LoadURL(url_a);
WaitForNotification("Inspector.targetReloadedAfterCrash", true);
}
// Same as in DevToolsProtocolTest.InspectorTargetCrashedNavigate, but with a
// cross-process navigation at the end.
// Regression test for https://crbug.com/990315
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
InspectorTargetCrashedNavigateCrossProcess) {
set_agent_host_can_close();
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a = embedded_test_server()->GetURL("a.com", "/title1.html");
GURL url_b = embedded_test_server()->GetURL("b.com", "/title1.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), url_a, 1);
Attach();
SendCommandSync("Inspector.enable");
{
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(shell());
shell()->LoadURL(GURL(blink::kChromeUICrashURL));
WaitForNotification("Inspector.targetCrashed");
}
ClearNotifications();
shell()->LoadURL(url_b);
WaitForNotification("Inspector.targetReloadedAfterCrash", true);
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_InspectorTargetCrashedReload DISABLED_InspectorTargetCrashedReload
#else
#define MAYBE_InspectorTargetCrashedReload InspectorTargetCrashedReload
#endif
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
MAYBE_InspectorTargetCrashedReload) {
set_agent_host_can_close();
GURL url = GURL("data:text/html,<body></body>");
NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
Attach();
SendCommandSync("Inspector.enable");
{
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(shell());
shell()->LoadURL(GURL(blink::kChromeUICrashURL));
WaitForNotification("Inspector.targetCrashed");
}
ClearNotifications();
SendCommandAsync("Page.reload");
WaitForNotification("Inspector.targetReloadedAfterCrash", true);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ReconnectPreservesState) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Shell* second = CreateBrowser();
NavigateToURLBlockUntilNavigationsComplete(second, test_url, 1);
Attach();
SendCommandSync("Runtime.enable");
agent_host_->DisconnectWebContents();
agent_host_->ConnectWebContents(second->web_contents());
WaitForNotification("Runtime.executionContextsCleared");
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSitePauseInBeforeUnload) {
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURLBlockUntilNavigationsComplete(shell(),
embedded_test_server()->GetURL("A.com", "/devtools/navigation.html"), 1);
Attach();
SendCommandSync("Debugger.enable");
ASSERT_TRUE(content::ExecJs(
shell(),
"window.onbeforeunload = function() { debugger; return null; }"));
shell()->LoadURL(
embedded_test_server()->GetURL("B.com", "/devtools/navigation.html"));
WaitForNotification("Debugger.paused");
TestNavigationObserver observer(shell()->web_contents(), 1);
SendCommandSync("Debugger.resume");
observer.Wait();
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, InspectDuringFrameSwap) {
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url1 =
embedded_test_server()->GetURL("A.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1);
ShellAddedObserver new_shell_observer;
EXPECT_TRUE(ExecJs(shell(), "window.open('about:blank','foo');"));
Shell* new_shell = new_shell_observer.GetShell();
EXPECT_TRUE(new_shell->web_contents()->HasOpener());
agent_host_ = DevToolsAgentHost::GetOrCreateFor(new_shell->web_contents());
agent_host_->AttachClient(this);
GURL test_url2 =
embedded_test_server()->GetURL("B.com", "/devtools/navigation.html");
// After this navigation, if the bug exists, the process will crash.
NavigateToURLBlockUntilNavigationsComplete(new_shell, test_url2, 1);
// Ensure that the A.com process is still alive by executing a script in the
// original tab.
//
// TODO(alexmos, nasko): A better way to do this is to navigate the original
// tab to another site, watch for process exit, and check whether there was a
// crash. However, currently there's no way to wait for process exit
// regardless of whether it's a crash or not. RenderProcessHostWatcher
// should be fixed to support waiting on both WATCH_FOR_PROCESS_EXIT and
// WATCH_FOR_HOST_DESTRUCTION, and then used here.
EXPECT_EQ(true, EvalJs(shell(), "!!window.open('', 'foo');"));
GURL test_url3 =
embedded_test_server()->GetURL("A.com", "/devtools/navigation.html");
// After this navigation, if the bug exists, the process will crash.
NavigateToURLBlockUntilNavigationsComplete(new_shell, test_url3, 1);
// Ensure that the A.com process is still alive by executing a script in the
// original tab.
EXPECT_EQ(true, EvalJs(shell(), "!!window.open('', 'foo');"));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DoubleCrash) {
set_agent_host_can_close();
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
SendCommandSync("ServiceWorker.enable");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
CrashTab(shell()->web_contents());
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
CrashTab(shell()->web_contents());
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
// Should not crash at this point.
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ReloadBlankPage) {
Shell* window = Shell::CreateNewWindow(
shell()->web_contents()->GetBrowserContext(),
GURL("javascript:x=1"),
nullptr,
gfx::Size());
WaitForLoadStop(window->web_contents());
Attach();
SendCommandAsync("Page.reload");
// Should not crash at this point.
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, EvaluateInBlankPage) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
base::Value::Dict params;
params.Set("expression", "window");
SendCommandSync("Runtime.evaluate", std::move(params));
EXPECT_FALSE(result()->Find("exceptionDetails"));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
EvaluateInBlankPageAfterNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
base::Value::Dict params;
params.Set("expression", "window");
SendCommandSync("Runtime.evaluate", std::move(params));
EXPECT_FALSE(result()->Find("exceptionDetails"));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, JavaScriptDialogNotifications) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
TestJavaScriptDialogManager dialog_manager;
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
wc->SetDelegate(&dialog_manager);
SendCommandSync("Page.enable");
base::Value::Dict params;
params.Set("expression", "prompt('hello?', 'default')");
SendCommandAsync("Runtime.evaluate", std::move(params));
base::Value::Dict notification =
WaitForNotification("Page.javascriptDialogOpening");
EXPECT_EQ(*notification.FindString("url"), "about:blank");
EXPECT_EQ(*notification.FindString("message"), "hello?");
EXPECT_EQ(*notification.FindString("type"), "prompt");
EXPECT_EQ(*notification.FindString("defaultPrompt"), "default");
params = base::Value::Dict();
params.Set("accept", true);
params.Set("promptText", "hi!");
SendCommandAsync("Page.handleJavaScriptDialog", std::move(params));
notification = WaitForNotification("Page.javascriptDialogClosed", true);
EXPECT_THAT(notification.FindBool("result"), testing::Optional(true));
EXPECT_TRUE(dialog_manager.is_handled());
EXPECT_THAT(*notification.FindString("userInput"), Eq("hi!"));
wc->SetDelegate(nullptr);
wc->SetJavaScriptDialogManagerForTesting(nullptr);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, JavaScriptDialogInterop) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
TestJavaScriptDialogManager dialog_manager;
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
wc->SetDelegate(&dialog_manager);
SendCommandSync("Page.enable");
SendCommandSync("Runtime.enable");
base::Value::Dict params;
params.Set("expression", "alert('42')");
SendCommandAsync("Runtime.evaluate", std::move(params));
WaitForNotification("Page.javascriptDialogOpening");
dialog_manager.Handle();
WaitForNotification("Page.javascriptDialogClosed", true);
wc->SetDelegate(nullptr);
wc->SetJavaScriptDialogManagerForTesting(nullptr);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, PageDisableWithOpenedDialog) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
TestJavaScriptDialogManager dialog_manager;
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
wc->SetDelegate(&dialog_manager);
SendCommandSync("Page.enable");
SendCommandSync("Runtime.enable");
base::Value::Dict params;
params.Set("expression", "alert('42')");
SendCommandAsync("Runtime.evaluate", std::move(params));
WaitForNotification("Page.javascriptDialogOpening");
EXPECT_TRUE(wc->IsJavaScriptDialogShowing());
EXPECT_FALSE(dialog_manager.is_handled());
SendCommandAsync("Page.disable");
EXPECT_TRUE(wc->IsJavaScriptDialogShowing());
EXPECT_FALSE(dialog_manager.is_handled());
dialog_manager.Handle();
EXPECT_FALSE(wc->IsJavaScriptDialogShowing());
params = base::Value::Dict();
params.Set("expression", "42");
SendCommandSync("Runtime.evaluate", std::move(params));
wc->SetDelegate(nullptr);
wc->SetJavaScriptDialogManagerForTesting(nullptr);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, PageDisableWithNoDialogManager) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
wc->SetDelegate(nullptr);
SendCommandSync("Page.enable");
SendCommandSync("Runtime.enable");
base::Value::Dict params;
params.Set("expression", "alert('42');");
SendCommandAsync("Runtime.evaluate", std::move(params));
WaitForNotification("Page.javascriptDialogOpening");
EXPECT_TRUE(wc->IsJavaScriptDialogShowing());
SendCommandSync("Page.disable");
EXPECT_FALSE(wc->IsJavaScriptDialogShowing());
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, BeforeUnloadDialog) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
TestJavaScriptDialogManager dialog_manager;
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
wc->SetDelegate(&dialog_manager);
SendCommandSync("Runtime.enable");
base::Value::Dict params;
params = base::Value::Dict();
params.Set("expression", "window.onbeforeunload=()=>{return 'prompt';}");
params.Set("userGesture", true);
SendCommandSync("Runtime.evaluate", std::move(params));
SendCommandSync("Page.enable");
SendCommandAsync("Page.reload");
base::Value::Dict notification =
WaitForNotification("Page.javascriptDialogOpening", true);
EXPECT_THAT(*notification.FindString("url"), Eq("about:blank"));
EXPECT_THAT(*notification.FindString("type"), Eq("beforeunload"));
params = base::Value::Dict();
params.Set("accept", true);
SendCommandAsync("Page.handleJavaScriptDialog", std::move(params));
WaitForNotification("Page.javascriptDialogClosed", true);
wc->SetDelegate(nullptr);
wc->SetJavaScriptDialogManagerForTesting(nullptr);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, BrowserCreateAndCloseTarget) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
EXPECT_EQ(1u, shell()->windows().size());
base::Value::Dict params;
params.Set("url", "about:blank");
SendCommandSync("Target.createTarget", std::move(params));
const std::string* target_id = result()->FindString("targetId");
ASSERT_TRUE(target_id);
EXPECT_EQ(2u, shell()->windows().size());
// TODO(eseckler): Since the RenderView is closed asynchronously, we currently
// don't verify that the command actually closes the shell.
params = base::Value::Dict();
params.Set("targetId", *target_id);
SendCommandSync("Target.closeTarget", std::move(params));
EXPECT_THAT(result()->FindBool("success"), testing::Optional(true));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, BrowserGetTargets) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
SendCommandSync("Target.getTargets");
const base::Value::List* target_infos = result()->FindList("targetInfos");
ASSERT_TRUE(target_infos);
EXPECT_EQ(1u, target_infos->size());
const base::Value& target_info_value = target_infos->front();
EXPECT_TRUE(target_info_value.is_dict());
const base::DictionaryValue& target_info =
base::Value::AsDictionaryValue(target_info_value);
std::string target_id, type, title, url;
EXPECT_TRUE(target_info.GetString("targetId", &target_id));
EXPECT_TRUE(target_info.GetString("type", &type));
EXPECT_TRUE(target_info.GetString("title", &title));
EXPECT_TRUE(target_info.GetString("url", &url));
EXPECT_EQ("page", type);
EXPECT_EQ("about:blank", title);
EXPECT_EQ("about:blank", url);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, VirtualTimeTest) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
base::Value::Dict params;
params.Set("policy", "pause");
SendCommandSync("Emulation.setVirtualTimePolicy", std::move(params));
params = base::Value::Dict();
params.Set("expression",
"setTimeout(function(){console.log('before')}, 999);"
"setTimeout(function(){console.log('at')}, 1000);"
"setTimeout(function(){console.log('after')}, 1001);");
SendCommandSync("Runtime.evaluate", std::move(params));
// Let virtual time advance for one second.
params = base::Value::Dict();
params.Set("policy", "advance");
params.Set("budget", 1000);
SendCommandSync("Emulation.setVirtualTimePolicy", std::move(params));
WaitForNotification("Emulation.virtualTimeBudgetExpired");
params = base::Value::Dict();
params.Set("expression", "console.log('done')");
SendCommandSync("Runtime.evaluate", std::move(params));
// The third timer should not fire.
EXPECT_THAT(console_messages_, ElementsAre("before", "at", "done"));
// Let virtual time advance for another second, which should make the third
// timer fire.
params = base::Value::Dict();
params.Set("policy", "advance");
params.Set("budget", 1000);
SendCommandSync("Emulation.setVirtualTimePolicy", std::move(params));
WaitForNotification("Emulation.virtualTimeBudgetExpired");
EXPECT_THAT(console_messages_, ElementsAre("before", "at", "done", "after"));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CertificateError) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(https_server.Start());
GURL test_url = https_server.GetURL("/devtools/navigation.html");
base::Value::Dict command_params;
shell()->LoadURL(GURL("about:blank"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
SendCommandSync("Network.enable");
SendCommandAsync("Security.enable");
command_params = base::Value::Dict();
command_params.Set("override", true);
SendCommandSync("Security.setOverrideCertificateErrors",
std::move(command_params));
// Test cancel.
SendCommandSync("Network.clearBrowserCache");
SendCommandSync("Network.clearBrowserCookies");
TestNavigationObserver cancel_observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
base::Value::Dict params =
WaitForNotification("Security.certificateError", false);
EXPECT_TRUE(shell()->web_contents()->GetController().GetPendingEntry());
EXPECT_EQ(
test_url,
shell()->web_contents()->GetController().GetPendingEntry()->GetURL());
command_params = base::Value::Dict();
command_params.Set("eventId", *params.FindInt("eventId"));
command_params.Set("action", "cancel");
SendCommandAsync("Security.handleCertificateError",
std::move(command_params));
cancel_observer.Wait();
EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
EXPECT_EQ(GURL("about:blank"), shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->GetURL());
// Test continue.
SendCommandSync("Network.clearBrowserCache");
SendCommandSync("Network.clearBrowserCookies");
TestNavigationObserver continue_observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
params = WaitForNotification("Security.certificateError", false);
command_params = base::Value::Dict();
command_params.Set("eventId", *params.FindInt("eventId"));
command_params.Set("action", "continue");
SendCommandAsync("Security.handleCertificateError",
std::move(command_params));
WaitForNotification("Network.loadingFinished", true);
continue_observer.Wait();
EXPECT_EQ(test_url, shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->GetURL());
// Reset override.
SendCommandSync("Security.disable");
// Test ignoring all certificate errors.
command_params = base::Value::Dict();
command_params.Set("ignore", true);
SendCommandSync("Security.setIgnoreCertificateErrors",
std::move(command_params));
SendCommandSync("Network.clearBrowserCache");
SendCommandSync("Network.clearBrowserCookies");
TestNavigationObserver continue_observer2(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
WaitForNotification("Network.loadingFinished", true);
continue_observer2.Wait();
EXPECT_EQ(test_url, shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->GetURL());
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
CertificateErrorRequestInterception) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(https_server.Start());
GURL test_url = https_server.GetURL("/devtools/navigation.html");
shell()->LoadURL(GURL("about:blank"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
SendCommandSync("Network.enable");
SendCommandAsync("Security.enable");
SendCommandSync("Network.setRequestInterception",
std::move(base::JSONReader::Read(
"{\"patterns\": [{\"urlPattern\": \"*\"}]}")
->GetDict()));
SendCommandSync(
"Security.setIgnoreCertificateErrors",
std::move(base::JSONReader::Read("{\"ignore\": true}")->GetDict()));
SendCommandSync("Network.clearBrowserCache");
SendCommandSync("Network.clearBrowserCookies");
TestNavigationObserver continue_observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
base::Value::Dict params =
WaitForNotification("Network.requestIntercepted", false);
std::string interceptionId = *params.FindString("interceptionId");
SendCommandAsync("Network.continueInterceptedRequest",
std::move(base::JSONReader::Read("{\"interceptionId\": \"" +
interceptionId + "\"}")
->GetDict()));
continue_observer.Wait();
EXPECT_EQ(test_url, shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->GetURL());
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CertificateErrorBrowserTarget) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(https_server.Start());
GURL test_url = https_server.GetURL("/devtools/navigation.html");
base::Value::Dict params;
base::Value::Dict command_params;
shell()->LoadURL(GURL("about:blank"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// Clear cookies and cache to avoid interference with cert error events.
Attach();
SendCommandSync("Network.enable");
SendCommandSync("Network.clearBrowserCache");
SendCommandSync("Network.clearBrowserCookies");
Detach();
// Test that browser target can ignore cert errors.
AttachToBrowserTarget();
command_params = base::Value::Dict();
command_params.Set("ignore", true);
SendCommandSync("Security.setIgnoreCertificateErrors",
std::move(command_params));
TestNavigationObserver continue_observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
continue_observer.Wait();
EXPECT_EQ(test_url, shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->GetURL());
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, SubresourceWithCertificateError) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
https_server.ServeFilesFromSourceDirectory("content/test/data/devtools");
ASSERT_TRUE(https_server.Start());
GURL test_url = https_server.GetURL("/image.html");
base::Value::Dict command_params;
shell()->LoadURL(GURL("about:blank"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
Attach();
SendCommandAsync("Security.enable");
command_params = base::Value::Dict();
command_params.Set("override", true);
SendCommandSync("Security.setOverrideCertificateErrors",
std::move(command_params));
TestNavigationObserver observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
// Expect certificateError event for main frame.
base::Value::Dict params =
WaitForNotification("Security.certificateError", false);
command_params = base::Value::Dict();
command_params.Set("eventId", *params.FindInt("eventId"));
command_params.Set("action", "continue");
SendCommandAsync("Security.handleCertificateError",
std::move(command_params));
// Expect certificateError event for image.
params = WaitForNotification("Security.certificateError", false);
command_params = base::Value::Dict();
command_params.Set("eventId", *params.FindInt("eventId"));
command_params.Set("action", "continue");
SendCommandAsync("Security.handleCertificateError",
std::move(command_params));
observer.Wait();
EXPECT_EQ(test_url, shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->GetURL());
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, TargetDiscovery) {
std::set<std::string> ids;
base::Value::Dict command_params;
ASSERT_TRUE(embedded_test_server()->Start());
GURL first_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), first_url, 1);
GURL second_url = embedded_test_server()->GetURL("/devtools/navigation.html");
Shell* second = CreateBrowser();
NavigateToURLBlockUntilNavigationsComplete(second, second_url, 1);
Attach();
int attached_count = 0;
command_params = base::Value::Dict();
command_params.Set("discover", true);
SendCommandSync("Target.setDiscoverTargets", std::move(command_params));
base::Value::Dict params = WaitForNotification("Target.targetCreated", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.type"), Eq("page"));
attached_count += *params.FindBoolByDottedPath("targetInfo.attached") ? 1 : 0;
std::string target_id = *params.FindStringByDottedPath("targetInfo.targetId");
EXPECT_THAT(ids.count(target_id), Eq(0u));
ids.insert(target_id);
params = WaitForNotification("Target.targetCreated", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.type"), Eq("page"));
attached_count += *params.FindBoolByDottedPath("targetInfo.attached") ? 1 : 0;
target_id = *params.FindStringByDottedPath("targetInfo.targetId");
EXPECT_THAT(ids.count(target_id), Eq(0u));
ids.insert(target_id);
EXPECT_FALSE(HasExistingNotification());
EXPECT_EQ(1, attached_count);
GURL third_url = embedded_test_server()->GetURL("/devtools/navigation.html");
Shell* third = CreateBrowser();
NavigateToURLBlockUntilNavigationsComplete(third, third_url, 1);
params = WaitForNotification("Target.targetCreated", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.type"), Eq("page"));
std::string attached_id =
*params.FindStringByDottedPath("targetInfo.targetId");
EXPECT_THAT(ids.count(attached_id), Eq(0u));
ids.insert(attached_id);
EXPECT_THAT(params.FindBoolByDottedPath("targetInfo.attached"),
testing::Optional(false));
params = WaitForNotification("Target.targetInfoChanged", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.url"),
Eq("about:blank"));
params = WaitForNotification("Target.targetInfoChanged", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.url"),
Eq(third_url.spec()));
EXPECT_FALSE(HasExistingNotification());
second->Close();
second = nullptr;
params = WaitForNotification("Target.targetDestroyed", true);
target_id = *params.FindString("targetId");
EXPECT_THAT(ids.erase(target_id), Eq(1u));
EXPECT_FALSE(HasExistingNotification());
command_params = base::Value::Dict();
command_params.Set("targetId", attached_id);
SendCommandSync("Target.attachToTarget", std::move(command_params));
params = WaitForNotification("Target.targetInfoChanged", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.targetId"),
Eq(attached_id));
EXPECT_THAT(params.FindBoolByDottedPath("targetInfo.attached"),
testing::Optional(true));
params = WaitForNotification("Target.attachedToTarget", true);
std::string session_id = *params.FindString("sessionId");
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.targetId"),
Eq(attached_id));
EXPECT_FALSE(HasExistingNotification());
WebContents::CreateParams create_params(
ShellContentBrowserClient::Get()->browser_context());
std::unique_ptr<content::WebContents> web_contents(
content::WebContents::Create(create_params));
EXPECT_FALSE(HasExistingNotification());
NavigateToURLBlockUntilNavigationsComplete(web_contents.get(), first_url, 1);
// The notification does not come when there's no delegate.
EXPECT_FALSE(HasExistingNotification("Target.targetCreated"));
web_contents->SetDelegate(this);
// Attaching a delegate causes the notification to be sent.
params = WaitForNotification("Target.targetCreated", true);
EXPECT_THAT(*params.FindStringByDottedPath("targetInfo.type"), Eq("page"));
EXPECT_FALSE(HasExistingNotification());
command_params = base::Value::Dict();
command_params.Set("discover", false);
SendCommandSync("Target.setDiscoverTargets", std::move(command_params));
EXPECT_FALSE(HasExistingNotification());
command_params = base::Value::Dict();
command_params.Set("sessionId", session_id);
SendCommandSync("Target.detachFromTarget", std::move(command_params));
params = WaitForNotification("Target.detachedFromTarget", true);
EXPECT_THAT(*params.FindString("sessionId"), Eq(session_id));
EXPECT_THAT(*params.FindString("targetId"), Eq(attached_id));
EXPECT_FALSE(HasExistingNotification());
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, SetAndGetCookies) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/title1.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
// Set two cookies, one of which matches the loaded URL and another that
// doesn't.
base::Value::Dict command_params;
command_params = base::Value::Dict();
command_params.Set("url", test_url.spec());
command_params.Set("name", "cookie_for_this_url");
command_params.Set("value", "mendacious");
SendCommandAsync("Network.setCookie", std::move(command_params));
command_params = base::Value::Dict();
command_params.Set("url", "https://www.chromium.org");
command_params.Set("name", "cookie_for_another_url");
command_params.Set("value", "polyglottal");
SendCommandAsync("Network.setCookie", std::move(command_params));
// First get the cookies for just the loaded URL.
SendCommandSync("Network.getCookies");
const base::Value::List* cookies = result()->FindList("cookies");
ASSERT_TRUE(cookies);
EXPECT_EQ(1u, cookies->size());
const base::Value& cookie_value = cookies->front();
EXPECT_TRUE(cookie_value.is_dict());
const base::DictionaryValue& cookie =
base::Value::AsDictionaryValue(cookie_value);
std::string name;
std::string value;
EXPECT_TRUE(cookie.GetString("name", &name));
EXPECT_TRUE(cookie.GetString("value", &value));
EXPECT_EQ("cookie_for_this_url", name);
EXPECT_EQ("mendacious", value);
// Then get all the cookies in the cookie jar.
SendCommandSync("Network.getAllCookies");
cookies = result()->FindList("cookies");
ASSERT_TRUE(cookies);
EXPECT_EQ(2u, cookies->size());
// Note: the cookies will be returned in unspecified order.
size_t found = 0;
for (const base::Value& cookie_value : *cookies) {
EXPECT_TRUE(cookie_value.is_dict());
const base::DictionaryValue& cookie =
base::Value::AsDictionaryValue(cookie_value);
EXPECT_TRUE(cookie.GetString("name", &name));
if (name == "cookie_for_this_url") {
EXPECT_TRUE(cookie.GetString("value", &value));
EXPECT_EQ("mendacious", value);
found++;
} else if (name == "cookie_for_another_url") {
EXPECT_TRUE(cookie.GetString("value", &value));
EXPECT_EQ("polyglottal", value);
found++;
} else {
FAIL();
}
}
EXPECT_EQ(2u, found);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
AutoAttachToOOPIFAfterNavigationStarted) {
ASSERT_TRUE(embedded_test_server()->Start());
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"b.com"});
GURL a_url = embedded_test_server()->GetURL("a.com", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), a_url));
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
// Create iframe and start navigation.
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
TestNavigationManager navigation_manager(shell()->web_contents(), b_url);
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("const iframe = document.createElement('iframe');\n"
"iframe.src = $1;\n"
"document.body.appendChild(iframe);\n",
b_url)));
// Pause subframe navigation.
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
// Attach to DevTools after subframe starts navigating (but before it
// finishes).
Attach();
DevToolsAgentHostImpl* main_frame_agent =
RenderFrameDevToolsAgentHost::GetFor(main_frame);
EXPECT_NE(main_frame_agent, nullptr);
// Start auto-attach.
base::Value::Dict command_params;
command_params = base::Value::Dict();
command_params.Set("autoAttach", true);
command_params.Set("waitForDebuggerOnStart", false);
command_params.Set("flatten", true);
SendCommandSync("Target.setAutoAttach", std::move(command_params));
// Child frame should be created at this point, but isn't an OOPIF yet, so
// shouldn't have its own DevToolsAgentHost yet.
FrameTreeNode* child = main_frame->child_at(0);
EXPECT_NE(child, nullptr);
EXPECT_EQ(RenderFrameDevToolsAgentHost::GetFor(child), main_frame_agent);
// Resume navigation.
navigation_manager.WaitForNavigationFinished();
// Target for OOPIF should get attached.
auto notification = WaitForNotification("Target.attachedToTarget", true);
EXPECT_NE(RenderFrameDevToolsAgentHost::GetFor(child), main_frame_agent);
EXPECT_THAT(notification.FindBool("waitingForDebugger"),
testing::Optional(false));
}
class DevToolsProtocolDeviceEmulationTest : public DevToolsProtocolTest {
public:
~DevToolsProtocolDeviceEmulationTest() override {}
void EmulateDeviceSize(gfx::Size size) {
base::Value::Dict params;
params.Set("width", size.width());
params.Set("height", size.height());
params.Set("deviceScaleFactor", 0);
params.Set("mobile", false);
SendCommandSync("Emulation.setDeviceMetricsOverride", std::move(params));
}
gfx::Size GetViewSize() {
return shell()
->web_contents()
->GetPrimaryMainFrame()
->GetView()
->GetViewBounds()
.size();
}
};
// Setting frame size (through RWHV) is not supported on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_DeviceSize DISABLED_DeviceSize
#else
#define MAYBE_DeviceSize DeviceSize
#endif
IN_PROC_BROWSER_TEST_F(DevToolsProtocolDeviceEmulationTest, MAYBE_DeviceSize) {
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url1 =
embedded_test_server()->GetURL("A.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1);
Attach();
const gfx::Size original_size = GetViewSize();
const gfx::Size emulated_size_1 =
gfx::Size(original_size.width() - 50, original_size.height() - 50);
const gfx::Size emulated_size_2 =
gfx::Size(original_size.width() - 100, original_size.height() - 100);
EmulateDeviceSize(emulated_size_1);
EXPECT_EQ(emulated_size_1, GetViewSize());
EmulateDeviceSize(emulated_size_2);
EXPECT_EQ(emulated_size_2, GetViewSize());
GURL test_url2 =
embedded_test_server()->GetURL("B.com", "/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2, 1);
EXPECT_EQ(emulated_size_2, GetViewSize());
SendCommandSync("Emulation.clearDeviceMetricsOverride");
EXPECT_EQ(original_size, GetViewSize());
}
// Setting frame size (through RWHV) is not supported on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_RenderKillDoesNotCrashBrowser \
DISABLED_RenderKillDoesNotCrashBrowser
#else
#define MAYBE_RenderKillDoesNotCrashBrowser RenderKillDoesNotCrashBrowser
#endif
IN_PROC_BROWSER_TEST_F(DevToolsProtocolDeviceEmulationTest,
MAYBE_RenderKillDoesNotCrashBrowser) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
EmulateDeviceSize(gfx::Size(200, 200));
{
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(shell());
NavigateToURLBlockUntilNavigationsComplete(
shell(), GURL(blink::kChromeUICrashURL), 1);
}
SendCommandSync("Emulation.clearDeviceMetricsOverride");
// Should not crash at this point.
}
class DevToolsProtocolDeviceEmulationPrerenderTest
: public DevToolsProtocolDeviceEmulationTest {
public:
DevToolsProtocolDeviceEmulationPrerenderTest()
: prerender_helper_(base::BindRepeating(
&DevToolsProtocolDeviceEmulationPrerenderTest::GetWebContents,
base::Unretained(this))) {}
~DevToolsProtocolDeviceEmulationPrerenderTest() override = default;
void SetUpOnMainThread() override {
DevToolsProtocolDeviceEmulationTest::SetUpOnMainThread();
prerender_helper_.SetUp(embedded_test_server());
}
// WebContentsDelegate overrides.
bool IsPrerender2Supported(WebContents& web_contents) override {
return true;
}
WebContents* GetWebContents() const { return shell()->web_contents(); }
protected:
test::PrerenderTestHelper prerender_helper_;
};
// Setting frame size (through RWHV) is not supported on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_DeviceSize DISABLED_DeviceSize
#else
#define MAYBE_DeviceSize DeviceSize
#endif
IN_PROC_BROWSER_TEST_F(DevToolsProtocolDeviceEmulationPrerenderTest,
MAYBE_DeviceSize) {
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
const gfx::Size original_size = GetViewSize();
const gfx::Size emulated_size =
gfx::Size(original_size.width() - 50, original_size.height() - 50);
EmulateDeviceSize(emulated_size);
EXPECT_EQ(emulated_size, GetViewSize());
// Start a prerender and ensure frame size isn't changed.
GURL prerender_url =
embedded_test_server()->GetURL("/devtools/navigation.html?prerender");
prerender_helper_.AddPrerender(prerender_url);
EXPECT_EQ(emulated_size, GetViewSize());
// Activate the prerendered page and ensure frame size isn't changed.
prerender_helper_.NavigatePrimaryPage(prerender_url);
EXPECT_EQ(emulated_size, GetViewSize());
SendCommandSync("Emulation.clearDeviceMetricsOverride");
EXPECT_EQ(original_size, GetViewSize());
}
class DevToolsProtocolTouchTest : public DevToolsProtocolTest {
public:
~DevToolsProtocolTouchTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
switches::kTouchEventFeatureDetection,
switches::kTouchEventFeatureDetectionDisabled);
}
};
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTouchTest, EnableTouch) {
base::Value::Dict params;
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url1 =
embedded_test_server()->GetURL("A.com", "/devtools/enable_touch.html");
GURL test_url2 =
embedded_test_server()->GetURL("B.com", "/devtools/enable_touch.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1);
Attach();
params = base::Value::Dict();
SendCommandSync("Page.enable", std::move(params));
EXPECT_EQ(true, EvalJs(shell()->web_contents(), "checkProtos(false)"));
params = base::Value::Dict();
params.Set("enabled", true);
SendCommandSync("Emulation.setTouchEmulationEnabled", std::move(params));
EXPECT_EQ(true, EvalJs(shell()->web_contents(), "checkProtos(false)"));
params = base::Value::Dict();
params.Set("url", test_url2.spec());
SendCommandAsync("Page.navigate", std::move(params));
WaitForNotification("Page.frameStoppedLoading");
EXPECT_EQ(true, EvalJs(shell()->web_contents(), "checkProtos(true)"));
params = base::Value::Dict();
params.Set("enabled", false);
SendCommandSync("Emulation.setTouchEmulationEnabled", std::move(params));
EXPECT_EQ(true, EvalJs(shell()->web_contents(), "checkProtos(true)"));
params = base::Value::Dict();
SendCommandAsync("Page.reload", std::move(params));
WaitForNotification("Page.frameStoppedLoading");
EXPECT_EQ(true, EvalJs(shell()->web_contents(), "checkProtos(false)"));
}
class DevToolsProtocolBackForwardCacheTest : public DevToolsProtocolTest {
public:
DevToolsProtocolBackForwardCacheTest() {
feature_list_.InitWithFeaturesAndParameters(
{{features::kBackForwardCache,
{{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}}},
// Allow BackForwardCache for all devices regardless of their memory.
{features::kBackForwardCacheMemoryControls});
}
~DevToolsProtocolBackForwardCacheTest() override = default;
// content::WebContentsDelegate:
bool IsBackForwardCacheSupported() override { return true; }
std::string Evaluate(const std::string& script,
const base::Location& location) {
base::Value::Dict params;
params.Set("expression", script);
SendCommandSync("Runtime.evaluate", std::move(params));
const std::string* result_value =
result()->FindStringByDottedPath("result.value");
DCHECK(result_value) << "Valued to evaluate " << script << " from "
<< location.ToString();
return *result_value;
}
private:
base::test::ScopedFeatureList feature_list_;
};
// This test checks that the DevTools continue to work when the page is stored
// in and restored from back-forward cache. In particular:
// - that the session continues to be attached and the navigations are handled
// correctly.
// - when the old page is stored in the cache, the messages are still handled by
// the new page.
// - when the page is restored from the cache, it continues to handle protocol
// messages.
IN_PROC_BROWSER_TEST_F(DevToolsProtocolBackForwardCacheTest, Basic) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A and inject some state.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
EXPECT_TRUE(ExecJs(shell(), "var state = 'page1'"));
// 2) Attach DevTools session.
Attach();
// 3) Extract the state via the DevTools protocol.
EXPECT_EQ("page1", Evaluate("state", FROM_HERE));
// 3) Navigate to B and inject some different state.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(ExecJs(shell(), "var state = 'page2'"));
// 4) Ensure that the DevTools protocol commands are handled by the new page
// (even though the old page is alive and is stored in the back-forward
// cache).
EXPECT_EQ("page2", Evaluate("state", FROM_HERE));
// 5) Go back.
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// 6) Ensure that the page has been restored from the cache and responds to
// the DevTools commands.
EXPECT_EQ("page1", Evaluate("state", FROM_HERE));
}
// Download tests are flaky on Android: https://crbug.com/7546
#if !BUILDFLAG(IS_ANDROID)
namespace {
static DownloadManagerImpl* DownloadManagerForShell(Shell* shell) {
// We're in a content_browsertest; we know that the DownloadManager
// is a DownloadManagerImpl.
return static_cast<DownloadManagerImpl*>(
shell->web_contents()->GetBrowserContext()->GetDownloadManager());
}
static void RemoveShellDelegate(Shell* shell) {
content::ShellDownloadManagerDelegate* shell_delegate =
static_cast<content::ShellDownloadManagerDelegate*>(
DownloadManagerForShell(shell)->GetDelegate());
shell_delegate->SetDownloadManager(nullptr);
DownloadManagerForShell(shell)->SetDelegate(nullptr);
}
class CountingDownloadFile : public download::DownloadFileImpl {
public:
CountingDownloadFile(
std::unique_ptr<download::DownloadSaveInfo> save_info,
const base::FilePath& default_downloads_directory,
std::unique_ptr<download::InputStream> stream,
uint32_t download_id,
base::WeakPtr<download::DownloadDestinationObserver> observer)
: download::DownloadFileImpl(std::move(save_info),
default_downloads_directory,
std::move(stream),
download_id,
observer) {}
~CountingDownloadFile() override {
DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
active_files_--;
}
void Initialize(
InitializeCallback callback,
CancelRequestCallback cancel_request_callback,
const download::DownloadItem::ReceivedSlices& received_slices) override {
DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
active_files_++;
download::DownloadFileImpl::Initialize(std::move(callback),
std::move(cancel_request_callback),
received_slices);
}
static void GetNumberActiveFiles(int* result) {
DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
*result = active_files_;
}
// Can be called on any thread, and will block (running message loop)
// until data is returned.
static int GetNumberActiveFilesFromFileThread() {
int result = -1;
base::RunLoop run_loop;
download::GetDownloadTaskRunner()->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&CountingDownloadFile::GetNumberActiveFiles, &result),
run_loop.QuitWhenIdleClosure());
run_loop.Run();
DCHECK_NE(-1, result);
return result;
}
private:
static int active_files_;
};
int CountingDownloadFile::active_files_ = 0;
class CountingDownloadFileFactory : public download::DownloadFileFactory {
public:
CountingDownloadFileFactory() {}
~CountingDownloadFileFactory() override {}
// DownloadFileFactory interface.
download::DownloadFile* CreateFile(
std::unique_ptr<download::DownloadSaveInfo> save_info,
const base::FilePath& default_downloads_directory,
std::unique_ptr<download::InputStream> stream,
uint32_t download_id,
base::WeakPtr<download::DownloadDestinationObserver> observer) override {
return new CountingDownloadFile(std::move(save_info),
default_downloads_directory,
std::move(stream), download_id, observer);
}
};
// Get the next created download.
class DownloadCreateObserver : DownloadManager::Observer {
public:
explicit DownloadCreateObserver(DownloadManager* manager)
: manager_(manager), item_(nullptr), received_item_response_(false) {
manager_->AddObserver(this);
}
~DownloadCreateObserver() override {
if (manager_)
manager_->RemoveObserver(this);
manager_ = nullptr;
}
void ManagerGoingDown(DownloadManager* manager) override {
DCHECK_EQ(manager_, manager);
manager_->RemoveObserver(this);
manager_ = nullptr;
}
void OnDownloadCreated(DownloadManager* manager,
download::DownloadItem* download) override {
received_item_response_ = true;
if (!item_)
item_ = download;
if (completion_closure_)
std::move(completion_closure_).Run();
}
void OnDownloadDropped(DownloadManager* manager) override {
received_item_response_ = true;
item_ = nullptr;
if (completion_closure_)
std::move(completion_closure_).Run();
}
download::DownloadItem* WaitForFinished() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!received_item_response_) {
base::RunLoop run_loop;
completion_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
return item_;
}
private:
DownloadManager* manager_;
download::DownloadItem* item_;
bool received_item_response_;
base::OnceClosure completion_closure_;
};
bool IsDownloadInState(download::DownloadItem::DownloadState state,
download::DownloadItem* item) {
return item->GetState() == state;
}
class DevToolsDownloadContentTest : public DevToolsProtocolTest {
protected:
void SetUpOnMainThread() override {
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
// Set shell default download manager to test proxy reset behavior.
test_delegate_ = std::make_unique<ShellDownloadManagerDelegate>();
test_delegate_->SetDownloadBehaviorForTesting(
downloads_directory_.GetPath());
DownloadManager* manager = DownloadManagerForShell(shell());
manager->GetDelegate()->Shutdown();
manager->SetDelegate(test_delegate_.get());
test_delegate_->SetDownloadManager(manager);
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&content::SlowDownloadHttpResponse::HandleSlowDownloadRequest));
ASSERT_TRUE(embedded_test_server()->Start());
}
void SetDownloadBehavior(const std::string& behavior) {
base::Value::Dict params;
params.Set("behavior", behavior);
SendCommandSync("Page.setDownloadBehavior", std::move(params));
EXPECT_GE(received_responses_count(), 1);
}
void SetDownloadBehavior(const std::string& behavior,
const std::string& download_path) {
base::Value::Dict params;
params.Set("behavior", behavior);
params.Set("downloadPath", download_path);
SendCommandSync("Page.setDownloadBehavior", std::move(params));
EXPECT_GE(received_responses_count(), 1);
}
// Create a DownloadTestObserverTerminal that will wait for the
// specified number of downloads to finish.
DownloadTestObserver* CreateWaiter(Shell* shell, int num_downloads) {
DownloadManager* download_manager = DownloadManagerForShell(shell);
return new DownloadTestObserverTerminal(
download_manager, num_downloads,
DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
}
// Note: Cannot be used with other alternative DownloadFileFactorys
void SetupEnsureNoPendingDownloads() {
DownloadManagerForShell(shell())->SetDownloadFileFactoryForTesting(
std::unique_ptr<download::DownloadFileFactory>(
new CountingDownloadFileFactory()));
}
void WaitForCompletion(download::DownloadItem* download) {
DownloadUpdatedObserver(
download, base::BindRepeating(&IsDownloadInState,
download::DownloadItem::COMPLETE))
.WaitForEvent();
}
bool EnsureNoPendingDownloads() {
return CountingDownloadFile::GetNumberActiveFilesFromFileThread() == 0;
}
// Checks that |path| is has |file_size| bytes, and matches the |value|
// string.
bool VerifyFile(const base::FilePath& path,
const std::string& value,
const int64_t file_size) {
std::string file_contents;
{
base::ScopedAllowBlockingForTesting allow_blocking;
bool read = base::ReadFileToString(path, &file_contents);
EXPECT_TRUE(read) << "Failed reading file: " << path.value() << std::endl;
if (!read)
return false; // Couldn't read the file.
}
// Note: we don't handle really large files (more than size_t can hold)
// so we will fail in that case.
size_t expected_size = static_cast<size_t>(file_size);
// Check the size.
EXPECT_EQ(expected_size, file_contents.size());
if (expected_size != file_contents.size())
return false;
// Check the contents.
EXPECT_EQ(value, file_contents);
if (memcmp(file_contents.c_str(), value.c_str(), expected_size) != 0)
return false;
return true;
}
// Start a download and return the item.
download::DownloadItem* StartDownloadAndReturnItem(Shell* shell, GURL url) {
std::unique_ptr<DownloadCreateObserver> observer(
new DownloadCreateObserver(DownloadManagerForShell(shell)));
shell->LoadURL(url);
return observer->WaitForFinished();
}
private:
// Location of the downloads directory for these tests
base::ScopedTempDir downloads_directory_;
std::unique_ptr<ShellDownloadManagerDelegate> test_delegate_;
};
} // namespace
// Check that downloading a single file works.
IN_PROC_BROWSER_TEST_F(DevToolsDownloadContentTest, SingleDownload) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
std::string download_path =
temp_dir.GetPath().AppendASCII("download").AsUTF8Unsafe();
SetupEnsureNoPendingDownloads();
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
SetDownloadBehavior("allow", download_path);
// Create a download, wait until it's started, and confirm
// we're in the expected state.
download::DownloadItem* download = StartDownloadAndReturnItem(
shell(), embedded_test_server()->GetURL("/download/download-test.lib"));
ASSERT_EQ(download::DownloadItem::IN_PROGRESS, download->GetState());
WaitForCompletion(download);
ASSERT_EQ(download::DownloadItem::COMPLETE, download->GetState());
}
// Check that downloads can be cancelled gracefully.
IN_PROC_BROWSER_TEST_F(DevToolsDownloadContentTest, DownloadCancelled) {
base::ScopedAllowBlockingForTesting allow_blocking;
SetupEnsureNoPendingDownloads();
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
SetDownloadBehavior("allow", "download");
// Create a download, wait until it's started, and confirm
// we're in the expected state.
download::DownloadItem* download = StartDownloadAndReturnItem(
shell(), embedded_test_server()->GetURL(
content::SlowDownloadHttpResponse::kUnknownSizeUrl));
ASSERT_EQ(download::DownloadItem::IN_PROGRESS, download->GetState());
// Cancel the download and wait for download system quiesce.
download->Cancel(true);
DownloadTestFlushObserver flush_observer(DownloadManagerForShell(shell()));
flush_observer.WaitForFlush();
// Get the important info from other threads and check it.
EXPECT_TRUE(EnsureNoPendingDownloads());
}
// Check that denying downloads works.
IN_PROC_BROWSER_TEST_F(DevToolsDownloadContentTest, DeniedDownload) {
base::ScopedAllowBlockingForTesting allow_blocking;
SetupEnsureNoPendingDownloads();
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
SetDownloadBehavior("deny");
// Create a download, wait and confirm it was cancelled.
download::DownloadItem* download = StartDownloadAndReturnItem(
shell(), embedded_test_server()->GetURL("/download/download-test.lib"));
DownloadTestFlushObserver flush_observer(DownloadManagerForShell(shell()));
flush_observer.WaitForFlush();
EXPECT_TRUE(EnsureNoPendingDownloads());
ASSERT_EQ(download::DownloadItem::CANCELLED, download->GetState());
}
// Check that defaulting downloads works as expected.
IN_PROC_BROWSER_TEST_F(DevToolsDownloadContentTest, DefaultDownload) {
base::ScopedAllowBlockingForTesting allow_blocking;
SetupEnsureNoPendingDownloads();
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
SetDownloadBehavior("default");
// Create a download, wait until it's started, and confirm
// we're in the expected state.
download::DownloadItem* download = StartDownloadAndReturnItem(
shell(), embedded_test_server()->GetURL(
content::SlowDownloadHttpResponse::kUnknownSizeUrl));
ASSERT_EQ(download::DownloadItem::IN_PROGRESS, download->GetState());
// Cancel the download and wait for download system quiesce.
download->Cancel(true);
DownloadTestFlushObserver flush_observer(DownloadManagerForShell(shell()));
flush_observer.WaitForFlush();
// Get the important info from other threads and check it.
EXPECT_TRUE(EnsureNoPendingDownloads());
}
// Check that defaulting downloads cancels when there's no proxy
// download delegate.
IN_PROC_BROWSER_TEST_F(DevToolsDownloadContentTest, DefaultDownloadHeadless) {
base::ScopedAllowBlockingForTesting allow_blocking;
SetupEnsureNoPendingDownloads();
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
RemoveShellDelegate(shell());
SetDownloadBehavior("default");
// Create a download, wait and confirm it was cancelled.
download::DownloadItem* download = StartDownloadAndReturnItem(
shell(), embedded_test_server()->GetURL("/download/download-test.lib"));
DownloadTestFlushObserver flush_observer(DownloadManagerForShell(shell()));
flush_observer.WaitForFlush();
EXPECT_TRUE(EnsureNoPendingDownloads());
ASSERT_EQ(download::DownloadItem::CANCELLED, download->GetState());
}
// Flaky on ChromeOS https://crbug.com/860312
// Also flaky on Wndows and other platforms: http://crbug.com/1070302
// Check that downloading multiple (in this case, 2) files does not result in
// corrupted files.
IN_PROC_BROWSER_TEST_F(DevToolsDownloadContentTest, DISABLED_MultiDownload) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
std::string download1_path =
temp_dir.GetPath().AppendASCII("download1").AsUTF8Unsafe();
std::string download2_path =
temp_dir.GetPath().AppendASCII("download2").AsUTF8Unsafe();
SetupEnsureNoPendingDownloads();
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
SetDownloadBehavior("allow", download1_path);
// Create a download, wait until it's started, and confirm
// we're in the expected state.
download::DownloadItem* download1 = StartDownloadAndReturnItem(
shell(), embedded_test_server()->GetURL(
content::SlowDownloadHttpResponse::kUnknownSizeUrl));
ASSERT_EQ(download::DownloadItem::IN_PROGRESS, download1->GetState());
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
SetDownloadBehavior("allow", download2_path);
// Start the second download and wait until it's done.
GURL url(embedded_test_server()->GetURL("/download/download-test.lib"));
download::DownloadItem* download2 = StartDownloadAndReturnItem(shell(), url);
WaitForCompletion(download2);
ASSERT_EQ(download::DownloadItem::IN_PROGRESS, download1->GetState());
ASSERT_EQ(download::DownloadItem::COMPLETE, download2->GetState());
// Allow the first request to finish.
std::unique_ptr<DownloadTestObserver> observer2(CreateWaiter(shell(), 1));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
content::SlowDownloadHttpResponse::kFinishSlowResponseUrl)));
observer2->WaitForFinished(); // Wait for the third request.
EXPECT_EQ(
1u, observer2->NumDownloadsSeenInState(download::DownloadItem::COMPLETE));
// Get the important info from other threads and check it.
EXPECT_TRUE(EnsureNoPendingDownloads());
// The |DownloadItem|s should now be done and have the final file names.
// Verify that the files have the expected data and size.
// |file1| should be full of '*'s, and |file2| should be the same as the
// source file.
base::FilePath file1(download1->GetTargetFilePath());
ASSERT_EQ(file1.DirName().MaybeAsASCII(), download1_path);
size_t file_size1 =
content::SlowDownloadHttpResponse::kFirstResponsePartSize +
content::SlowDownloadHttpResponse::kSecondResponsePartSize;
std::string expected_contents(file_size1, '*');
ASSERT_TRUE(VerifyFile(file1, expected_contents, file_size1));
base::FilePath file2(download2->GetTargetFilePath());
ASSERT_EQ(file2.DirName().MaybeAsASCII(), download2_path);
ASSERT_TRUE(base::ContentsEqual(
file2, GetTestFilePath("download", "download-test.lib")));
}
#endif // !defined(ANDROID)
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, UnsafeOperations) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
base::Value::Dict params;
params.Set("url", "http://www.example.com/hello.js");
params.Set("data", "Tm90aGluZyB0byBzZWUgaGVyZSE=");
SendCommandSync("Page.addCompilationCache", params.Clone());
EXPECT_TRUE(result());
Detach();
SetAllowUnsafeOperations(false);
Attach();
SendCommandSync("Page.addCompilationCache", params.Clone());
EXPECT_THAT(
error()->FindInt("code"),
testing::Optional(static_cast<int>(crdtp::DispatchCode::SERVER_ERROR)));
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, TracingWithPerfettoConfig) {
base::trace_event::TraceConfig chrome_config;
perfetto::TraceConfig perfetto_config;
std::string perfetto_config_encoded;
chrome_config = base::trace_event::TraceConfig();
perfetto_config = tracing::GetDefaultPerfettoConfig(
chrome_config,
/*privacy_filtering_enabled=*/false,
/*convert_to_legacy_json=*/false,
perfetto::protos::gen::ChromeConfig::USER_INITIATED);
base::Base64Encode(perfetto_config.SerializeAsString(),
&perfetto_config_encoded);
base::Value::Dict params;
params.Set("perfettoConfig", perfetto_config_encoded);
params.Set("transferMode", "ReturnAsStream");
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
EXPECT_TRUE(SendCommandSync("Tracing.start", std::move(params)));
EXPECT_TRUE(SendCommandSync("Tracing.end"));
WaitForNotification("Tracing.tracingComplete", true);
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, NavigateToAboutBlankLoaderId) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
base::Value::Dict params;
params.Set("url", "about:blank");
const base::Value::Dict* result =
SendCommandSync("Page.navigate", std::move(params));
EXPECT_THAT(result->FindString("loaderId"),
testing::Pointee(testing::Not("")));
}
class SystemTracingDevToolsProtocolTest : public DevToolsProtocolTest {
protected:
const base::Value::Dict* StartSystemTrace() {
perfetto::TraceConfig perfetto_config = tracing::GetDefaultPerfettoConfig(
base::trace_event::TraceConfig(),
/*privacy_filtering_enabled=*/false,
/*convert_to_legacy_json=*/false,
perfetto::protos::gen::ChromeConfig::USER_INITIATED);
std::string perfetto_config_encoded;
base::Base64Encode(perfetto_config.SerializeAsString(),
&perfetto_config_encoded);
base::Value::Dict params;
params.Set("perfettoConfig", perfetto_config_encoded);
params.Set("transferMode", "ReturnAsStream");
params.Set("tracingBackend", "system");
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
Attach();
return SendCommandSync("Tracing.start", std::move(params));
}
};
IN_PROC_BROWSER_TEST_F(SystemTracingDevToolsProtocolTest,
StartSystemTracingFailsWhenSystemConsumerDisabled) {
EXPECT_FALSE(StartSystemTrace());
}
#if BUILDFLAG(IS_POSIX)
class PosixSystemTracingDevToolsProtocolTest
: public SystemTracingDevToolsProtocolTest {
public:
PosixSystemTracingDevToolsProtocolTest() {
feature_list_.InitAndEnableFeature(features::kEnablePerfettoSystemTracing);
tracing::PerfettoTracedProcess::Get()
->SetAllowSystemTracingConsumerForTesting(true);
const char* producer_sock = getenv("PERFETTO_PRODUCER_SOCK_NAME");
saved_producer_sock_name_ = producer_sock ? producer_sock : std::string();
const char* consumer_sock = getenv("PERFETTO_CONSUMER_SOCK_NAME");
saved_consumer_sock_name_ = consumer_sock ? consumer_sock : std::string();
}
~PosixSystemTracingDevToolsProtocolTest() override {
if (!saved_producer_sock_name_.empty()) {
SetProducerSockEnvName(saved_producer_sock_name_);
} else {
EXPECT_EQ(0, unsetenv("PERFETTO_PRODUCER_SOCK_NAME"));
}
if (!saved_consumer_sock_name_.empty()) {
SetConsumerSockEnvName(saved_consumer_sock_name_);
} else {
EXPECT_EQ(0, unsetenv("PERFETTO_CONSUMER_SOCK_NAME"));
}
}
protected:
void SetProducerSockEnvName(const std::string& value) {
ASSERT_EQ(0, setenv("PERFETTO_PRODUCER_SOCK_NAME", value.c_str(),
/*overwrite=*/true));
}
void SetConsumerSockEnvName(const std::string& value) {
ASSERT_EQ(0, setenv("PERFETTO_CONSUMER_SOCK_NAME", value.c_str(),
/*overwrite=*/true));
}
private:
base::test::ScopedFeatureList feature_list_;
std::string saved_producer_sock_name_;
std::string saved_consumer_sock_name_;
};
class InvalidSystemTracingDevToolsProtocolTest
: public PosixSystemTracingDevToolsProtocolTest {
public:
void SetUp() override {
// Use a non-existing backend.
SetProducerSockEnvName("non_existing");
SetConsumerSockEnvName("non_existing");
PosixSystemTracingDevToolsProtocolTest::SetUp();
}
};
IN_PROC_BROWSER_TEST_F(InvalidSystemTracingDevToolsProtocolTest,
StartTracingFailsWithInvalidSockets) {
EXPECT_FALSE(StartSystemTrace());
}
class FakeSystemTracingDevToolsProtocolTest
: public PosixSystemTracingDevToolsProtocolTest {
public:
FakeSystemTracingDevToolsProtocolTest()
: deferred_task_runner_(new base::DeferredSequencedTaskRunner()) {}
void SetUp() override {
SetupService();
PosixSystemTracingDevToolsProtocolTest::SetUp();
}
void PreRunTestOnMainThread() override {
deferred_task_runner_->StartWithTaskRunner(
base::SequencedTaskRunnerHandle::Get());
PosixSystemTracingDevToolsProtocolTest::PreRunTestOnMainThread();
}
private:
void SetupService() {
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
system_service_ = std::make_unique<tracing::MockSystemService>(
temp_dir_, std::make_unique<base::tracing::PerfettoTaskRunner>(
deferred_task_runner_));
SetProducerSockEnvName(system_service_->producer());
SetConsumerSockEnvName(system_service_->consumer());
}
base::ScopedTempDir temp_dir_;
scoped_refptr<base::DeferredSequencedTaskRunner> deferred_task_runner_;
std::unique_ptr<tracing::MockSystemService> system_service_;
};
// No system consumer support on Android to reduce Chrome binary size.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_TracingWithFakeSystemBackend DISABLED_TracingWithFakeSystemBackend
#else
#define MAYBE_TracingWithFakeSystemBackend TracingWithFakeSystemBackend
#endif
IN_PROC_BROWSER_TEST_F(FakeSystemTracingDevToolsProtocolTest,
MAYBE_TracingWithFakeSystemBackend) {
EXPECT_TRUE(StartSystemTrace());
EXPECT_TRUE(SendCommandSync("Tracing.end"));
WaitForNotification("Tracing.tracingComplete", true);
}
class FakeSystemTracingForbiddenDevToolsProtocolTest
: public PosixSystemTracingDevToolsProtocolTest {
public:
void SetUp() override {
tracing::PerfettoTracedProcess::Get()
->SetAllowSystemTracingConsumerForTesting(false);
PosixSystemTracingDevToolsProtocolTest::SetUp();
}
};
// No system consumer support on Android to reduce Chrome binary size.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_SystemConsumerForbidden DISABLED_SystemConsumerForbidden
#else
#define MAYBE_SystemConsumerForbidden SystemConsumerForbidden
#endif
IN_PROC_BROWSER_TEST_F(FakeSystemTracingForbiddenDevToolsProtocolTest,
MAYBE_SystemConsumerForbidden) {
EXPECT_FALSE(StartSystemTrace());
}
#endif // BUILDFLAG(IS_POSIX)
class NetworkResponseProtocolTest : public DevToolsProtocolTest {
protected:
base::Value::Dict FetchAndWaitForResponse(const GURL& url) {
WebContents* web_contents = shell()->web_contents();
std::string script = JsReplace("fetch($1).then(r => r.status)", url.spec());
EvalJsResult status = EvalJs(web_contents, script);
CHECK_EQ(200, status);
// Look for the requestId.
auto matches_url = [](const GURL& url, const base::Value::Dict& params) {
const std::string* got_url = params.FindStringByDottedPath("request.url");
return got_url && *got_url == url.spec();
};
base::Value::Dict request = WaitForMatchingNotification(
"Network.requestWillBeSent", base::BindRepeating(matches_url, url));
const std::string* request_id = request.FindString("requestId");
CHECK(request_id) << "Could not find request ID";
// Look for the response.
auto matches_id = [](const std::string& request_id,
const base::Value::Dict& params) {
const std::string* id = params.FindString("requestId");
return id && *id == request_id;
};
return WaitForMatchingNotification(
"Network.responseReceived",
base::BindRepeating(matches_id, *request_id));
}
};
// Test that the SecurityDetails field of the resource response matches the
// server.
IN_PROC_BROWSER_TEST_F(NetworkResponseProtocolTest, SecurityDetails) {
// Configure a specific TLS configuration to compare against.
net::SSLServerConfig server_config;
server_config.version_min = net::SSL_PROTOCOL_VERSION_TLS1_2;
server_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1_2;
// TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
server_config.cipher_suite_for_testing = 0xc02f;
server_config.curves_for_testing = {NID_X25519};
server_config.signature_algorithm_for_testing = SSL_SIGN_RSA_PSS_RSAE_SHA384;
net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
server.SetSSLConfig(net::EmbeddedTestServer::ServerCertificate::CERT_OK,
server_config);
server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(server.Start());
NavigateToURLBlockUntilNavigationsComplete(shell(),
server.GetURL("/title1.html"), 1);
Attach();
SendCommandAsync("Network.enable");
base::Value::Dict response =
FetchAndWaitForResponse(server.GetURL("/empty.html"));
const std::string* protocol =
response.FindStringByDottedPath("response.securityDetails.protocol");
ASSERT_TRUE(protocol);
EXPECT_EQ("TLS 1.2", *protocol);
const std::string* key_exchange =
response.FindStringByDottedPath("response.securityDetails.keyExchange");
ASSERT_TRUE(key_exchange);
EXPECT_EQ("ECDHE_RSA", *key_exchange);
const std::string* cipher =
response.FindStringByDottedPath("response.securityDetails.cipher");
ASSERT_TRUE(cipher);
EXPECT_EQ("AES_128_GCM", *cipher);
// AEAD ciphers should not report a MAC.
EXPECT_FALSE(response.FindStringByDottedPath("response.securityDetails.mac"));
const std::string* group = response.FindStringByDottedPath(
"response.securityDetails.keyExchangeGroup");
ASSERT_TRUE(group);
EXPECT_EQ("X25519", *group);
absl::optional<int> sigalg = response.FindIntByDottedPath(
"response.securityDetails.serverSignatureAlgorithm");
EXPECT_EQ(SSL_SIGN_RSA_PSS_RSAE_SHA384, sigalg);
absl::optional<bool> ech = response.FindBoolByDottedPath(
"response.securityDetails.encryptedClientHello");
EXPECT_EQ(false, ech);
const std::string* subject =
response.FindStringByDottedPath("response.securityDetails.subjectName");
ASSERT_TRUE(subject);
EXPECT_EQ(server.GetCertificate()->subject().common_name, *subject);
const std::string* issuer =
response.FindStringByDottedPath("response.securityDetails.issuer");
ASSERT_TRUE(issuer);
EXPECT_EQ(server.GetCertificate()->issuer().common_name, *issuer);
// The default certificate has a single SAN, 127.0.0.1.
const base::Value* sans =
response.FindByDottedPath("response.securityDetails.sanList");
ASSERT_TRUE(sans);
ASSERT_EQ(1u, sans->GetListDeprecated().size());
EXPECT_EQ(base::Value("127.0.0.1"), sans->GetListDeprecated()[0]);
absl::optional<double> valid_from =
response.FindDoubleByDottedPath("response.securityDetails.validFrom");
EXPECT_EQ(server.GetCertificate()->valid_start().ToDoubleT(), valid_from);
absl::optional<double> valid_to =
response.FindDoubleByDottedPath("response.securityDetails.validTo");
EXPECT_EQ(server.GetCertificate()->valid_expiry().ToDoubleT(), valid_to);
}
// Test SecurityDetails, but with a TLS 1.3 cipher suite, which should not
// report a key exchange component.
IN_PROC_BROWSER_TEST_F(NetworkResponseProtocolTest, SecurityDetailsTLS13) {
// Configure a specific TLS configuration to compare against.
net::SSLServerConfig server_config;
server_config.version_min = net::SSL_PROTOCOL_VERSION_TLS1_3;
server_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1_3;
server_config.curves_for_testing = {NID_X25519};
server_config.signature_algorithm_for_testing = SSL_SIGN_RSA_PSS_RSAE_SHA384;
net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
server.SetSSLConfig(net::EmbeddedTestServer::ServerCertificate::CERT_OK,
server_config);
server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(server.Start());
NavigateToURLBlockUntilNavigationsComplete(shell(),
server.GetURL("/title1.html"), 1);
Attach();
SendCommandAsync("Network.enable");
base::Value::Dict response =
FetchAndWaitForResponse(server.GetURL("/empty.html"));
const std::string* protocol =
response.FindStringByDottedPath("response.securityDetails.protocol");
ASSERT_TRUE(protocol);
EXPECT_EQ("TLS 1.3", *protocol);
const std::string* key_exchange =
response.FindStringByDottedPath("response.securityDetails.keyExchange");
ASSERT_TRUE(key_exchange);
EXPECT_EQ("", *key_exchange);
const std::string* cipher =
response.FindStringByDottedPath("response.securityDetails.cipher");
ASSERT_TRUE(cipher);
// Depending on whether the host machine has AES hardware, the server may
// pick AES-GCM or ChaCha20-Poly1305.
EXPECT_TRUE(*cipher == "AES_128_GCM" || *cipher == "CHACHA20_POLY1305");
// AEAD ciphers should not report a MAC.
EXPECT_FALSE(response.FindStringByDottedPath("response.securityDetails.mac"));
const std::string* group = response.FindStringByDottedPath(
"response.securityDetails.keyExchangeGroup");
ASSERT_TRUE(group);
EXPECT_EQ("X25519", *group);
absl::optional<int> sigalg = response.FindIntByDottedPath(
"response.securityDetails.serverSignatureAlgorithm");
EXPECT_EQ(SSL_SIGN_RSA_PSS_RSAE_SHA384, sigalg);
absl::optional<bool> ech = response.FindBoolByDottedPath(
"response.securityDetails.encryptedClientHello");
EXPECT_EQ(false, ech);
}
// Test SecurityDetails, but with a legacy cipher suite, which should report a
// separate MAC component and no group.
IN_PROC_BROWSER_TEST_F(NetworkResponseProtocolTest,
SecurityDetailsLegacyCipher) {
// Configure a specific TLS configuration to compare against.
net::SSLServerConfig server_config;
server_config.version_min = net::SSL_PROTOCOL_VERSION_TLS1_2;
server_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1_2;
// TLS_RSA_WITH_AES_128_CBC_SHA
server_config.cipher_suite_for_testing = 0x002f;
net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
server.SetSSLConfig(net::EmbeddedTestServer::ServerCertificate::CERT_OK,
server_config);
server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(server.Start());
NavigateToURLBlockUntilNavigationsComplete(shell(),
server.GetURL("/title1.html"), 1);
Attach();
SendCommandAsync("Network.enable");
base::Value::Dict response =
FetchAndWaitForResponse(server.GetURL("/empty.html"));
const std::string* key_exchange =
response.FindStringByDottedPath("response.securityDetails.keyExchange");
ASSERT_TRUE(key_exchange);
EXPECT_EQ("RSA", *key_exchange);
const std::string* cipher =
response.FindStringByDottedPath("response.securityDetails.cipher");
ASSERT_TRUE(cipher);
EXPECT_EQ("AES_128_CBC", *cipher);
const std::string* mac =
response.FindStringByDottedPath("response.securityDetails.mac");
ASSERT_TRUE(mac);
EXPECT_EQ("HMAC-SHA1", *mac);
// RSA ciphers should not report a MAC.
EXPECT_FALSE(response.FindStringByDottedPath(
"response.securityDetails.keyExchangeGroup"));
}
// Test that complex certificate SAN lists are reported in SecurityDetails.
IN_PROC_BROWSER_TEST_F(NetworkResponseProtocolTest, SecurityDetailsSAN) {
net::EmbeddedTestServer::ServerCertificateConfig cert_config;
cert_config.dns_names = {"a.example", "b.example", "*.c.example"};
cert_config.ip_addresses = {net::IPAddress::IPv4Localhost(),
net::IPAddress::IPv6Localhost(),
net::IPAddress(1, 2, 3, 4)};
net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
server.SetSSLConfig(cert_config);
server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(server.Start());
NavigateToURLBlockUntilNavigationsComplete(shell(),
server.GetURL("/title1.html"), 1);
Attach();
SendCommandAsync("Network.enable");
base::Value::Dict response =
FetchAndWaitForResponse(server.GetURL("/empty.html"));
const base::Value* sans =
response.FindByDottedPath("response.securityDetails.sanList");
ASSERT_TRUE(sans);
ASSERT_EQ(6u, sans->GetListDeprecated().size());
EXPECT_EQ(base::Value("a.example"), sans->GetListDeprecated()[0]);
EXPECT_EQ(base::Value("b.example"), sans->GetListDeprecated()[1]);
EXPECT_EQ(base::Value("*.c.example"), sans->GetListDeprecated()[2]);
EXPECT_EQ(base::Value("127.0.0.1"), sans->GetListDeprecated()[3]);
EXPECT_EQ(base::Value("::1"), sans->GetListDeprecated()[4]);
EXPECT_EQ(base::Value("1.2.3.4"), sans->GetListDeprecated()[5]);
}
class NetworkResponseProtocolECHTest : public NetworkResponseProtocolTest {
public:
// a.test is covered by `CERT_TEST_NAMES`.
static constexpr char kHostname[] = "a.test";
static constexpr char kPublicName[] = "public-name.test";
static constexpr char kDohServerHostname[] = "doh.test";
NetworkResponseProtocolECHTest()
: ech_server_{net::EmbeddedTestServer::TYPE_HTTPS} {
scoped_feature_list_.InitWithFeaturesAndParameters(
/*enabled_features=*/
{{net::features::kEncryptedClientHello, {}},
{net::features::kUseDnsHttpsSvcb,
{{"UseDnsHttpsSvcbEnforceSecureResponse", "true"}}}},
/*disabled_features=*/{});
}
void SetUpOnMainThread() override {
// Configure `ech_server_` to use ECH.
net::SSLServerConfig server_config;
std::vector<uint8_t> ech_config_list;
server_config.ech_keys = net::MakeTestEchKeys(
kPublicName, /*max_name_len=*/64, &ech_config_list);
ASSERT_TRUE(server_config.ech_keys);
ech_server_.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ech_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES,
server_config);
ASSERT_TRUE(ech_server_.Start());
// Start a DoH server, which ensures we use a resolver with HTTPS RR
// support. Configure it to serve records for `ech_server_`.
doh_server_.SetHostname(kDohServerHostname);
url::SchemeHostPort ech_host(GetURL("/"));
doh_server_.AddAddressRecord(ech_host.host(),
net::IPAddress::IPv4Localhost());
doh_server_.AddRecord(net::BuildTestHttpsServiceRecord(
net::dns_util::GetNameForHttpsQuery(ech_host),
/*priority=*/1, /*service_name=*/ech_host.host(),
{net::BuildTestHttpsServiceEchConfigParam(ech_config_list)}));
ASSERT_TRUE(doh_server_.Start());
// Add a single bootstrapping rule so we can resolve the DoH server.
host_resolver()->AddRule(kDohServerHostname, "127.0.0.1");
// Configure the network service to use the test DoH server.
absl::optional<net::DnsOverHttpsConfig> doh_config =
net::DnsOverHttpsConfig::FromString(doh_server_.GetTemplate());
ASSERT_TRUE(doh_config.has_value());
SetTestDohConfig(net::SecureDnsMode::kSecure,
std::move(doh_config.value()));
SetReplaceSystemDnsConfig();
}
GURL GetURL(base::StringPiece path) {
return ech_server_.GetURL(kHostname, path);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
net::TestDohServer doh_server_;
net::EmbeddedTestServer ech_server_;
};
// Test SecurityDetails reports when Encrypted ClientHello was negotiated.
IN_PROC_BROWSER_TEST_F(NetworkResponseProtocolECHTest, SecurityDetailsECH) {
NavigateToURLBlockUntilNavigationsComplete(shell(), GetURL("/title1.html"),
1);
Attach();
SendCommandAsync("Network.enable");
base::Value::Dict response = FetchAndWaitForResponse(GetURL("/empty.html"));
absl::optional<bool> ech = response.FindBoolByDottedPath(
"response.securityDetails.encryptedClientHello");
EXPECT_EQ(true, ech);
}
IN_PROC_BROWSER_TEST_F(PrerenderDevToolsProtocolTest,
ReportPrerenderDisallowedAPICancellationDetails) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
// Navigate to an initial page.
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
// Make a prerendered page.
int host_id = AddPrerender(kPrerenderingUrl);
auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
Attach();
SendCommand("Page.enable", nullptr, true);
SendCommand("Runtime.enable", nullptr, true);
// Executing `navigator.getGamepads()` to start binding the GamepadMonitor
// interface, and this is expected to cause prerender cancellation because
// the API is disallowed.
ExecuteScriptAsyncWithoutUserGesture(prerender_render_frame_host,
"navigator.getGamepads()");
base::Value::Dict result =
WaitForNotification("Page.prerenderAttemptCompleted", true);
// Verify Mojo capability control cancels prerendering.
EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
EXPECT_THAT(*result.FindString("reasonDetails"),
Eq("device.mojom.GamepadMonitor"));
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderHost::FinalStatus::kMojoBinderPolicy, 1);
}
} // namespace content