// 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 #include #include #include #include "base/base64.h" #include "base/bind.h" #include "base/command_line.h" #include "base/json/json_reader.h" #include "base/macros.h" #include "base/metrics/histogram_functions.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/browser_process.h" #include "chrome/browser/certificate_viewer.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/profiles/profile.h" #include "chrome/browser/profiles/profile_keep_alive_types.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/scoped_profile_keep_alive.h" #include "chrome/browser/task_manager/web_contents_tags.h" #include "chrome/browser/ui/browser.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/infobars/content/content_infobar_manager.h" #include "components/javascript_dialogs/app_modal_dialog_manager.h" #include "components/keep_alive_registry/keep_alive_types.h" #include "components/keep_alive_registry/scoped_keep_alive.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/sessions/content/session_tab_helper.h" #include "components/sync_preferences/pref_service_syncable.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/file_select_listener.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/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/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 DevToolsWindows; base::LazyInstance::Leaky g_devtools_window_instances = LAZY_INSTANCE_INITIALIZER; base::LazyInstance< std::vector>>::Leaky g_creation_callbacks = LAZY_INSTANCE_INITIALIZER; 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"; 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) { absl::optional parsed = base::JSONReader::Read(json); if (!parsed || !parsed->is_dict()) return; DictionaryPrefUpdate update(profile->GetPrefs(), prefs::kDevToolsPreferences); for (auto dict_value : parsed->DictItems()) { if (!dict_value.second.is_string()) continue; update.Get()->SetKey(dict_value.first, std::move(dict_value.second)); } } // 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; bool 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 nullptr; source->GetController().LoadURLWithParams( content::NavigationController::LoadURLParams(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; } bool 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 false; } BrowserWindow* window = GetInspectedBrowserWindow(); return window && window->HandleKeyboardEvent(event); } void DevToolsToolboxDelegate::WebContentsDestroyed() { delete this; } BrowserWindow* DevToolsToolboxDelegate::GetInspectedBrowserWindow() { WebContents* inspected_contents = inspected_contents_observer_->web_contents(); if (!inspected_contents) return nullptr; Browser* browser = nullptr; int tab = 0; if (FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab)) return browser->window(); return nullptr; } // 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::kDevToolsFlags)) { url_string += "&" + command_line->GetSwitchValueASCII( switches::kDevToolsFlags); } if (command_line->HasSwitch(switches::kCustomDevtoolsFrontend)) { url_string += "&debugFrontend=true"; } 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 whitelisted_keys_; DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder); }; void DevToolsEventForwarder::SetWhitelistedShortcuts( const std::string& message) { absl::optional parsed_message = base::JSONReader::Read(message); 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.FindIntKey("keyCode").value_or(0); if (key_code == 0) continue; int modifiers = list_item.FindIntKey("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 content::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(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 event_data(base::Value::Type::DICTIONARY); event_data.SetStringKey("type", event_type); event_data.SetStringKey("key", ui::KeycodeConverter::DomKeyToKeyString( static_cast(event.dom_key))); event_data.SetStringKey("code", ui::KeycodeConverter::DomCodeToCodeString( static_cast(event.dom_code))); event_data.SetIntKey("keyCode", key_code); event_data.SetIntKey("modifiers", modifiers); devtools_window_->bindings_->CallClientMethod( "DevToolsAPI", "keyEventUnhandled", 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_); } // 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); }; // 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 web_contents) : keep_alive_(KeepAliveOrigin::DEVTOOLS_WINDOW, KeepAliveRestartOption::DISABLED), 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( profile, ProfileKeepAliveOrigin::kDevToolsWindow); } } static std::unique_ptr TakeWebContents( std::unique_ptr instance) { return std::move(instance->web_contents_); } private: ScopedKeepAlive keep_alive_; std::unique_ptr profile_keep_alive_; std::unique_ptr web_contents_; }; // 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) == callback) { g_creation_callbacks.Get().erase(g_creation_callbacks.Get().begin() + i); return; } } } DevToolsWindow::~DevToolsWindow() { if (throttle_) throttle_->ResumeThrottle(); if (reattach_complete_callback_) { std::move(reattach_complete_callback_).Run(); } life_stage_ = kClosing; UpdateBrowserWindow(); UpdateBrowserToolbar(); capture_handle_.RunAndReset(); owned_toolbox_web_contents_.reset(); DevToolsWindows* instances = g_devtools_window_instances.Pointer(); auto it(std::find(instances->begin(), instances->end(), this)); DCHECK(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::SequencedTaskRunnerHandle::Get()->DeleteSoon( FROM_HERE, std::move(owned_main_web_contents_)); } } // 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 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_ : window->toolbox_web_contents_; } // static DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents( WebContents* inspected_web_contents) { if (!inspected_web_contents || !g_devtools_window_instances.IsCreated()) return nullptr; DevToolsWindows* instances = g_devtools_window_instances.Pointer(); for (auto it(instances->begin()); it != instances->end(); ++it) { if ((*it)->GetInspectedWebContents() == inspected_web_contents) return *it; } return nullptr; } // 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 (auto 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& 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 agent_host, Profile* profile) { OpenDevToolsWindow(agent_host, profile, false /* use_bundled_frontend */); } // static void DevToolsWindow::OpenDevToolsWindowWithBundledFrontend( scoped_refptr agent_host, Profile* profile) { OpenDevToolsWindow(agent_host, profile, true /* use_bundled_frontend */); } // static void DevToolsWindow::OpenDevToolsWindow( scoped_refptr 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& 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, DevToolsOpenedByAction opened_by) { if (action.type() == DevToolsToggleAction::kToggle && browser->is_type_devtools()) { browser->tab_strip_model()->CloseAllTabs(); return; } ToggleDevToolsWindow(browser->tab_strip_model()->GetActiveWebContents(), action.type() == DevToolsToggleAction::kInspect, action, "", opened_by); } // static void DevToolsWindow::OpenExternalFrontend( Profile* profile, const std::string& frontend_url, const scoped_refptr& 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 DevToolsWindow* DevToolsWindow::OpenNodeFrontendWindow(Profile* profile) { for (DevToolsWindow* window : g_devtools_window_instances.Get()) { if (window->frontend_type_ == kFrontendNode) { window->ActivateWindow(); return window; } } DevToolsWindow* window = Create(profile, nullptr, kFrontendNode, std::string(), false, std::string(), std::string(), false); 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()) { return profile; } return profile->GetOriginalProfile(); } // static void DevToolsWindow::ToggleDevToolsWindow( content::WebContents* inspected_web_contents, bool force_open, const DevToolsToggleAction& action, const std::string& settings, DevToolsOpenedByAction opened_by) { scoped_refptr agent( DevToolsAgentHost::GetOrCreateFor(inspected_web_contents)); DevToolsWindow* window = FindDevToolsWindow(agent.get()); bool do_open = force_open; if (!window) { 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: 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; if (opened_by != DevToolsOpenedByAction::kUnknown) LogDevToolsOpenedByAction(opened_by); } // 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 agent( DevToolsAgentHost::GetOrCreateFor(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()); LogDevToolsOpenedByAction(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 std::unique_ptr 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(handle, window); } void DevToolsWindow::UpdateInspectedWebContents( content::WebContents* new_web_contents, base::OnceCallback callback) { DCHECK(!reattach_complete_callback_); reattach_complete_callback_ = std::move(callback); inspected_contents_observer_ = std::make_unique(new_web_contents); bindings_->AttachTo( content::DevToolsAgentHost::GetOrCreateFor(new_web_contents)); bindings_->CallClientMethod( "DevToolsAPI", "reattachMainTarget", {}, {}, {}, base::BindOnce(&DevToolsWindow::OnReattachMainTargetComplete, base::Unretained(this))); } 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 = nullptr; int inspected_tab_index = -1; FindInspectedBrowserAndTabIndex(GetInspectedWebContents(), &inspected_browser, &inspected_tab_index); DCHECK(inspected_browser); DCHECK_NE(-1, inspected_tab_index); 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); TabStripModel* tab_strip_model = inspected_browser->tab_strip_model(); tab_strip_model->ActivateTabAt(inspected_tab_index, {TabStripModel::GestureType::kOther}); 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_); OverrideAndSyncDevToolsRendererPrefs(); 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_); 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. // 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 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser( Browser* browser) { DCHECK(browser->is_type_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; DevToolsWindow* window = AsDevToolsWindow(browser); 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 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::make_unique(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); // 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_ = std::make_unique(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_ = std::make_unique(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> copy( g_creation_callbacks.Get()); 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))); } // 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_normal()) { can_dock = false; } } // Create WebContents with devtools. GURL url(GetDevToolsURL(profile, frontend_type, frontend_url, can_dock, panel, has_other_clients)); std::unique_ptr 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; 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"); switch (frontend_type) { case kFrontendDefault: url = kDefaultFrontendURL + remote_base; 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; } 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 nullptr; DevToolsWindows* instances = g_devtools_window_instances.Pointer(); for (auto it(instances->begin()); it != instances->end(); ++it) { if ((*it)->bindings_->IsAttachedTo(agent_host)) return *it; } return nullptr; } // static DevToolsWindow* DevToolsWindow::AsDevToolsWindow( content::WebContents* web_contents) { if (!web_contents || !g_devtools_window_instances.IsCreated()) return nullptr; DevToolsWindows* instances = g_devtools_window_instances.Pointer(); for (auto it(instances->begin()); it != instances->end(); ++it) { if ((*it)->main_web_contents_ == web_contents) return *it; } return nullptr; } // static DevToolsWindow* DevToolsWindow::AsDevToolsWindow(Browser* browser) { DCHECK(browser->is_type_devtools()); if (browser->tab_strip_model()->empty()) return nullptr; WebContents* contents = browser->tab_strip_model()->GetWebContentsAt(0); return AsDevToolsWindow(contents); } WebContents* DevToolsWindow::OpenURLFromTab( WebContents* source, const content::OpenURLParams& params) { DCHECK(source == main_web_contents_); if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) { return OpenURLFromInspectedTab(params); } 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); } 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 new_contents, const GURL& target_url, 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), target_url, 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("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); } } 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(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::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; } bool 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 true; } BrowserWindow* inspected_window = GetInspectedBrowserWindow(); return inspected_window && inspected_window->HandleKeyboardEvent(event); } content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager( WebContents* source) { return javascript_dialogs::AppModalDialogManager::GetInstance(); } void DevToolsWindow::RunFileChooser( content::RenderFrameHost* render_frame_host, scoped_refptr 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 (is_docked_ && GetInspectedBrowserWindow()) main_web_contents_->Focus(); else if (!is_docked_ && browser_ && !browser_->window()->IsActive()) browser_->window()->Activate(); } void DevToolsWindow::CloseWindow() { DCHECK(is_docked_); life_stage_ = kClosing; main_web_contents_->DispatchBeforeUnload(false /* auto_cancel */); } void DevToolsWindow::Inspect(scoped_refptr 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 && browser_) { // 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_ = nullptr; // TODO(crbug.com/1221967): WebContents should be removed with a reason // other than kInsertedIntoOtherTabStrip, it's not getting reinserted into // another tab strip. owned_main_web_contents_ = std::make_unique( tab_strip_model->DetachWebContentsAtForInsertion( 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->GetMainFrame()->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_ = std::make_unique( 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::DictionaryValue color; color.SetInteger("r", r); color.SetInteger("g", g); color.SetInteger("b", b); color.SetInteger("a", a); bindings_->CallClientMethod("DevToolsAPI", "eyeDropperPickedColor", std::move(color)); } void DevToolsWindow::InspectedContentsClosing() { if (!close_on_detach_) return; 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 (browser_ && crashed) { browser_->window()->Close(); } } void DevToolsWindow::ShowCertificateViewer(const std::string& cert_chain) { absl::optional value = base::JSONReader::Read(cert_chain); CHECK(value && value->is_list()); std::vector 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 cert_string_piece; for (const auto& str : decoded) cert_string_piece.push_back(str); scoped_refptr cert = net::X509Certificate::CreateFromDERCertChain(cert_string_piece); CHECK(cert); WebContents* inspected_contents = is_docked_ ? GetInspectedWebContents() : main_web_contents_; Browser* browser = nullptr; 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) { 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(); if (!prefs->GetDictionary(prefs::kAppWindowPlacement)->HasKey(kDevToolsApp)) { // Ensure there is always a default size so that // BrowserFrame::InitBrowserFrame can retrieve it later. DictionaryPrefUpdate update(prefs, prefs::kAppWindowPlacement); base::Value* wp_prefs = update.Get(); base::Value dev_tools_defaults(base::Value::Type::DICTIONARY); dev_tools_defaults.SetIntKey("left", 100); dev_tools_defaults.SetIntKey("top", 100); dev_tools_defaults.SetIntKey("right", 740); dev_tools_defaults.SetIntKey("bottom", 740); dev_tools_defaults.SetBoolKey("maximized", false); dev_tools_defaults.SetBoolKey("always_on_top", false); wp_prefs->SetKey(kDevToolsApp, std::move(dev_tools_defaults)); } if (Browser::GetCreationStatusForProfile(profile_) != Browser::CreationStatus::kOk) { return; } browser_ = Browser::Create(Browser::CreateParams::CreateForDevTools(profile_)); browser_->tab_strip_model()->AddWebContents( OwnedMainWebContents::TakeWebContents( std::move(owned_main_web_contents_)), -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL, TabStripModel::ADD_ACTIVE); OverrideAndSyncDevToolsRendererPrefs(); } BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() { Browser* browser = nullptr; int tab; return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(), &browser, &tab) ? browser->window() : nullptr; } 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(params->line_number)), base::Value(static_cast(params->column_number))); break; } default: NOTREACHED(); break; } } void DevToolsWindow::UpdateBrowserToolbar() { BrowserWindow* inspected_window = GetInspectedBrowserWindow(); if (inspected_window) inspected_window->UpdateToolbar(nullptr); } 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() : nullptr; } 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 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; bindings_->CallClientMethod("DevToolsAPI", "reloadInspectedPage", base::Value(bypass_cache)); return true; } void DevToolsWindow::RegisterModalDialogManager(Browser* browser) { web_modal::WebContentsModalDialogManager::CreateForWebContents( main_web_contents_); web_modal::WebContentsModalDialogManager::FromWebContents(main_web_contents_) ->SetDelegate(browser); } void DevToolsWindow::OnReattachMainTargetComplete(base::Value) { std::move(reattach_complete_callback_).Run(); } 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(); }