// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/devtools/devtools_window.h"

#include <algorithm>
#include <memory>
#include <set>
#include <string_view>
#include <utility>

#include "base/base64.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/strings/escape.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/certificate_viewer.h"
#include "chrome/browser/devtools/aida_client.h"
#include "chrome/browser/devtools/devtools_availability_checker.h"
#include "chrome/browser/devtools/devtools_eye_dropper.h"
#include "chrome/browser/devtools/features.h"
#include "chrome/browser/devtools/process_sharing_infobar_delegate.h"
#include "chrome/browser/file_select_helper.h"
#include "chrome/browser/infobars/confirm_infobar_creator.h"
#include "chrome/browser/policy/developer_tools_policy_checker.h"
#include "chrome/browser/policy/developer_tools_policy_checker_factory.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/task_manager/web_contents_tags.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
#include "chrome/browser/ui/webui/devtools/devtools_ui.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/infobars/core/infobar.h"
#include "components/input/native_web_keyboard_event.h"
#include "components/javascript_dialogs/app_modal_dialog_manager.h"
#include "components/language/core/browser/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/search_engines/util.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/vector_icons/vector_icons.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "components/zoom/page_zoom.h"
#include "components/zoom/zoom_controller.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/color_chooser.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_manager_delegate.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/keyboard_event_processing_result.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/navigation_throttle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "net/cert/x509_certificate.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/common/input/web_gesture_event.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/public_buildflags.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/page_transition_types.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/keycodes/keyboard_codes.h"

#include "base/win/windows_h_disallowed.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "extensions/browser/view_type_utils.h"
#endif

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#else
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"  // nogncheck crbug.com/40147906
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#endif

using blink::WebInputEvent;
using content::BrowserThread;
using content::DevToolsAgentHost;
using content::WebContents;

namespace {

typedef std::vector<DevToolsWindow*> DevToolsWindows;
DevToolsWindows& GetDevToolsWindowInstances() {
  static base::NoDestructor<DevToolsWindows> instances;
  return *instances;
}

std::vector<base::RepeatingCallback<void(DevToolsWindow*)>>&
GetCreationCallbacks() {
  static base::NoDestructor<
      std::vector<base::RepeatingCallback<void(DevToolsWindow*)>>>
      creation_callbacks;
  return *creation_callbacks;
}

static const char kKeyUpEventName[] = "keyup";
static const char kKeyDownEventName[] = "keydown";
static const char kDefaultFrontendURL[] =
    "devtools://devtools/bundled/devtools_app.html";
static const char kNodeFrontendURL[] =
    "devtools://devtools/bundled/node_app.html";
static const char kWorkerFrontendURL[] =
    "devtools://devtools/bundled/worker_app.html";
static const char kJSFrontendURL[] = "devtools://devtools/bundled/js_app.html";
static const char kFallbackFrontendURL[] =
    "devtools://devtools/bundled/inspector.html";

void SetPreferencesFromJson(Profile* profile, const std::string& json) {
  std::optional<base::Value::Dict> parsed =
      base::JSONReader::ReadDict(json, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  if (!parsed) {
    return;
  }
  ScopedDictPrefUpdate update(profile->GetPrefs(), prefs::kDevToolsPreferences);
  for (auto dict_value : *parsed) {
    if (!dict_value.second.is_string()) {
      continue;
    }
    update->Set(dict_value.first, std::move(dict_value.second));
  }
}

// DevToolsToolboxDelegate ----------------------------------------------------

class DevToolsToolboxDelegate : public content::WebContentsObserver,
                                public content::WebContentsDelegate {
 public:
  DevToolsToolboxDelegate(WebContents* toolbox_contents,
                          WebContents* inspected_web_contents);

  DevToolsToolboxDelegate(const DevToolsToolboxDelegate&) = delete;
  DevToolsToolboxDelegate& operator=(const DevToolsToolboxDelegate&) = delete;

  ~DevToolsToolboxDelegate() override;

  content::WebContents* OpenURLFromTab(
      content::WebContents* source,
      const content::OpenURLParams& params,
      base::OnceCallback<void(content::NavigationHandle&)>
          navigation_handle_callback) override;
  content::KeyboardEventProcessingResult PreHandleKeyboardEvent(
      content::WebContents* source,
      const input::NativeWebKeyboardEvent& event) override;
  bool HandleKeyboardEvent(content::WebContents* source,
                           const input::NativeWebKeyboardEvent& event) override;
  void WebContentsDestroyed() override;

 private:
#if !BUILDFLAG(IS_ANDROID)
  BrowserWindow* GetInspectedBrowserWindow();
#endif
  base::WeakPtr<content::WebContents> inspected_web_contents_;
};

DevToolsToolboxDelegate::DevToolsToolboxDelegate(WebContents* toolbox_contents,
                                                 WebContents* web_contents)
    : WebContentsObserver(toolbox_contents),
      inspected_web_contents_(web_contents ? web_contents->GetWeakPtr()
                                           : nullptr) {}

DevToolsToolboxDelegate::~DevToolsToolboxDelegate() = default;

content::WebContents* DevToolsToolboxDelegate::OpenURLFromTab(
    content::WebContents* source,
    const content::OpenURLParams& params,
    base::OnceCallback<void(content::NavigationHandle&)>
        navigation_handle_callback) {
  DCHECK(source == web_contents());
  if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
    return nullptr;
  }
  base::WeakPtr<content::NavigationHandle> navigation_handle =
      source->GetController().LoadURLWithParams(
          content::NavigationController::LoadURLParams(params));

  if (navigation_handle_callback && navigation_handle) {
    std::move(navigation_handle_callback).Run(*navigation_handle);
  }
  return source;
}

content::KeyboardEventProcessingResult
DevToolsToolboxDelegate::PreHandleKeyboardEvent(
    content::WebContents* source,
    const input::NativeWebKeyboardEvent& event) {
#if !BUILDFLAG(IS_ANDROID)
  BrowserWindow* window = GetInspectedBrowserWindow();
  if (window) {
    return window->PreHandleKeyboardEvent(event);
  }
#endif
  return content::KeyboardEventProcessingResult::NOT_HANDLED;
}

bool DevToolsToolboxDelegate::HandleKeyboardEvent(
    content::WebContents* source,
    const input::NativeWebKeyboardEvent& event) {
#if BUILDFLAG(IS_ANDROID)
  return false;
#else
  if (event.windows_key_code == 0x08) {
    // Do not navigate back in history on Windows (http://crbug.com/74156).
    return false;
  }
  BrowserWindow* window = GetInspectedBrowserWindow();
  return window && window->HandleKeyboardEvent(event);
#endif
}

void DevToolsToolboxDelegate::WebContentsDestroyed() {
  delete this;
}

#if !BUILDFLAG(IS_ANDROID)
BrowserWindow* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
  if (!inspected_web_contents_) {
    return nullptr;
  }
  Browser* browser = chrome::FindBrowserWithTab(inspected_web_contents_.get());
  return browser ? browser->window() : nullptr;
}
#endif

// static
GURL DecorateFrontendURL(const GURL& base_url) {
  std::string frontend_url = base_url.spec();
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

  if (command_line->HasSwitch(switches::kDevToolsFlags)) {
    frontend_url = frontend_url +
                   ((frontend_url.find("?") == std::string::npos) ? "?" : "&") +
                   command_line->GetSwitchValueASCII(switches::kDevToolsFlags);
  }

  if (command_line->HasSwitch(switches::kCustomDevtoolsFrontend)) {
    frontend_url = frontend_url +
                   ((frontend_url.find("?") == std::string::npos) ? "?" : "&") +
                   "debugFrontend=true";
  }

  return GURL(frontend_url);
}

}  // namespace

// DevToolsEventForwarder -----------------------------------------------------

class DevToolsEventForwarder {
 public:
  explicit DevToolsEventForwarder(DevToolsWindow* window)
      : devtools_window_(window) {}

  DevToolsEventForwarder(const DevToolsEventForwarder&) = delete;
  DevToolsEventForwarder& operator=(const DevToolsEventForwarder&) = delete;

  // Registers whitelisted shortcuts with the forwarder.
  // Only registered keys will be forwarded to the DevTools frontend.
  void SetWhitelistedShortcuts(const std::string& message);

  // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
  // Returns |true| if the event has been forwarded, |false| otherwise.
  bool ForwardEvent(const input::NativeWebKeyboardEvent& event);

 private:
  static bool KeyWhitelistingAllowed(int key_code, int modifiers);
  static int CombineKeyCodeAndModifiers(int key_code, int modifiers);

  raw_ptr<DevToolsWindow> devtools_window_;
  std::set<int> whitelisted_keys_;
};

