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

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

#include <algorithm>
#include <utility>

#include "base/base64.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/certificate_viewer.h"
#include "chrome/browser/data_use_measurement/data_use_web_contents_observer.h"
#include "chrome/browser/devtools/chrome_devtools_manager_delegate.h"
#include "chrome/browser/devtools/devtools_eye_dropper.h"
#include "chrome/browser/file_select_helper.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/task_manager/web_contents_tags.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.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_strip_model.h"
#include "chrome/browser/ui/webui/devtools_ui.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/app_modal/javascript_dialog_manager.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/sync_preferences/pref_service_syncable.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/devtools_agent_host.h"
#include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/native_web_keyboard_event.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/url_constants.h"
#include "net/base/escape.h"
#include "third_party/blink/public/platform/web_gesture_event.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/public_buildflags.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"

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

namespace {

typedef std::vector<DevToolsWindow*> DevToolsWindows;
base::LazyInstance<DevToolsWindows>::Leaky g_devtools_window_instances =
    LAZY_INSTANCE_INITIALIZER;

base::LazyInstance<std::vector<base::Callback<void(DevToolsWindow*)>>>::Leaky
    g_creation_callbacks = LAZY_INSTANCE_INITIALIZER;

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

bool FindInspectedBrowserAndTabIndex(
    WebContents* inspected_web_contents, Browser** browser, int* tab) {
  if (!inspected_web_contents)
    return false;

  for (auto* b : *BrowserList::GetInstance()) {
    int tab_index =
        b->tab_strip_model()->GetIndexOfWebContents(inspected_web_contents);
    if (tab_index != TabStripModel::kNoTab) {
      *browser = b;
      *tab = tab_index;
      return true;
    }
  }
  return false;
}

void SetPreferencesFromJson(Profile* profile, const std::string& json) {
  base::DictionaryValue* dict = nullptr;
  std::unique_ptr<base::Value> parsed = base::JSONReader::Read(json);
  if (!parsed || !parsed->GetAsDictionary(&dict))
    return;
  DictionaryPrefUpdate update(profile->GetPrefs(), prefs::kDevToolsPreferences);
  for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
    if (!it.value().is_string())
      continue;
    update.Get()->SetWithoutPathExpansion(
        it.key(), it.value().CreateDeepCopy());
  }
}

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

class DevToolsToolboxDelegate
    : public content::WebContentsObserver,
      public content::WebContentsDelegate {
 public:
  DevToolsToolboxDelegate(
      WebContents* toolbox_contents,
      DevToolsWindow::ObserverWithAccessor* web_contents_observer);
  ~DevToolsToolboxDelegate() override;

  content::WebContents* OpenURLFromTab(
      content::WebContents* source,
      const content::OpenURLParams& params) override;
  content::KeyboardEventProcessingResult PreHandleKeyboardEvent(
      content::WebContents* source,
      const content::NativeWebKeyboardEvent& event) override;
  void HandleKeyboardEvent(
      content::WebContents* source,
      const content::NativeWebKeyboardEvent& event) override;
  void WebContentsDestroyed() override;

 private:
  BrowserWindow* GetInspectedBrowserWindow();
  DevToolsWindow::ObserverWithAccessor* inspected_contents_observer_;
  DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate);
};

DevToolsToolboxDelegate::DevToolsToolboxDelegate(
    WebContents* toolbox_contents,
    DevToolsWindow::ObserverWithAccessor* web_contents_observer)
    : WebContentsObserver(toolbox_contents),
      inspected_contents_observer_(web_contents_observer) {
}

DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
}

content::WebContents* DevToolsToolboxDelegate::OpenURLFromTab(
    content::WebContents* source,
    const content::OpenURLParams& params) {
  DCHECK(source == web_contents());
  if (!params.url.SchemeIs(content::kChromeDevToolsScheme))
    return NULL;
  content::NavigationController::LoadURLParams load_url_params(params.url);
  source->GetController().LoadURLWithParams(load_url_params);
  return source;
}

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

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

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

