910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include <cstddef>
#include <memory>
#include <string_view>
#include <utility>

#include "base/base64.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/safe_sprintf.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "build/build_config.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 "components/services/storage/shared_storage/shared_storage_manager.h"
#include "content/browser/devtools/protocol/browser_handler.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/protocol/system_info.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/preloading/prerender/prerender_final_status.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/back_forward_cache_util.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/public/test/test_utils.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 "content/test/content_browser_test_utils_internal.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/network/public/cpp/features.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.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/features.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/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"
#include "url/origin.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;
using testing::Pointee;

namespace content {

namespace {

const int kBudgetAllowed = 12;

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) {
    FrameTreeNodeId host_id = prerender_helper_->GetHostForUrl(url);
    return !!host_id;
  }

  FrameTreeNodeId AddPrerender(const GURL& prerendering_url) {
    return prerender_helper_->AddPrerender(prerendering_url);
  }

  RenderFrameHostImpl* GetPrerenderedMainFrameHost(FrameTreeNodeId host_id) {
    return static_cast<RenderFrameHostImpl*>(
        prerender_helper_->GetPrerenderedMainFrameHost(host_id));
  }

  void NavigatePrimaryPage(const GURL& url) {
    prerender_helper_->NavigatePrimaryPage(url);
  }

  // WebContentsDelegate overrides.
  PreloadingEligibility IsPrerender2Supported(
      WebContents& web_contents,
      PreloadingTriggerType trigger_type) override {
    return PreloadingEligibility::kEligible;
  }

  WebContents* web_contents() const { return shell()->web_contents(); }

  std::string AttachToTabTargetAndGetSessionId() {
    AttachToTabTarget(shell()->web_contents());
    shell()->web_contents()->SetDelegate(this);

    {
      base::Value::Dict params;
      params.Set("discover", true);
      SendCommandSync("Target.setDiscoverTargets", std::move(params));
    }

    std::string frame_target_id;
    for (int targetCount = 1; true; targetCount++) {
      base::Value::Dict result;
      result = WaitForNotification("Target.targetCreated", true);
      if (*result.FindStringByDottedPath("targetInfo.type") == "page") {
        frame_target_id =
            std::string(*result.FindStringByDottedPath("targetInfo.targetId"));
        break;
      }
      CHECK_LT(targetCount, 2);
    }

    {
      base::Value::Dict params;
      params.Set("targetId", frame_target_id);
      params.Set("flatten", true);
      const base::Value::Dict* result =
          SendCommandSync("Target.attachToTarget", std::move(params));
      CHECK(result);
      std::string session_id(*result->FindString("sessionId"));
      CHECK(session_id != "");
      return session_id;
    }
  }

 private:
  std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
};

class SyntheticMouseEventTest : public DevToolsProtocolTest {
 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.GetHost(),
                                     blink::ZoomFactorToZoomLevel(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 {
SkBitmap DecodePNG(std::string_view base64_data) {
  std::optional<std::vector<uint8_t>> data = base::Base64Decode(base64_data);
  SkBitmap bitmap = gfx::PNGCodec::Decode(data.value());
  CHECK(!bitmap.isNull());
  return bitmap;
}

SkBitmap DecodeJPEG(std::string_view base64_data) {
  std::optional<std::vector<uint8_t>> data = base::Base64Decode(base64_data);
  SkBitmap bitmap = gfx::JPEGCodec::Decode(data.value());
  CHECK(!bitmap.isNull());
  return bitmap;
}

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;

  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) {
      if (x * device_scale_factor >= actual_bmp.width() ||
          y * device_scale_factor >= actual_bmp.height() ||
          x >= expected_bmp.width() || y >= expected_bmp.height()) {
        continue;
      }

      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:
  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));

    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) {
        return DecodePNG(*base64);
      } else if (encoding == ScreenshotEncoding::JPEG) {
        return DecodeJPEG(*base64);
      } else {
        // Decode not implemented.
      }
    }
    return SkBitmap();
  }

  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) {
    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));
  }

  gfx::Size GetPageContentSize() {
    const base::Value::Dict* content_size =
        SendCommandSync("Page.getLayoutMetrics")->FindDict("cssContentSize");
    return gfx::Size(content_size->FindInt("width").value(),
                     content_size->FindInt("height").value());
  }

  // 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 GetViewSize() {
    return static_cast<RenderWidgetHostViewBase*>(
               shell()->web_contents()->GetRenderWidgetHostView())
        ->GetCompositorViewportPixelSize();
  }

  // We compare the bitmap with the captured screenshot to verify the
  // size and color of the screenshot.
  SkBitmap GenerateBitmap(gfx::Size size, SkColor color) {
    SkBitmap expected_bitmap;
    expected_bitmap.allocN32Pixels(size.width(), size.height());
    expected_bitmap.eraseColor(color);
    return expected_bitmap;
  }

  void SetDefaultBackgroundColorOverride(int r, int g, int b, float a) {
    auto params = base::Value::Dict();
    base::Value::Dict color;
    color.Set("r", r);
    color.Set("g", g);
    color.Set("b", b);
    color.Set("a", a);
    params.Set("color", std::move(color));
    SendCommandSync("Emulation.setDefaultBackgroundColorOverride",
                    std::move(params));
  }

  void SetDeviceMetricsOverride(int width,
                                int height,
                                float device_scale_factor,
                                bool mobile,
                                std::optional<bool> fitWindow) {
    auto params = base::Value::Dict();
    params.Set("width", width);
    params.Set("height", height);
    params.Set("deviceScaleFactor", device_scale_factor);
    params.Set("mobile", mobile);
    if (fitWindow.has_value()) {
      params.Set("fitWindow", fitWindow.value());
    }
    SendCommandSync("Emulation.setDeviceMetricsOverride", std::move(params));
  }

  // 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.
    SetDeviceMetricsOverride(frame_size.width(), frame_size.height(),
                             device_scale_factor, false, std::nullopt);

    // 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 =
        GenerateBitmap(scaled_box_size, 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::Get()->GetPrimaryDisplay().device_scale_factor();
    }

    CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG,
                                  /*from_surface=*/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/40488022) This test fails consistently on low-end Android
  // devices.
  if (base::SysInfo::IsLowEndDevice())
    return;

  // Load dummy page before getting the view size.
  shell()->LoadURL(GURL("data:text/html,"));
  gfx::Size view_size = GetViewSize();

  // Make a page a bit bigger than the view to force scrollbars to be shown.
  shell()->LoadURL(
      GURL(base::StringPrintf("data:text/html,"
                              R"(<body style='background:%%23123456;height:%dpx;
                         width:%dpx'></body>)",
                              /*content_height*/ view_size.height() + 10,
                              /*content_width*/ view_size.width() + 10)));

  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  Attach();

  // Generate expected screenshot without any scrollbars.
  gfx::Size actual_page_size = GetPageContentSize();
  SkBitmap expected_bitmap =
      GenerateBitmap(actual_page_size, SkColorSetRGB(0x12, 0x34, 0x56));

  float device_scale_factor =
      display::Screen::Get()->GetPrimaryDisplay().device_scale_factor();

  // Verify there are no scrollbars on the screenshot.
  CaptureScreenshotAndCompareTo(
      expected_bitmap, ScreenshotEncoding::PNG, /*from_surface=*/true,
      device_scale_factor,
      /*clip=*/
      gfx::RectF(0, 0, actual_page_size.width(), actual_page_size.height()),
      /*clip_scale=*/1, /*capture_beyond_viewport=*/true);
  CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);
}

IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest,
                       CaptureScreenshotBeyondViewport_IFrame) {
  // TODO(crbug.com/40488022) This test fails consistently on low-end Android
  // devices.
  if (base::SysInfo::IsLowEndDevice())
    return;

  // Load dummy page before getting the view size.
  shell()->LoadURL(GURL("data:text/html,"));
  gfx::Size view_size = GetViewSize();

  // Make a page a bit bigger than the view to force scrollbars to be shown.
  int content_height = view_size.height() + 50;
  int content_width = view_size.width() + 50;
  int margin = 100;
  shell()->LoadURL(
      GURL(base::StringPrintf("data:text/html,"
                              R"(
            <body style=' height:%dpx;
                          width:%dpx;'>
            <iframe style=" height:%dpx;
                            width:%dpx;
                            background:%%23123456;
                            border:none;">
            </iframe></body>
          )",
                              content_height + margin, content_width + margin,
                              content_height, content_width)));

  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  Attach();

  // Generate expected screenshot without any scrollbars.
  SkBitmap expected_bitmap =
      GenerateBitmap(gfx::Size(content_width, content_height),
                     SkColorSetRGB(0x12, 0x34, 0x56));

  float device_scale_factor =
      display::Screen::Get()->GetPrimaryDisplay().device_scale_factor();

  // Verify there are no scrollbars on the screenshot.
  // Even if margin is 0 then the iframe appears 8px away from beginning of the
  // page
  CaptureScreenshotAndCompareTo(
      expected_bitmap, ScreenshotEncoding::PNG, /*from_surface=*/true,
      device_scale_factor,
      /*clip=*/gfx::RectF(8, 8, content_width, content_height),
      /*clip_scale=*/1, /*capture_beyond_viewport=*/true);
}

// ChromeOS and Android has fading out scrollbars, which makes the test flacky.
// TODO(crbug.com/40157725) Android has a problem with changing scale.
// TODO(crbug.com/40156819) Android Lollipop has a problem with capturing
// screenshot.
// TODO(crbug.com/40815512): Failing on MacOS.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) || 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/40488022) 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();

  gfx::Size view_size = GetViewSize();

  // Capture a screenshot not "from surface", meaning without emulation and
  // without changing preferences, as-is.
  SkBitmap expected_bitmap =
      CaptureScreenshot(ScreenshotEncoding::PNG, /*from_surface=*/false);

  float device_scale_factor =
      display::Screen::Get()->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, /*from_surface=*/true,
      device_scale_factor,
      /*clip=*/gfx::RectF(0, 0, view_size.width(), view_size.height()),
      /*clip_scale=*/1, /*capture_beyond_viewport=*/true);
}

// ChromeOS and Android don't support software compositing.
#if !BUILDFLAG(IS_CHROMEOS) && !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
// TODO(crbug.com/396301195): Failing on Win 10 Tests x64 dbg bot.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
#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();

  SetDeviceMetricsOverride(/*width=*/1280, /*height=*/8440,
                           /*device_scale_factor=*/1,
                           /*mobile=*/false,
                           /*fitWindow=*/std::nullopt);
  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) && !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();

  SetDefaultBackgroundColorOverride(/*r=*/0x00, /*g=*/0x00, /*b=*/0xff,
                                    /*a=*/1.0);

  gfx::Size view_size = GetViewSize();
  SkBitmap expected_bitmap =
      GenerateBitmap(view_size, SkColorSetRGB(0x00, 0x00, 0xff));
  CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG,
                                /*from_surface=*/true);

  // Tests that resetting Emulation.setDefaultBackgroundColorOverride
  // clears the background color override.
  SendCommandSync("Emulation.setDefaultBackgroundColorOverride");
  expected_bitmap.eraseColor(SK_ColorWHITE);
  CaptureScreenshotAndCompareTo(expected_bitmap, ScreenshotEncoding::PNG,
                                /*from_surface=*/true);
}

// Bellow tests verify that setDefaultBackgroundColor and captureScreenshot
// support a fully and semi-transparent background,
// and that setDeviceMetricsOverride doesn't affect it.
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest,
// TODO(crbug.com/40875549): Fix this failing test
#if BUILDFLAG(IS_ANDROID)
                       DISABLED_TransparentScreenshotsViewport) {
#else
                       TransparentScreenshotsViewport) {
#endif
  if (base::SysInfo::IsLowEndDevice())
    return;

  shell()->LoadURL(
      GURL("data:text/html,<body style='background:transparent'></body>"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  Attach();

  SetDefaultBackgroundColorOverride(/*r=*/0, /*g=*/0, /*b=*/0, /*a=*/0);

  gfx::Size view_size = GetViewSize();
  SkBitmap expected_viewport_bitmap =
      GenerateBitmap(view_size, SK_ColorTRANSPARENT);

  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true);

#if !BUILDFLAG(IS_ANDROID)

  float device_scale_factor =
      display::Screen::Get()->GetPrimaryDisplay().device_scale_factor();

  // Check that device emulation does not affect the transparency.
  SetDeviceMetricsOverride(view_size.width(), view_size.height(),
                           /*device_scale_factor=*/0,
                           /*mobile=*/false,
                           /*fitWindow=*/false);
  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor);

  SendCommandSync("Emulation.clearDeviceMetricsOverride");
#endif  // !BUILDFLAG(IS_ANDROID)

  SetDefaultBackgroundColorOverride(/*r=*/255, /*g=*/0, /*b=*/0,
                                    /*a=*/1.0 / 255 * 16);

  expected_viewport_bitmap.eraseColor(SkColorSetARGB(16, 255, 0, 0));

  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true);

#if !BUILDFLAG(IS_ANDROID)
  // Check that device emulation does not affect the transparency.

  SetDeviceMetricsOverride(view_size.width(), view_size.height(),
                           /*device_scale_factor=*/0, /*mobile=*/false,
                           /*fitWindow=*/false);

  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor);

  SendCommandSync("Emulation.clearDeviceMetricsOverride");
#endif  // !BUILDFLAG(IS_ANDROID)
}

IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest,
// TODO(crbug.com/40875549): Fix this failing test
#if BUILDFLAG(IS_ANDROID)
                       DISABLED_TransparentScreenshotsBeyondViewport) {
#else
                       TransparentScreenshotsBeyondViewport) {
#endif
  if (base::SysInfo::IsLowEndDevice())
    return;

  shell()->LoadURL(
      GURL("data:text/html,<body style='background:transparent'></body>"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  Attach();

  SetDefaultBackgroundColorOverride(/*r=*/0, /*g=*/0, /*b=*/0, /*a=*/0);

  gfx::Size view_size = GetViewSize();

  // When capturing full page screenshots, the page content size can differ
  // from the defined dimensions. Therefore, we need to check for the actual
  // layout metrics of the page and compare that with our result.
  SkBitmap expected_full_page_bitmap =
      GenerateBitmap(GetPageContentSize(), SK_ColorTRANSPARENT);
  SkBitmap expected_clip_bitmap =
      GenerateBitmap(view_size, SK_ColorTRANSPARENT);

  float device_scale_factor =
      display::Screen::Get()->GetPrimaryDisplay().device_scale_factor();
  gfx::RectF clip;
  clip.SetRect(0, 0, view_size.width(), view_size.height());

  // checks for beyond_viewport
  CaptureScreenshotAndCompareTo(expected_clip_bitmap, ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                clip, /*clip_scale=*/1,
                                /*capture_beyond_viewport=*/true);
  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

#if !BUILDFLAG(IS_ANDROID)

  // Check that device emulation does not affect the transparency.
  SetDeviceMetricsOverride(view_size.width(), view_size.height(),
                           /*device_scale_factor=*/0,
                           /*mobile=*/false,
                           /*fitWindow=*/false);

  // checks for beyond_viewport
  CaptureScreenshotAndCompareTo(expected_clip_bitmap, ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                clip,
                                /*clip_scale=*/1,
                                /*capture_beyond_viewport=*/true);

  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

  SendCommandSync("Emulation.clearDeviceMetricsOverride");
#endif  // !BUILDFLAG(IS_ANDROID)

  SetDefaultBackgroundColorOverride(/*r=*/255, /*g=*/0, /*b=*/0,
                                    /*a=*/1.0 / 255 * 16);

  // When capturing full page screenshots, the page content size can differ
  // from the defined dimensions. Therefore, we need to check for the actual
  // layout metrics of the page and compare that with our result.
  expected_full_page_bitmap =
      GenerateBitmap(GetPageContentSize(), SkColorSetARGB(16, 255, 0, 0));

  expected_clip_bitmap =
      GenerateBitmap(view_size, SkColorSetARGB(16, 255, 0, 0));

  // Check for beyond-viewport
  CaptureScreenshotAndCompareTo(expected_clip_bitmap, ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                clip,
                                /*clip_scale=*/1,
                                /*capture_beyond_viewport=*/true);

  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

#if !BUILDFLAG(IS_ANDROID)
  // Check that device emulation does not affect the transparency.

  SetDeviceMetricsOverride(view_size.width(), view_size.height(),
                           /*device_scale_factor=*/0, /*mobile=*/false,
                           /*fitWindow=*/false);

  // Checks for beyond_viewport
  CaptureScreenshotAndCompareTo(expected_clip_bitmap, ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/clip,
                                /*clip_scale=*/1,
                                /*capture_beyond_viewport=*/true);

  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

  SendCommandSync("Emulation.clearDeviceMetricsOverride");
#endif  // !BUILDFLAG(IS_ANDROID)
}

// TODO(crbug.com/40239673): Semi-transparent screenshots of viewport fail on
// android devices - a scrollbar is showing.
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, TransparentScreenshotsFull) {
  if (base::SysInfo::IsLowEndDevice())
    return;

  shell()->LoadURL(
      GURL("data:text/html,<body style='background:transparent'></body>"));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  Attach();

  SetDefaultBackgroundColorOverride(/*r=*/0, /*g=*/0, /*b=*/0, /*a=*/0);

  gfx::Size view_size = GetViewSize();
  SkBitmap expected_viewport_bitmap =
      GenerateBitmap(view_size, SK_ColorTRANSPARENT);

  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true);  //.

  float device_scale_factor =
      display::Screen::Get()->GetPrimaryDisplay().device_scale_factor();
  gfx::RectF clip;
  clip.SetRect(0, 0, view_size.width(), view_size.height());

  // checks for beyond_viewport
  CaptureScreenshotAndCompareTo(
      expected_viewport_bitmap, ScreenshotEncoding::PNG,
      /*from_surface=*/true, device_scale_factor, clip, /*clip_scale=*/1,
      /*capture_beyond_viewport=*/true);

  // When capturing full page screenshots, the page content size can differ
  // from the defined dimensions. Therefore, we need to check for the actual
  // layout metrics of the page and compare that with our result.
  SkBitmap expected_full_page_bitmap =
      GenerateBitmap(GetPageContentSize(), SK_ColorTRANSPARENT);

  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

  // Check that device emulation does not affect the transparency.
  SetDeviceMetricsOverride(view_size.width(), view_size.height(),
                           /*device_scale_factor=*/0,
                           /*mobile=*/false,
                           /*fitWindow=*/false);

  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor);

  // checks for beyond_viewport
  CaptureScreenshotAndCompareTo(
      expected_viewport_bitmap, ScreenshotEncoding::PNG,
      /*from_surface=*/true, device_scale_factor, clip,
      /*clip_scale=*/1,
      /*capture_beyond_viewport=*/true);

  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

  SendCommandSync("Emulation.clearDeviceMetricsOverride");

  SetDefaultBackgroundColorOverride(/*r=*/255, /*g=*/0, /*b=*/0,
                                    /*a=*/1.0 / 255 * 16);

  expected_viewport_bitmap.eraseColor(SkColorSetARGB(16, 255, 0, 0));

  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true);

  // Check for beyond-viewport
  CaptureScreenshotAndCompareTo(
      expected_viewport_bitmap, ScreenshotEncoding::PNG,
      /*from_surface=*/true, device_scale_factor, clip,
      /*clip_scale=*/1,
      /*capture_beyond_viewport=*/true);

  // When capturing full page screenshots, the page content size can differ
  // from the defined dimensions. Therefore, we need to check for the actual
  // layout metrics of the page and compare that with our result.
  expected_full_page_bitmap =
      GenerateBitmap(GetPageContentSize(), SkColorSetARGB(16, 255, 0, 0));

  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

  // Check that device emulation does not affect the transparency.

  SetDeviceMetricsOverride(view_size.width(), view_size.height(),
                           /*device_scale_factor=*/0, /*mobile=*/false,
                           /*fitWindow=*/false);

  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor);

  // Checks for beyond_viewport
  CaptureScreenshotAndCompareTo(expected_viewport_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/clip,
                                /*clip_scale=*/1,
                                /*capture_beyond_viewport=*/true);

  CaptureScreenshotAndCompareTo(expected_full_page_bitmap,
                                ScreenshotEncoding::PNG,
                                /*from_surface=*/true, device_scale_factor,
                                /*clip=*/gfx::RectF(), /*clip_scale=*/0,
                                /*capture_beyond_viewport=*/true);

  SendCommandSync("Emulation.clearDeviceMetricsOverride");
}
#endif  // !BUILDFLAG(IS_ANDROID)

#if !BUILDFLAG(IS_ANDROID)
// Verifies that CaptureScreenshotsBeyondViewport supports emulation with the
// use of setDeviceMetricsOverride and setDefaultBackgroundColorOverride
IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest,
                       CaptureScreenshotBeyondViewport_Emulation) {
  // TODO(crbug.com/40488022) This test fails consistently on low-end Android
  // devices.
  if (base::SysInfo::IsLowEndDevice())
    return;

  // Load dummy page before getting the view size.
  shell()->LoadURL(GURL("data:text/html,"));

  gfx::Size view_size = GetViewSize();

  // Make a page a bigger than the view to have fullpage behaviour.
  int content_height = view_size.height() + 100;
  int content_width = view_size.width() + 100;

  shell()->LoadURL(
      GURL(base::StringPrintf("data:text/html,"
                              R"(<body style='background:%%23123456;height:%dpx;
                              width:%dpx'></body>)",
                              content_height, content_width)));

  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  Attach();

  SetDefaultBackgroundColorOverride(/*r=*/0x12, /*g=*/0x34, /*b=*/0x56,
                                    /*a=*/1.0);

  float device_scale_factor =
      display::Screen::Get()->GetPrimaryDisplay().device_scale_factor();

  // Check device emulation.
  // Additionally checks if emulation doesnt affect color change
  SetDeviceMetricsOverride(content_width, content_height,
                           /*device_scale_factor=*/0, /*mobile=*/false,
                           /*fitWindow=*/false);
  gfx::Size actual_page_size = GetPageContentSize();
  SkBitmap expected_bitmap =
      GenerateBitmap(actual_page_size, SkColorSetRGB(0x12, 0x34, 0x56));

  // Test for no Clip
  CaptureScreenshotAndCompareTo(
      expected_bitmap, ScreenshotEncoding::PNG,
      /*from_surface=*/true, device_scale_factor, /*clip=*/gfx::RectF(),
      /*clip_scale=*/0, /*capture_beyond_viewport=*/true);
  // Test for Clip
  CaptureScreenshotAndCompareTo(
      expected_bitmap, ScreenshotEncoding::PNG, /*from_surface=*/true,
      device_scale_factor,
      /*clip=*/
      gfx::RectF(0, 0, actual_page_size.width(), actual_page_size.height()),
      /*clip_scale=*/1, /*capture_beyond_viewport=*/true);
  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);
}

IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
                       NoCrashDeviceMetricsOverrideAutoResize) {
  NavigateToURLBlockUntilNavigationsComplete(
      shell(), GURL("data:text/html,<body></body>"), 1);

  // Enable auto resize.
  gfx::Size min_size(10, 10);
  gfx::Size max_size(100, 100);

  RenderWidgetHostImpl* rwh = static_cast<RenderWidgetHostImpl*>(
      shell()->web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
  rwh->GetView()->EnableAutoResize(min_size, max_size);

  Attach();

  // Send command.
  auto params = base::Value::Dict();
  params.Set("width", 50);
  params.Set("height", 50);
  params.Set("deviceScaleFactor", 1);
  params.Set("mobile", false);
  ASSERT_FALSE(
      SendCommandSync("Emulation.setDeviceMetricsOverride", std::move(params)));

  // Should not crash and should return an error.
  EXPECT_EQ(*error()->FindInt("code"),
            static_cast<int>(crdtp::DispatchCode::SERVER_ERROR));
  EXPECT_EQ(*error()->FindString("message"),
            "Target does not support metrics override");
}

#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/40825729): 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"));
}

// TODO(crbug.com/440535492): Flaky on Win dbg. Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_PageCrashClearsPendingCommands \
  DISABLED_PageCrashClearsPendingCommands
#else
#define MAYBE_PageCrashClearsPendingCommands PageCrashClearsPendingCommands
#endif
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
                       MAYBE_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/40811521): 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/40811670): 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.
}

// TODO(crbug.com/440535492): Flaky on Win dbg. Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_InspectorTargetCrashedNavigate \
  DISABLED_InspectorTargetCrashedNavigate
#else
#define MAYBE_InspectorTargetCrashedNavigate InspectorTargetCrashedNavigate
#endif
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
                       MAYBE_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);
}

// TODO(crbug.com/440535492): Flaky on Win dbg. Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_TargetGetTargetsAfterCrash DISABLED_TargetGetTargetsAfterCrash
#else
#define MAYBE_TargetGetTargetsAfterCrash TargetGetTargetsAfterCrash
#endif
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, MAYBE_TargetGetTargetsAfterCrash) {
  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");
  SendCommandSync("Target.getTargets");
  EXPECT_EQ(1u, result()->FindList("targetInfos")->size());

  {
    ScopedAllowRendererCrashes scoped_allow_renderer_crashes(shell());
    shell()->LoadURL(GURL(blink::kChromeUICrashURL));
    WaitForNotification("Inspector.targetCrashed");
  }

  SendCommandSync("Target.getTargets");
  EXPECT_EQ(1u, result()->FindList("targetInfos")->size());
}

// 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);
}

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);
}

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);
}

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);
}

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 `blink::WebView` 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();
  const base::Value::Dict* target_info = target_info_value.GetIfDict();
  ASSERT_TRUE(target_info);
  const std::string* target_id = target_info->FindString("targetId");
  const std::string* type = target_info->FindString("type");
  const std::string* title = target_info->FindString("title");
  const std::string* url = target_info->FindString("url");
  ASSERT_TRUE(target_id);
  ASSERT_TRUE(type);
  ASSERT_TRUE(title);
  ASSERT_TRUE(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\": \"*\"}]}",
                                base::JSON_PARSE_CHROMIUM_EXTENSIONS)
                                ->GetDict()));

  SendCommandSync(
      "Security.setIgnoreCertificateErrors",
      std::move(base::JSONReader::Read("{\"ignore\": true}",
                                       base::JSON_PARSE_CHROMIUM_EXTENSIONS)
                    ->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 + "\"}",
                    base::JSON_PARSE_CHROMIUM_EXTENSIONS)
                    ->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());
}

class CertificateErrorIgnoredBrowserTargetTest : public DevToolsProtocolTest {
 public:
  CertificateErrorIgnoredBrowserTargetTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

  void SetUpOnMainThread() override {
    DevToolsProtocolTest::SetUpOnMainThread();
    net::SSLServerConfig ssl_config;
    // ssl_config.client_cert_type =
    //     net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
    // https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);

    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()));

    // Create a second client to concurrently connect to browser target,
    // as the DevToolsProtocolTest class only has one client available to
    // connect.
    browser_client.AttachToBrowserTarget();
    base::Value::Dict command_params;
    command_params = base::Value::Dict();
    command_params.Set("ignore", true);
    browser_client.SendCommandSync("Security.setIgnoreCertificateErrors",
                                   std::move(command_params));

    // Connect the default client to the page.
    Attach();
    SendCommandSync("Debugger.enable");
    // Clear cookies and cache to avoid interference with cert error events.
    SendCommandSync("Network.enable");
    SendCommandSync("Network.clearBrowserCache");
    SendCommandSync("Network.clearBrowserCookies");

    TestNavigationObserver continue_observer(shell()->web_contents(), 1);
    shell()->LoadURL(test_url);
    continue_observer.Wait();

    EXPECT_EQ(test_url, shell()
                            ->web_contents()
                            ->GetController()
                            .GetLastCommittedEntry()
                            ->GetURL());
  }

  void TearDownOnMainThread() override {
    // Detach the additional client
    browser_client.DetachProtocolClient();
    DevToolsProtocolTest::TearDownOnMainThread();
  }

  ~CertificateErrorIgnoredBrowserTargetTest() override = default;

 protected:
  TestDevToolsProtocolClient browser_client;
  net::EmbeddedTestServer https_server_;
};

IN_PROC_BROWSER_TEST_F(CertificateErrorIgnoredBrowserTargetTest,
                       CertificateErrorBrowserTargetServiceWorkerFetch) {
  // Install a service worker over bad HTTPS cert and wait for the controller to
  // change.
  base::Value::Dict params;
  ASSERT_TRUE(content::ExecJs(
      shell()->web_contents(),
      "navigator.serviceWorker.register('/devtools/service_worker.js');"
      "navigator.serviceWorker.oncontrollerchange = () => {debugger;};"));
  WaitForNotification("Debugger.paused");
  SendCommandSync("Debugger.resume");
  // Reload the page so that request is intercepted by SW.
  SendCommandSync("Page.reload");
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ("intercepted",
            EvalJs(shell()->web_contents(), "document.body.textContent"));
}