void DevToolsEventForwarder::SetWhitelistedShortcuts(
    const std::string& message) {
  std::optional<base::Value> parsed_message =
      base::JSONReader::Read(message, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  if (!parsed_message || !parsed_message->is_list()) {
    return;
  }
  for (const auto& list_item : parsed_message->GetList()) {
    if (!list_item.is_dict()) {
      continue;
    }
    int key_code = list_item.GetDict().FindInt("keyCode").value_or(0);
    if (key_code == 0) {
      continue;
    }
    int modifiers = list_item.GetDict().FindInt("modifiers").value_or(0);
    if (!KeyWhitelistingAllowed(key_code, modifiers)) {
      LOG(WARNING) << "Key whitelisting forbidden: "
                   << "(" << key_code << "," << modifiers << ")";
      continue;
    }
    whitelisted_keys_.insert(CombineKeyCodeAndModifiers(key_code, modifiers));
  }
}

bool DevToolsEventForwarder::ForwardEvent(
    const input::NativeWebKeyboardEvent& event) {
  std::string event_type;
  switch (event.GetType()) {
    case WebInputEvent::Type::kKeyDown:
    case WebInputEvent::Type::kRawKeyDown:
      event_type = kKeyDownEventName;
      break;
    case WebInputEvent::Type::kKeyUp:
      event_type = kKeyUpEventName;
      break;
    default:
      return false;
  }

  int key_code = ui::LocatedToNonLocatedKeyboardCode(
      static_cast<ui::KeyboardCode>(event.windows_key_code));
  int modifiers = event.GetModifiers() &
                  (WebInputEvent::kShiftKey | WebInputEvent::kControlKey |
                   WebInputEvent::kAltKey | WebInputEvent::kMetaKey);
  int key = CombineKeyCodeAndModifiers(key_code, modifiers);
  if (whitelisted_keys_.find(key) == whitelisted_keys_.end()) {
    return false;
  }

  base::Value::Dict event_data;
  event_data.Set("type", event_type);
  event_data.Set("key", ui::KeycodeConverter::DomKeyToKeyString(
                            static_cast<ui::DomKey>(event.dom_key)));
  event_data.Set("code", ui::KeycodeConverter::DomCodeToCodeString(
                             static_cast<ui::DomCode>(event.dom_code)));
  event_data.Set("keyCode", key_code);
  event_data.Set("modifiers", modifiers);
  devtools_window_->bindings_->CallClientMethod(
      "DevToolsAPI", "keyEventUnhandled", base::Value(std::move(event_data)));
  return true;
}

int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code,
                                                       int modifiers) {
  return key_code | (modifiers << 16);
}

bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code,
                                                    int modifiers) {
  return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) ||
         modifiers != 0;
}

void DevToolsWindow::OpenNodeFrontend() {
  DevToolsWindow::OpenNodeFrontendWindow(
      profile_, DevToolsOpenedByAction::kOpenForNodeFromAnotherTarget);
}

// DevToolsWindow::Throttle ------------------------------------------

class DevToolsWindow::Throttle : public content::NavigationThrottle {
 public:
  Throttle(content::NavigationThrottleRegistry& registry,
           DevToolsWindow* devtools_window)
      : content::NavigationThrottle(registry),
        devtools_window_(devtools_window) {
    devtools_window_->throttle_ = this;
  }

  Throttle(const Throttle&) = delete;
  Throttle& operator=(const Throttle&) = delete;

  ~Throttle() override {
    if (devtools_window_) {
      devtools_window_->throttle_ = nullptr;
    }
  }

  // content::NavigationThrottle implementation:
  NavigationThrottle::ThrottleCheckResult WillStartRequest() override {
    return DEFER;
  }

  const char* GetNameForLogging() override { return "DevToolsWindowThrottle"; }

  void ResumeThrottle() {
    if (devtools_window_) {
      devtools_window_->throttle_ = nullptr;
      devtools_window_ = nullptr;
    }
    Resume();
  }

 private:
  raw_ptr<DevToolsWindow> devtools_window_;
};

void DevToolsWindow::MainWebContentsObserver::RenderFrameHostChanged(
    content::RenderFrameHost* old_frame,
    content::RenderFrameHost* new_frame) {
  window_->MainWebContentRenderFrameHostChanged(old_frame, new_frame);
}

DevToolsWindow::MainWebContentsObserver::~MainWebContentsObserver() = default;

// Helper class that holds the owned main WebContents for the docked
// devtools window and maintains a keepalive object that keeps the browser
// main loop alive long enough for the WebContents to clean up properly.
class DevToolsWindow::OwnedMainWebContents {
 public:
  explicit OwnedMainWebContents(
      std::unique_ptr<content::WebContents> web_contents)
      :
#if !BUILDFLAG(IS_ANDROID)
        keep_alive_(KeepAliveOrigin::DEVTOOLS_WINDOW,
                    KeepAliveRestartOption::DISABLED),
#endif
        web_contents_(std::move(web_contents)) {
    Profile* profile = GetProfileForDevToolsWindow(web_contents_.get());
    DCHECK(profile);
    if (!profile->IsOffTheRecord()) {
      // ScopedProfileKeepAlive does not support OTR profiles.
      profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
          profile, ProfileKeepAliveOrigin::kDevToolsWindow);
    }
  }

  static std::unique_ptr<content::WebContents> TakeWebContents(
      std::unique_ptr<OwnedMainWebContents> instance) {
    return std::move(instance->web_contents_);
  }

 private:
#if !BUILDFLAG(IS_ANDROID)
  ScopedKeepAlive keep_alive_;
#endif
  std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
  std::unique_ptr<content::WebContents> web_contents_;
};

// DevToolsWindow -------------------------------------------------------------

const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp";

// static
void DevToolsWindow::AddCreationCallbackForTest(
    const CreationCallback& callback) {
  GetCreationCallbacks().push_back(callback);
}

// static
void DevToolsWindow::RemoveCreationCallbackForTest(
    const CreationCallback& callback) {
  if (auto it = std::ranges::find(GetCreationCallbacks(), callback);
      it != GetCreationCallbacks().end()) {
    GetCreationCallbacks().erase(it);
  }
}

DevToolsWindow::~DevToolsWindow() {
  if (throttle_) {
    throttle_->ResumeThrottle();
  }

  life_stage_ = kClosing;

  base::RecordAction(base::UserMetricsAction("DevTools_Close"));

  UpdateBrowserWindow();
  UpdateBrowserToolbar();

  if (sharing_infobar_) {
    sharing_infobar_->RemoveSelf();
  }

  capture_handle_.RunAndReset();
  owned_toolbox_web_contents_.reset();
#if !BUILDFLAG(IS_ANDROID)
  browser_list_observation_.Reset();
#endif

  DevToolsWindows& instances = GetDevToolsWindowInstances();
  auto it = std::ranges::find(instances, this);
  CHECK(it != instances.end());
  instances.erase(it);

  if (!close_callback_.is_null()) {
    std::move(close_callback_).Run();
  }
  // Defer deletion of the main web contents, since we could get here
  // via RenderFrameHostImpl method that expects WebContents to live
  // for some time. See http://crbug.com/997299 for details.
  if (owned_main_web_contents_) {
    base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
        FROM_HERE, std::move(owned_main_web_contents_));
  }

  // If window gets destroyed during a test run, need to stop the test.
  if (!ready_for_test_callback_.is_null()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(ready_for_test_callback_));
  }
}

