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
14class Browser;
15class BrowserWindow;
16class DevToolsWindowTesting;
17class DevToolsEventForwarder;
18
19namespace content {
20class DevToolsAgentHost;
21struct NativeWebKeyboardEvent;
22class RenderViewHost;
23}
24
25namespace user_prefs {
26class PrefRegistrySyncable;
27}
28
29class 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