IN_PROC_BROWSER_TEST_F(
    CertificateErrorIgnoredBrowserTargetTest,
    CertificateErrorBrowserTargetServiceWorkerImportScripts) {
  // Install a service worker over bad HTTPS cert and wait for the controller to
  // change.
  base::Value::Dict params;
  ASSERT_TRUE(content::ExecJs(
      shell()->web_contents(),
      "navigator.serviceWorker.register('/devtools/"
      "service_worker_import_classic.js');"
      "navigator.serviceWorker.oncontrollerchange = () => {debugger;};"));
  WaitForNotification("Debugger.paused");

  SendCommandSync("Debugger.resume");

  // Reload the page so that request is intercepted by SW.
  SendCommandSync("Page.reload");
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  EXPECT_EQ("imported",
            EvalJs(shell()->web_contents(), "document.body.textContent"));
}

IN_PROC_BROWSER_TEST_F(CertificateErrorIgnoredBrowserTargetTest,
                       CertificateErrorBrowserTargetServiceWorkerModuleImport) {
  // Install a service worker over bad HTTPS cert and wait for the controller to
  // change.
  base::Value::Dict params;
  ASSERT_TRUE(content::ExecJs(
      shell()->web_contents(),
      "navigator.serviceWorker.register('/devtools/"
      "service_worker_import_module.js', {type: 'module'});"
      "navigator.serviceWorker.oncontrollerchange = () => {debugger;};"));
  WaitForNotification("Debugger.paused");

  SendCommandSync("Debugger.resume");

  // Reload the page so that request is intercepted by SW.
  SendCommandSync("Page.reload");
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  EXPECT_EQ("imported",
            EvalJs(shell()->web_contents(), "document.body.textContent"));
}

IN_PROC_BROWSER_TEST_F(CertificateErrorIgnoredBrowserTargetTest,
                       CertificateErrorBrowserTargetDedicatedWorker) {
  // Install a dedicated worker over bad HTTPS cert.
  base::Value::Dict params;
  ASSERT_TRUE(content::ExecJs(
      shell()->web_contents(),
      "const myWorker = new Worker('/devtools/dedicated_worker.js');"
      "myWorker.onmessage = (msg) => {document.body.textContent = msg.data; "
      "debugger;};"
      "myWorker.postMessage('test');"));

  WaitForNotification("Debugger.paused");
  SendCommandSync("Debugger.resume");

  EXPECT_EQ("reply test",
            EvalJs(shell()->web_contents(), "document.body.textContent"));
}

IN_PROC_BROWSER_TEST_F(
    CertificateErrorIgnoredBrowserTargetTest,
    CertificateErrorBrowserTargetDedicatedWorkerImportClassic) {
  // Install a dedicated worker over bad HTTPS cert.
  base::Value::Dict params;
  ASSERT_TRUE(content::ExecJs(
      shell()->web_contents(),
      "const myWorker = new "
      "Worker('/devtools/dedicated_worker_import_classic.js');"
      "myWorker.onmessage = (msg) => {document.body.textContent = msg.data; "
      "debugger;};"
      "myWorker.postMessage('test');"));

  WaitForNotification("Debugger.paused");
  SendCommandSync("Debugger.resume");

  EXPECT_EQ("reply imported test",
            EvalJs(shell()->web_contents(), "document.body.textContent"));
}

IN_PROC_BROWSER_TEST_F(
    CertificateErrorIgnoredBrowserTargetTest,
    CertificateErrorBrowserTargetDedicatedWorkerImportModule) {
  // Install a dedicated worker over bad HTTPS cert.
  base::Value::Dict params;
  ASSERT_TRUE(content::ExecJs(
      shell()->web_contents(),
      "const myWorker = new "
      "Worker('/devtools/dedicated_worker_import_module.js', {type: 'module'});"
      "myWorker.onmessage = (msg) => {document.body.textContent = msg.data; "
      "debugger;};"
      "myWorker.postMessage('test');"));

  WaitForNotification("Debugger.paused");
  SendCommandSync("Debugger.resume");

  EXPECT_EQ("reply imported test",
            EvalJs(shell()->web_contents(), "document.body.textContent"));
}

// SharedWorkers are not enabled on Android. https://crbug.com/154571
#if BUILDFLAG(IS_ANDROID)
constexpr bool kIsSharedWorkerEnabled = false;
#else
constexpr bool kIsSharedWorkerEnabled = true;
#endif

IN_PROC_BROWSER_TEST_F(CertificateErrorIgnoredBrowserTargetTest,
                       CertificateErrorBrowserTargetSharedWorker) {
  if (!kIsSharedWorkerEnabled) {
    return;
  }
  // Install a shared worker over bad HTTPS cert.
  base::Value::Dict params;
  ASSERT_TRUE(content::ExecJs(
      shell()->web_contents(),
      "const myWorker = new SharedWorker('/devtools/shared_worker.js');"
      "myWorker.port.start();"
      "myWorker.port.onmessage = (msg) => {document.body.textContent = "
      "msg.data; debugger;};"
      "myWorker.port.postMessage('test');"));
  WaitForNotification("Debugger.paused");
  SendCommandSync("Debugger.resume");

  EXPECT_EQ("reply test",
            EvalJs(shell()->web_contents(), "document.body.textContent"));
}

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 std::string* name = nullptr;
  const std::string* value = nullptr;
  {
    ASSERT_TRUE(cookies->front().is_dict());
    const base::Value::Dict& cookie = cookies->front().GetDict();
    name = cookie.FindString("name");
    value = cookie.FindString("value");
    ASSERT_TRUE(name);
    ASSERT_TRUE(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) {
    ASSERT_TRUE(cookie_value.is_dict());
    const base::Value::Dict& cookie = cookie_value.GetDict();
    name = cookie.FindString("name");
    value = cookie.FindString("value");
    ASSERT_TRUE(name);
    ASSERT_TRUE(value);
    if (*name == "cookie_for_this_url") {
      EXPECT_EQ("mendacious", *value);
      found++;
    } else if (*name == "cookie_for_another_url") {
      EXPECT_EQ("polyglottal", *value);
      found++;
    } else {
      FAIL();
    }
  }
  EXPECT_EQ(2u, found);
}

IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
                       ReturnsCookiesOnlyForAttachableUrls) {
  SetNotAttachableHosts({"b.test"});
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());
  std::string cookies_to_set = "/set-cookie?foo=bar";

  GURL url = embedded_test_server()->GetURL("b.test", cookies_to_set);
  EXPECT_TRUE(NavigateToURL(shell(), url));
  url = embedded_test_server()->GetURL("c.test", cookies_to_set);
  EXPECT_TRUE(NavigateToURL(shell(), url));
  url = embedded_test_server()->GetURL(
      "a.test", "/cross_site_iframe_factory.html?a.test(b.test(),c.test())");
  EXPECT_TRUE(NavigateToURL(shell(), url));

  Attach();
  const base::Value::List* storage_cookies =
      SendCommandSync("Storage.getCookies")->FindList("cookies");
  ASSERT_EQ(1ul, storage_cookies->size());
  EXPECT_EQ("foo", *storage_cookies->front().GetDict().FindString("name"));
  EXPECT_EQ("c.test", *storage_cookies->front().GetDict().FindString("domain"));

  const base::Value::List* network_all_cookies =
      SendCommandSync("Network.getAllCookies")->FindList("cookies");
  ASSERT_EQ(1ul, network_all_cookies->size());
  EXPECT_EQ("foo", *network_all_cookies->front().GetDict().FindString("name"));
  EXPECT_EQ("c.test",
            *network_all_cookies->front().GetDict().FindString("domain"));

  const base::Value::List* network_cookies_no_param =
      SendCommandSync("Network.getCookies")->FindList("cookies");
  ASSERT_EQ(1ul, network_cookies_no_param->size());
  EXPECT_EQ("foo",
            *network_cookies_no_param->front().GetDict().FindString("name"));
  EXPECT_EQ("c.test",
            *network_cookies_no_param->front().GetDict().FindString("domain"));

  base::Value::List urls;
  urls.Append(embedded_test_server()
                  ->GetURL("b.com", "/cross_site_iframe_factory.html?b.test()")
                  .spec());
  urls.Append(embedded_test_server()
                  ->GetURL("c.com", "/cross_site_iframe_factory.html?c.test()")
                  .spec());
  base::Value::Dict params;
  params.Set("urls", std::move(urls));
  const base::Value::List* network_cookies_with_param =
      SendCommandSync("Network.getAllCookies", std::move(params))
          ->FindList("cookies");
  ASSERT_EQ(1ul, network_cookies_with_param->size());
  EXPECT_EQ("foo",
            *network_cookies_with_param->front().GetDict().FindString("name"));
  EXPECT_EQ("c.test", *network_cookies_with_param->front().GetDict().FindString(
                          "domain"));
}

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.
  ASSERT_TRUE(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_.RegisterServerRequestMonitor(embedded_test_server());
  }

  // WebContentsDelegate overrides.
  PreloadingEligibility IsPrerender2Supported(
      WebContents& web_contents,
      PreloadingTriggerType trigger_type) override {
    return PreloadingEligibility::kEligible;
  }

  WebContents* GetWebContents() const { return shell()->web_contents(); }

  std::string AttachToTabTargetAndGetSessionId() {
    AttachToTabTarget(shell()->web_contents());
    shell()->web_contents()->SetDelegate(this);

    {
      base::Value::Dict params;
      params.Set("discover", true);
      SendCommandSync("Target.setDiscoverTargets", std::move(params));
    }

    std::string frame_target_id;
    for (int targetCount = 1; true; targetCount++) {
      base::Value::Dict result;
      result = WaitForNotification("Target.targetCreated", true);
      if (*result.FindStringByDottedPath("targetInfo.type") == "page") {
        frame_target_id =
            std::string(*result.FindStringByDottedPath("targetInfo.targetId"));
        break;
      }
      CHECK_LT(targetCount, 2);
    }

    {
      base::Value::Dict params;
      params.Set("targetId", frame_target_id);
      params.Set("flatten", true);
      const base::Value::Dict* result =
          SendCommandSync("Target.attachToTarget", std::move(params));
      CHECK(result);
      std::string session_id(*result->FindString("sessionId"));
      CHECK(session_id != "");
      return session_id;
    }
  }

 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);
  std::string session_id = AttachToTabTargetAndGetSessionId();

  const gfx::Size original_size = GetViewSize();
  const gfx::Size emulated_size =
      gfx::Size(original_size.width() - 50, original_size.height() - 50);

  {
    const gfx::Size size = emulated_size;
    base::Value::Dict params;
    params.Set("width", size.width());
    params.Set("height", size.height());
    params.Set("deviceScaleFactor", 0);
    params.Set("mobile", false);
    SendSessionCommand("Emulation.setDeviceMetricsOverride", std::move(params),
                       session_id, true);
  }
  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());

  SendSessionCommand("Emulation.clearDeviceMetricsOverride",
                     base::Value::Dict(), session_id, true);
  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(
        GetDefaultEnabledBackForwardCacheFeaturesForTesting(
            /*ignore_outstanding_network_request=*/false),
        GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }
  ~DevToolsProtocolBackForwardCacheTest() override = default;

  // content::WebContentsDelegate:
  bool IsBackForwardCacheSupported(
      content::WebContents& web_contents) 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,
      const base::FilePath& duplicate_download_file_path,
      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:
  raw_ptr<DownloadManager> manager_;
  raw_ptr<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 (UNSAFE_TODO(
            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());
}

// Check that defaulting downloads cancels when there's no proxy
// download delegate.
IN_PROC_BROWSER_TEST_F(DevToolsDownloadContentTest,
                       SetDownloadBehaviorAccessChecks) {
  SetMayWriteLocalFiles(false);
  Attach();
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  base::Value::Dict params;
  params.Set("behavior", "allow");
  params.Set("downloadPath",
             temp_dir.GetPath().AppendASCII("download").AsUTF8Unsafe());

  SendCommandSync("Page.setDownloadBehavior", params.Clone());
  ASSERT_TRUE(error());
  EXPECT_EQ(*error()->FindString("message"), "Not allowed");
  Detach();
  SetMayWriteLocalFiles(true);
  Attach();
  SendCommandSync("Page.setDownloadBehavior", std::move(params));
  EXPECT_FALSE(error());
}

IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, GetAnnotatedPageContent) {
  GURL url = GURL("data:text/html,<body>Hello, world!</body>");
  NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
  Attach();
  const base::Value::Dict* result_ptr = SendCommandSync(
      "Page.getAnnotatedPageContent",
      base::Value::Dict().Set("includeActionableInformation", false));
  ASSERT_FALSE(result_ptr);
  EXPECT_EQ(*error()->FindInt("code"),
            static_cast<int>(crdtp::DispatchCode::SERVER_ERROR));
  EXPECT_EQ(*error()->FindString("message"),
            "Failed to get annotated page content");
}

// 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_config_encoded =
      base::Base64Encode(perfetto_config.SerializeAsString());

  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);

    std::string perfetto_config_encoded =
        base::Base64Encode(perfetto_config.SerializeAsString());

    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::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();
  }
};

// TODO(https://crbug.com/328350104): Fails ASAN builds
#if defined(ADDRESS_SANITIZER)
#define MAYBE_StartTracingFailsWithInvalidSockets \
  DISABLED_StartTracingFailsWithInvalidSockets
#else
#define MAYBE_StartTracingFailsWithInvalidSockets \
  StartTracingFailsWithInvalidSockets