// static
void DevToolsWindow::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterDictionaryPref(prefs::kDevToolsEditedFiles);
  registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths);
  registry->RegisterStringPref(prefs::kDevToolsAdbKey, std::string());
  registry->RegisterInt64Pref(prefs::kDevToolsLastOpenTimestamp, 0L);

  registry->RegisterBooleanPref(prefs::kDevToolsDiscoverUsbDevicesEnabled,
                                true);
  registry->RegisterBooleanPref(prefs::kDevToolsPortForwardingEnabled, false);
  registry->RegisterBooleanPref(prefs::kDevToolsPortForwardingDefaultSet,
                                false);
  registry->RegisterDictionaryPref(prefs::kDevToolsPortForwardingConfig);
  registry->RegisterBooleanPref(prefs::kDevToolsDiscoverTCPTargetsEnabled,
                                true);
  registry->RegisterListPref(prefs::kDevToolsTCPDiscoveryConfig);
  registry->RegisterDictionaryPref(prefs::kDevToolsPreferences);
  registry->RegisterBooleanPref(
      prefs::kDevToolsSyncPreferences,
      DevToolsSettings::kSyncDevToolsPreferencesDefault,
      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
  registry->RegisterDictionaryPref(
      prefs::kDevToolsSyncedPreferencesSyncEnabled,
      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
  registry->RegisterDictionaryPref(
      prefs::kDevToolsSyncedPreferencesSyncDisabled);
  registry->RegisterIntegerPref(
      prefs::kDevToolsGenAiSettings,
      static_cast<int>(DevToolsGenAiEnterprisePolicyValue::kAllow));
  registry->RegisterIntegerPref(
      prefs::kDevToolsGoogleDeveloperProgramProfileAvailability,
      /* enabled */ 0);
}

// static
content::WebContents* DevToolsWindow::GetInTabWebContents(
    WebContents* inspected_web_contents,
    DevToolsContentsResizingStrategy* out_strategy) {
  DevToolsWindow* window =
      GetInstanceForInspectedWebContents(inspected_web_contents);
  if (!window || window->life_stage_ == kClosing) {
    return nullptr;
  }

  // Not yet loaded window is treated as docked, but we should not present it
  // until we decided on docking.
  bool is_docked_set = window->life_stage_ == kLoadCompleted ||
                       window->life_stage_ == kIsDockedSet;
  if (!is_docked_set) {
    return nullptr;
  }

  // Undocked window should have toolbox web contents.
  if (!window->is_docked_ && !window->toolbox_web_contents_) {
    return nullptr;
  }

  if (out_strategy) {
    out_strategy->CopyFrom(window->contents_resizing_strategy_);
  }

  return window->is_docked_ ? window->main_web_contents_.get()
                            : window->toolbox_web_contents_.get();
}

// static
DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents(
    WebContents* inspected_web_contents) {
  if (!inspected_web_contents) {
    return nullptr;
  }
  for (auto& instance : GetDevToolsWindowInstances()) {
    if (instance->GetInspectedWebContents() == inspected_web_contents) {
      return instance;
    }
  }
  return nullptr;
}

// static
bool DevToolsWindow::IsDevToolsWindow(content::WebContents* web_contents) {
  if (!web_contents) {
    return false;
  }
  for (auto& instance : GetDevToolsWindowInstances()) {
    if (instance->main_web_contents_ == web_contents ||
        instance->toolbox_web_contents_ == web_contents) {
      return true;
    }
  }
  return false;
}

// static
void DevToolsWindow::OpenDevToolsWindowForWorker(
    Profile* profile,
    const scoped_refptr<DevToolsAgentHost>& worker_agent,
    DevToolsOpenedByAction opened_by) {
  DevToolsWindow* window = FindDevToolsWindow(worker_agent.get());
  if (!window) {
    base::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
    window = Create(profile, nullptr, kFrontendWorker, std::string(), false, "",
                    "", worker_agent->IsAttached(),
                    /* browser_connection */ true, opened_by);
    if (!window) {
      return;
    }
    window->bindings_->AttachViaBrowserTarget(worker_agent);
  }
  window->ScheduleShow(DevToolsToggleAction::Show());
}

// static
void DevToolsWindow::OpenDevToolsWindow(
    content::WebContents* inspected_web_contents,
    DevToolsOpenedByAction opened_by) {
  ToggleDevToolsWindow(inspected_web_contents, nullptr, true,
                       DevToolsToggleAction::Show(), "", opened_by);
}

// static
void DevToolsWindow::OpenDevToolsWindow(
    content::WebContents* inspected_web_contents,
    Profile* profile,
    DevToolsOpenedByAction opened_by,
    const content::DevToolsManagerDelegate::DevToolsOptions& devtools_options) {
  ToggleDevToolsWindow(inspected_web_contents, profile, true,
                       DevToolsToggleAction::Show(), "", opened_by,
                       devtools_options);
}

// static
void DevToolsWindow::OpenDevToolsWindow(
    scoped_refptr<content::DevToolsAgentHost> agent_host,
    Profile* profile,
    DevToolsOpenedByAction opened_by,
    const content::DevToolsManagerDelegate::DevToolsOptions& devtools_options) {
  OpenDevToolsWindow(agent_host, profile, false /* use_bundled_frontend */,
                     opened_by, devtools_options);
}

// static
void DevToolsWindow::OpenDevToolsWindowWithBundledFrontend(
    scoped_refptr<content::DevToolsAgentHost> agent_host,
    Profile* profile,
    DevToolsOpenedByAction opened_by) {
  OpenDevToolsWindow(agent_host, profile, true /* use_bundled_frontend */,
                     opened_by);
}

// static
void DevToolsWindow::OpenDevToolsWindow(
    scoped_refptr<content::DevToolsAgentHost> agent_host,
    Profile* profile,
    bool use_bundled_frontend,
    DevToolsOpenedByAction opened_by,
    const content::DevToolsManagerDelegate::DevToolsOptions& devtools_options) {
  if (!profile) {
    profile = Profile::FromBrowserContext(agent_host->GetBrowserContext());
  }

  if (!profile) {
    return;
  }

  std::string type = agent_host->GetType();

  bool is_worker = type == DevToolsAgentHost::kTypeServiceWorker ||
                   type == DevToolsAgentHost::kTypeSharedWorker ||
                   type == DevToolsAgentHost::kTypeSharedStorageWorklet;

  if (!agent_host->GetFrontendURL().empty()) {
    DevToolsWindow::OpenExternalFrontend(profile, agent_host->GetFrontendURL(),
                                         agent_host, use_bundled_frontend,
                                         opened_by);
    return;
  }

  if (is_worker) {
    DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host, opened_by);
    return;
  }

  if (type == content::DevToolsAgentHost::kTypeFrame) {
    DevToolsWindow::OpenDevToolsWindowForFrame(profile, agent_host, opened_by);
    return;
  }

  content::WebContents* web_contents = agent_host->GetWebContents();
  if (web_contents) {
    DevToolsWindow::OpenDevToolsWindow(web_contents, profile, opened_by,
                                       devtools_options);
  }
}

// static
void DevToolsWindow::OpenDevToolsWindow(
    content::WebContents* inspected_web_contents,
    const DevToolsToggleAction& action,
    DevToolsOpenedByAction opened_by) {
  ToggleDevToolsWindow(inspected_web_contents, nullptr, true, action, "",
                       opened_by);
}

// static
void DevToolsWindow::OpenDevToolsWindowForFrame(
    Profile* profile,
    const scoped_refptr<content::DevToolsAgentHost>& agent_host,
    DevToolsOpenedByAction opened_by) {
  DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
  if (!window) {
    window = DevToolsWindow::Create(
        profile, nullptr, kFrontendDefault, std::string(), false, std::string(),
        std::string(), agent_host->IsAttached(), false, opened_by);
    if (!window) {
      return;
    }
    window->bindings_->AttachTo(agent_host);
  }
  window->ScheduleShow(DevToolsToggleAction::Show());
}

// static
void DevToolsWindow::OpenExternalFrontend(
    Profile* profile,
    const std::string& frontend_url,
    const scoped_refptr<content::DevToolsAgentHost>& agent_host,
    bool use_bundled_frontend,
    DevToolsOpenedByAction opened_by) {
  DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
  if (window) {
    window->ScheduleShow(DevToolsToggleAction::Show());
    return;
  }

  std::string type = agent_host->GetType();
  if (type == "node") {
    // Direct node targets will always open using ToT front-end.
    window = Create(profile, nullptr, kFrontendV8, std::string(), false,
                    std::string(), std::string(), agent_host->IsAttached(),
                    /* browser_connection */ false, opened_by);
  } else {
    bool is_worker = type == DevToolsAgentHost::kTypeServiceWorker ||
                     type == DevToolsAgentHost::kTypeSharedWorker ||
                     type == DevToolsAgentHost::kTypeSharedStorageWorklet;

    FrontendType frontend_type =
        is_worker ? kFrontendRemoteWorker
                  : (type == "tab" ? kFrontendRemoteTab : kFrontendRemote);
    std::string effective_frontend_url =
        use_bundled_frontend ? kFallbackFrontendURL
                             : DevToolsUI::GetProxyURL(frontend_url).spec();
    window =
        Create(profile, nullptr, frontend_type, effective_frontend_url, false,
               std::string(), std::string(), agent_host->IsAttached(),
               /* browser_connection */ false, opened_by);
  }
  if (!window) {
    return;
  }
  window->bindings_->AttachTo(agent_host);
  window->close_on_detach_ = false;
  window->ScheduleShow(DevToolsToggleAction::Show());
}

// static
DevToolsWindow* DevToolsWindow::OpenNodeFrontendWindow(
    Profile* profile,
    DevToolsOpenedByAction opened_by) {
  for (DevToolsWindow* window : GetDevToolsWindowInstances()) {
    if (window->frontend_type_ == kFrontendNode) {
      window->ActivateWindow();
      return window;
    }
  }

  DevToolsWindow* window = Create(
      profile, nullptr, kFrontendNode, std::string(), false, std::string(),
      std::string(), false, /* browser_connection */ false, opened_by);
  if (!window) {
    return nullptr;
  }
  window->bindings_->AttachTo(DevToolsAgentHost::CreateForDiscovery());
  window->ScheduleShow(DevToolsToggleAction::Show());
  return window;
}

// static
Profile* DevToolsWindow::GetProfileForDevToolsWindow(
    content::WebContents* web_contents) {
  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  if (profile->IsPrimaryOTRProfile() || profile->IsDevToolsOTRProfile()) {
    return profile;
  }
  return profile->GetOriginalProfile();
}