BrowserWindow* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
  WebContents* inspected_contents =
      inspected_contents_observer_->web_contents();
  if (!inspected_contents)
    return NULL;
  Browser* browser = NULL;
  int tab = 0;
  if (FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab))
    return browser->window();
  return NULL;
}

// static
GURL DecorateFrontendURL(const GURL& base_url) {
  std::string frontend_url = base_url.spec();
  std::string url_string(
      frontend_url +
      ((frontend_url.find("?") == std::string::npos) ? "?" : "&") +
      "dockSide=undocked"); // TODO(dgozman): remove this support in M38.
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kEnableDevToolsExperiments))
    url_string += "&experiments=true";

  if (command_line->HasSwitch(switches::kDevToolsFlags)) {
    url_string += "&" + command_line->GetSwitchValueASCII(
        switches::kDevToolsFlags);
  }

#if BUILDFLAG(DEBUG_DEVTOOLS)
  url_string += "&debugFrontend=true";
#endif  // BUILDFLAG(DEBUG_DEVTOOLS)

  return GURL(url_string);
}

}  // namespace

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

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

  // 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 content::NativeWebKeyboardEvent& event);

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

  DevToolsWindow* devtools_window_;
  std::set<int> whitelisted_keys_;

  DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder);
};

void DevToolsEventForwarder::SetWhitelistedShortcuts(
    const std::string& message) {
  std::unique_ptr<base::Value> parsed_message = base::JSONReader::Read(message);
  base::ListValue* shortcut_list;
  if (!parsed_message || !parsed_message->GetAsList(&shortcut_list))
      return;
  base::ListValue::iterator it = shortcut_list->begin();
  for (; it != shortcut_list->end(); ++it) {
    base::DictionaryValue* dictionary;
    if (!it->GetAsDictionary(&dictionary))
      continue;
    int key_code = 0;
    dictionary->GetInteger("keyCode", &key_code);
    if (key_code == 0)
      continue;
    int modifiers = 0;
    dictionary->GetInteger("modifiers", &modifiers);
    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 content::NativeWebKeyboardEvent& event) {
  std::string event_type;
  switch (event.GetType()) {
    case WebInputEvent::kKeyDown:
    case WebInputEvent::kRawKeyDown:
      event_type = kKeyDownEventName;
      break;
    case WebInputEvent::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::DictionaryValue event_data;
  event_data.SetString("type", event_type);
  event_data.SetString("key", ui::KeycodeConverter::DomKeyToKeyString(
                                  static_cast<ui::DomKey>(event.dom_key)));
  event_data.SetString("code", ui::KeycodeConverter::DomCodeToCodeString(
                                   static_cast<ui::DomCode>(event.dom_code)));
  event_data.SetInteger("keyCode", key_code);
  event_data.SetInteger("modifiers", modifiers);
  devtools_window_->bindings_->CallClientFunction(
      "DevToolsAPI.keyEventUnhandled", &event_data, NULL, NULL);
  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_);
}

// DevToolsWindow::ObserverWithAccessor -------------------------------

DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
    WebContents* web_contents)
    : WebContentsObserver(web_contents) {
}

DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
}

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

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

  ~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:
  DevToolsWindow* devtools_window_;

  DISALLOW_COPY_AND_ASSIGN(Throttle);
};

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

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

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

// static
void DevToolsWindow::RemoveCreationCallbackForTest(
    const CreationCallback& callback) {
  for (size_t i = 0; i < g_creation_callbacks.Get().size(); ++i) {
    if (g_creation_callbacks.Get().at(i).Equals(callback)) {
      g_creation_callbacks.Get().erase(g_creation_callbacks.Get().begin() + i);
      return;
    }
  }
}

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

  life_stage_ = kClosing;

  UpdateBrowserWindow();
  UpdateBrowserToolbar();

  owned_toolbox_web_contents_.reset();

  DevToolsWindows* instances = g_devtools_window_instances.Pointer();
  DevToolsWindows::iterator it(
      std::find(instances->begin(), instances->end(), this));
  DCHECK(it != instances->end());
  instances->erase(it);

  if (!close_callback_.is_null()) {
    close_callback_.Run();
    close_callback_ = base::Closure();
  }
}

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

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

