C++程序  |  375行  |  16.5 KB

// 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.

#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_
#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "chrome/browser/devtools/devtools_embedder_message_dispatcher.h"
#include "chrome/browser/devtools/devtools_file_helper.h"
#include "chrome/browser/devtools/devtools_file_system_indexer.h"
#include "chrome/browser/devtools/devtools_toggle_action.h"
#include "content/public/browser/devtools_client_host.h"
#include "content/public/browser/devtools_frontend_host_delegate.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/web_contents_delegate.h"

class Browser;
class BrowserWindow;
class DevToolsControllerTest;
class Profile;

namespace base {
class Value;
}

namespace content {
class DevToolsAgentHost;
class DevToolsClientHost;
struct FileChooserParams;
class RenderViewHost;
class WebContents;
}

namespace IPC {
class Message;
}

namespace user_prefs {
class PrefRegistrySyncable;
}

enum DevToolsDockSide {
  DEVTOOLS_DOCK_SIDE_UNDOCKED = 0,
  DEVTOOLS_DOCK_SIDE_BOTTOM,
  DEVTOOLS_DOCK_SIDE_RIGHT,
  DEVTOOLS_DOCK_SIDE_MINIMIZED
};

class DevToolsWindow : private content::NotificationObserver,
                       private content::WebContentsDelegate,
                       private content::DevToolsFrontendHostDelegate,
                       private DevToolsEmbedderMessageDispatcher::Delegate {
 public:
  typedef base::Callback<void(bool)> InfoBarCallback;

  static const char kDevToolsApp[];

  virtual ~DevToolsWindow();

  static std::string GetDevToolsWindowPlacementPrefKey();
  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
  // Return the DevToolsWindow for the given RenderViewHost if one exists,
  // otherwise NULL.
  static DevToolsWindow* GetInstanceForInspectedRenderViewHost(
      content::RenderViewHost* inspected_rvh);
  static DevToolsWindow* GetDockedInstanceForInspectedTab(
      content::WebContents* inspected_tab);
  static bool IsDevToolsWindow(content::RenderViewHost* window_rvh);
  static DevToolsWindow* OpenDevToolsWindowForWorker(
      Profile* profile,
      content::DevToolsAgentHost* worker_agent);
  static DevToolsWindow* CreateDevToolsWindowForWorker(Profile* profile);
  static DevToolsWindow* OpenDevToolsWindow(
      content::RenderViewHost* inspected_rvh);
  static DevToolsWindow* ToggleDevToolsWindow(
      Browser* browser,
      const DevToolsToggleAction& action);
  static void OpenExternalFrontend(Profile* profile,
                                   const std::string& frontend_uri,
                                   content::DevToolsAgentHost* agent_host);

  // Exposed for testing, normal clients should not use this method.
  static DevToolsWindow* ToggleDevToolsWindow(
      content::RenderViewHost* inspected_rvh,
      bool force_open,
      const DevToolsToggleAction& action);

  static void InspectElement(
      content::RenderViewHost* inspected_rvh, int x, int y);

  static int GetMinimumWidth();
  static int GetMinimumHeight();
  static int GetMinimizedHeight();

  // content::DevToolsFrontendHostDelegate:
  virtual void InspectedContentsClosing() OVERRIDE;

  content::WebContents* web_contents() { return web_contents_; }
  Browser* browser() { return browser_; }  // For tests.
  DevToolsDockSide dock_side() const { return dock_side_; }

  content::RenderViewHost* GetRenderViewHost();
  content::DevToolsClientHost* GetDevToolsClientHostForTest();

  // Returns preferred devtools window width for given |container_width|. It
  // tries to use the saved window width, or, if none exists, 1/3 of the
  // container width, then clamps to try and ensure both devtools and content
  // are at least somewhat visible.
  // Called only for the case when devtools window is docked to the side.
  int GetWidth(int container_width);

  // Returns preferred devtools window height for given |container_height|.
  // Uses the same logic as GetWidth.
  // Called only for the case when devtools window is docked to bottom.
  int GetHeight(int container_height);

  // Stores preferred devtools window width for this instance.
  void SetWidth(int width);

  // Stores preferred devtools window height for this instance.
  void SetHeight(int height);

  void Show(const DevToolsToggleAction& action);

  // BeforeUnload interception ////////////////////////////////////////////////

  // In order to preserve any edits the user may have made in devtools, the
  // beforeunload event of the inspected page is hooked - devtools gets the
  // first shot at handling beforeunload and presents a dialog to the user. If
  // the user accepts the dialog then the script is given a chance to handle
  // it. This way 2 dialogs may be displayed: one from the devtools asking the
  // user to confirm that they're ok with their devtools edits going away and
  // another from the webpage as the result of its beforeunload handler.
  // The following set of methods handle beforeunload event flow through
  // devtools window. When the |contents| with devtools opened on them are
  // getting closed, the following sequence of calls takes place:
  // 1. |DevToolsWindow::InterceptPageBeforeUnload| is called and indicates
  //    whether devtools intercept the beforeunload event.
  //    If InterceptPageBeforeUnload() returns true then the following steps
  //    will take place; otherwise only step 4 will be reached and none of the
  //    corresponding functions in steps 2 & 3 will get called.
  // 2. |DevToolsWindow::InterceptPageBeforeUnload| fires beforeunload event
  //    for devtools frontend, which will asynchronously call
  //    |WebContentsDelegate::BeforeUnloadFired| method.
  //    In case of docked devtools window, devtools are set as a delegate for
  //    its frontend, so method |DevToolsWindow::BeforeUnloadFired| will be
  //    called directly.
  //    If devtools window is undocked it's not set as the delegate so the call
  //    to BeforeUnloadFired is proxied through HandleBeforeUnload() rather
  //    than getting called directly.
  // 3a. If |DevToolsWindow::BeforeUnloadFired| is called with |proceed|=false
  //     it calls throught to the content's BeforeUnloadFired(), which from the
  //     WebContents perspective looks the same as the |content|'s own
  //     beforeunload dialog having had it's 'stay on this page' button clicked.
  // 3b. If |proceed| = true, then it fires beforeunload event on |contents|
  //     and everything proceeds as it normally would without the Devtools
  //     interception.
  // 4. If the user cancels the dialog put up by either the WebContents or
  //    devtools frontend, then |contents|'s |BeforeUnloadFired| callback is
  //    called with the proceed argument set to false, this causes
  //    |DevToolsWindow::OnPageCloseCancelled| to be called.

  // Devtools window in undocked state is not set as a delegate of
  // its frontend. Instead, an instance of browser is set as the delegate, and
  // thus beforeunload event callback from devtools frontend is not delivered
  // to the instance of devtools window, which is solely responsible for
  // managing custom beforeunload event flow.
  // This is a helper method to route callback from
  // |Browser::BeforeUnloadFired| back to |DevToolsWindow::BeforeUnloadFired|.
  // * |proceed| - true if the user clicked 'ok' in the beforeunload dialog,
  //   false otherwise.
  // * |proceed_to_fire_unload| - output parameter, whether we should continue
  //   to fire the unload event or stop things here.
  // Returns true if devtools window is in a state of intercepting beforeunload
  // event and if it will manage unload process on its own.
  static bool HandleBeforeUnload(content::WebContents* contents,
                                 bool proceed,
                                 bool* proceed_to_fire_unload);

  // Returns true if this contents beforeunload event was intercepted by
  // devtools and false otherwise. If the event was intercepted, caller should
  // not fire beforeunlaod event on |contents| itself as devtools window will
  // take care of it, otherwise caller should continue handling the event as
  // usual.
  static bool InterceptPageBeforeUnload(content::WebContents* contents);

  // Returns true if devtools browser has already fired its beforeunload event
  // as a result of beforeunload event interception.
  static bool HasFiredBeforeUnloadEventForDevToolsBrowser(Browser* browser);

  // Returns true if devtools window would like to hook beforeunload event
  // of this |contents|.
  static bool NeedsToInterceptBeforeUnload(content::WebContents* contents);

  // Notify devtools window that closing of |contents| was cancelled
  // by user.
  static void OnPageCloseCanceled(content::WebContents* contents);

  void SetDockSideForTest(DevToolsDockSide dock_side);

 private:
  friend class DevToolsControllerTest;

  DevToolsWindow(Profile* profile,
                 const GURL& frontend_url,
                 content::RenderViewHost* inspected_rvh,
                 DevToolsDockSide dock_side);

  static DevToolsWindow* Create(Profile* profile,
                                const GURL& frontend_url,
                                content::RenderViewHost* inspected_rvh,
                                DevToolsDockSide dock_side,
                                bool shared_worker_frontend,
                                bool external_frontend,
                                bool can_dock);
  static GURL GetDevToolsURL(Profile* profile,
                             const GURL& base_url,
                             DevToolsDockSide dock_side,
                             bool shared_worker_frontend,
                             bool external_frontend,
                             bool can_dock);
  static DevToolsWindow* FindDevToolsWindow(content::DevToolsAgentHost*);
  static DevToolsWindow* AsDevToolsWindow(content::RenderViewHost*);
  static DevToolsDockSide GetDockSideFromPrefs(Profile* profile);
  static std::string SideToString(DevToolsDockSide dock_side);
  static DevToolsDockSide SideFromString(const std::string& dock_side);
  static bool FindInspectedBrowserAndTabIndex(
      content::WebContents* inspected_web_contents, Browser**, int* tab);

  // content::NotificationObserver:
  virtual void Observe(int type,
                       const content::NotificationSource& source,
                       const content::NotificationDetails& details) OVERRIDE;

  // content::WebContentsDelegate:
  virtual content::WebContents* OpenURLFromTab(
      content::WebContents* source,
      const content::OpenURLParams& params) OVERRIDE;
  virtual void AddNewContents(content::WebContents* source,
                              content::WebContents* new_contents,
                              WindowOpenDisposition disposition,
                              const gfx::Rect& initial_pos,
                              bool user_gesture,
                              bool* was_blocked) OVERRIDE;
  virtual void CloseContents(content::WebContents* source) OVERRIDE;
  virtual void BeforeUnloadFired(content::WebContents* tab,
                                 bool proceed,
                                 bool* proceed_to_fire_unload) OVERRIDE;
  virtual bool PreHandleKeyboardEvent(
      content::WebContents* source,
      const content::NativeWebKeyboardEvent& event,
      bool* is_keyboard_shortcut) OVERRIDE;
  virtual void HandleKeyboardEvent(
      content::WebContents* source,
      const content::NativeWebKeyboardEvent& event) OVERRIDE;
  virtual content::JavaScriptDialogManager*
      GetJavaScriptDialogManager() OVERRIDE;
  virtual content::ColorChooser* OpenColorChooser(
      content::WebContents* web_contents,
      SkColor color,
      const std::vector<content::ColorSuggestion>& suggestions) OVERRIDE;
  virtual void RunFileChooser(
      content::WebContents* web_contents,
      const content::FileChooserParams& params) OVERRIDE;
  virtual void WebContentsFocused(content::WebContents* contents) OVERRIDE;

  // content::DevToolsFrontendHostDelegate override:
  virtual void DispatchOnEmbedder(const std::string& message) OVERRIDE;

  // DevToolsEmbedderMessageDispatcher::Delegate overrides:
  virtual void ActivateWindow() OVERRIDE;
  virtual void ActivateContents(content::WebContents* contents) OVERRIDE;
  virtual void CloseWindow() OVERRIDE;
  virtual void SetWindowBounds(int x, int y, int width, int height) OVERRIDE;
  virtual void MoveWindow(int x, int y) OVERRIDE;
  virtual void SetDockSide(const std::string& side) OVERRIDE;
  virtual void OpenInNewTab(const std::string& url) OVERRIDE;
  virtual void SaveToFile(const std::string& url,
                          const std::string& content,
                          bool save_as) OVERRIDE;
  virtual void AppendToFile(const std::string& url,
                            const std::string& content) OVERRIDE;
  virtual void RequestFileSystems() OVERRIDE;
  virtual void AddFileSystem() OVERRIDE;
  virtual void RemoveFileSystem(const std::string& file_system_path) OVERRIDE;
  virtual void UpgradeDraggedFileSystemPermissions(
      const std::string& file_system_url) OVERRIDE;
  virtual void IndexPath(int request_id,
                         const std::string& file_system_path) OVERRIDE;
  virtual void StopIndexing(int request_id) OVERRIDE;
  virtual void SearchInPath(int request_id,
                            const std::string& file_system_path,
                            const std::string& query) OVERRIDE;

  // DevToolsFileHelper callbacks.
  void FileSavedAs(const std::string& url);
  void CanceledFileSaveAs(const std::string& url);
  void AppendedTo(const std::string& url);
  void FileSystemsLoaded(
      const std::vector<DevToolsFileHelper::FileSystem>& file_systems);
  void FileSystemAdded(const DevToolsFileHelper::FileSystem& file_system);
  void IndexingTotalWorkCalculated(int request_id,
                                   const std::string& file_system_path,
                                   int total_work);
  void IndexingWorked(int request_id,
                      const std::string& file_system_path,
                      int worked);
  void IndexingDone(int request_id, const std::string& file_system_path);
  void SearchCompleted(int request_id,
                       const std::string& file_system_path,
                       const std::vector<std::string>& file_paths);
  void ShowDevToolsConfirmInfoBar(const base::string16& message,
                                  const InfoBarCallback& callback);

  void CreateDevToolsBrowser();
  BrowserWindow* GetInspectedBrowserWindow();
  bool IsInspectedBrowserPopup();
  void UpdateFrontendDockSide();
  void ScheduleAction(const DevToolsToggleAction& action);
  void DoAction();
  void UpdateTheme();
  void AddDevToolsExtensionsToClient();
  void CallClientFunction(const std::string& function_name,
                          const base::Value* arg1,
                          const base::Value* arg2,
                          const base::Value* arg3);
  void UpdateBrowserToolbar();
  bool IsDocked();
  void Restore();
  content::WebContents* GetInspectedWebContents();
  void DocumentOnLoadCompletedInMainFrame();

  class InspectedWebContentsObserver;
  scoped_ptr<InspectedWebContentsObserver> inspected_contents_observer_;
  class FrontendWebContentsObserver;
  friend class FrontendWebContentsObserver;
  scoped_ptr<FrontendWebContentsObserver> frontend_contents_observer_;

  Profile* profile_;
  content::WebContents* web_contents_;
  Browser* browser_;
  DevToolsDockSide dock_side_;
  bool is_loaded_;
  DevToolsToggleAction action_on_load_;
  content::NotificationRegistrar registrar_;
  scoped_ptr<content::DevToolsClientHost> frontend_host_;
  scoped_ptr<DevToolsFileHelper> file_helper_;
  scoped_refptr<DevToolsFileSystemIndexer> file_system_indexer_;
  typedef std::map<
      int,
      scoped_refptr<DevToolsFileSystemIndexer::FileSystemIndexingJob> >
      IndexingJobsMap;
  IndexingJobsMap indexing_jobs_;
  int width_;
  int height_;
  DevToolsDockSide dock_side_before_minimized_;
  // True if we're in the process of handling a beforeunload event originating
  // from the inspected webcontents, see InterceptPageBeforeUnload for details.
  bool intercepted_page_beforeunload_;

  scoped_ptr<DevToolsEmbedderMessageDispatcher> embedder_message_dispatcher_;
  base::WeakPtrFactory<DevToolsWindow> weak_factory_;
  DISALLOW_COPY_AND_ASSIGN(DevToolsWindow);
};

#endif  // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_