// static
void DevToolsWindow::ToggleDevToolsWindow(
    content::WebContents* inspected_web_contents,
    Profile* profile,
    bool force_open,
    const DevToolsToggleAction& action,
    const std::string& settings,
    DevToolsOpenedByAction toggled_by,
    const content::DevToolsManagerDelegate::DevToolsOptions& devtools_options) {
  scoped_refptr<DevToolsAgentHost> agent(
      DevToolsAgentHost::GetOrCreateForTab(inspected_web_contents));
  DevToolsWindow* window = FindDevToolsWindow(agent.get());
  bool do_open = force_open;
  if (!window) {
    if (!profile) {
      profile = GetProfileForDevToolsWindow(inspected_web_contents);
    }
    base::RecordAction(base::UserMetricsAction("DevTools_InspectRenderer"));
    std::string panel;
    switch (action.type()) {
      case DevToolsToggleAction::kInspect:
      case DevToolsToggleAction::kShowElementsPanel:
        panel = "elements";
        break;
      case DevToolsToggleAction::kShowConsolePanel:
        panel = "console";
        break;
      case DevToolsToggleAction::kPauseInDebugger:
        panel = "sources";
        break;
      case DevToolsToggleAction::kShow:
        if (devtools_options.panel_id.has_value()) {
          panel = devtools_options.panel_id.value();
        }
        break;
      case DevToolsToggleAction::kToggle:
      case DevToolsToggleAction::kReveal:
      case DevToolsToggleAction::kNoOp:
        break;
    }
    window = Create(profile, inspected_web_contents, kFrontendDefault,
                    std::string(), true, settings, panel, agent->IsAttached(),
                    /* browser_connection */ false, toggled_by);
    if (!window) {
      if (base::FeatureList::IsEnabled(features::kDevToolsShowPolicyDialog)) {
#if !BUILDFLAG(IS_ANDROID)

        auto dialog_model =
            ui::DialogModel::Builder(
                std::make_unique<ui::DialogModelDelegate>())
                .SetTitle(l10n_util::GetStringUTF16(IDS_DEVTOOLS_NOT_ALLOWED))
                .AddParagraph(ui::DialogModelLabel(
                    l10n_util::GetStringUTF16(IDS_DEVTOOLS_BLOCKED_BY_POLICY)))
                .SetIcon(ui::ImageModel::FromVectorIcon(
                    vector_icons::kBusinessIcon, ui::kColorIcon,
                    extension_misc::EXTENSION_ICON_SMALL))
                .AddOkButton(base::DoNothing(),
                             ui::DialogModel::Button::Params().SetLabel(
                                 l10n_util::GetStringUTF16(IDS_OK)))
                .Build();
        chrome::ShowTabModal(std::move(dialog_model), inspected_web_contents);
#endif
      }
      return;
    }
    window->bindings_->AttachTo(agent.get());
    do_open = true;
    if (toggled_by != DevToolsOpenedByAction::kUnknown) {
      LogDevToolsOpenedByAction(toggled_by);
      LogDevToolsOpenedUKM(inspected_web_contents);
    }
  }

  // Update toolbar to reflect DevTools changes.
  window->UpdateBrowserToolbar();

  // If window is docked and visible, we hide it on toggle. If window is
  // undocked, we show (activate) it.
  if (!window->is_docked_ || do_open) {
    window->ScheduleShow(action);
  } else {
    DevToolsClosedByAction closed_by;
    switch (toggled_by) {
      case DevToolsOpenedByAction::kMainMenuOrMainShortcut:
        closed_by = DevToolsClosedByAction::kMainMenuOrMainShortcut;
        break;
      case DevToolsOpenedByAction::kToggleShortcut:
        closed_by = DevToolsClosedByAction::kToggleShortcut;
        break;
      case DevToolsOpenedByAction::kPinnedToolbarButton:
        closed_by = DevToolsClosedByAction::kPinnedToolbarButton;
        break;
      default:
        closed_by = DevToolsClosedByAction::kUnknown;
        break;
    }
    window->Close(closed_by);
  }
}

// static
void DevToolsWindow::InspectElement(
    content::RenderFrameHost* inspected_frame_host,
    int x,
    int y) {
  WebContents* web_contents =
      WebContents::FromRenderFrameHost(inspected_frame_host);
  scoped_refptr<DevToolsAgentHost> agent(
      DevToolsAgentHost::GetOrCreateForTab(web_contents));
  agent->InspectElement(inspected_frame_host, x, y);
  bool should_measure_time = !FindDevToolsWindow(agent.get());
  base::TimeTicks start_time = base::TimeTicks::Now();
  // TODO(loislo): we should initiate DevTools window opening from within
  // renderer. Otherwise, we still can hit a race condition here.
  OpenDevToolsWindow(web_contents, DevToolsToggleAction::ShowElementsPanel(),
                     DevToolsOpenedByAction::kContextMenuInspect);
  DevToolsWindow* window = FindDevToolsWindow(agent.get());
  if (window && should_measure_time) {
    window->inspect_element_start_time_ = start_time;
  }
}

// static
void DevToolsWindow::LogDevToolsOpenedByAction(
    DevToolsOpenedByAction opened_by) {
  base::UmaHistogramEnumeration("DevTools.OpenedByAction", opened_by);
}

// static
void DevToolsWindow::LogDevToolsOpenedUKM(content::WebContents* web_contents) {
  ukm::SourceId source_id =
      web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
  ukm::builders::DevTools_Opened(source_id).SetHasOccurred(true).Record(
      ukm::UkmRecorder::Get());
}

// static
void DevToolsWindow::MaybeCreateAndAddNavigationThrottle(
    content::NavigationThrottleRegistry& registry) {
  WebContents* web_contents = registry.GetNavigationHandle().GetWebContents();
  if (!web_contents || !web_contents->HasLiveOriginalOpenerChain() ||
      !web_contents->GetController()
           .GetLastCommittedEntry()
           ->IsInitialEntry()) {
    return;
  }

  WebContents* opener = registry.GetNavigationHandle()
                            .GetWebContents()
                            ->GetFirstWebContentsInLiveOriginalOpenerChain();
  DevToolsWindow* window = GetInstanceForInspectedWebContents(opener);
  if (!window || !window->open_new_window_for_popups_ ||
      GetInstanceForInspectedWebContents(web_contents)) {
    return;
  }

  DevToolsWindow::OpenDevToolsWindow(
      web_contents, DevToolsOpenedByAction::kAutomaticForNewTarget);
  window = GetInstanceForInspectedWebContents(web_contents);
  if (!window) {
    return;
  }

  registry.AddThrottle(std::make_unique<Throttle>(registry, window));
}

void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) {
  if (life_stage_ == kLoadCompleted) {
    Show(action);
    return;
  }

  // Action will be done only after load completed.
  action_on_load_ = action;

  if (!can_dock_) {
    // No harm to show always-undocked window right away.
    is_docked_ = false;
    Show(DevToolsToggleAction::Show());
  }
}

void DevToolsWindow::Show(const DevToolsToggleAction& action) {
  if (life_stage_ == kClosing) {
    return;
  }

  if (action.type() == DevToolsToggleAction::kNoOp) {
    return;
  }

#if BUILDFLAG(IS_ANDROID)
  if (TabModelList::models().empty()) {
    return;
  }

  TabModel* tab_model = TabModelList::models()[0];
  if (!tab_model) {
    return;
  }

  if (!owned_main_web_contents_) {
    return;
  }
  // TODO(crbug.com/406406862): Show it in a web app window instead of a tab.
  tab_model->CreateTab(
      nullptr,
      OwnedMainWebContents::TakeWebContents(std::move(owned_main_web_contents_))
          .release(),
      true);
  OverrideAndSyncDevToolsRendererPrefs();
#else
  if (is_docked_) {
    DCHECK(can_dock_);
    content::WebContents* inspected_web_contents = GetInspectedWebContents();
    DCHECK(inspected_web_contents);
    Browser* inspected_browser =
        chrome::FindBrowserWithTab(inspected_web_contents);
    DCHECK(inspected_browser);

    RegisterModalDialogManager(inspected_browser);

    // Tell inspected browser to update splitter and switch to inspected panel.
    BrowserWindow* inspected_window = inspected_browser->window();
    main_web_contents_->SetDelegate(this);

    // Inspected tab needs to be activated, however, this cannot be done from
    // here because TabStripModel does not allow reentrancy for its public
    // methods, see http://crbug.com/452538043.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&DevToolsWindow::ActivateInspectedTab,
                                  weak_factory_.GetWeakPtr()));

    inspected_window->UpdateDevTools(inspected_web_contents);
    main_web_contents_->SetInitialFocus();
    inspected_window->Show();
    // On Aura, focusing once is not enough. Do it again.
    // Note that focusing only here but not before isn't enough either. We just
    // need to focus twice.
    main_web_contents_->SetInitialFocus();

    PrefsTabHelper::CreateForWebContents(main_web_contents_);
    OverrideAndSyncDevToolsRendererPrefs();

    MaybeShowSharedProcessInfobar();

    DoAction(action);
    return;
  }

  // Avoid consecutive window switching if the devtools window has been opened
  // and the Inspect Element shortcut is pressed in the inspected tab.
  bool should_show_window =
      !browser_ || (action.type() != DevToolsToggleAction::kInspect);

  if (!browser_) {
    CreateDevToolsBrowser();
  }

  // Ignore action if browser does not exist and could not be created.
  if (!browser_) {
    return;
  }

  RegisterModalDialogManager(browser_);
  MaybeShowSharedProcessInfobar();

  if (should_show_window) {
    browser_->GetWindow()->Show();
    main_web_contents_->SetInitialFocus();
  }
  if (toolbox_web_contents_) {
    UpdateBrowserWindow();
  }
#endif
  DoAction(action);
}

#if !BUILDFLAG(IS_ANDROID)
void DevToolsWindow::ActivateInspectedTab() {
  content::WebContents* inspected_web_contents = GetInspectedWebContents();
  if (!inspected_web_contents) {
    return;
  }

  Browser* inspected_browser =
      chrome::FindBrowserWithTab(inspected_web_contents);
  if (!inspected_browser) {
    return;
  }

  TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
  if (tab_strip_model->GetActiveWebContents() != inspected_web_contents) {
    int inspected_tab_index =
        tab_strip_model->GetIndexOfWebContents(inspected_web_contents);
    CHECK_NE(TabStripModel::kNoTab, inspected_tab_index);

    tab_strip_model->ActivateTabAt(
        inspected_tab_index,
        TabStripUserGestureDetails(
            TabStripUserGestureDetails::GestureType::kOther));
  }
}
#endif  // !BUILDFLAG(IS_ANDROID)

// static
bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents,
                                        bool proceed,
                                        bool* proceed_to_fire_unload) {
  DevToolsWindow* window = AsDevToolsWindow(frontend_contents);
  if (!window) {
    return false;
  }
  if (!window->intercepted_page_beforeunload_) {
    return false;
  }
  window->BeforeUnloadFired(frontend_contents, proceed, proceed_to_fire_unload);
  return true;
}