// 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 NULL;

  // 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 NULL;

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

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

  return window->is_docked_ ? window->main_web_contents_ :
      window->toolbox_web_contents_;
}

// static
DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents(
    WebContents* inspected_web_contents) {
  if (!inspected_web_contents || !g_devtools_window_instances.IsCreated())
    return NULL;
  DevToolsWindows* instances = g_devtools_window_instances.Pointer();
  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
       ++it) {
    if ((*it)->GetInspectedWebContents() == inspected_web_contents)
      return *it;
  }
  return NULL;
}

// static
bool DevToolsWindow::IsDevToolsWindow(content::WebContents* web_contents) {
  if (!web_contents || !g_devtools_window_instances.IsCreated())
    return false;
  DevToolsWindows* instances = g_devtools_window_instances.Pointer();
  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
       ++it) {
    if ((*it)->main_web_contents_ == web_contents ||
        (*it)->toolbox_web_contents_ == web_contents)
      return true;
  }
  return false;
}

// static
void DevToolsWindow::OpenDevToolsWindowForWorker(
    Profile* profile,
    const scoped_refptr<DevToolsAgentHost>& worker_agent) {
  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());
    if (!window)
      return;
    window->bindings_->AttachTo(worker_agent);
  }
  window->ScheduleShow(DevToolsToggleAction::Show());
}

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

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

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

// static
void DevToolsWindow::OpenDevToolsWindow(
    scoped_refptr<content::DevToolsAgentHost> agent_host,
    Profile* profile,
    bool use_bundled_frontend) {
  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;

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

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

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

  content::WebContents* web_contents = agent_host->GetWebContents();
  if (web_contents)
    DevToolsWindow::OpenDevToolsWindow(web_contents);
}

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

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

// static
void DevToolsWindow::ToggleDevToolsWindow(
    Browser* browser,
    const DevToolsToggleAction& action) {
  if (action.type() == DevToolsToggleAction::kToggle &&
      browser->is_devtools()) {
    browser->tab_strip_model()->CloseAllTabs();
    return;
  }

  ToggleDevToolsWindow(
      browser->tab_strip_model()->GetActiveWebContents(),
      action.type() == DevToolsToggleAction::kInspect,
      action, "");
}

// static
void DevToolsWindow::OpenExternalFrontend(
    Profile* profile,
    const std::string& frontend_url,
    const scoped_refptr<content::DevToolsAgentHost>& agent_host,
    bool use_bundled_frontend) {
  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());
  } else {
    bool is_worker = type == DevToolsAgentHost::kTypeServiceWorker ||
                     type == DevToolsAgentHost::kTypeSharedWorker;

    FrontendType frontend_type =
        is_worker ? kFrontendRemoteWorker : 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());
  }
  if (!window)
    return;
  window->bindings_->AttachTo(agent_host);
  window->close_on_detach_ = false;
  window->ScheduleShow(DevToolsToggleAction::Show());
}

