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 "chrome/browser/devtools/devtools_contents_resizing_strategy.h" 9 #include "chrome/browser/devtools/devtools_toggle_action.h" 10 #include "chrome/browser/devtools/devtools_ui_bindings.h" 11 #include "content/public/browser/web_contents_delegate.h" 12 #include "content/public/browser/web_contents_observer.h" 13 14 class Browser; 15 class BrowserWindow; 16 class DevToolsWindowTesting; 17 class DevToolsEventForwarder; 18 19 namespace content { 20 class DevToolsAgentHost; 21 struct NativeWebKeyboardEvent; 22 class RenderViewHost; 23 } 24 25 namespace user_prefs { 26 class PrefRegistrySyncable; 27 } 28 29 class DevToolsWindow : public DevToolsUIBindings::Delegate, 30 public content::WebContentsDelegate { 31 public: 32 class ObserverWithAccessor : public content::WebContentsObserver { 33 public: 34 explicit ObserverWithAccessor(content::WebContents* web_contents); 35 virtual ~ObserverWithAccessor(); 36 37 private: 38 DISALLOW_COPY_AND_ASSIGN(ObserverWithAccessor); 39 }; 40 41 static const char kDevToolsApp[]; 42 43 virtual ~DevToolsWindow(); 44 45 static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); 46 47 // Return the DevToolsWindow for the given WebContents if one exists, 48 // otherwise NULL. 49 static DevToolsWindow* GetInstanceForInspectedWebContents( 50 content::WebContents* inspected_web_contents); 51 52 // Return the docked DevTools WebContents for the given inspected WebContents 53 // if one exists and should be shown in browser window, otherwise NULL. 54 // This method will return only fully initialized window ready to be 55 // presented in UI. 56 // If |out_strategy| is not NULL, it will contain resizing strategy. 57 // For immediately-ready-to-use but maybe not yet fully initialized DevTools 58 // use |GetInstanceForInspectedRenderViewHost| instead. 59 static content::WebContents* GetInTabWebContents( 60 content::WebContents* inspected_tab, 61 DevToolsContentsResizingStrategy* out_strategy); 62 63 static bool IsDevToolsWindow(content::WebContents* web_contents); 64 65 // Open or reveal DevTools window, and perform the specified action. 66 static DevToolsWindow* OpenDevToolsWindow( 67 content::WebContents* inspected_web_contents, 68 const DevToolsToggleAction& action); 69 70 // Open or reveal DevTools window, with no special action. 71 static DevToolsWindow* OpenDevToolsWindow( 72 content::WebContents* inspected_web_contents); 73 74 // Perform specified action for current WebContents inside a |browser|. 75 // This may close currently open DevTools window. 76 static DevToolsWindow* ToggleDevToolsWindow( 77 Browser* browser, 78 const DevToolsToggleAction& action); 79 80 // External frontend is always undocked. 81 static void OpenExternalFrontend( 82 Profile* profile, 83 const std::string& frontend_uri, 84 const scoped_refptr<content::DevToolsAgentHost>& agent_host, 85 bool isWorker); 86 87 // Worker frontend is always undocked. 88 static DevToolsWindow* OpenDevToolsWindowForWorker( 89 Profile* profile, 90 const scoped_refptr<content::DevToolsAgentHost>& worker_agent); 91 92 static void InspectElement(content::WebContents* inspected_web_contents, 93 int x, 94 int y); 95 96 // Sets closure to be called after load is done. If already loaded, calls 97 // closure immediately. 98 void SetLoadCompletedCallback(const base::Closure& closure); 99 100 // Forwards an unhandled keyboard event to the DevTools frontend. 101 bool ForwardKeyboardEvent(const content::NativeWebKeyboardEvent& event); 102 103 // BeforeUnload interception //////////////////////////////////////////////// 104 105 // In order to preserve any edits the user may have made in devtools, the 106 // beforeunload event of the inspected page is hooked - devtools gets the 107 // first shot at handling beforeunload and presents a dialog to the user. If 108 // the user accepts the dialog then the script is given a chance to handle 109 // it. This way 2 dialogs may be displayed: one from the devtools asking the 110 // user to confirm that they're ok with their devtools edits going away and 111 // another from the webpage as the result of its beforeunload handler. 112 // The following set of methods handle beforeunload event flow through 113 // devtools window. When the |contents| with devtools opened on them are 114 // getting closed, the following sequence of calls takes place: 115 // 1. |DevToolsWindow::InterceptPageBeforeUnload| is called and indicates 116 // whether devtools intercept the beforeunload event. 117 // If InterceptPageBeforeUnload() returns true then the following steps 118 // will take place; otherwise only step 4 will be reached and none of the 119 // corresponding functions in steps 2 & 3 will get called. 120 // 2. |DevToolsWindow::InterceptPageBeforeUnload| fires beforeunload event 121 // for devtools frontend, which will asynchronously call 122 // |WebContentsDelegate::BeforeUnloadFired| method. 123 // In case of docked devtools window, devtools are set as a delegate for 124 // its frontend, so method |DevToolsWindow::BeforeUnloadFired| will be 125 // called directly. 126 // If devtools window is undocked it's not set as the delegate so the call 127 // to BeforeUnloadFired is proxied through HandleBeforeUnload() rather 128 // than getting called directly. 129 // 3a. If |DevToolsWindow::BeforeUnloadFired| is called with |proceed|=false 130 // it calls throught to the content's BeforeUnloadFired(), which from the 131 // WebContents perspective looks the same as the |content|'s own 132 // beforeunload dialog having had it's 'stay on this page' button clicked. 133 // 3b. If |proceed| = true, then it fires beforeunload event on |contents| 134 // and everything proceeds as it normally would without the Devtools 135 // interception. 136 // 4. If the user cancels the dialog put up by either the WebContents or 137 // devtools frontend, then |contents|'s |BeforeUnloadFired| callback is 138 // called with the proceed argument set to false, this causes 139 // |DevToolsWindow::OnPageCloseCancelled| to be called. 140 141 // Devtools window in undocked state is not set as a delegate of 142 // its frontend. Instead, an instance of browser is set as the delegate, and 143 // thus beforeunload event callback from devtools frontend is not delivered 144 // to the instance of devtools window, which is solely responsible for 145 // managing custom beforeunload event flow. 146 // This is a helper method to route callback from 147 // |Browser::BeforeUnloadFired| back to |DevToolsWindow::BeforeUnloadFired|. 148 // * |proceed| - true if the user clicked 'ok' in the beforeunload dialog, 149 // false otherwise. 150 // * |proceed_to_fire_unload| - output parameter, whether we should continue 151 // to fire the unload event or stop things here. 152 // Returns true if devtools window is in a state of intercepting beforeunload 153 // event and if it will manage unload process on its own. 154 static bool HandleBeforeUnload(content::WebContents* contents, 155 bool proceed, 156 bool* proceed_to_fire_unload); 157 158 // Returns true if this contents beforeunload event was intercepted by 159 // devtools and false otherwise. If the event was intercepted, caller should 160 // not fire beforeunlaod event on |contents| itself as devtools window will 161 // take care of it, otherwise caller should continue handling the event as 162 // usual. 163 static bool InterceptPageBeforeUnload(content::WebContents* contents); 164 165 // Returns true if devtools browser has already fired its beforeunload event 166 // as a result of beforeunload event interception. 167 static bool HasFiredBeforeUnloadEventForDevToolsBrowser(Browser* browser); 168 169 // Returns true if devtools window would like to hook beforeunload event 170 // of this |contents|. 171 static bool NeedsToInterceptBeforeUnload(content::WebContents* contents); 172 173 // Notify devtools window that closing of |contents| was cancelled 174 // by user. 175 static void OnPageCloseCanceled(content::WebContents* contents); 176 177 private: 178 friend class DevToolsWindowTesting; 179 180 // DevTools lifecycle typically follows this way: 181 // - Toggle/Open: client call; 182 // - Create; 183 // - ScheduleShow: setup window to be functional, but not yet show; 184 // - DocumentOnLoadCompletedInMainFrame: frontend loaded; 185 // - SetIsDocked: frontend decided on docking state; 186 // - OnLoadCompleted: ready to present frontend; 187 // - Show: actually placing frontend WebContents to a Browser or docked place; 188 // - DoAction: perform action passed in Toggle/Open; 189 // - ...; 190 // - CloseWindow: initiates before unload handling; 191 // - CloseContents: destroys frontend; 192 // - DevToolsWindow is dead once it's main_web_contents dies. 193 enum LifeStage { 194 kNotLoaded, 195 kOnLoadFired, // Implies SetIsDocked was not yet called. 196 kIsDockedSet, // Implies DocumentOnLoadCompleted was not yet called. 197 kLoadCompleted, 198 kClosing 199 }; 200 201 DevToolsWindow(Profile* profile, 202 const GURL& frontend_url, 203 content::WebContents* inspected_web_contents, 204 bool can_dock); 205 206 static DevToolsWindow* Create(Profile* profile, 207 const GURL& frontend_url, 208 content::WebContents* inspected_web_contents, 209 bool shared_worker_frontend, 210 bool external_frontend, 211 bool can_dock, 212 const std::string& settings); 213 static GURL GetDevToolsURL(Profile* profile, 214 const GURL& base_url, 215 bool shared_worker_frontend, 216 bool external_frontend, 217 bool can_dock, 218 const std::string& settings); 219 static DevToolsWindow* FindDevToolsWindow(content::DevToolsAgentHost*); 220 static DevToolsWindow* AsDevToolsWindow(content::WebContents*); 221 static DevToolsWindow* CreateDevToolsWindowForWorker(Profile* profile); 222 static DevToolsWindow* ToggleDevToolsWindow( 223 content::WebContents* web_contents, 224 bool force_open, 225 const DevToolsToggleAction& action, 226 const std::string& settings); 227 228 // content::WebContentsDelegate: 229 virtual content::WebContents* OpenURLFromTab( 230 content::WebContents* source, 231 const content::OpenURLParams& params) OVERRIDE; 232 virtual void ActivateContents(content::WebContents* contents) OVERRIDE; 233 virtual void AddNewContents(content::WebContents* source, 234 content::WebContents* new_contents, 235 WindowOpenDisposition disposition, 236 const gfx::Rect& initial_pos, 237 bool user_gesture, 238 bool* was_blocked) OVERRIDE; 239 virtual void WebContentsCreated(content::WebContents* source_contents, 240 int opener_render_frame_id, 241 const base::string16& frame_name, 242 const GURL& target_url, 243 content::WebContents* new_contents) OVERRIDE; 244 virtual void CloseContents(content::WebContents* source) OVERRIDE; 245 virtual void ContentsZoomChange(bool zoom_in) OVERRIDE; 246 virtual void BeforeUnloadFired(content::WebContents* tab, 247 bool proceed, 248 bool* proceed_to_fire_unload) OVERRIDE; 249 virtual bool PreHandleKeyboardEvent( 250 content::WebContents* source, 251 const content::NativeWebKeyboardEvent& event, 252 bool* is_keyboard_shortcut) OVERRIDE; 253 virtual void HandleKeyboardEvent( 254 content::WebContents* source, 255 const content::NativeWebKeyboardEvent& event) OVERRIDE; 256 virtual content::JavaScriptDialogManager* 257 GetJavaScriptDialogManager() OVERRIDE; 258 virtual content::ColorChooser* OpenColorChooser( 259 content::WebContents* web_contents, 260 SkColor color, 261 const std::vector<content::ColorSuggestion>& suggestions) OVERRIDE; 262 virtual void RunFileChooser( 263 content::WebContents* web_contents, 264 const content::FileChooserParams& params) OVERRIDE; 265 virtual void WebContentsFocused(content::WebContents* contents) OVERRIDE; 266 virtual bool PreHandleGestureEvent( 267 content::WebContents* source, 268 const blink::WebGestureEvent& event) OVERRIDE; 269 270 // content::DevToolsUIBindings::Delegate overrides 271 virtual void ActivateWindow() OVERRIDE; 272 virtual void CloseWindow() OVERRIDE; 273 virtual void SetInspectedPageBounds(const gfx::Rect& rect) OVERRIDE; 274 virtual void InspectElementCompleted() OVERRIDE; 275 virtual void MoveWindow(int x, int y) OVERRIDE; 276 virtual void SetIsDocked(bool is_docked) OVERRIDE; 277 virtual void OpenInNewTab(const std::string& url) OVERRIDE; 278 virtual void SetWhitelistedShortcuts(const std::string& message) OVERRIDE; 279 virtual void InspectedContentsClosing() OVERRIDE; 280 virtual void OnLoadCompleted() OVERRIDE; 281 virtual InfoBarService* GetInfoBarService() OVERRIDE; 282 virtual void RenderProcessGone() OVERRIDE; 283 284 void CreateDevToolsBrowser(); 285 BrowserWindow* GetInspectedBrowserWindow(); 286 void ScheduleShow(const DevToolsToggleAction& action); 287 void Show(const DevToolsToggleAction& action); 288 void DoAction(const DevToolsToggleAction& action); 289 void LoadCompleted(); 290 void UpdateBrowserToolbar(); 291 void UpdateBrowserWindow(); 292 content::WebContents* GetInspectedWebContents(); 293 294 scoped_ptr<ObserverWithAccessor> inspected_contents_observer_; 295 296 Profile* profile_; 297 content::WebContents* main_web_contents_; 298 content::WebContents* toolbox_web_contents_; 299 DevToolsUIBindings* bindings_; 300 Browser* browser_; 301 bool is_docked_; 302 const bool can_dock_; 303 LifeStage life_stage_; 304 DevToolsToggleAction action_on_load_; 305 DevToolsContentsResizingStrategy contents_resizing_strategy_; 306 // True if we're in the process of handling a beforeunload event originating 307 // from the inspected webcontents, see InterceptPageBeforeUnload for details. 308 bool intercepted_page_beforeunload_; 309 base::Closure load_completed_callback_; 310 base::Closure close_callback_; 311 312 base::TimeTicks inspect_element_start_time_; 313 scoped_ptr<DevToolsEventForwarder> event_forwarder_; 314 315 friend class DevToolsEventForwarder; 316 DISALLOW_COPY_AND_ASSIGN(DevToolsWindow); 317 }; 318 319 #endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_ 320