#endif
IN_PROC_BROWSER_TEST_F(InvalidSystemTracingDevToolsProtocolTest,
                       MAYBE_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::SequencedTaskRunner::GetCurrentDefault());

    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::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_ECDSA_WITH_AES_128_GCM_SHA256
  server_config.cipher_suite_for_testing = 0xc02b;
  server_config.curves_for_testing = {NID_X25519};
  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
  cert_config.signature_algorithm_for_testing = SSL_SIGN_ECDSA_SECP384R1_SHA384;
  net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
  server.SetSSLConfig(cert_config, 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_ECDSA", *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);

  std::optional<int> sigalg = response.FindIntByDottedPath(
      "response.securityDetails.serverSignatureAlgorithm");
  EXPECT_EQ(SSL_SIGN_ECDSA_SECP384R1_SHA384, sigalg);

  std::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->GetList().size());
  EXPECT_EQ(base::Value("127.0.0.1"), sans->GetList()[0]);

  std::optional<double> valid_from =
      response.FindDoubleByDottedPath("response.securityDetails.validFrom");
  EXPECT_EQ(server.GetCertificate()->valid_start().InSecondsFSinceUnixEpoch(),
            valid_from);

  std::optional<double> valid_to =
      response.FindDoubleByDottedPath("response.securityDetails.validTo");
  EXPECT_EQ(server.GetCertificate()->valid_expiry().InSecondsFSinceUnixEpoch(),
            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};
  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
  cert_config.signature_algorithm_for_testing = SSL_SIGN_ECDSA_SECP256R1_SHA256;
  net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
  server.SetSSLConfig(cert_config, 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);

  std::optional<int> sigalg = response.FindIntByDottedPath(
      "response.securityDetails.serverSignatureAlgorithm");
  EXPECT_EQ(SSL_SIGN_ECDSA_SECP256R1_SHA256, sigalg);

  std::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->GetList().size());
  EXPECT_EQ(base::Value("a.example"), sans->GetList()[0]);
  EXPECT_EQ(base::Value("b.example"), sans->GetList()[1]);
  EXPECT_EQ(base::Value("*.c.example"), sans->GetList()[2]);
  EXPECT_EQ(base::Value("127.0.0.1"), sans->GetList()[3]);
  EXPECT_EQ(base::Value("::1"), sans->GetList()[4]);
  EXPECT_EQ(base::Value("1.2.3.4"), sans->GetList()[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::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.
    std::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(std::string_view 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.
// Flaky: https://crbug.com/1521189
IN_PROC_BROWSER_TEST_F(NetworkResponseProtocolECHTest,
                       DISABLED_SecurityDetailsECH) {
  NavigateToURLBlockUntilNavigationsComplete(shell(), GetURL("/title1.html"),
                                             1);

  Attach();
  SendCommandAsync("Network.enable");

  base::Value::Dict response = FetchAndWaitForResponse(GetURL("/empty.html"));
  std::optional<bool> ech = response.FindBoolByDottedPath(
      "response.securityDetails.encryptedClientHello");
  EXPECT_EQ(true, ech);
}

IN_PROC_BROWSER_TEST_F(
    PrerenderDevToolsProtocolTest,
    PrerenderStatusUpdatedReportsFailureWithDisallowedMojoInterface) {
  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.
  FrameTreeNodeId host_id = AddPrerender(kPrerenderingUrl);
  auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
  Attach();
  SendCommandSync("Preload.enable");

  // 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;
  while (true) {
    result = WaitForNotification("Preload.prerenderStatusUpdated", true);
    if (*result.FindString("status") == "Failure") {
      break;
    }
  }

  EXPECT_THAT(*result.FindString("disallowedMojoInterface"),
              Eq("device.mojom.GamepadMonitor"));
}

IN_PROC_BROWSER_TEST_F(
    PrerenderDevToolsProtocolTest,
    PrerenderStatusUpdatedReportsFailureWithPrerenderMismatchedHeaders) {
  const std::string user_agent_override = "foo";
  ASSERT_TRUE(embedded_test_server()->Start());
  // Navigate to an initial page.
  const GURL initial_url = GetUrl("/empty.html");
  ASSERT_TRUE(NavigateToURL(shell(), initial_url));

  // Enable user agent override for future navigations.
  UserAgentInjector injector(shell()->web_contents(), user_agent_override);

  const GURL prerendering_url = GetUrl("/empty.html?prerender");

  // Start prerendering.
  const FrameTreeNodeId host_id = AddPrerender(prerendering_url);

  Attach();
  SendCommandSync("Preload.enable");

  RenderFrameHostImpl* prerender_rfh =
      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
  EXPECT_EQ(user_agent_override, EvalJs(prerender_rfh, "navigator.userAgent"));

  // Stop overriding user agent from now on.
  injector.set_is_overriding_user_agent(false);

  // Activate the prerendered page.
  test::PrerenderHostObserver host_observer(*web_contents(), host_id);
  NavigatePrimaryPage(prerendering_url);
  host_observer.WaitForDestroyed();

  base::Value::Dict result;
  while (true) {
    result = WaitForNotification("Preload.prerenderStatusUpdated", true);
    if (*result.FindString("status") == "Failure") {
      break;
    }
  }
  EXPECT_TRUE(result.Find("mismatchedHeaders"));
}

IN_PROC_BROWSER_TEST_F(PrerenderDevToolsProtocolTest,
                       RenderFrameDevToolsAgentHostCacheEvictionCrash) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL kInitialUrl = GetUrl("/empty.html");
  const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
  WebContentsImpl* web_contents_impl =
      static_cast<WebContentsImpl*>(web_contents());

  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));

  // Attaching a session via a "tab" target is required to opt-in into
  // FTN swapping mode during prerender activation.
  AttachToTabTarget(web_contents_impl);
  base::Value::Dict command_params;
  command_params.Set("autoAttach", true);
  command_params.Set("waitForDebuggerOnStart", false);
  command_params.Set("flatten", true);
  SendCommandSync("Target.setAutoAttach", std::move(command_params));

  // Stash current RFDTAH for WebContents that is about to be retained
  // by BFCache after prerender navigation and flushed later.
  auto old_host = DevToolsAgentHost::GetOrCreateFor(web_contents_impl);
  RenderFrameDeletedObserver delete_observer(
      web_contents_impl->GetPrimaryMainFrame());

  // Activating a prerender should cause FTN swapping on the RFH and put
  // the old one into the BFCache with frame_tree_node_ == nullptr.
  AddPrerender(kPrerenderingUrl);
  NavigatePrimaryPage(kPrerenderingUrl);

  EXPECT_FALSE(delete_observer.deleted());
  web_contents_impl->GetController().GetBackForwardCache().Flush();
  delete_observer.WaitUntilDeleted();

  // Assure methods on disconnected host are safe to call.
  EXPECT_THAT(old_host->GetTitle(), testing::Eq(""));
}

IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ResponseAfterReload) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL("a.test", "/title1.html");

  NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);

  Attach();

  SendCommandSync("Fetch.enable");
  SendCommandAsync("Page.reload");

  base::Value::Dict command_params;
  command_params.Set("discover", true);
  SendCommandSync("Target.setDiscoverTargets", std::move(command_params));

  {
    content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
    shell()->LoadURL(GURL(blink::kChromeUICrashURL));
    WaitForNotification("Target.targetCrashed", true);
  }

  SetProtocolCommandId(42);
  SendCommandAsync("Network.enable");

  SetProtocolCommandId(42);
  SendCommandSync("Page.reload");

  SendCommandAsync("Fetch.disable");
  SendCommandSync("Network.enable");
}

class SharedStorageDevToolsProtocolTest : public DevToolsProtocolTest {
 public:
  SharedStorageDevToolsProtocolTest() {
    feature_list_
        .InitWithFeaturesAndParameters(/*enabled_features=*/
                                       {{network::features::kSharedStorageAPI,
                                         {{"SharedStorageBitBudget",
                                           base::NumberToString(
                                               kBudgetAllowed)}}},
                                        {features::
                                             kPrivacySandboxAdsAPIsOverride,
                                         {}}},
                                       /*disabled_features=*/{});
  }