// static
void DevToolsWindow::OpenNodeFrontendWindow(Profile* profile) {
  for (DevToolsWindow* window : g_devtools_window_instances.Get()) {
    if (window->frontend_type_ == kFrontendNode) {
      window->ActivateWindow();
      return;
    }
  }

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

// static
void DevToolsWindow::ToggleDevToolsWindow(
    content::WebContents* inspected_web_contents,
    bool force_open,
    const DevToolsToggleAction& action,
    const std::string& settings) {
  scoped_refptr<DevToolsAgentHost> agent(
      DevToolsAgentHost::GetOrCreateFor(inspected_web_contents));
  DevToolsWindow* window = FindDevToolsWindow(agent.get());
  bool do_open = force_open;
  if (!window) {
    Profile* profile = Profile::FromBrowserContext(
        inspected_web_contents->GetBrowserContext());
    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::kShow:
      case DevToolsToggleAction::kToggle:
      case DevToolsToggleAction::kReveal:
      case DevToolsToggleAction::kNoOp:
        break;
    }
    window = Create(profile, inspected_web_contents, kFrontendDefault,
                    std::string(), true, settings, panel, agent->IsAttached());
    if (!window)
      return;
    window->bindings_->AttachTo(agent.get());
    do_open = true;
  }

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

// 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::GetOrCreateFor(web_contents));
  agent->InspectElement(inspected_frame_host, x, y);
  bool should_measure_time = FindDevToolsWindow(agent.get()) == NULL;
  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());
  DevToolsWindow* window = FindDevToolsWindow(agent.get());
  if (window && should_measure_time)
    window->inspect_element_start_time_ = start_time;
}

// static
std::unique_ptr<content::NavigationThrottle>
DevToolsWindow::MaybeCreateNavigationThrottle(
    content::NavigationHandle* handle) {
  WebContents* web_contents = handle->GetWebContents();
  if (!web_contents || !web_contents->HasOriginalOpener() ||
      web_contents->GetController().GetLastCommittedEntry()) {
    return nullptr;
  }

  WebContents* opener = WebContents::FromRenderFrameHost(
      handle->GetWebContents()->GetOriginalOpener());
  DevToolsWindow* window = GetInstanceForInspectedWebContents(opener);
  if (!window || !window->open_new_window_for_popups_ ||
      GetInstanceForInspectedWebContents(web_contents))
    return nullptr;

  DevToolsWindow::OpenDevToolsWindow(web_contents);
  window = GetInstanceForInspectedWebContents(web_contents);
  if (!window)
    return nullptr;

  return std::make_unique<Throttle>(handle, 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 (is_docked_) {
    DCHECK(can_dock_);
    Browser* inspected_browser = NULL;
    int inspected_tab_index = -1;
    FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
                                    &inspected_browser,
                                    &inspected_tab_index);
    DCHECK(inspected_browser);
    DCHECK(inspected_tab_index != -1);

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

    TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
    tab_strip_model->ActivateTabAt(inspected_tab_index, true);

    inspected_window->UpdateDevTools();
    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_);
    main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();

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

  if (should_show_window) {
    browser_->window()->Show();
    main_web_contents_->SetInitialFocus();
  }
  if (toolbox_web_contents_)
    UpdateBrowserWindow();

  DoAction(action);
}

// 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.
  if (!DevToolsWindow::InterceptPageBeforeUnload(window->main_web_contents_)) {
    window->main_web_contents_->DispatchBeforeUnload();
  }
  return true;
}

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

// static
bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
    Browser* browser) {
  DCHECK(browser->is_devtools());
  // When FastUnloadController is used, devtools frontend will be detached
  // from the browser window at this point which means we've already fired
  // beforeunload.
  if (browser->tab_strip_model()->empty())
    return true;
  WebContents* contents =
      browser->tab_strip_model()->GetWebContentsAt(0);
  DevToolsWindow* window = AsDevToolsWindow(contents);
  if (!window)
    return false;
  return window->intercepted_page_beforeunload_;
}