// static
bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
  DevToolsWindow* window =
      DevToolsWindow::GetInstanceForInspectedWebContents(contents);
  if (!window || window->intercepted_page_beforeunload_) {
    return false;
  }

  // Not yet loaded frontend will not handle beforeunload.
  if (window->life_stage_ != kLoadCompleted) {
    return false;
  }

  window->intercepted_page_beforeunload_ = true;
  // Handle case of devtools inspecting another devtools instance by passing
  // the call up to the inspecting devtools instance.
  // TODO(chrisha): Make devtools handle |auto_cancel=false| unload handler
  // dispatches; otherwise, discarding queries can cause unload dialogs to
  // pop-up for tabs with an attached devtools.
  if (!DevToolsWindow::InterceptPageBeforeUnload(window->main_web_contents_)) {
    window->main_web_contents_->DispatchBeforeUnload(false /* auto_cancel */);
  }
  return true;
}

// static
bool DevToolsWindow::NeedsToInterceptBeforeUnload(WebContents* contents) {
  DevToolsWindow* window =
      DevToolsWindow::GetInstanceForInspectedWebContents(contents);
  return window && !window->intercepted_page_beforeunload_ &&
         window->life_stage_ == kLoadCompleted;
}

// static
void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) {
  DevToolsWindow* window =
      DevToolsWindow::GetInstanceForInspectedWebContents(contents);
  if (!window) {
    return;
  }
  window->intercepted_page_beforeunload_ = false;
  // Propagate to devtools opened on devtools if any.
  DevToolsWindow::OnPageCloseCanceled(window->main_web_contents_);
}

DevToolsWindow::DevToolsWindow(FrontendType frontend_type,
                               Profile* profile,
                               std::unique_ptr<WebContents> main_web_contents,
                               DevToolsUIBindings* bindings,
                               WebContents* inspected_web_contents,
                               bool can_dock,
                               DevToolsOpenedByAction opened_by)
    : frontend_type_(frontend_type),
      profile_(profile),
      main_web_contents_(main_web_contents.get()),
      main_web_contents_observer_(*main_web_contents_, *this),
      toolbox_web_contents_(nullptr),
      bindings_(bindings),
      browser_(nullptr),
      is_docked_(true),
      owned_main_web_contents_(
          std::make_unique<OwnedMainWebContents>(std::move(main_web_contents))),
      can_dock_(can_dock),
      close_on_detach_(true),
      // This initialization allows external front-end to work without changes.
      // We don't wait for docking call, but instead immediately show undocked.
      life_stage_(can_dock ? kNotLoaded : kIsDockedSet),
      action_on_load_(DevToolsToggleAction::NoOp()),
      intercepted_page_beforeunload_(false),
      ready_for_test_(false),
      opened_by_(opened_by),
      closed_by_(DevToolsClosedByAction::kUnknown) {
  // Set up delegate, so we get fully-functional window immediately.
  // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
  main_web_contents_->SetDelegate(this);
  // Bindings take ownership over devtools as its delegate.
  bindings_->SetDelegate(this);
  // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
  // ZoomController.
  zoom::ZoomController::CreateForWebContents(main_web_contents_);
  zoom::ZoomController::FromWebContents(main_web_contents_)
      ->SetShowsNotificationBubble(false);

  GetDevToolsWindowInstances().push_back(this);

  // There is no inspected_web_contents in case of various workers.
  if (inspected_web_contents) {
    Observe(inspected_web_contents);
  }

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  extensions::SetViewType(main_web_contents_,
                          extensions::mojom::ViewType::kDeveloperTools);
#endif

  // Initialize docked page to be of the right size.
  if (can_dock_ && inspected_web_contents) {
    content::RenderWidgetHostView* inspected_view =
        inspected_web_contents->GetRenderWidgetHostView();
    if (inspected_view && main_web_contents_->GetRenderWidgetHostView()) {
      gfx::Size size = inspected_view->GetViewBounds().size();
      main_web_contents_->GetRenderWidgetHostView()->SetSize(size);
    }
  }

  event_forwarder_ = std::make_unique<DevToolsEventForwarder>(this);

  // Tag the DevTools main WebContents with its TaskManager specific UserData
  // so that it shows up in the task manager.
  task_manager::WebContentsTags::CreateForDevToolsContents(main_web_contents_);

  std::vector<base::RepeatingCallback<void(DevToolsWindow*)>> copy(
      GetCreationCallbacks());
  for (const auto& callback : copy) {
    callback.Run(this);
  }

  pref_change_registrar_.Init(profile_->GetPrefs());
  pref_change_registrar_.Add(
      language::prefs::kAcceptLanguages,
      base::BindRepeating(&DevToolsWindow::OnLocaleChanged,
                          base::Unretained(this)));
  pref_change_registrar_.Add(
      prefs::kDeveloperToolsAvailabilityAllowlist,
      base::BindRepeating(&DevToolsWindow::OnDevToolsPolicyChanged,
                          base::Unretained(this)));
  pref_change_registrar_.Add(
      prefs::kDeveloperToolsAvailabilityBlocklist,
      base::BindRepeating(&DevToolsWindow::OnDevToolsPolicyChanged,
                          base::Unretained(this)));

  int64_t now_timestamp =
      base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds();
  profile_->GetPrefs()->SetInt64(prefs::kDevToolsLastOpenTimestamp,
                                 now_timestamp);

  policy::DeveloperToolsPolicyChecker* checker =
      policy::DeveloperToolsPolicyCheckerFactory::GetForBrowserContext(
          profile_);
  if (checker) {
    policy_checker_callback_subscription_ =
        checker->AddObserver(base::BindRepeating(
            &DevToolsWindow::OnDevToolsPolicyChanged, base::Unretained(this)));
  }
}

void DevToolsWindow::OnDevToolsPolicyChanged() {
  if (!AllowDevToolsFor(profile_, GetInspectedWebContents())) {
    CloseWindow();
  }
}

void DevToolsWindow::OnPolicyUpdated(const policy::PolicyNamespace& ns,
                                     const policy::PolicyMap& previous,
                                     const policy::PolicyMap& current) {
  OnDevToolsPolicyChanged();
}

// static
bool DevToolsWindow::AllowDevToolsFor(Profile* profile,
                                      content::WebContents* web_contents) {
  // Don't allow DevTools UI in kiosk mode, because the DevTools UI would be
  // broken there. See https://crbug.com/514551 for context.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) {
    return false;
  }
  return IsInspectionAllowed(profile, web_contents);
}

// static
DevToolsWindow* DevToolsWindow::Create(
    Profile* profile,
    content::WebContents* inspected_web_contents,
    FrontendType frontend_type,
    const std::string& frontend_url,
    bool can_dock,
    const std::string& settings,
    const std::string& panel,
    bool has_other_clients,
    bool browser_connection,
    DevToolsOpenedByAction opened_by) {
  if (!AllowDevToolsFor(profile, inspected_web_contents)) {
    return nullptr;
  }

#if BUILDFLAG(IS_ANDROID)
  // Docking is not supported yet.
  can_dock = false;
#else
  if (inspected_web_contents) {
    // Check for a place to dock.
    Browser* browser = chrome::FindBrowserWithTab(inspected_web_contents);
    if (!browser || !browser->window()->CanDockDevTools()) {
      can_dock = false;
    }

#if BUILDFLAG(ENABLE_CEF)
    if (can_dock && browser && browser->cef_delegate()) {
      // Don't dock DevTools for CEF-managed browsers.
      can_dock = false;
    }
#endif
  }
#endif

  // Create WebContents with devtools.
  GURL url(GetDevToolsURL(profile, frontend_type, frontend_url, can_dock, panel,
                          has_other_clients, browser_connection));
  std::unique_ptr<WebContents> main_web_contents =
      WebContents::Create(WebContents::CreateParams(profile));
  main_web_contents->GetController().LoadURL(
      DecorateFrontendURL(url), content::Referrer(),
      ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
  DevToolsUIBindings* bindings =
      DevToolsUIBindings::ForWebContents(main_web_contents.get());

  if (!bindings) {
    return nullptr;
  }
  if (!settings.empty()) {
    SetPreferencesFromJson(profile, settings);
  }
  return new DevToolsWindow(frontend_type, profile,
                            std::move(main_web_contents), bindings,
                            inspected_web_contents, can_dock, opened_by);
}

// static
GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
                                    FrontendType frontend_type,
                                    const std::string& frontend_url,
                                    bool can_dock,
                                    const std::string& panel,
                                    bool has_other_clients,
                                    bool browser_connection) {
  std::string url;

  std::string remote_base =
      "?remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();

  const std::string valid_frontend =
      frontend_url.empty() ? chrome::kChromeUIDevToolsURL : frontend_url;

  // remoteFrontend is here for backwards compatibility only.
  std::string remote_frontend =
      valid_frontend + ((valid_frontend.find("?") == std::string::npos)
                            ? "?remoteFrontend=true"
                            : "&remoteFrontend=true");
  std::string tab_target = "&targetType=tab";
  switch (frontend_type) {
    case kFrontendDefault:
      url = kDefaultFrontendURL + remote_base + tab_target;
      if (can_dock) {
        url += "&can_dock=true";
      }
      if (!panel.empty()) {
        url += "&panel=" + panel;
      }
      break;
    case kFrontendWorker:
      url = kWorkerFrontendURL + remote_base;
      break;
    case kFrontendV8:
      url = kJSFrontendURL + remote_base;
      break;
    case kFrontendNode:
      url = kNodeFrontendURL + remote_base;
      break;
    case kFrontendRemote:
      url = remote_frontend;
      break;
    case kFrontendRemoteWorker:
      // isSharedWorker is here for backwards compatibility only.
      url = remote_frontend + "&isSharedWorker=true";
      break;
    case kFrontendRemoteTab:
      url = remote_frontend + tab_target;
      break;
  }

  if (has_other_clients) {
    url += "&hasOtherClients=true";
  }
  if (browser_connection) {
    url += "&browserConnection=true";
  }

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kUnsafelyDisableDevToolsSelfXssWarnings)) {
    url += "&disableSelfXssWarnings=true";
  }

