1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_ 6 #define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_ 7 8 #include <memory> 9 #include <string> 10 11 #include "base/macros.h" 12 #include "chrome/browser/devtools/devtools_contents_resizing_strategy.h" 13 #include "chrome/browser/devtools/devtools_toggle_action.h" 14 #include "chrome/browser/devtools/devtools_ui_bindings.h" 15 #include "content/public/browser/web_contents_delegate.h" 16 #include "content/public/browser/web_contents_observer.h" 17 18 class Browser; 19 class BrowserWindow; 20 class DevToolsWindowTesting; 21 class DevToolsEventForwarder; 22 class DevToolsEyeDropper; 23 24 namespace content { 25 class DevToolsAgentHost; 26 struct NativeWebKeyboardEvent; 27 class NavigationHandle; 28 class NavigationThrottle; 29 class RenderFrameHost; 30 } 31 32 namespace user_prefs { 33 class PrefRegistrySyncable; 34 } 35 36 // Values that represent different actions to open DevTools window. 37 // These values are written to logs. New enum values can be added, but existing 38 // enums must never be renumbered or deleted and reused. 39 enum class DevToolsOpenedByAction { 40 kUnknown = 0, 41 // Main menu -> More Tools -> Developer Tools 42 // or Ctrl+Shift+I shortcut 43 kMainMenuOrMainShortcut = 1, 44 // Ctrl+Shift+J shortcut to jump to Console 45 kConsoleShortcut = 2, 46 // Context menu -> Inspect 47 kContextMenuInspect = 3, 48 // Ctrl+Shift+C shortcut to turn on inspect mode 49 kInspectorModeShortcut = 4, 50 // Toggle-open via F12 51 kToggleShortcut = 5, 52 // Add values above this line with a corresponding label in 53 // tools/metrics/histograms/enums.xml 54 kMaxValue = kToggleShortcut, 55 }; 56 57 class DevToolsWindow : public DevToolsUIBindings::Delegate, 58 public content::WebContentsDelegate { 59 public: 60 class ObserverWithAccessor : public content::WebContentsObserver { 61 public: 62 explicit ObserverWithAccessor(content::WebContents* web_contents); 63 ~ObserverWithAccessor() override; 64 65 private: 66 DISALLOW_COPY_AND_ASSIGN(ObserverWithAccessor); 67 }; 68 69 static const char kDevToolsApp[]; 70 71 ~DevToolsWindow() override; 72 73 static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); 74 75 // Returns whether DevTools are allowed for the specified 76 // |profile| and |web_contents|. If |web_contents| is null, 77 // only checks for |profile| in general. 78 static bool AllowDevToolsFor(Profile* profile, 79 content::WebContents* web_contents); 80 81 // Return the DevToolsWindow for the given WebContents if one exists, 82 // otherwise nullptr. 83 static DevToolsWindow* GetInstanceForInspectedWebContents( 84 content::WebContents* inspected_web_contents); 85 86 // Return the docked DevTools WebContents for the given inspected WebContents 87 // if one exists and should be shown in browser window, otherwise nullptr. 88 // This method will return only fully initialized window ready to be 89 // presented in UI. 90 // If |out_strategy| is not nullptr, it will contain resizing strategy. 91 // For immediately-ready-to-use but maybe not yet fully initialized DevTools 92 // use |GetInstanceForInspectedRenderViewHost| instead. 93 static content::WebContents* GetInTabWebContents( 94 content::WebContents* inspected_tab, 95 DevToolsContentsResizingStrategy* out_strategy); 96 97 static bool IsDevToolsWindow(content::WebContents* web_contents); 98 static DevToolsWindow* AsDevToolsWindow(content::WebContents* web_contents); 99 static DevToolsWindow* AsDevToolsWindow(Browser* browser); 100 static DevToolsWindow* FindDevToolsWindow(content::DevToolsAgentHost*); 101 102 // Open or reveal DevTools window, and perform the specified action. 103 // How to get pointer to the created window see comments for 104 // ToggleDevToolsWindow(). 105 static void OpenDevToolsWindow(content::WebContents* inspected_web_contents, 106 const DevToolsToggleAction& action); 107 108 // Open or reveal DevTools window, with no special action. 109 // How to get pointer to the created window see comments for 110 // ToggleDevToolsWindow(). 111 static void OpenDevToolsWindow(content::WebContents* inspected_web_contents); 112 113 // Open or reveal DevTools window, with no special action. Use |profile| to 114 // open client window in, default to |host|'s profile if none given. 115 static void OpenDevToolsWindow( 116 scoped_refptr<content::DevToolsAgentHost> host, 117 Profile* profile); 118 // Similar to previous one, but forces the bundled frontend to be used. 119 static void OpenDevToolsWindowWithBundledFrontend( 120 scoped_refptr<content::DevToolsAgentHost> host, 121 Profile* profile); 122 123 // Perform specified action for current WebContents inside a |browser|. 124 // This may close currently open DevTools window. 125 // If DeveloperToolsAvailability policy disallows developer tools for the 126 // current WebContents, no DevTools window created. In case if needed pointer 127 // to the created window one should use DevToolsAgentHost and 128 // DevToolsWindow::FindDevToolsWindow(). E.g.: 129 // 130 // scoped_refptr<content::DevToolsAgentHost> agent( 131 // content::DevToolsAgentHost::GetOrCreateFor(inspected_web_contents)); 132 // DevToolsWindow::ToggleDevToolsWindow( 133 // inspected_web_contents, DevToolsToggleAction::Show()); 134 // DevToolsWindow* window = DevToolsWindow::FindDevToolsWindow(agent.get()); 135 // 136 static void ToggleDevToolsWindow( 137 Browser* browser, 138 const DevToolsToggleAction& action, 139 DevToolsOpenedByAction opened_by = DevToolsOpenedByAction::kUnknown); 140 141 // Node frontend is always undocked. 142 static DevToolsWindow* OpenNodeFrontendWindow(Profile* profile); 143 144 static void InspectElement(content::RenderFrameHost* inspected_frame_host, 145 int x, 146 int y); 147 148 static void LogDevToolsOpenedByAction(DevToolsOpenedByAction opened_by); 149 150 static std::unique_ptr<content::NavigationThrottle> 151 MaybeCreateNavigationThrottle(content::NavigationHandle* handle); 152 153 // Updates the WebContents inspected by the DevToolsWindow by reattaching 154 // the binding to |new_web_contents|. Called when swapping an outer 155 // WebContents with its inner WebContents. 156 void UpdateInspectedWebContents(content::WebContents* new_web_contents, 157 base::OnceCallback<void()> callback); 158 159 // Sets closure to be called after load is done. If already loaded, calls 160 // closure immediately. 161 void SetLoadCompletedCallback(base::OnceClosure closure); 162 163 // Forwards an unhandled keyboard event to the DevTools frontend. 164 bool ForwardKeyboardEvent(const content::NativeWebKeyboardEvent& event); 165 166 // Reloads inspected web contents as if it was triggered from DevTools. 167 // Returns true if it has successfully handled reload, false if the caller 168 // is to proceed reload without DevTools interception. 169 bool ReloadInspectedWebContents(bool bypass_cache); 170 171 content::WebContents* OpenURLFromTab( 172 content::WebContents* source, 173 const content::OpenURLParams& params) override; 174 175 content::WebContents* OpenURLFromInspectedTab( 176 const content::OpenURLParams& params); 177 178 // BeforeUnload interception //////////////////////////////////////////////// 179 180 // In order to preserve any edits the user may have made in devtools, the 181 // beforeunload event of the inspected page is hooked - devtools gets the 182 // first shot at handling beforeunload and presents a dialog to the user. If 183 // the user accepts the dialog then the script is given a chance to handle 184 // it. This way 2 dialogs may be displayed: one from the devtools asking the 185 // user to confirm that they're ok with their devtools edits going away and 186 // another from the webpage as the result of its beforeunload handler. 187 // The following set of methods handle beforeunload event flow through 188 // devtools window. When the |contents| with devtools opened on them are 189 // getting closed, the following sequence of calls takes place: 190 // 1. |DevToolsWindow::InterceptPageBeforeUnload| is called and indicates 191 // whether devtools intercept the beforeunload event. 192 // If InterceptPageBeforeUnload() returns true then the following steps 193 // will take place; otherwise only step 4 will be reached and none of the 194 // corresponding functions in steps 2 & 3 will get called. 195 // 2. |DevToolsWindow::InterceptPageBeforeUnload| fires beforeunload event 196 // for devtools frontend, which will asynchronously call 197 // |WebContentsDelegate::BeforeUnloadFired| method. 198 // In case of docked devtools window, devtools are set as a delegate for 199 // its frontend, so method |DevToolsWindow::BeforeUnloadFired| will be 200 // called directly. 201 // If devtools window is undocked it's not set as the delegate so the call 202 // to BeforeUnloadFired is proxied through HandleBeforeUnload() rather 203 // than getting called directly. 204 // 3a. If |DevToolsWindow::BeforeUnloadFired| is called with |proceed|=false 205 // it calls throught to the content's BeforeUnloadFired(), which from the 206 // WebContents perspective looks the same as the |content|'s own 207 // beforeunload dialog having had it's 'stay on this page' button clicked. 208 // 3b. If |proceed| = true, then it fires beforeunload event on |contents| 209 // and everything proceeds as it normally would without the Devtools 210 // interception. 211 // 4. If the user cancels the dialog put up by either the WebContents or 212 // devtools frontend, then |contents|'s |BeforeUnloadFired| callback is 213 // called with the proceed argument set to false, this causes 214 // |DevToolsWindow::OnPageCloseCancelled| to be called. 215 216 // Devtools window in undocked state is not set as a delegate of 217 // its frontend. Instead, an instance of browser is set as the delegate, and 218 // thus beforeunload event callback from devtools frontend is not delivered 219 // to the instance of devtools window, which is solely responsible for 220 // managing custom beforeunload event flow. 221 // This is a helper method to route callback from 222 // |Browser::BeforeUnloadFired| back to |DevToolsWindow::BeforeUnloadFired|. 223 // * |proceed| - true if the user clicked 'ok' in the beforeunload dialog, 224 // false otherwise. 225 // * |proceed_to_fire_unload| - output parameter, whether we should continue 226 // to fire the unload event or stop things here. 227 // Returns true if devtools window is in a state of intercepting beforeunload 228 // event and if it will manage unload process on its own. 229 static bool HandleBeforeUnload(content::WebContents* contents, 230 bool proceed, 231 bool* proceed_to_fire_unload); 232 233 // Returns true if this contents beforeunload event was intercepted by 234 // devtools and false otherwise. If the event was intercepted, caller should 235 // not fire beforeunlaod event on |contents| itself as devtools window will 236 // take care of it, otherwise caller should continue handling the event as 237 // usual. 238 static bool InterceptPageBeforeUnload(content::WebContents* contents); 239 240 // Returns true if devtools browser has already fired its beforeunload event 241 // as a result of beforeunload event interception. 242 static bool HasFiredBeforeUnloadEventForDevToolsBrowser(Browser* browser); 243 244 // Returns true if devtools window would like to hook beforeunload event 245 // of this |contents|. 246 static bool NeedsToInterceptBeforeUnload(content::WebContents* contents); 247 248 // Notify devtools window that closing of |contents| was cancelled 249 // by user. 250 static void OnPageCloseCanceled(content::WebContents* contents); 251 252 content::WebContents* GetInspectedWebContents(); 253 254 private: 255 friend class DevToolsWindowTesting; 256 friend class DevToolsWindowCreationObserver; 257 friend class HatsNextWebDialogBrowserTest; 258 259 using CreationCallback = base::Callback<void(DevToolsWindow*)>; 260 static void AddCreationCallbackForTest(const CreationCallback& callback); 261 static void RemoveCreationCallbackForTest(const CreationCallback& callback); 262 263 static void OpenDevToolsWindowForFrame( 264 Profile* profile, 265 const scoped_refptr<content::DevToolsAgentHost>& agent_host); 266 static void OpenDevToolsWindowForWorker( 267 Profile* profile, 268 const scoped_refptr<content::DevToolsAgentHost>& worker_agent); 269 270 // DevTools lifecycle typically follows this way: 271 // - Toggle/Open: client call; 272 // - Create; 273 // - ScheduleShow: setup window to be functional, but not yet show; 274 // - DocumentOnLoadCompletedInMainFrame: frontend loaded; 275 // - SetIsDocked: frontend decided on docking state; 276 // - OnLoadCompleted: ready to present frontend; 277 // - Show: actually placing frontend WebContents to a Browser or docked place; 278 // - DoAction: perform action passed in Toggle/Open; 279 // - ...; 280 // - CloseWindow: initiates before unload handling; 281 // - CloseContents: destroys frontend; 282 // - DevToolsWindow is dead once it's main_web_contents dies. 283 enum LifeStage { 284 kNotLoaded, 285 kOnLoadFired, // Implies SetIsDocked was not yet called. 286 kIsDockedSet, // Implies DocumentOnLoadCompleted was not yet called. 287 kLoadCompleted, 288 kClosing 289 }; 290 291 enum FrontendType { 292 kFrontendDefault, 293 kFrontendWorker, 294 kFrontendV8, 295 kFrontendNode, 296 kFrontendRemote, 297 kFrontendRemoteWorker, 298 }; 299 300 DevToolsWindow(FrontendType frontend_type, 301 Profile* profile, 302 std::unique_ptr<content::WebContents> main_web_contents, 303 DevToolsUIBindings* bindings, 304 content::WebContents* inspected_web_contents, 305 bool can_dock); 306 307 // External frontend is always undocked. 308 static void OpenExternalFrontend( 309 Profile* profile, 310 const std::string& frontend_uri, 311 const scoped_refptr<content::DevToolsAgentHost>& agent_host, 312 bool use_bundled_frontend); 313 static void OpenDevToolsWindow(scoped_refptr<content::DevToolsAgentHost> host, 314 Profile* profile, 315 bool use_bundled_frontend); 316 317 static DevToolsWindow* Create(Profile* profile, 318 content::WebContents* inspected_web_contents, 319 FrontendType frontend_type, 320 const std::string& frontend_url, 321 bool can_dock, 322 const std::string& settings, 323 const std::string& panel, 324 bool has_other_clients); 325 static GURL GetDevToolsURL(Profile* profile, 326 FrontendType frontend_type, 327 const std::string& frontend_url, 328 bool can_dock, 329 const std::string& panel, 330 bool has_other_clients); 331 332 static void ToggleDevToolsWindow( 333 content::WebContents* web_contents, 334 bool force_open, 335 const DevToolsToggleAction& action, 336 const std::string& settings, 337 DevToolsOpenedByAction opened_by = DevToolsOpenedByAction::kUnknown); 338 339 // content::WebContentsDelegate: 340 void ActivateContents(content::WebContents* contents) override; 341 void AddNewContents(content::WebContents* source, 342 std::unique_ptr<content::WebContents> new_contents, 343 const GURL& target_url, 344 WindowOpenDisposition disposition, 345 const gfx::Rect& initial_rect, 346 bool user_gesture, 347 bool* was_blocked) override; 348 void WebContentsCreated(content::WebContents* source_contents, 349 int opener_render_process_id, 350 int opener_render_frame_id, 351 const std::string& frame_name, 352 const GURL& target_url, 353 content::WebContents* new_contents) override; 354 void CloseContents(content::WebContents* source) override; 355 void ContentsZoomChange(bool zoom_in) override; 356 void BeforeUnloadFired(content::WebContents* tab, 357 bool proceed, 358 bool* proceed_to_fire_unload) override; 359 content::KeyboardEventProcessingResult PreHandleKeyboardEvent( 360 content::WebContents* source, 361 const content::NativeWebKeyboardEvent& event) override; 362 bool HandleKeyboardEvent( 363 content::WebContents* source, 364 const content::NativeWebKeyboardEvent& event) override; 365 content::JavaScriptDialogManager* GetJavaScriptDialogManager( 366 content::WebContents* source) override; 367 content::ColorChooser* OpenColorChooser( 368 content::WebContents* web_contents, 369 SkColor color, 370 const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) 371 override; 372 void RunFileChooser(content::RenderFrameHost* render_frame_host, 373 scoped_refptr<content::FileSelectListener> listener, 374 const blink::mojom::FileChooserParams& params) override; 375 bool PreHandleGestureEvent(content::WebContents* source, 376 const blink::WebGestureEvent& event) override; 377 378 // content::DevToolsUIBindings::Delegate overrides 379 void ActivateWindow() override; 380 void CloseWindow() override; 381 void Inspect(scoped_refptr<content::DevToolsAgentHost> host) override; 382 void SetInspectedPageBounds(const gfx::Rect& rect) override; 383 void InspectElementCompleted() override; 384 void SetIsDocked(bool is_docked) override; 385 void OpenInNewTab(const std::string& url) override; 386 void SetWhitelistedShortcuts(const std::string& message) override; 387 void SetEyeDropperActive(bool active) override; 388 void OpenNodeFrontend() override; 389 void InspectedContentsClosing() override; 390 void OnLoadCompleted() override; 391 void ReadyForTest() override; 392 void ConnectionReady() override; 393 void SetOpenNewWindowForPopups(bool value) override; 394 InfoBarService* GetInfoBarService() override; 395 void RenderProcessGone(bool crashed) override; 396 void ShowCertificateViewer(const std::string& cert_viewer) override; 397 398 void ColorPickedInEyeDropper(int r, int g, int b, int a); 399 400 // This method create a new Browser object, and passes ownership of 401 // owned_main_web_contents_ to the tab strip of the Browser. 402 void CreateDevToolsBrowser(); 403 BrowserWindow* GetInspectedBrowserWindow(); 404 void ScheduleShow(const DevToolsToggleAction& action); 405 void Show(const DevToolsToggleAction& action); 406 void DoAction(const DevToolsToggleAction& action); 407 void LoadCompleted(); 408 void UpdateBrowserToolbar(); 409 void UpdateBrowserWindow(); 410 411 // Registers a WebContentsModalDialogManager for our WebContents in order to 412 // display web modal dialogs triggered by it. 413 void RegisterModalDialogManager(Browser* browser); 414 415 void OnReattachMainTargetComplete(base::Value); 416 417 // Called when the accepted language changes. |navigator.language| of the 418 // DevTools window should match the application language. When the user 419 // changes the accepted language then this listener flips the language back 420 // to the application language for the DevTools renderer process. 421 // Please note that |navigator.language| will have the wrong language for 422 // a very short period of time (until this handler has reset it again). 423 void OnLocaleChanged(); 424 void OverrideAndSyncDevToolsRendererPrefs(); 425 426 std::unique_ptr<ObserverWithAccessor> inspected_contents_observer_; 427 428 FrontendType frontend_type_; 429 Profile* profile_; 430 content::WebContents* main_web_contents_; 431 432 // DevToolsWindow is informed of the creation of the |toolbox_web_contents_| 433 // in WebContentsCreated right before ownership is passed to to DevToolsWindow 434 // in AddNewContents(). The former call has information not available in the 435 // latter, so it's easiest to record a raw pointer first in 436 // |toolbox_web_contents_|, and then update ownership immediately afterwards. 437 // TODO(erikchen): If we updated AddNewContents() to also pass back the 438 // target url, then we wouldn't need to listen to WebContentsCreated at all. 439 content::WebContents* toolbox_web_contents_; 440 std::unique_ptr<content::WebContents> owned_toolbox_web_contents_; 441 442 DevToolsUIBindings* bindings_; 443 Browser* browser_; 444 445 // When DevToolsWindow is docked, it owns main_web_contents_. When it isn't 446 // docked, the tab strip model owns the main_web_contents_. 447 bool is_docked_; 448 class OwnedMainWebContents; 449 std::unique_ptr<OwnedMainWebContents> owned_main_web_contents_; 450 451 const bool can_dock_; 452 bool close_on_detach_; 453 LifeStage life_stage_; 454 DevToolsToggleAction action_on_load_; 455 DevToolsContentsResizingStrategy contents_resizing_strategy_; 456 // True if we're in the process of handling a beforeunload event originating 457 // from the inspected webcontents, see InterceptPageBeforeUnload for details. 458 bool intercepted_page_beforeunload_; 459 base::OnceClosure load_completed_callback_; 460 base::OnceClosure close_callback_; 461 bool ready_for_test_; 462 base::OnceClosure ready_for_test_callback_; 463 464 base::TimeTicks inspect_element_start_time_; 465 std::unique_ptr<DevToolsEventForwarder> event_forwarder_; 466 std::unique_ptr<DevToolsEyeDropper> eye_dropper_; 467 468 class Throttle; 469 Throttle* throttle_ = nullptr; 470 bool open_new_window_for_popups_ = false; 471 472 base::OnceCallback<void()> reattach_complete_callback_; 473 474 PrefChangeRegistrar pref_change_registrar_; 475 476 friend class DevToolsEventForwarder; 477 DISALLOW_COPY_AND_ASSIGN(DevToolsWindow); 478 }; 479 480 #endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_ 481