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