#if BUILDFLAG(CHROME_FOR_TESTING)
  url += "&isChromeForTesting=true";
#endif

  return DevToolsUIBindings::SanitizeFrontendURL(GURL(url));
}

// static
DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
    DevToolsAgentHost* agent_host) {
  if (!agent_host) {
    return nullptr;
  }
  for (auto& instance : GetDevToolsWindowInstances()) {
    if (instance->bindings_->IsAttachedTo(agent_host)) {
      return instance;
    }
  }
  return nullptr;
}

// static
DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
    content::WebContents* web_contents) {
  if (!web_contents) {
    return nullptr;
  }
  for (auto& instance : GetDevToolsWindowInstances()) {
    if (instance->main_web_contents_ == web_contents) {
      return instance;
    }
  }
  return nullptr;
}

WebContents* DevToolsWindow::OpenURLFromTab(
    WebContents* source,
    const content::OpenURLParams& params,
    base::OnceCallback<void(content::NavigationHandle&)>
        navigation_handle_callback) {
  DCHECK(source == main_web_contents_);
  if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
    // TODO(https://crbug.com/40275094): Plumb the `navigation_handle_callback`.
    return OpenURLFromInspectedTab(params);
  }
  // TODO(https://crbug.com/40275094): Plumb the `navigation_handle_callback`.
  main_web_contents_->GetController().Reload(content::ReloadType::NORMAL,
                                             false);
  return main_web_contents_;
}

WebContents* DevToolsWindow::OpenURLFromInspectedTab(
    const content::OpenURLParams& params) {
  WebContents* inspected_web_contents = GetInspectedWebContents();
  if (!inspected_web_contents) {
    return nullptr;
  }
  content::OpenURLParams modified = params;
  modified.referrer = content::Referrer();
  return inspected_web_contents->OpenURL(modified,
                                         /*navigation_handle_callback=*/{});
}

void DevToolsWindow::ActivateContents(WebContents* contents) {
  if (is_docked_) {
    WebContents* inspected_tab = GetInspectedWebContents();
    if (inspected_tab) {
      inspected_tab->GetDelegate()->ActivateContents(inspected_tab);
    }
  } else {
#if BUILDFLAG(IS_ANDROID)
    NOTIMPLEMENTED();
#else
    if (browser_) {
      browser_->GetWindow()->Activate();
    }
#endif
  }
}

WebContents* DevToolsWindow::AddNewContents(
    WebContents* source,
    std::unique_ptr<WebContents> new_contents,
    const GURL& target_url,
    WindowOpenDisposition disposition,
    const blink::mojom::WindowFeatures& window_features,
    bool user_gesture,
    bool* was_blocked) {
  if (new_contents.get() == toolbox_web_contents_) {
    owned_toolbox_web_contents_ = std::move(new_contents);
    owned_toolbox_web_contents_->SetOwnerLocationForDebug(FROM_HERE);

    toolbox_web_contents_->SetDelegate(
        new DevToolsToolboxDelegate(toolbox_web_contents_, web_contents()));
    if (main_web_contents_->GetRenderWidgetHostView() &&
        toolbox_web_contents_->GetRenderWidgetHostView()) {
      gfx::Size size =
          main_web_contents_->GetRenderWidgetHostView()->GetViewBounds().size();
      toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size);
    }
    UpdateBrowserWindow();
    return nullptr;
  }

  WebContents* inspected_web_contents = GetInspectedWebContents();
  if (inspected_web_contents) {
    inspected_web_contents->GetDelegate()->AddNewContents(
        source, std::move(new_contents), target_url, disposition,
        window_features, user_gesture, was_blocked);
  }
  return nullptr;
}

void DevToolsWindow::WebContentsCreated(WebContents* source_contents,
                                        int opener_render_process_id,
                                        int opener_render_frame_id,
                                        const std::string& frame_name,
                                        const GURL& target_url,
                                        WebContents* new_contents) {
  if (target_url.SchemeIs(content::kChromeDevToolsScheme) &&
      target_url.GetPath().rfind("device_mode_emulation_frame.html") !=
          std::string::npos) {
    CHECK(can_dock_);

    // Ownership will be passed in DevToolsWindow::AddNewContents.
    capture_handle_.RunAndReset();
    if (owned_toolbox_web_contents_) {
      owned_toolbox_web_contents_.reset();
    }
    toolbox_web_contents_ = new_contents;

    // Tag the DevTools toolbox WebContents with its TaskManager specific
    // UserData so that it shows up in the task manager.
    task_manager::WebContentsTags::CreateForDevToolsContents(
        toolbox_web_contents_);

    // The toolbox holds a placeholder for the inspected WebContents. When the
    // placeholder is resized, a frame is requested. The inspected WebContents
    // is resized when the frame is rendered. Force rendering of the toolbox at
    // all times, to make sure that a frame can be rendered even when the
    // inspected WebContents fully covers the toolbox. https://crbug.com/828307
    capture_handle_ = toolbox_web_contents_->IncrementCapturerCount(
        gfx::Size(),
        /*stay_hidden=*/false,
        /*stay_awake=*/false, /*is_activity=*/true);
  }
}

void DevToolsWindow::CloseContents(WebContents* source) {
  // We shouldn't get here as long as we're owned by the browser.
  CHECK(!browser_);
  life_stage_ = kClosing;
  UpdateBrowserWindow();
  // In case of docked main_web_contents_, we own it so delete here.
  // Embedding DevTools window will be deleted as a result of
  // DevToolsUIBindings destruction.
  CHECK(owned_main_web_contents_);
  owned_main_web_contents_.reset();
}

void DevToolsWindow::ContentsZoomChange(bool zoom_in) {
  DCHECK(is_docked_);
  zoom::PageZoom::Zoom(main_web_contents_, zoom_in ? content::PAGE_ZOOM_IN
                                                   : content::PAGE_ZOOM_OUT);
}

void DevToolsWindow::BeforeUnloadFired(WebContents* tab,
                                       bool proceed,
                                       bool* proceed_to_fire_unload) {
  if (!intercepted_page_beforeunload_) {
    // Docked devtools window closed directly.
    if (proceed) {
      bindings_->Detach();
    }
    *proceed_to_fire_unload = proceed;
  } else {
    // Inspected page is attempting to close.
    WebContents* inspected_web_contents = GetInspectedWebContents();
    if (proceed) {
      inspected_web_contents->DispatchBeforeUnload(false /* auto_cancel */);
    } else {
      bool should_proceed;
      inspected_web_contents->GetDelegate()->BeforeUnloadFired(
          inspected_web_contents, false, &should_proceed);
      DCHECK(!should_proceed);
    }
    *proceed_to_fire_unload = false;
  }
}

content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager(
    WebContents* source) {
  return javascript_dialogs::AppModalDialogManager::GetInstance();
}

void DevToolsWindow::RunFileChooser(
    content::RenderFrameHost* render_frame_host,
    scoped_refptr<content::FileSelectListener> listener,
    const blink::mojom::FileChooserParams& params) {
  FileSelectHelper::RunFileChooser(render_frame_host, std::move(listener),
                                   params);
}

bool DevToolsWindow::PreHandleGestureEvent(
    WebContents* source,
    const blink::WebGestureEvent& event) {
  // Disable pinch zooming.
  return blink::WebInputEvent::IsPinchGestureEventType(event.GetType());
}

void DevToolsWindow::ActivateWindow() {
  if (life_stage_ != kLoadCompleted) {
    return;
  }
#if BUILDFLAG(IS_ANDROID)
  NOTIMPLEMENTED();
#else
  if (is_docked_ && GetInspectedBrowserWindow()) {
    main_web_contents_->Focus();
  } else if (!is_docked_ && browser_ && !browser_->GetWindow()->IsActive()) {
    browser_->GetWindow()->Activate();
  }
#endif
}

void DevToolsWindow::CloseWindow() {
  Close(DevToolsClosedByAction::kCloseButton);
}

void DevToolsWindow::Close(DevToolsClosedByAction closed_by) {
  DCHECK(is_docked_);
  life_stage_ = kClosing;
  main_web_contents_->DispatchBeforeUnload(false /* auto_cancel */);
  closed_by_ = closed_by;

  if (sharing_infobar_) {
    sharing_infobar_->RemoveSelf();
    checked_sharing_process_id_ = content::ChildProcessHost::kInvalidUniqueID;
  }
}

void DevToolsWindow::Inspect(scoped_refptr<content::DevToolsAgentHost> host) {
  DevToolsWindow::OpenDevToolsWindow(host, profile_,
                                     DevToolsOpenedByAction::kUnknown);
}

void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect& rect) {
  DevToolsContentsResizingStrategy strategy(rect);
  if (contents_resizing_strategy_.Equals(strategy)) {
    return;
  }

  contents_resizing_strategy_.CopyFrom(strategy);
  UpdateBrowserWindow();
}

void DevToolsWindow::InspectElementCompleted() {
  if (!inspect_element_start_time_.is_null()) {
    UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
                        base::TimeTicks::Now() - inspect_element_start_time_);
    inspect_element_start_time_ = base::TimeTicks();
  }
}