// 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)
    : frontend_type_(frontend_type),
      profile_(profile),
      main_web_contents_(main_web_contents.get()),
      toolbox_web_contents_(nullptr),
      bindings_(bindings),
      browser_(nullptr),
      is_docked_(true),
      owned_main_web_contents_(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.
      // Passing "dockSide=undocked" parameter ensures proper UI.
      life_stage_(can_dock ? kNotLoaded : kIsDockedSet),
      action_on_load_(DevToolsToggleAction::NoOp()),
      intercepted_page_beforeunload_(false),
      ready_for_test_(false) {
  // 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);
  data_use_measurement::DataUseWebContentsObserver::CreateForWebContents(
      main_web_contents_);
  // 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);

  g_devtools_window_instances.Get().push_back(this);

  // There is no inspected_web_contents in case of various workers.
  if (inspected_web_contents)
    inspected_contents_observer_.reset(
        new ObserverWithAccessor(inspected_web_contents));

  // 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_.reset(new 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::Callback<void(DevToolsWindow*)>> copy(
      g_creation_callbacks.Get());
  for (const auto& callback : copy)
    callback.Run(this);
}

// 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 ChromeDevToolsManagerDelegate::AllowInspection(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) {
  if (!AllowDevToolsFor(profile, inspected_web_contents))
    return nullptr;

  if (inspected_web_contents) {
    // Check for a place to dock.
    Browser* browser = nullptr;
    int tab;
    if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
                                         &browser, &tab) ||
        browser->is_type_popup()) {
      can_dock = false;
    }
  }

  // Create WebContents with devtools.
  GURL url(GetDevToolsURL(profile, frontend_type, frontend_url, can_dock, panel,
                          has_other_clients));
  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);
}

// 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) {
  std::string url;

// Modules are always bundled in CrOS.
#if defined(OS_CHROMEOS)
  std::string remote_base = "?";
#else
  std::string remote_base =
      "?remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
#endif

  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");
  switch (frontend_type) {
    case kFrontendDefault:
      url = kDefaultFrontendURL + remote_base;
      if (can_dock)
        url += "&can_dock=true";
      if (panel.size())
        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;
  }

  if (has_other_clients)
    url += "&hasOtherClients=true";
  return DevToolsUIBindings::SanitizeFrontendURL(GURL(url));
}

// static
DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
    DevToolsAgentHost* agent_host) {
  if (!agent_host || !g_devtools_window_instances.IsCreated())
    return NULL;
  DevToolsWindows* instances = g_devtools_window_instances.Pointer();
  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
       ++it) {
    if ((*it)->bindings_->IsAttachedTo(agent_host))
      return *it;
  }
  return NULL;
}

// static
DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
    content::WebContents* web_contents) {
  if (!web_contents || !g_devtools_window_instances.IsCreated())
    return NULL;
  DevToolsWindows* instances = g_devtools_window_instances.Pointer();
  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
       ++it) {
    if ((*it)->main_web_contents_ == web_contents)
      return *it;
  }
  return NULL;
}

WebContents* DevToolsWindow::OpenURLFromTab(
    WebContents* source,
    const content::OpenURLParams& params) {
  DCHECK(source == main_web_contents_);
  if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
    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);
  }
  bindings_->Reload();
  return main_web_contents_;
}

void DevToolsWindow::ActivateContents(WebContents* contents) {
  if (is_docked_) {
    WebContents* inspected_tab = GetInspectedWebContents();
    if (inspected_tab)
      inspected_tab->GetDelegate()->ActivateContents(inspected_tab);
  } else if (browser_) {
    browser_->window()->Activate();
  }
}

void DevToolsWindow::AddNewContents(WebContents* source,
                                    std::unique_ptr<WebContents> new_contents,
                                    WindowOpenDisposition disposition,
                                    const gfx::Rect& initial_rect,
                                    bool user_gesture,
                                    bool* was_blocked) {
  if (new_contents.get() == toolbox_web_contents_) {
    owned_toolbox_web_contents_ = std::move(new_contents);

    toolbox_web_contents_->SetDelegate(
        new DevToolsToolboxDelegate(toolbox_web_contents_,
                                    inspected_contents_observer_.get()));
    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;
  }

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

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.path().rfind("toolbox.html") != std::string::npos) {
    CHECK(can_dock_);

    // Ownership will be passed in DevToolsWindow::AddNewContents.
    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_);
    data_use_measurement::DataUseWebContentsObserver::CreateForWebContents(
        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
    toolbox_web_contents_->IncrementCapturerCount(gfx::Size());
  }
}