  void MakeBudgetWithdrawal(const GURL& url, double bits) {
    auto* manager = shell()
                        ->web_contents()
                        ->GetBrowserContext()
                        ->GetDefaultStoragePartition()
                        ->GetSharedStorageManager();
    ASSERT_TRUE(manager);
    base::test::TestFuture<storage::SharedStorageManager::OperationResult>
        future;
    manager->MakeBudgetWithdrawal(net::SchemefulSite(url), bits,
                                  future.GetCallback());
    EXPECT_EQ(storage::SharedStorageManager::OperationResult::kSuccess,
              future.Get());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(SharedStorageDevToolsProtocolTest,
                       ResetSharedStorageBudget) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL("a.test", "/title1.html");
  NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
  Attach();

  base::Value::Dict command_params;
  command_params.Set("enable", true);
  SendCommandSync("Storage.setSharedStorageTracking",
                  std::move(command_params));
  ASSERT_FALSE(error());

  // Set an entry in order to initialize shared storage database for
  // `origin_str`.
  command_params = base::Value::Dict();
  std::string origin_str = url.GetWithEmptyPath().spec();
  command_params.Set("ownerOrigin", origin_str);
  command_params.Set("key", "key1");
  command_params.Set("value", "value1");
  SendCommandSync("Storage.setSharedStorageEntry", std::move(command_params));
  ASSERT_FALSE(error());

  // "remainingBudget" should currently be at its max, `kBudgetAllowed`.
  command_params = base::Value::Dict();
  command_params.Set("ownerOrigin", origin_str);
  SendCommandSync("Storage.getSharedStorageMetadata",
                  std::move(command_params));
  ASSERT_TRUE(result());
  EXPECT_THAT(result()->FindDoubleByDottedPath("metadata.remainingBudget"),
              testing::Optional(kBudgetAllowed));

  // Make some withdrawals.
  MakeBudgetWithdrawal(url, 1.0);
  MakeBudgetWithdrawal(url, 2.5);

  // "remainingBudget" should have decreased the appropriate amount.
  command_params = base::Value::Dict();
  command_params.Set("ownerOrigin", origin_str);
  SendCommandSync("Storage.getSharedStorageMetadata",
                  std::move(command_params));
  ASSERT_TRUE(result());
  EXPECT_THAT(result()->FindDoubleByDottedPath("metadata.remainingBudget"),
              testing::Optional(kBudgetAllowed - 1.0 - 2.5));

  // Reset the budget.
  command_params = base::Value::Dict();
  command_params.Set("ownerOrigin", origin_str);
  SendCommandSync("Storage.resetSharedStorageBudget",
                  std::move(command_params));
  ASSERT_FALSE(error());

  // "remainingBudget" should be back at its max, `kBudgetAllowed`.
  command_params = base::Value::Dict();
  command_params.Set("ownerOrigin", origin_str);
  SendCommandSync("Storage.getSharedStorageMetadata",
                  std::move(command_params));
  ASSERT_TRUE(result());
  EXPECT_THAT(result()->FindDoubleByDottedPath("metadata.remainingBudget"),
              testing::Optional(kBudgetAllowed));
}

IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, TestRawHeadersWithRedirects) {
  net::EmbeddedTestServer& https_test_server = embedded_https_test_server();
  https_test_server.AddDefaultHandlers();
  https_test_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
  https_test_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
  ASSERT_TRUE(https_test_server.Start());
  ASSERT_TRUE(embedded_test_server()->Start());  // For first redirect.
  // Localhost does not support HSTS, so we must serve from "a.test" instead.
  GURL https_url = https_test_server.GetURL("a.test", "/hello.html");
  base::Time expiry = base::Time::Now() + base::Days(1000);
  bool include_subdomains = false;
  content::StoragePartition* partition = shell()
                                             ->web_contents()
                                             ->GetBrowserContext()
                                             ->GetDefaultStoragePartition();
  base::RunLoop run_loop;
  partition->GetNetworkContext()->AddHSTS(
      https_url.GetHost(), expiry, include_subdomains, run_loop.QuitClosure());
  run_loop.Run();

  GURL::Replacements replace_scheme;
  replace_scheme.SetSchemeStr("http");
  GURL http_url = https_url.ReplaceComponents(replace_scheme);
  GURL redirect_url =
      embedded_test_server()->GetURL("/server-redirect?" + http_url.spec());

  NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
  Attach();
  SendCommandSync("Network.enable");

  base::Value::Dict params;
  params.Set("url", redirect_url.spec());
  SendCommandAsync("Page.navigate", std::move(params));

  {
    const base::Value::Dict orig_request =
        WaitForNotification("Network.requestWillBeSent", true);
    EXPECT_THAT(orig_request.FindStringByDottedPath("request.url"),
                Pointee(redirect_url));
    EXPECT_THAT(orig_request.FindDict("redirectResponse"), testing::IsNull());
  }
  {
    // The first redirect is a real, server-issued redirect:
    // http://127.0.0.1:N/server-redirect?http://a.test:M/hello.html =>
    // http://a.test:M/hello.html

    const base::Value::Dict redirected_request =
        WaitForNotification("Network.requestWillBeSent", true);
    EXPECT_THAT(redirected_request.FindStringByDottedPath("request.url"),
                Pointee(http_url));
    EXPECT_THAT(redirected_request.FindBool("redirectHasExtraInfo"),
                testing::Optional(true));
    EXPECT_THAT(
        redirected_request.FindIntByDottedPath("redirectResponse.status"),
        testing::Optional(301));
    EXPECT_THAT(redirected_request.FindStringByDottedPath(
                    "redirectResponse.headers.Location"),
                Pointee(http_url.spec()));
  }
  {
    // The second redirect is an interan HSTS redirect:
    // http://a.test:M/hello.html => https://a.test:M/hello.html
    const base::Value::Dict redirected_request =
        WaitForNotification("Network.requestWillBeSent", true);
    EXPECT_THAT(redirected_request.FindStringByDottedPath("request.url"),
                Pointee(https_url));
    EXPECT_THAT(redirected_request.FindBool("redirectHasExtraInfo"),
                testing::Optional(false));
    EXPECT_THAT(
        redirected_request.FindIntByDottedPath("redirectResponse.status"),
        testing::Optional(307));
    EXPECT_THAT(redirected_request.FindStringByDottedPath(
                    "redirectResponse.headers.Location"),
                Pointee(https_url.spec()));
  }

  // Nothing of interest to check for the request headers, except that the event
  // is there.
  WaitForNotification("Network.requestWillBeSentExtraInfo", true);

  {
    const base::Value::Dict response_extra_info =
        WaitForNotification("Network.responseReceivedExtraInfo", true);
    EXPECT_THAT(response_extra_info.FindIntByDottedPath("statusCode"),
                testing::Optional(301));
    EXPECT_THAT(response_extra_info.FindStringByDottedPath("headers.Location"),
                Pointee(http_url.spec()));
  }

  // Nothing of interest to check for the request headers, except that the event
  // is there.
  WaitForNotification("Network.requestWillBeSentExtraInfo", true);

  {
    const base::Value::Dict response_extra_info =
        WaitForNotification("Network.responseReceivedExtraInfo", true);
    EXPECT_THAT(response_extra_info.FindIntByDottedPath("statusCode"),
                testing::Optional(200));
    EXPECT_THAT(response_extra_info.FindStringByDottedPath("headers.Location"),
                testing::IsNull());
  }
  {
    const base::Value::Dict response_received =
        WaitForNotification("Network.responseReceived", true);
    EXPECT_THAT(response_received.FindBool("hasExtraInfo"),
                testing::Optional(true));
    EXPECT_THAT(response_received.FindIntByDottedPath("response.status"),
                testing::Optional(200));
    EXPECT_THAT(response_received.FindStringByDottedPath("response.statusText"),
                Pointee(std::string("OK")));
  }
}

IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, OpenDevTools_FailWhenUnavailable) {
  AttachToBrowserTarget();

  SendCommandSync("Target.getTargets");
  const base::Value::List* list = result()->FindList("targetInfos");
  EXPECT_EQ(1u, list->size());
  const std::string targetId = *list->front().GetDict().FindString("targetId");

  base::Value::Dict params;
  params.Set("targetId", targetId);
  SendCommandSync("Target.openDevTools", std::move(params));

  EXPECT_EQ(*error()->FindString("message"),
            "Failed to create DevTools window");
}

}  // namespace content