void DevToolsWindow::SetIsDocked(bool dock_requested) {
  if (life_stage_ == kClosing) {
    return;
  }

  DCHECK(can_dock_ || !dock_requested);
  if (!can_dock_) {
    dock_requested = false;
  }

  bool was_docked = is_docked_;
  is_docked_ = dock_requested;

  if (life_stage_ != kLoadCompleted) {
    // This is a first time call we waited for to initialize.
    life_stage_ = life_stage_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
    if (life_stage_ == kLoadCompleted) {
      LoadCompleted();
    }
    return;
  }

  if (dock_requested == was_docked) {
    return;
  }

#if BUILDFLAG(IS_ANDROID)
  LOG_IF(WARNING, dock_requested) << "Docking is not supported yet.";
#else
  if (dock_requested && !was_docked && browser_) {
    // Detach window from the external devtools browser. It will lead to
    // the browser object's close and delete. Remove observer first.
    TabStripModel* const tab_strip_model = browser_->GetTabStripModel();
    DCHECK(!owned_main_web_contents_);

    // Removing the only WebContents from the tab strip of browser_ will
    // eventually lead to the destruction of browser_ as well, which is why it's
    // okay to just null the raw pointer here.
    browser_ = nullptr;

    // TODO(crbug.com/40773744): WebContents should be removed with a reason
    // other than kInsertedIntoOtherTabStrip, it's not getting reinserted into
    // another tab strip.
    std::unique_ptr<WebContents> web_contents =
        tab_strip_model->DetachWebContentsAtForInsertion(
            tab_strip_model->GetIndexOfWebContents(main_web_contents_));
    owned_main_web_contents_ =
        std::make_unique<OwnedMainWebContents>(std::move(web_contents));
  } else if (!dock_requested && was_docked) {
    UpdateBrowserWindow();
  }
#endif

  Show(DevToolsToggleAction::Show());
}

int DevToolsWindow::GetDockStateForLogging() {
  const int kUndocked = 0;
  const int kLeft = 1;
  const int kBottom = 2;
  const int kRight = 3;
  if (!is_docked_) {
    return kUndocked;
  }

  gfx::Rect inspected_page_bounds = contents_resizing_strategy_.bounds();
  if (inspected_page_bounds.x() > 0) {
    return kLeft;
  }
  gfx::Rect devtools_bounds =
      main_web_contents_->GetRenderWidgetHostView()->GetViewBounds();
  return inspected_page_bounds.width() == devtools_bounds.width() ? kBottom
                                                                  : kRight;
}

int DevToolsWindow::GetOpenedByForLogging() {
  return static_cast<int>(opened_by_);
}

int DevToolsWindow::GetClosedByForLogging() {
  return static_cast<int>(closed_by_);
}

void DevToolsWindow::OpenInNewTab(const GURL& url) {
  GURL fixed_url = url;
  WebContents* inspected_web_contents = GetInspectedWebContents();
  int child_id = content::ChildProcessHost::kInvalidUniqueID;
  if (inspected_web_contents) {
    content::RenderViewHost* render_view_host =
        inspected_web_contents->GetPrimaryMainFrame()->GetRenderViewHost();
    if (render_view_host) {
      child_id = render_view_host->GetProcess()->GetDeprecatedID();
    }
  }
  // Use about:blank instead of an empty GURL. The browser treats an empty GURL
  // as navigating to the home page, which may be privileged (chrome://newtab/).
  if (!content::ChildProcessSecurityPolicy::GetInstance()->CanRequestURL(
          child_id, fixed_url)) {
    fixed_url = GURL(url::kAboutBlankURL);
  }
  content::OpenURLParams params(fixed_url, content::Referrer(),
                                WindowOpenDisposition::NEW_FOREGROUND_TAB,
                                ui::PAGE_TRANSITION_LINK, false);
  if (!inspected_web_contents ||
      !inspected_web_contents->OpenURL(params,
                                       /*navigation_handle_callback=*/{})) {
#if BUILDFLAG(IS_ANDROID)
    NOTIMPLEMENTED();
#elif !BUILDFLAG(ENABLE_CEF)
    // Remove default behavior when CEF handles the open via OnOpenURLFromTab.
    // See CEF issue #3735.
    chrome::ScopedTabbedBrowserDisplayer displayer(profile_);
    chrome::AddSelectedTabWithURL(displayer.browser(), fixed_url,
                                  ui::PAGE_TRANSITION_LINK);
#endif
  }
}

void DevToolsWindow::OpenInNewTab(const std::string& url) {
  OpenInNewTab(GURL(url));
}

void DevToolsWindow::OpenSearchResultsInNewTab(const std::string& query) {
  TemplateURLService* url_service =
      TemplateURLServiceFactory::GetForProfile(profile_);
  DCHECK(url_service);
  GURL url =
      GetDefaultSearchURLForSearchTerms(url_service, base::UTF8ToUTF16(query));
  OpenInNewTab(url);
}

void DevToolsWindow::SetWhitelistedShortcuts(const std::string& message) {
  event_forwarder_->SetWhitelistedShortcuts(message);
}

void DevToolsWindow::SetEyeDropperActive(bool active) {
  WebContents* web_contents = GetInspectedWebContents();
  if (!web_contents) {
    return;
  }
  if (active) {
    eye_dropper_ = std::make_unique<DevToolsEyeDropper>(
        web_contents,
        base::BindRepeating(&DevToolsWindow::ColorPickedInEyeDropper,
                            base::Unretained(this)));
  } else {
    eye_dropper_.reset();
  }
}

void DevToolsWindow::ColorPickedInEyeDropper(int r, int g, int b, int a) {
  base::Value::Dict color;
  color.Set("r", r);
  color.Set("g", g);
  color.Set("b", b);
  color.Set("a", a);
  bindings_->CallClientMethod("DevToolsAPI", "eyeDropperPickedColor",
                              base::Value(std::move(color)));
}

void DevToolsWindow::InspectedContentsClosing() {
  if (!close_on_detach_) {
    return;
  }
  closed_by_ = DevToolsClosedByAction::kTargetDetach;
  intercepted_page_beforeunload_ = false;
  life_stage_ = kClosing;
  main_web_contents_->ClosePage();
}

infobars::ContentInfoBarManager* DevToolsWindow::GetInfoBarManager() {
  return is_docked_ ? infobars::ContentInfoBarManager::FromWebContents(
                          GetInspectedWebContents())
                    : infobars::ContentInfoBarManager::FromWebContents(
                          main_web_contents_);
}

void DevToolsWindow::RenderProcessGone(bool crashed) {
  // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
  // Undocked main_web_contents_ are owned and handled by browser.
  // see crbug.com/369932
  if (is_docked_) {
    CloseContents(main_web_contents_);
  } else {
#if !BUILDFLAG(IS_ANDROID)
    if (browser_ && crashed) {
      browser_->GetWindow()->Close();
    }
#endif
  }
}