void DevToolsWindow::CloseContents(WebContents* source) {
  CHECK(is_docked_);
  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();
    } else {
      bool should_proceed;
      inspected_web_contents->GetDelegate()->BeforeUnloadFired(
          inspected_web_contents, false, &should_proceed);
      DCHECK(!should_proceed);
    }
    *proceed_to_fire_unload = false;
  }
}

content::KeyboardEventProcessingResult DevToolsWindow::PreHandleKeyboardEvent(
    WebContents* source,
    const content::NativeWebKeyboardEvent& event) {
  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
  if (inspected_window) {
    return inspected_window->PreHandleKeyboardEvent(event);
  }
  return content::KeyboardEventProcessingResult::NOT_HANDLED;
}

void DevToolsWindow::HandleKeyboardEvent(
    WebContents* source,
    const content::NativeWebKeyboardEvent& event) {
  if (event.windows_key_code == 0x08) {
    // Do not navigate back in history on Windows (http://crbug.com/74156).
    return;
  }
  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
  if (inspected_window)
    inspected_window->HandleKeyboardEvent(event);
}

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

content::ColorChooser* DevToolsWindow::OpenColorChooser(
    WebContents* web_contents,
    SkColor initial_color,
    const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
  return chrome::ShowColorChooser(web_contents, initial_color);
}

void DevToolsWindow::RunFileChooser(content::RenderFrameHost* render_frame_host,
                                    const content::FileChooserParams& params) {
  FileSelectHelper::RunFileChooser(render_frame_host, 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 (is_docked_ && GetInspectedBrowserWindow())
    main_web_contents_->Focus();
  else if (!is_docked_ && !browser_->window()->IsActive())
    browser_->window()->Activate();
}

void DevToolsWindow::CloseWindow() {
  DCHECK(is_docked_);
  life_stage_ = kClosing;
  main_web_contents_->DispatchBeforeUnload();
}

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

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 (dock_requested && !was_docked) {
    // Detach window from the external devtools browser. It will lead to
    // the browser object's close and delete. Remove observer first.
    TabStripModel* tab_strip_model = browser_->tab_strip_model();
    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_ = NULL;

    owned_main_web_contents_ = tab_strip_model->DetachWebContentsAt(
        tab_strip_model->GetIndexOfWebContents(main_web_contents_));
  } else if (!dock_requested && was_docked) {
    UpdateBrowserWindow();
  }

  Show(DevToolsToggleAction::Show());
}

void DevToolsWindow::OpenInNewTab(const std::string& 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->GetRenderViewHost();
    if (render_view_host)
      child_id = render_view_host->GetProcess()->GetID();
  }
  // 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)) {
    chrome::ScopedTabbedBrowserDisplayer displayer(profile_);
    chrome::AddSelectedTabWithURL(displayer.browser(), fixed_url,
                                  ui::PAGE_TRANSITION_LINK);
  }
}

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_.reset(new DevToolsEyeDropper(
        web_contents, base::Bind(&DevToolsWindow::ColorPickedInEyeDropper,
                                 base::Unretained(this))));
  } else {
    eye_dropper_.reset();
  }
}

void DevToolsWindow::ColorPickedInEyeDropper(int r, int g, int b, int a) {
  base::DictionaryValue color;
  color.SetInteger("r", r);
  color.SetInteger("g", g);
  color.SetInteger("b", b);
  color.SetInteger("a", a);
  bindings_->CallClientFunction("DevToolsAPI.eyeDropperPickedColor", &color,
                                nullptr, nullptr);
}

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