void DevToolsWindow::ShowCertificateViewer(const std::string& cert_chain) {
#if BUILDFLAG(ENABLE_WEBUI_CERTIFICATE_VIEWER)
  std::optional<base::Value> value =
      base::JSONReader::Read(cert_chain, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  CHECK(value && value->is_list());
  std::vector<std::string> decoded;
  for (const auto& item : value->GetList()) {
    CHECK(item.is_string());
    std::string temp;
    CHECK(base::Base64Decode(item.GetString(), &temp));
    decoded.push_back(std::move(temp));
  }

  std::vector<std::string_view> cert_string_piece;
  for (const auto& str : decoded) {
    cert_string_piece.push_back(str);
  }
  scoped_refptr<net::X509Certificate> cert =
      net::X509Certificate::CreateFromDERCertChain(cert_string_piece);
  CHECK(cert);

  WebContents* inspected_contents =
      is_docked_ ? GetInspectedWebContents() : main_web_contents_.get();
  if (!inspected_contents) {
    return;
  }
#if BUILDFLAG(IS_ANDROID)
  NOTIMPLEMENTED();
#else
  Browser* browser = chrome::FindBrowserWithTab(inspected_contents);
  if (!browser) {
    return;
  }
  gfx::NativeWindow parent = browser->window()->GetNativeWindow();
  ::ShowCertificateViewer(inspected_contents, parent, cert.get());
#endif
#endif  // BUILDFLAG(ENABLE_WEBUI_CERTIFICATE_VIEWER)
}

void DevToolsWindow::OnLoadCompleted() {
  // First seed inspected tab id for extension APIs.
  WebContents* inspected_web_contents = GetInspectedWebContents();
  if (inspected_web_contents) {
    sessions::SessionTabHelper* session_tab_helper =
        sessions::SessionTabHelper::FromWebContents(inspected_web_contents);
    if (session_tab_helper) {
      bindings_->CallClientMethod(
          "DevToolsAPI", "setInspectedTabId",
          base::Value(session_tab_helper->session_id().id()));
    }
  }

  if (life_stage_ == kClosing) {
    return;
  }

  // We could be in kLoadCompleted state already if frontend reloads itself.
  if (life_stage_ != kLoadCompleted) {
    // Load is completed when both kIsDockedSet and kOnLoadFired happened.
    // Here we set kOnLoadFired.
    life_stage_ = life_stage_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired;
  }
  if (life_stage_ == kLoadCompleted) {
    LoadCompleted();
  }
}

void DevToolsWindow::ReadyForTest() {
  ready_for_test_ = true;
  if (!ready_for_test_callback_.is_null()) {
    std::move(ready_for_test_callback_).Run();
  }
}

void DevToolsWindow::ConnectionReady() {
  if (throttle_) {
    throttle_->ResumeThrottle();
  }
}

void DevToolsWindow::SetOpenNewWindowForPopups(bool value) {
  open_new_window_for_popups_ = value;
}

void DevToolsWindow::CreateDevToolsBrowser() {
  PrefService* prefs = profile_->GetPrefs();
  bool resetPrefs = false;
  if (!prefs->GetDict(prefs::kAppWindowPlacement).Find(kDevToolsApp)) {
    // Ensure there is always a default size so that
    // BrowserWidget::InitBrowserFrame can retrieve it later.
    resetPrefs = true;
  } else {
    // Reset to default if stored window size is too small.
    const base::Value::Dict& devtoolsPlacement =
        prefs->GetDict(prefs::kAppWindowPlacement)
            .Find(kDevToolsApp)
            ->GetDict();
    const int right = devtoolsPlacement.FindInt("right").value();
    const int left = devtoolsPlacement.FindInt("left").value();
    const int top = devtoolsPlacement.FindInt("top").value();
    const int bottom = devtoolsPlacement.FindInt("bottom").value();
    const int THRESHOLD = 400;  // go/smoldevtools
    resetPrefs = right - left < THRESHOLD || bottom - top < THRESHOLD;
  }
  if (resetPrefs) {
    ScopedDictPrefUpdate update(prefs, prefs::kAppWindowPlacement);
    base::Value::Dict& wp_prefs = update.Get();
    base::Value::Dict dev_tools_defaults;
    dev_tools_defaults.Set("left", 100);
    dev_tools_defaults.Set("top", 100);
    dev_tools_defaults.Set("right", 740);
    dev_tools_defaults.Set("bottom", 740);
    dev_tools_defaults.Set("maximized", false);
    dev_tools_defaults.Set("always_on_top", false);
    wp_prefs.Set(kDevToolsApp, std::move(dev_tools_defaults));
  }

#if BUILDFLAG(IS_ANDROID)
  NOTIMPLEMENTED();
#else
  if (Browser::GetCreationStatusForProfile(profile_) !=
      Browser::CreationStatus::kOk) {
    return;
  }

  auto* inspected_web_contents = GetInspectedWebContents();
  auto* opener = chrome::FindBrowserWithTab(inspected_web_contents);
  auto devtools_contents = OwnedMainWebContents::TakeWebContents(
      std::move(owned_main_web_contents_));

#if BUILDFLAG(ENABLE_CEF)
  // If a Browser is created, it will take ownership of |devtools_contents|.
  browser_ = cef::BrowserDelegate::CreateDevToolsBrowser(
      profile_, opener, inspected_web_contents, devtools_contents);
#endif

  if (!browser_) {
    auto create_params = Browser::CreateParams::CreateForDevTools(profile_);
    create_params.opener = opener;

    browser_ = Browser::Create(std::move(create_params));
    browser_->GetTabStripModel()->AddWebContents(
        std::move(devtools_contents),
        -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL, AddTabTypes::ADD_ACTIVE);
  }
#endif
  OverrideAndSyncDevToolsRendererPrefs();
}

void DevToolsWindow::DoAction(const DevToolsToggleAction& action) {
  switch (action.type()) {
    case DevToolsToggleAction::kInspect:
      bindings_->CallClientMethod("DevToolsAPI", "enterInspectElementMode");
      break;

    case DevToolsToggleAction::kShowElementsPanel:
    case DevToolsToggleAction::kPauseInDebugger:
    case DevToolsToggleAction::kShowConsolePanel:
    case DevToolsToggleAction::kShow:
    case DevToolsToggleAction::kToggle:
      // Do nothing.
      break;

    case DevToolsToggleAction::kReveal: {
      const DevToolsToggleAction::RevealParams* params = action.params();
      CHECK(params);
      bindings_->CallClientMethod(
          "DevToolsAPI", "revealSourceLine", base::Value(params->url),
          base::Value(static_cast<int>(params->line_number)),
          base::Value(static_cast<int>(params->column_number)));
      break;
    }
    default:
      NOTREACHED();
  }
}

WebContents* DevToolsWindow::GetInspectedWebContents() {
  return web_contents();
}

void DevToolsWindow::LoadCompleted() {
  Show(action_on_load_);
  action_on_load_ = DevToolsToggleAction::NoOp();
  if (!load_completed_callback_.is_null()) {
    std::move(load_completed_callback_).Run();
  }
}

void DevToolsWindow::SetLoadCompletedCallback(base::OnceClosure closure) {
  if (life_stage_ == kLoadCompleted || life_stage_ == kClosing) {
    if (!closure.is_null()) {
      std::move(closure).Run();
    }
    return;
  }
  load_completed_callback_ = std::move(closure);
}

bool DevToolsWindow::ForwardKeyboardEvent(
    const input::NativeWebKeyboardEvent& event) {
  return event_forwarder_->ForwardEvent(event);
}

bool DevToolsWindow::ReloadInspectedWebContents(bool bypass_cache) {
  // Only route reload via front-end if the agent is attached.
  WebContents* wc = GetInspectedWebContents();
  if (!wc || wc->GetCrashedStatus() != base::TERMINATION_STATUS_STILL_RUNNING) {
    return false;
  }
  bindings_->CallClientMethod("DevToolsAPI", "reloadInspectedPage",
                              base::Value(bypass_cache));
  return true;
}

void DevToolsWindow::OnLocaleChanged() {
  OverrideAndSyncDevToolsRendererPrefs();
}

void DevToolsWindow::OverrideAndSyncDevToolsRendererPrefs() {
  main_web_contents_->GetMutableRendererPrefs()->can_accept_load_drops = false;
  main_web_contents_->GetMutableRendererPrefs()->accept_languages =
      g_browser_process->GetApplicationLocale();
  main_web_contents_->SyncRendererPrefs();
}

void DevToolsWindow::MaybeShowSharedProcessInfobar() {
  WebContents* inspected_web_contents = GetInspectedWebContents();
  if (!inspected_web_contents) {
    return;
  }

  // Only show the infobar only if the RenderProcessHost id changes.
  int rph_id = inspected_web_contents->GetPrimaryMainFrame()
                   ->GetProcess()
                   ->GetDeprecatedID();
  if (checked_sharing_process_id_ == rph_id) {
    return;
  }
  checked_sharing_process_id_ = rph_id;

  if (!base::FeatureList::IsEnabled(
          ::features::kDevToolsSharedProcessInfobar) ||
      !base::FeatureList::IsEnabled(
          ::features::kProcessPerSiteUpToMainFrameThreshold)) {
    return;
  }

  content::SiteInstance* site_instance =
      inspected_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
  const GURL& site_url = site_instance->GetSiteURL();
  if (site_url.SchemeIs(extensions::kExtensionScheme) ||
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
      site_url.SchemeIs(content::kArkWebUIScheme) ||
      site_url.SchemeIs(extensions::kArkwebExtensionScheme) ||
#endif
      site_url.SchemeIs(content::kChromeDevToolsScheme) ||
      site_url.SchemeIs(content::kChromeUIScheme)) {
    return;
  }

  size_t primary_main_frame_count = 0;
  inspected_web_contents->GetPrimaryMainFrame()
      ->GetProcess()
      ->ForEachRenderFrameHost(
          [&primary_main_frame_count](
              content::RenderFrameHost* render_frame_host) {
            if (render_frame_host->IsInPrimaryMainFrame()) {
              ++primary_main_frame_count;
            }
          });

  // Dismiss old infobar.
  if (sharing_infobar_) {
    sharing_infobar_->RemoveSelf();
  }

  if (primary_main_frame_count > 1) {
#if !BUILDFLAG(IS_ANDROID)
    auto* info_bar_manager = GetInfoBarManager();
    sharing_infobar_ = info_bar_manager->AddInfoBar(
        CreateConfirmInfoBar(std::make_unique<ProcessSharingInfobarDelegate>(
            inspected_web_contents)));
    info_bar_manager->AddObserver(this);
#else
    NOTIMPLEMENTED();
#endif
  }
}

#if !BUILDFLAG(IS_ANDROID)
void DevToolsWindow::OnBrowserRemoved(Browser* browser) {
  // If the modal dialog manager has this browser as its delegate, clear the
  // reference.
  web_modal::WebContentsModalDialogManager* dialog_manager =
      web_modal::WebContentsModalDialogManager::FromWebContents(
          main_web_contents_);
  if (dialog_manager && dialog_manager->delegate() == browser) {
    dialog_manager->SetDelegate(nullptr);
  }
}
#endif

void DevToolsWindow::OnInfoBarRemoved(infobars::InfoBar* infobar,
                                      bool animate) {
  if (sharing_infobar_ == infobar) {
    infobar->owner()->RemoveObserver(this);
    sharing_infobar_ = nullptr;
  }
}

void DevToolsWindow::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  if (!navigation_handle->HasCommitted() ||
      navigation_handle->IsSameDocument() ||
      !navigation_handle->IsInPrimaryMainFrame()) {
    return;
  }

  if (!AllowDevToolsFor(profile_, web_contents())) {
    main_web_contents_->ClosePage();
  }
}

void DevToolsWindow::PrimaryPageChanged(content::Page& page) {
  MaybeShowSharedProcessInfobar();
}

void DevToolsWindow::MainWebContentRenderFrameHostChanged(
    content::RenderFrameHost* old_frame,
    content::RenderFrameHost* new_frame) {
  DevToolsUIBindings* new_bindings =
      DevToolsUIBindings::ForWebContents(main_web_contents_);
  if (!new_bindings || new_bindings == bindings_) {
    return;
  }
  bindings_->TransferDelegate(*new_bindings);
  bindings_ = new_bindings;
}

raw_ptr<content::WebContents> DevToolsWindow::GetDevToolsWebContents() {
  return main_web_contents_;
}