InfoBarService* DevToolsWindow::GetInfoBarService() {
  return is_docked_ ?
      InfoBarService::FromWebContents(GetInspectedWebContents()) :
      InfoBarService::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 (browser_ && crashed) {
    browser_->window()->Close();
  }
}

void DevToolsWindow::ShowCertificateViewer(const std::string& cert_chain) {
  std::unique_ptr<base::Value> value = base::JSONReader::Read(cert_chain);
  if (!value || value->type() != base::Value::Type::LIST) {
    NOTREACHED();
    return;
  }

  std::unique_ptr<base::ListValue> list =
      base::ListValue::From(std::move(value));
  std::vector<std::string> decoded;
  for (size_t i = 0; i < list->GetSize(); ++i) {
    base::Value* item;
    if (!list->Get(i, &item) || !item->is_string()) {
      NOTREACHED();
      return;
    }
    std::string temp;
    if (!item->GetAsString(&temp)) {
      NOTREACHED();
      return;
    }
    if (!base::Base64Decode(temp, &temp)) {
      NOTREACHED();
      return;
    }
    decoded.push_back(temp);
  }

  std::vector<base::StringPiece> 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);
  if (!cert) {
    NOTREACHED();
    return;
  }

  WebContents* inspected_contents =
      is_docked_ ? GetInspectedWebContents() : main_web_contents_;
  Browser* browser = NULL;
  int tab = 0;
  if (!FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab))
    return;
  gfx::NativeWindow parent = browser->window()->GetNativeWindow();
  ::ShowCertificateViewer(inspected_contents, parent, cert.get());
}

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

  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()) {
    ready_for_test_callback_.Run();
    ready_for_test_callback_ = base::Closure();
  }
}

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();
  if (!prefs->GetDictionary(prefs::kAppWindowPlacement)->HasKey(kDevToolsApp)) {
    DictionaryPrefUpdate update(prefs, prefs::kAppWindowPlacement);
    base::DictionaryValue* wp_prefs = update.Get();
    auto dev_tools_defaults = std::make_unique<base::DictionaryValue>();
    dev_tools_defaults->SetInteger("left", 100);
    dev_tools_defaults->SetInteger("top", 100);
    dev_tools_defaults->SetInteger("right", 740);
    dev_tools_defaults->SetInteger("bottom", 740);
    dev_tools_defaults->SetBoolean("maximized", false);
    dev_tools_defaults->SetBoolean("always_on_top", false);
    wp_prefs->Set(kDevToolsApp, std::move(dev_tools_defaults));
  }

  browser_ = new Browser(Browser::CreateParams::CreateForDevTools(profile_));
  browser_->tab_strip_model()->AddWebContents(
      std::move(owned_main_web_contents_), -1,
      ui::PAGE_TRANSITION_AUTO_TOPLEVEL, TabStripModel::ADD_ACTIVE);
  main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
}

BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
  Browser* browser = NULL;
  int tab;
  return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
                                         &browser, &tab) ?
      browser->window() : NULL;
}

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

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

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

void DevToolsWindow::UpdateBrowserToolbar() {
  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
  if (inspected_window)
    inspected_window->UpdateToolbar(NULL);
}

void DevToolsWindow::UpdateBrowserWindow() {
  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
  if (inspected_window)
    inspected_window->UpdateDevTools();
}

WebContents* DevToolsWindow::GetInspectedWebContents() {
  return inspected_contents_observer_
             ? inspected_contents_observer_->web_contents()
             : NULL;
}

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

void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) {
  if (life_stage_ == kLoadCompleted || life_stage_ == kClosing) {
    if (!closure.is_null())
      closure.Run();
    return;
  }
  load_completed_callback_ = closure;
}

bool DevToolsWindow::ForwardKeyboardEvent(
    const content::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;
  base::Value bypass_cache_value(bypass_cache);
  bindings_->CallClientFunction("DevToolsAPI.reloadInspectedPage",
                                &bypass_cache_value, nullptr, nullptr);
  return true;
}
