diff options
Diffstat (limited to 'chrome/browser/debugger')
-rw-r--r-- | chrome/browser/debugger/OWNERS | 3 | ||||
-rw-r--r-- | chrome/browser/debugger/browser_list_tabcontents_provider.cc | 96 | ||||
-rw-r--r-- | chrome/browser/debugger/browser_list_tabcontents_provider.h | 35 | ||||
-rw-r--r-- | chrome/browser/debugger/devtools_file_helper.cc | 199 | ||||
-rw-r--r-- | chrome/browser/debugger/devtools_file_helper.h | 60 | ||||
-rw-r--r-- | chrome/browser/debugger/devtools_sanity_browsertest.cc | 600 | ||||
-rw-r--r-- | chrome/browser/debugger/devtools_toggle_action.h | 15 | ||||
-rw-r--r-- | chrome/browser/debugger/devtools_window.cc | 914 | ||||
-rw-r--r-- | chrome/browser/debugger/devtools_window.h | 214 | ||||
-rw-r--r-- | chrome/browser/debugger/frontend/devtools_discovery_page.html | 142 | ||||
-rw-r--r-- | chrome/browser/debugger/frontend/devtools_discovery_page_resources.grd | 21 | ||||
-rw-r--r-- | chrome/browser/debugger/remote_debugging_server.cc | 27 | ||||
-rw-r--r-- | chrome/browser/debugger/remote_debugging_server.h | 32 |
13 files changed, 2358 insertions, 0 deletions
diff --git a/chrome/browser/debugger/OWNERS b/chrome/browser/debugger/OWNERS new file mode 100644 index 0000000..3f23035 --- /dev/null +++ b/chrome/browser/debugger/OWNERS @@ -0,0 +1,3 @@ +apavlov@chromium.org +pfeldman@chromium.org +yurys@chromium.org diff --git a/chrome/browser/debugger/browser_list_tabcontents_provider.cc b/chrome/browser/debugger/browser_list_tabcontents_provider.cc new file mode 100644 index 0000000..145728c --- /dev/null +++ b/chrome/browser/debugger/browser_list_tabcontents_provider.cc @@ -0,0 +1,96 @@ +// 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. + +#include "chrome/browser/debugger/browser_list_tabcontents_provider.h" + +#include "base/path_service.h" +#include "chrome/browser/history/top_sites.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/common/chrome_paths.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/url_constants.h" +#include "grit/devtools_discovery_page_resources.h" +#include "net/url_request/url_request_context_getter.h" +#include "ui/base/resource/resource_bundle.h" + +using content::DevToolsHttpHandlerDelegate; +using content::RenderViewHost; + +BrowserListTabContentsProvider::BrowserListTabContentsProvider( + Profile* profile) + : profile_(profile) { +} + +BrowserListTabContentsProvider::~BrowserListTabContentsProvider() { +} + +std::string BrowserListTabContentsProvider::GetDiscoveryPageHTML() { + std::set<Profile*> profiles; + for (BrowserList::const_iterator it = BrowserList::begin(), + end = BrowserList::end(); it != end; ++it) { + profiles.insert((*it)->profile()); + } + for (std::set<Profile*>::iterator it = profiles.begin(); + it != profiles.end(); ++it) { + history::TopSites* ts = (*it)->GetTopSites(); + if (ts) { + // TopSites updates itself after a delay. Ask TopSites to update itself + // when we're about to show the remote debugging landing page. + ts->SyncWithHistory(); + } + } + return ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_DEVTOOLS_DISCOVERY_PAGE_HTML).as_string(); +} + +bool BrowserListTabContentsProvider::BundlesFrontendResources() { + return true; +} + +FilePath BrowserListTabContentsProvider::GetDebugFrontendDir() { +#if defined(DEBUG_DEVTOOLS) + FilePath inspector_dir; + PathService::Get(chrome::DIR_INSPECTOR, &inspector_dir); + return inspector_dir; +#else + return FilePath(); +#endif +} + +std::string BrowserListTabContentsProvider::GetPageThumbnailData( + const GURL& url) { + for (BrowserList::const_iterator it = BrowserList::begin(), + end = BrowserList::end(); it != end; ++it) { + Profile* profile = (*it)->profile(); + history::TopSites* top_sites = profile->GetTopSites(); + if (!top_sites) + continue; + scoped_refptr<base::RefCountedMemory> data; + if (top_sites->GetPageThumbnail(url, &data)) + return std::string( + reinterpret_cast<const char*>(data->front()), data->size()); + } + + return std::string(); +} + +RenderViewHost* BrowserListTabContentsProvider::CreateNewTarget() { + if (BrowserList::empty()) + chrome::NewEmptyWindow(profile_); + + if (BrowserList::empty()) + return NULL; + + content::WebContents* web_contents = chrome::AddSelectedTabWithURL( + *BrowserList::begin(), + GURL(chrome::kAboutBlankURL), + content::PAGE_TRANSITION_LINK); + return web_contents->GetRenderViewHost(); +} diff --git a/chrome/browser/debugger/browser_list_tabcontents_provider.h b/chrome/browser/debugger/browser_list_tabcontents_provider.h new file mode 100644 index 0000000..e6e8039 --- /dev/null +++ b/chrome/browser/debugger/browser_list_tabcontents_provider.h @@ -0,0 +1,35 @@ +// 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_DEBUGGER_BROWSER_LIST_TABCONTENTS_PROVIDER_H_ +#define CHROME_BROWSER_DEBUGGER_BROWSER_LIST_TABCONTENTS_PROVIDER_H_ + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/browser/devtools_http_handler_delegate.h" + +class Profile; + +class BrowserListTabContentsProvider + : public content::DevToolsHttpHandlerDelegate { + public: + explicit BrowserListTabContentsProvider(Profile* profile); + virtual ~BrowserListTabContentsProvider(); + + // DevToolsHttpProtocolHandler::Delegate overrides. + virtual std::string GetDiscoveryPageHTML() OVERRIDE; + virtual bool BundlesFrontendResources() OVERRIDE; + virtual FilePath GetDebugFrontendDir() OVERRIDE; + virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE; + virtual content::RenderViewHost* CreateNewTarget() OVERRIDE; + + private: + Profile* profile_; + DISALLOW_COPY_AND_ASSIGN(BrowserListTabContentsProvider); +}; + +#endif // CHROME_BROWSER_DEBUGGER_BROWSER_LIST_TABCONTENTS_PROVIDER_H_ diff --git a/chrome/browser/debugger/devtools_file_helper.cc b/chrome/browser/debugger/devtools_file_helper.cc new file mode 100644 index 0000000..2acebea --- /dev/null +++ b/chrome/browser/debugger/devtools_file_helper.cc @@ -0,0 +1,199 @@ +// 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. + +#include "chrome/browser/debugger/devtools_file_helper.h" + +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/md5.h" +#include "base/value_conversions.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/download/download_prefs.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/chrome_select_file_policy.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/download_manager.h" +#include "ui/base/dialogs/select_file_dialog.h" + +using content::BrowserContext; +using content::BrowserThread; +using content::DownloadManager; + +namespace { + +base::LazyInstance<FilePath>::Leaky + g_last_save_path = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +class DevToolsFileHelper::SaveAsDialog : public ui::SelectFileDialog::Listener, + public base::RefCounted<SaveAsDialog> { + public: + explicit SaveAsDialog(DevToolsFileHelper* helper) + : helper_(helper) { + select_file_dialog_ = ui::SelectFileDialog::Create( + this, new ChromeSelectFilePolicy(NULL)); + } + + void ResetHelper() { + helper_ = NULL; + } + + void Show(const std::string& url, + const FilePath& initial_path, + const std::string& content) { + AddRef(); // Balanced in the three listener outcomes. + + url_ = url; + content_ = content; + + select_file_dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE, + string16(), + initial_path, + NULL, + 0, + FILE_PATH_LITERAL(""), + NULL, + NULL); + } + + // ui::SelectFileDialog::Listener implementation. + virtual void FileSelected(const FilePath& path, + int index, void* params) { + if (helper_) + helper_->FileSelected(url_, path, content_); + Release(); // Balanced in ::Show. + } + + virtual void MultiFilesSelected( + const std::vector<FilePath>& files, void* params) { + Release(); // Balanced in ::Show. + NOTREACHED() << "Should not be able to select multiple files"; + } + + virtual void FileSelectionCanceled(void* params) { + Release(); // Balanced in ::Show. + } + + private: + friend class base::RefCounted<SaveAsDialog>; + virtual ~SaveAsDialog() {} + + scoped_refptr<ui::SelectFileDialog> select_file_dialog_; + std::string url_; + std::string content_; + DevToolsFileHelper* helper_; +}; + +// static +void DevToolsFileHelper::WriteFile(const FilePath& path, + const std::string& content) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(!path.empty()); + + file_util::WriteFile(path, content.c_str(), content.length()); +} + +// static +void DevToolsFileHelper::AppendToFile(const FilePath& path, + const std::string& content) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(!path.empty()); + + file_util::AppendToFile(path, content.c_str(), content.length()); +} + +DevToolsFileHelper::DevToolsFileHelper(Profile* profile, Delegate* delegate) + : profile_(profile), + delegate_(delegate) { +} + +DevToolsFileHelper::~DevToolsFileHelper() { + if (save_as_dialog_) + save_as_dialog_->ResetHelper(); +} + +void DevToolsFileHelper::Save(const std::string& url, + const std::string& content, + bool save_as) { + PathsMap::iterator it = saved_files_.find(url); + if (it != saved_files_.end() && !save_as) { + FileSelected(url, it->second, content); + return; + } + + if (save_as_dialog_) + save_as_dialog_->ResetHelper(); + + const DictionaryValue* file_map = + profile_->GetPrefs()->GetDictionary(prefs::kDevToolsEditedFiles); + FilePath initial_path; + + const Value* path_value; + if (file_map->Get(base::MD5String(url), &path_value)) + base::GetValueAsFilePath(*path_value, &initial_path); + + if (initial_path.empty()) { + GURL gurl(url); + std::string suggested_file_name = gurl.is_valid() ? + gurl.ExtractFileName() : url; + + if (suggested_file_name.length() > 64) + suggested_file_name = suggested_file_name.substr(0, 64); + + if (!g_last_save_path.Pointer()->empty()) { + initial_path = g_last_save_path.Pointer()->DirName().AppendASCII( + suggested_file_name); + } else { + FilePath download_path = DownloadPrefs::FromDownloadManager( + BrowserContext::GetDownloadManager(profile_))->DownloadPath(); + initial_path = download_path.AppendASCII(suggested_file_name); + } + } + + save_as_dialog_ = new SaveAsDialog(this); + save_as_dialog_->Show(url, initial_path, content); +} + +void DevToolsFileHelper::Append(const std::string& url, + const std::string& content) { + PathsMap::iterator it = saved_files_.find(url); + if (it == saved_files_.end()) + return; + + delegate_->AppendedTo(url); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DevToolsFileHelper::AppendToFile, + it->second, + content)); +} + +void DevToolsFileHelper::FileSelected(const std::string& url, + const FilePath& path, + const std::string& content) { + *g_last_save_path.Pointer() = path; + saved_files_[url] = path; + + DictionaryPrefUpdate update(profile_->GetPrefs(), + prefs::kDevToolsEditedFiles); + DictionaryValue* files_map = update.Get(); + files_map->SetWithoutPathExpansion(base::MD5String(url), + base::CreateFilePathValue(path)); + delegate_->FileSavedAs(url); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DevToolsFileHelper::WriteFile, + path, + content)); +} diff --git a/chrome/browser/debugger/devtools_file_helper.h b/chrome/browser/debugger/devtools_file_helper.h new file mode 100644 index 0000000..641ca5c --- /dev/null +++ b/chrome/browser/debugger/devtools_file_helper.h @@ -0,0 +1,60 @@ +// 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_DEBUGGER_DEVTOOLS_FILE_HELPER_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_FILE_HELPER_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" + +class FilePath; +class Profile; + +class DevToolsFileHelper { + public: + class Delegate { + public: + virtual ~Delegate() {} + virtual void FileSavedAs(const std::string& url) = 0; + virtual void AppendedTo(const std::string& url) = 0; + }; + + DevToolsFileHelper(Profile* profile, Delegate* delegate); + ~DevToolsFileHelper(); + + // Saves |content| to the file and associates its path with given |url|. + // If client is calling this method with given |url| for the first time + // or |save_as| is true, confirmation dialog is shown to the user. + void Save(const std::string& url, + const std::string& content, + bool save_as); + + // Append |content| to the file that has been associated with given |url|. + // The |url| can be associated with a file via calling Save method. + // If the Save method has not been called for this |url|, then + // Append method does nothing. + void Append(const std::string& url, const std::string& content); + + void FileSelected(const std::string& url, + const FilePath& path, + const std::string& content); + + private: + static void WriteFile(const FilePath& path, const std::string& content); + static void AppendToFile(const FilePath& path, const std::string& content); + + class SaveAsDialog; + + Profile* profile_; + Delegate* delegate_; + scoped_refptr<SaveAsDialog> save_as_dialog_; + typedef std::map<std::string, FilePath> PathsMap; + PathsMap saved_files_; + DISALLOW_COPY_AND_ASSIGN(DevToolsFileHelper); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_FILE_HELPER_H_ diff --git a/chrome/browser/debugger/devtools_sanity_browsertest.cc b/chrome/browser/debugger/devtools_sanity_browsertest.cc new file mode 100644 index 0000000..4266e66 --- /dev/null +++ b/chrome/browser/debugger/devtools_sanity_browsertest.cc @@ -0,0 +1,600 @@ +// 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. + +#include "base/bind.h" +#include "base/cancelable_callback.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/path_service.h" +#include "base/stringprintf.h" +#include "base/test/test_timeouts.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/debugger/devtools_window.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/unpacked_installer.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/devtools_agent_host_registry.h" +#include "content/public/browser/devtools_client_host.h" +#include "content/public/browser/devtools_manager.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/worker_service.h" +#include "content/public/browser/worker_service_observer.h" +#include "content/public/test/browser_test_utils.h" +#include "net/test/test_server.h" + +using content::BrowserThread; +using content::DevToolsManager; +using content::DevToolsAgentHost; +using content::DevToolsAgentHostRegistry; +using content::NavigationController; +using content::RenderViewHost; +using content::WebContents; +using content::WorkerService; +using content::WorkerServiceObserver; + +namespace { + +// Used to block until a dev tools client window's browser is closed. +class BrowserClosedObserver : public content::NotificationObserver { + public: + explicit BrowserClosedObserver(Browser* browser) { + registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED, + content::Source<Browser>(browser)); + content::RunMessageLoop(); + } + + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + MessageLoopForUI::current()->Quit(); + } + + private: + content::NotificationRegistrar registrar_; + DISALLOW_COPY_AND_ASSIGN(BrowserClosedObserver); +}; + +// The delay waited in some cases where we don't have a notifications for an +// action we take. +const int kActionDelayMs = 500; + +const char kDebuggerTestPage[] = "files/devtools/debugger_test_page.html"; +const char kPauseWhenLoadingDevTools[] = + "files/devtools/pause_when_loading_devtools.html"; +const char kPauseWhenScriptIsRunning[] = + "files/devtools/pause_when_script_is_running.html"; +const char kPageWithContentScript[] = + "files/devtools/page_with_content_script.html"; +const char kNavigateBackTestPage[] = + "files/devtools/navigate_back.html"; +const char kChunkedTestPage[] = "chunked"; +const char kSlowTestPage[] = + "chunked?waitBeforeHeaders=100&waitBetweenChunks=100&chunksNumber=2"; +const char kSharedWorkerTestPage[] = + "files/workers/workers_ui_shared_worker.html"; +const char kReloadSharedWorkerTestPage[] = + "files/workers/debug_shared_worker_initialization.html"; + +void RunTestFunction(DevToolsWindow* window, const char* test_name) { + std::string result; + + // At first check that JavaScript part of the front-end is loaded by + // checking that global variable uiTests exists(it's created after all js + // files have been loaded) and has runTest method. + ASSERT_TRUE( + content::ExecuteJavaScriptAndExtractString( + window->GetRenderViewHost(), + L"", + L"window.domAutomationController.send(" + L"'' + (window.uiTests && (typeof uiTests.runTest)));", + &result)); + + if (result == "function") { + ASSERT_TRUE( + content::ExecuteJavaScriptAndExtractString( + window->GetRenderViewHost(), + L"", + UTF8ToWide(base::StringPrintf("uiTests.runTest('%s')", + test_name)), + &result)); + EXPECT_EQ("[OK]", result); + } else { + FAIL() << "DevTools front-end is broken."; + } +} + +class DevToolsSanityTest : public InProcessBrowserTest { + public: + DevToolsSanityTest() + : window_(NULL), + inspected_rvh_(NULL) {} + + protected: + void RunTest(const std::string& test_name, const std::string& test_page) { + OpenDevToolsWindow(test_page); + RunTestFunction(window_, test_name.c_str()); + CloseDevToolsWindow(); + } + + void OpenDevToolsWindow(const std::string& test_page) { + ASSERT_TRUE(test_server()->Start()); + GURL url = test_server()->GetURL(test_page); + ui_test_utils::NavigateToURL(browser(), url); + + content::WindowedNotificationObserver observer( + content::NOTIFICATION_LOAD_STOP, + content::NotificationService::AllSources()); + inspected_rvh_ = GetInspectedTab()->GetRenderViewHost(); + window_ = DevToolsWindow::OpenDevToolsWindow(inspected_rvh_); + observer.Wait(); + } + + WebContents* GetInspectedTab() { + return chrome::GetWebContentsAt(browser(), 0); + } + + void CloseDevToolsWindow() { + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + // UnregisterDevToolsClientHostFor may destroy window_ so store the browser + // first. + Browser* browser = window_->browser(); + DevToolsAgentHost* agent = DevToolsAgentHostRegistry::GetDevToolsAgentHost( + inspected_rvh_); + devtools_manager->UnregisterDevToolsClientHostFor(agent); + + // Wait only when DevToolsWindow has a browser. For docked DevTools, this + // is NULL and we skip the wait. + if (browser) + BrowserClosedObserver close_observer(browser); + } + + DevToolsWindow* window_; + RenderViewHost* inspected_rvh_; +}; + +void TimeoutCallback(const std::string& timeout_message) { + FAIL() << timeout_message; + MessageLoop::current()->Quit(); +} + +// Base class for DevTools tests that test devtools functionality for +// extensions and content scripts. +class DevToolsExtensionTest : public DevToolsSanityTest, + public content::NotificationObserver { + public: + DevToolsExtensionTest() : DevToolsSanityTest() { + PathService::Get(chrome::DIR_TEST_DATA, &test_extensions_dir_); + test_extensions_dir_ = test_extensions_dir_.AppendASCII("devtools"); + test_extensions_dir_ = test_extensions_dir_.AppendASCII("extensions"); + } + + protected: + // Load an extension from test\data\devtools\extensions\<extension_name> + void LoadExtension(const char* extension_name) { + FilePath path = test_extensions_dir_.AppendASCII(extension_name); + ASSERT_TRUE(LoadExtensionFromPath(path)) << "Failed to load extension."; + } + + private: + bool LoadExtensionFromPath(const FilePath& path) { + ExtensionService* service = extensions::ExtensionSystem::Get( + browser()->profile())->extension_service(); + size_t num_before = service->extensions()->size(); + { + content::NotificationRegistrar registrar; + registrar.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, + content::NotificationService::AllSources()); + base::CancelableClosure timeout( + base::Bind(&TimeoutCallback, "Extension load timed out.")); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, timeout.callback(), base::TimeDelta::FromSeconds(4)); + extensions::UnpackedInstaller::Create(service)->Load(path); + content::RunMessageLoop(); + timeout.Cancel(); + } + size_t num_after = service->extensions()->size(); + if (num_after != (num_before + 1)) + return false; + + return WaitForExtensionViewsToLoad(); + } + + bool WaitForExtensionViewsToLoad() { + // Wait for all the extension render views that exist to finish loading. + // NOTE: This assumes that the extension views list is not changing while + // this method is running. + + content::NotificationRegistrar registrar; + registrar.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, + content::NotificationService::AllSources()); + base::CancelableClosure timeout( + base::Bind(&TimeoutCallback, "Extension host load timed out.")); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, timeout.callback(), base::TimeDelta::FromSeconds(4)); + + ExtensionProcessManager* manager = + extensions::ExtensionSystem::Get(browser()->profile())-> + process_manager(); + ExtensionProcessManager::ViewSet all_views = manager->GetAllViews(); + for (ExtensionProcessManager::ViewSet::const_iterator iter = + all_views.begin(); + iter != all_views.end();) { + if (!(*iter)->IsLoading()) + ++iter; + else + content::RunMessageLoop(); + } + + timeout.Cancel(); + return true; + } + + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_EXTENSION_LOADED: + case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING: + MessageLoopForUI::current()->Quit(); + break; + default: + NOTREACHED(); + break; + } + } + + FilePath test_extensions_dir_; +}; + +class DevToolsExperimentalExtensionTest : public DevToolsExtensionTest { + public: + void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis); + } +}; + +class WorkerDevToolsSanityTest : public InProcessBrowserTest { + public: + WorkerDevToolsSanityTest() : window_(NULL) {} + + protected: + class WorkerData : public base::RefCountedThreadSafe<WorkerData> { + public: + WorkerData() : worker_process_id(0), worker_route_id(0) {} + int worker_process_id; + int worker_route_id; + + private: + friend class base::RefCountedThreadSafe<WorkerData>; + ~WorkerData() {} + }; + + class WorkerCreationObserver : public WorkerServiceObserver { + public: + explicit WorkerCreationObserver(WorkerData* worker_data) + : worker_data_(worker_data) { + } + + private: + virtual ~WorkerCreationObserver() {} + + virtual void WorkerCreated ( + const GURL& url, + const string16& name, + int process_id, + int route_id) OVERRIDE { + worker_data_->worker_process_id = process_id; + worker_data_->worker_route_id = route_id; + WorkerService::GetInstance()->RemoveObserver(this); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + MessageLoop::QuitClosure()); + delete this; + } + scoped_refptr<WorkerData> worker_data_; + }; + + class WorkerTerminationObserver : public WorkerServiceObserver { + public: + explicit WorkerTerminationObserver(WorkerData* worker_data) + : worker_data_(worker_data) { + } + + private: + virtual ~WorkerTerminationObserver() {} + + virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE { + ASSERT_EQ(worker_data_->worker_process_id, process_id); + ASSERT_EQ(worker_data_->worker_route_id, route_id); + WorkerService::GetInstance()->RemoveObserver(this); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + MessageLoop::QuitClosure()); + delete this; + } + scoped_refptr<WorkerData> worker_data_; + }; + + void RunTest(const char* test_name, const char* test_page) { + ASSERT_TRUE(test_server()->Start()); + GURL url = test_server()->GetURL(test_page); + ui_test_utils::NavigateToURL(browser(), url); + + scoped_refptr<WorkerData> worker_data = WaitForFirstSharedWorker(); + OpenDevToolsWindowForSharedWorker(worker_data.get()); + RunTestFunction(window_, test_name); + CloseDevToolsWindow(); + } + + static void TerminateWorkerOnIOThread(scoped_refptr<WorkerData> worker_data) { + if (WorkerService::GetInstance()->TerminateWorker( + worker_data->worker_process_id, worker_data->worker_route_id)) { + WorkerService::GetInstance()->AddObserver( + new WorkerTerminationObserver(worker_data)); + return; + } + FAIL() << "Failed to terminate worker.\n"; + } + + static void TerminateWorker(scoped_refptr<WorkerData> worker_data) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&TerminateWorkerOnIOThread, worker_data)); + content::RunMessageLoop(); + } + + static void WaitForFirstSharedWorkerOnIOThread( + scoped_refptr<WorkerData> worker_data) { + std::vector<WorkerService::WorkerInfo> worker_info = + WorkerService::GetInstance()->GetWorkers(); + if (!worker_info.empty()) { + worker_data->worker_process_id = worker_info[0].process_id; + worker_data->worker_route_id = worker_info[0].route_id; + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + MessageLoop::QuitClosure()); + return; + } + + WorkerService::GetInstance()->AddObserver( + new WorkerCreationObserver(worker_data.get())); + } + + static scoped_refptr<WorkerData> WaitForFirstSharedWorker() { + scoped_refptr<WorkerData> worker_data(new WorkerData()); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&WaitForFirstSharedWorkerOnIOThread, worker_data)); + content::RunMessageLoop(); + return worker_data; + } + + void OpenDevToolsWindowForSharedWorker(WorkerData* worker_data) { + Profile* profile = browser()->profile(); + window_ = DevToolsWindow::CreateDevToolsWindowForWorker(profile); + window_->Show(DEVTOOLS_TOGGLE_ACTION_SHOW); + DevToolsAgentHost* agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHostForWorker( + worker_data->worker_process_id, + worker_data->worker_route_id); + DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( + agent_host, + window_->devtools_client_host()); + RenderViewHost* client_rvh = window_->GetRenderViewHost(); + WebContents* client_contents = WebContents::FromRenderViewHost(client_rvh); + if (client_contents->IsLoading()) { + content::WindowedNotificationObserver observer( + content::NOTIFICATION_LOAD_STOP, + content::Source<NavigationController>( + &client_contents->GetController())); + observer.Wait(); + } + } + + void CloseDevToolsWindow() { + Browser* browser = window_->browser(); + browser->tab_strip_model()->CloseAllTabs(); + BrowserClosedObserver close_observer(browser); + } + + DevToolsWindow* window_; +}; + + +// Tests scripts panel showing. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestShowScriptsTab) { + RunTest("testShowScriptsTab", kDebuggerTestPage); +} + +// Tests that scripts tab is populated with inspected scripts even if it +// hadn't been shown by the moment inspected paged refreshed. +// @see http://crbug.com/26312 +IN_PROC_BROWSER_TEST_F( + DevToolsSanityTest, + TestScriptsTabIsPopulatedOnInspectedPageRefresh) { + // Clear inspector settings to ensure that Elements will be + // current panel when DevTools window is open. + content::GetContentClient()->browser()->ClearInspectorSettings( + GetInspectedTab()->GetRenderViewHost()); + RunTest("testScriptsTabIsPopulatedOnInspectedPageRefresh", + kDebuggerTestPage); +} + +// Tests that chrome.devtools extension is correctly exposed. +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + TestDevToolsExtensionAPI) { + LoadExtension("devtools_extension"); + RunTest("waitForTestResultsInConsole", ""); +} + +// Tests that chrome.devtools extension can communicate with background page +// using extension messaging. +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + TestDevToolsExtensionMessaging) { + LoadExtension("devtools_messaging"); + RunTest("waitForTestResultsInConsole", ""); +} + +// Tests that chrome.experimental.devtools extension is correctly exposed +// when the extension has experimental permission. +IN_PROC_BROWSER_TEST_F(DevToolsExperimentalExtensionTest, + TestDevToolsExperimentalExtensionAPI) { + LoadExtension("devtools_experimental"); + RunTest("waitForTestResultsInConsole", ""); +} + +// Tests that a content script is in the scripts list. +// http://crbug.com/114104 +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + TestContentScriptIsPresent) { + LoadExtension("simple_content_script"); + RunTest("testContentScriptIsPresent", kPageWithContentScript); +} + +// Tests that scripts are not duplicated after Scripts Panel switch. +// Disabled - see http://crbug.com/124300 +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, + TestNoScriptDuplicatesOnPanelSwitch) { + RunTest("testNoScriptDuplicatesOnPanelSwitch", kDebuggerTestPage); +} + +// Tests that debugger works correctly if pause event occurs when DevTools +// frontend is being loaded. +// http://crbug.com/106114 +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, + TestPauseWhenLoadingDevTools) { + RunTest("testPauseWhenLoadingDevTools", kPauseWhenLoadingDevTools); +} + +// Tests that pressing 'Pause' will pause script execution if the script +// is already running. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestPauseWhenScriptIsRunning) { + RunTest("testPauseWhenScriptIsRunning", kPauseWhenScriptIsRunning); +} + +// Tests network timing. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkTiming) { + RunTest("testNetworkTiming", kSlowTestPage); +} + +// Tests network size. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkSize) { + RunTest("testNetworkSize", kChunkedTestPage); +} + +// Tests raw headers text. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkSyncSize) { + RunTest("testNetworkSyncSize", kChunkedTestPage); +} + +// Tests raw headers text. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkRawHeadersText) { + RunTest("testNetworkRawHeadersText", kChunkedTestPage); +} + +// Tests that console messages are not duplicated on navigation back. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestConsoleOnNavigateBack) { + RunTest("testConsoleOnNavigateBack", kNavigateBackTestPage); +} + +// Tests that inspector will reattach to inspected page when it is reloaded +// after a crash. See http://crbug.com/101952 +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestReattachAfterCrash) { + OpenDevToolsWindow(kDebuggerTestPage); + + content::CrashTab(GetInspectedTab()); + content::WindowedNotificationObserver observer( + content::NOTIFICATION_LOAD_STOP, + content::Source<NavigationController>( + &chrome::GetActiveWebContents(browser())->GetController())); + chrome::Reload(browser(), CURRENT_TAB); + observer.Wait(); + + RunTestFunction(window_, "testReattachAfterCrash"); + CloseDevToolsWindow(); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestPageWithNoJavaScript) { + OpenDevToolsWindow("about:blank"); + std::string result; + ASSERT_TRUE( + content::ExecuteJavaScriptAndExtractString( + window_->GetRenderViewHost(), + L"", + L"window.domAutomationController.send(" + L"'' + (window.uiTests && (typeof uiTests.runTest)));", + &result)); + ASSERT_EQ("function", result) << "DevTools front-end is broken."; + CloseDevToolsWindow(); +} + +#if defined(OS_MACOSX) +#define MAYBE_InspectSharedWorker DISABLED_InspectSharedWorker +#else +#define MAYBE_InspectSharedWorker InspectSharedWorker +#endif +// Flakily fails with 25s timeout: http://crbug.com/89845 +IN_PROC_BROWSER_TEST_F(WorkerDevToolsSanityTest, MAYBE_InspectSharedWorker) { + RunTest("testSharedWorker", kSharedWorkerTestPage); +} + +// http://crbug.com/100538 +#if defined(OS_MACOSX) || defined(OS_WIN) +#define MAYBE_PauseInSharedWorkerInitialization DISABLED_PauseInSharedWorkerInitialization +#else +#define MAYBE_PauseInSharedWorkerInitialization PauseInSharedWorkerInitialization +#endif + +// http://crbug.com/106114 is masking +// MAYBE_PauseInSharedWorkerInitialization into +// DISABLED_PauseInSharedWorkerInitialization +IN_PROC_BROWSER_TEST_F(WorkerDevToolsSanityTest, + MAYBE_PauseInSharedWorkerInitialization) { + ASSERT_TRUE(test_server()->Start()); + GURL url = test_server()->GetURL(kReloadSharedWorkerTestPage); + ui_test_utils::NavigateToURL(browser(), url); + + scoped_refptr<WorkerData> worker_data = WaitForFirstSharedWorker(); + OpenDevToolsWindowForSharedWorker(worker_data.get()); + + TerminateWorker(worker_data); + + // Reload page to restart the worker. + ui_test_utils::NavigateToURL(browser(), url); + + // Wait until worker script is paused on the debugger statement. + RunTestFunction(window_, "testPauseInSharedWorkerInitialization"); + CloseDevToolsWindow(); +} + +// Tests DevToolsAgentHost::AddMessageToConsole. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestAddMessageToConsole) { + OpenDevToolsWindow("about:blank"); + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + DevToolsAgentHost* agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(inspected_rvh_); + devtools_manager->AddMessageToConsole(agent_host, + content::CONSOLE_MESSAGE_LEVEL_LOG, + "log"); + devtools_manager->AddMessageToConsole(agent_host, + content::CONSOLE_MESSAGE_LEVEL_ERROR, + "error"); + RunTestFunction(window_, "checkLogAndErrorMessages"); + CloseDevToolsWindow(); +} + +} // namespace diff --git a/chrome/browser/debugger/devtools_toggle_action.h b/chrome/browser/debugger/devtools_toggle_action.h new file mode 100644 index 0000000..83fbb5f --- /dev/null +++ b/chrome/browser/debugger/devtools_toggle_action.h @@ -0,0 +1,15 @@ +// Copyright (c) 2011 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_DEBUGGER_DEVTOOLS_TOGGLE_ACTION_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_TOGGLE_ACTION_H_ + +enum DevToolsToggleAction { + DEVTOOLS_TOGGLE_ACTION_SHOW, + DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE, + DEVTOOLS_TOGGLE_ACTION_INSPECT, + DEVTOOLS_TOGGLE_ACTION_TOGGLE +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_TOGGLE_ACTION_H_ diff --git a/chrome/browser/debugger/devtools_window.cc b/chrome/browser/debugger/devtools_window.cc new file mode 100644 index 0000000..3001b6b --- /dev/null +++ b/chrome/browser/debugger/devtools_window.cc @@ -0,0 +1,914 @@ +// 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. + +#include <algorithm> + +#include "base/command_line.h" +#include "base/json/json_writer.h" +#include "base/lazy_instance.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/debugger/devtools_window.h" +#include "chrome/browser/extensions/api/debugger/debugger_api.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/file_select_helper.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sessions/session_tab_helper.h" +#include "chrome/browser/themes/theme_service.h" +#include "chrome/browser/themes/theme_service_factory.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_list_impl.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/devtools_agent_host_registry.h" +#include "content/public/browser/devtools_manager.h" +#include "content/public/browser/favicon_status.h" +#include "content/public/browser/load_notification_details.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" +#include "content/public/common/bindings_policy.h" +#include "content/public/common/page_transition_types.h" +#include "grit/generated_resources.h" + +typedef std::vector<DevToolsWindow*> DevToolsWindowList; +namespace { +base::LazyInstance<DevToolsWindowList>::Leaky + g_instances = LAZY_INSTANCE_INITIALIZER; +} // namespace + +using content::DevToolsAgentHost; +using content::DevToolsAgentHostRegistry; +using content::DevToolsClientHost; +using content::DevToolsManager; +using content::FileChooserParams; +using content::NativeWebKeyboardEvent; +using content::NavigationController; +using content::NavigationEntry; +using content::OpenURLParams; +using content::RenderViewHost; +using content::WebContents; + +const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp"; + +const char kOldPrefBottom[] = "bottom"; +const char kOldPrefRight[] = "right"; + +const char kPrefBottom[] = "dock_bottom"; +const char kPrefRight[] = "dock_right"; +const char kPrefUndocked[] = "undocked"; + +const char kDockSideBottom[] = "bottom"; +const char kDockSideRight[] = "right"; +const char kDockSideUndocked[] = "undocked"; + +// Minimal height of devtools pane or content pane when devtools are docked +// to the browser window. +const int kMinDevToolsHeight = 50; +const int kMinDevToolsWidth = 150; +const int kMinContentsSize = 50; + +// static +void DevToolsWindow::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(prefs::kDevToolsOpenDocked, + true, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterStringPref(prefs::kDevToolsDockSide, + kDockSideBottom, + PrefService::UNSYNCABLE_PREF); + prefs->RegisterDictionaryPref(prefs::kDevToolsEditedFiles, + PrefService::UNSYNCABLE_PREF); +} + +// static +DevToolsWindow* DevToolsWindow::GetDockedInstanceForInspectedTab( + WebContents* inspected_web_contents) { + if (!inspected_web_contents) + return NULL; + + if (!DevToolsAgentHostRegistry::HasDevToolsAgentHost( + inspected_web_contents->GetRenderViewHost())) + return NULL; + DevToolsAgentHost* agent = DevToolsAgentHostRegistry::GetDevToolsAgentHost( + inspected_web_contents->GetRenderViewHost()); + DevToolsManager* manager = DevToolsManager::GetInstance(); + DevToolsClientHost* client_host = manager->GetDevToolsClientHostFor(agent); + DevToolsWindow* window = AsDevToolsWindow(client_host); + return window && window->IsDocked() ? window : NULL; +} + +// static +bool DevToolsWindow::IsDevToolsWindow(RenderViewHost* window_rvh) { + return AsDevToolsWindow(window_rvh) != NULL; +} + +// static +DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForWorker( + Profile* profile, + DevToolsAgentHost* worker_agent) { + DevToolsWindow* window; + DevToolsClientHost* client = content::DevToolsManager::GetInstance()-> + GetDevToolsClientHostFor(worker_agent); + if (client) { + window = AsDevToolsWindow(client); + if (!window) + return NULL; + } else { + window = DevToolsWindow::CreateDevToolsWindowForWorker(profile); + DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( + worker_agent, + window->frontend_host_); + } + window->Show(DEVTOOLS_TOGGLE_ACTION_SHOW); + return window; +} + +// static +DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker( + Profile* profile) { + return Create(profile, NULL, DEVTOOLS_DOCK_SIDE_UNDOCKED, true); +} + +// static +DevToolsWindow* DevToolsWindow::OpenDevToolsWindow( + RenderViewHost* inspected_rvh) { + return ToggleDevToolsWindow(inspected_rvh, true, + DEVTOOLS_TOGGLE_ACTION_SHOW); +} + +// static +DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow( + Browser* browser, + DevToolsToggleAction action) { + if (action == DEVTOOLS_TOGGLE_ACTION_TOGGLE && browser->is_devtools()) { + browser->tab_strip_model()->CloseAllTabs(); + return NULL; + } + RenderViewHost* inspected_rvh = + browser->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(); + + return ToggleDevToolsWindow(inspected_rvh, + action == DEVTOOLS_TOGGLE_ACTION_INSPECT, + action); +} + +void DevToolsWindow::InspectElement(RenderViewHost* inspected_rvh, + int x, + int y) { + DevToolsAgentHost* agent = DevToolsAgentHostRegistry::GetDevToolsAgentHost( + inspected_rvh); + DevToolsManager::GetInstance()->InspectElement(agent, x, y); + // TODO(loislo): we should initiate DevTools window opening from within + // renderer. Otherwise, we still can hit a race condition here. + OpenDevToolsWindow(inspected_rvh); +} + + +DevToolsWindow* DevToolsWindow::Create( + Profile* profile, + RenderViewHost* inspected_rvh, + DevToolsDockSide dock_side, + bool shared_worker_frontend) { + // Create WebContents with devtools. + WebContents* web_contents = + WebContents::Create(WebContents::CreateParams(profile)); + web_contents->GetRenderViewHost()->AllowBindings( + content::BINDINGS_POLICY_WEB_UI); + web_contents->GetController().LoadURL( + GetDevToolsUrl(profile, dock_side, shared_worker_frontend), + content::Referrer(), + content::PAGE_TRANSITION_AUTO_TOPLEVEL, + std::string()); + return new DevToolsWindow(web_contents, profile, inspected_rvh, dock_side); +} + +DevToolsWindow::DevToolsWindow(WebContents* web_contents, + Profile* profile, + RenderViewHost* inspected_rvh, + DevToolsDockSide dock_side) + : profile_(profile), + inspected_web_contents_(NULL), + web_contents_(web_contents), + browser_(NULL), + dock_side_(dock_side), + is_loaded_(false), + action_on_load_(DEVTOOLS_TOGGLE_ACTION_SHOW), + width_(-1), + height_(-1) { + frontend_host_ = DevToolsClientHost::CreateDevToolsFrontendHost(web_contents, + this); + file_helper_.reset(new DevToolsFileHelper(profile, this)); + + g_instances.Get().push_back(this); + // Wipe out page icon so that the default application icon is used. + NavigationEntry* entry = web_contents->GetController().GetActiveEntry(); + entry->GetFavicon().image = gfx::Image(); + entry->GetFavicon().valid = true; + + // Register on-load actions. + registrar_.Add( + this, + content::NOTIFICATION_LOAD_STOP, + content::Source<NavigationController>(&web_contents->GetController())); + registrar_.Add( + this, + chrome::NOTIFICATION_TAB_CLOSING, + content::Source<NavigationController>(&web_contents->GetController())); + registrar_.Add( + this, + chrome::NOTIFICATION_BROWSER_THEME_CHANGED, + content::Source<ThemeService>( + ThemeServiceFactory::GetForProfile(profile_))); + // There is no inspected_rvh in case of shared workers. + if (inspected_rvh) + inspected_web_contents_ = WebContents::FromRenderViewHost(inspected_rvh); +} + +DevToolsWindow::~DevToolsWindow() { + DevToolsWindowList& instances = g_instances.Get(); + DevToolsWindowList::iterator it = std::find(instances.begin(), + instances.end(), + this); + DCHECK(it != instances.end()); + instances.erase(it); +} + +void DevToolsWindow::InspectedContentsClosing() { + if (IsDocked()) { + // Update dev tools to reflect removed dev tools window. + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + inspected_window->UpdateDevTools(); + // In case of docked web_contents_, we own it so delete here. + delete web_contents_; + + delete this; + } else { + // First, initiate self-destruct to free all the registrars. + // Then close all tabs. Browser will take care of deleting web_contents_ + // for us. + Browser* browser = browser_; + delete this; + browser->tab_strip_model()->CloseAllTabs(); + } +} + +void DevToolsWindow::ContentsReplaced(WebContents* new_contents) { + inspected_web_contents_ = new_contents; +} + +void DevToolsWindow::Show(DevToolsToggleAction action) { + if (IsDocked()) { + Browser* inspected_browser; + int inspected_tab_index; + // Tell inspected browser to update splitter and switch to inspected panel. + if (!IsInspectedBrowserPopupOrPanel() && + FindInspectedBrowserAndTabIndex(&inspected_browser, + &inspected_tab_index)) { + BrowserWindow* inspected_window = inspected_browser->window(); + web_contents_->SetDelegate(this); + inspected_window->UpdateDevTools(); + web_contents_->GetView()->SetInitialFocus(); + inspected_window->Show(); + TabStripModel* tab_strip_model = inspected_browser->tab_strip_model(); + tab_strip_model->ActivateTabAt(inspected_tab_index, true); + ScheduleAction(action); + return; + } else { + // Sometimes we don't know where to dock. Stay undocked. + dock_side_ = DEVTOOLS_DOCK_SIDE_UNDOCKED; + } + } + + // Avoid consecutive window switching if the devtools window has been opened + // and the Inspect Element shortcut is pressed in the inspected tab. + bool should_show_window = + !browser_ || action != DEVTOOLS_TOGGLE_ACTION_INSPECT; + + if (!browser_) + CreateDevToolsBrowser(); + + if (should_show_window) { + browser_->window()->Show(); + web_contents_->GetView()->SetInitialFocus(); + } + + ScheduleAction(action); +} + +int DevToolsWindow::GetWidth(int container_width) { + if (width_ == -1) { + width_ = profile_->GetPrefs()-> + GetInteger(prefs::kDevToolsVSplitLocation); + } + + // By default, size devtools as 1/3 of the browser window. + if (width_ == -1) + width_ = container_width / 3; + + // Respect the minimum devtools width preset. + width_ = std::max(kMinDevToolsWidth, width_); + + // But it should never compromise the content window size unless the entire + // window is tiny. + width_ = std::min(container_width - kMinContentsSize, width_); + if (width_ < (kMinContentsSize / 2)) + width_ = container_width / 3; + return width_; +} + +int DevToolsWindow::GetHeight(int container_height) { + if (height_ == -1) { + height_ = profile_->GetPrefs()-> + GetInteger(prefs::kDevToolsHSplitLocation); + } + + // By default, size devtools as 1/3 of the browser window. + if (height_ == -1) + height_ = container_height / 3; + + // Respect the minimum devtools width preset. + height_ = std::max(kMinDevToolsHeight, height_); + + // But it should never compromise the content window size. + height_ = std::min(container_height - kMinContentsSize, height_); + if (height_ < (kMinContentsSize / 2)) + height_ = container_height / 3; + return height_; +} + +void DevToolsWindow::SetWidth(int width) { + width_ = width; + profile_->GetPrefs()->SetInteger(prefs::kDevToolsVSplitLocation, width); +} + +void DevToolsWindow::SetHeight(int height) { + height_ = height; + profile_->GetPrefs()->SetInteger(prefs::kDevToolsHSplitLocation, height); +} + +RenderViewHost* DevToolsWindow::GetRenderViewHost() { + return web_contents_->GetRenderViewHost(); +} + +void DevToolsWindow::CreateDevToolsBrowser() { + // TODO(pfeldman): Make browser's getter for this key static. + std::string wp_key; + wp_key.append(prefs::kBrowserWindowPlacement); + wp_key.append("_"); + wp_key.append(kDevToolsApp); + + PrefService* prefs = profile_->GetPrefs(); + if (!prefs->FindPreference(wp_key.c_str())) { + prefs->RegisterDictionaryPref(wp_key.c_str(), PrefService::UNSYNCABLE_PREF); + } + + const DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str()); + if (!wp_pref || wp_pref->empty()) { + DictionaryPrefUpdate update(prefs, wp_key.c_str()); + DictionaryValue* defaults = update.Get(); + defaults->SetInteger("left", 100); + defaults->SetInteger("top", 100); + defaults->SetInteger("right", 740); + defaults->SetInteger("bottom", 740); + defaults->SetBoolean("maximized", false); + defaults->SetBoolean("always_on_top", false); + } + + browser_ = new Browser(Browser::CreateParams::CreateForDevTools(profile_)); + browser_->tab_strip_model()->AddWebContents( + web_contents_, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL, + TabStripModel::ADD_ACTIVE); +} + +bool DevToolsWindow::FindInspectedBrowserAndTabIndex(Browser** browser, + int* tab) { + if (!inspected_web_contents_) + return false; + + bool found = FindInspectedBrowserAndTabIndexFromBrowserList( + chrome::BrowserListImpl::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE), + browser, + tab); + // On Windows 8 we can have the desktop environment and the ASH environment + // active concurrently. If we fail to find the inspected web contents in the + // native browser list, then we should look in the ASH browser list. +#if defined(OS_WIN) && defined(USE_AURA) + if (!found) { + found = FindInspectedBrowserAndTabIndexFromBrowserList( + chrome::BrowserListImpl::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH), + browser, + tab); + } +#endif + return found; +} + +bool DevToolsWindow::FindInspectedBrowserAndTabIndexFromBrowserList( + chrome::BrowserListImpl* browser_list, + Browser** browser, + int* tab) { + if (!inspected_web_contents_) + return false; + + for (chrome::BrowserListImpl::const_iterator it = browser_list->begin(); + it != browser_list->end(); ++it) { + int tab_index = (*it)->tab_strip_model()->GetIndexOfWebContents( + inspected_web_contents_); + if (tab_index != TabStripModel::kNoTab) { + *browser = *it; + *tab = tab_index; + return true; + } + } + return false; +} + +BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() { + Browser* browser = NULL; + int tab; + return FindInspectedBrowserAndTabIndex(&browser, &tab) ? + browser->window() : NULL; +} + +bool DevToolsWindow::IsInspectedBrowserPopupOrPanel() { + Browser* browser = NULL; + int tab; + if (!FindInspectedBrowserAndTabIndex(&browser, &tab)) + return false; + + return browser->is_type_popup() || browser->is_type_panel(); +} + +void DevToolsWindow::UpdateFrontendDockSide() { + base::StringValue dock_side(SideToString(dock_side_)); + CallClientFunction("InspectorFrontendAPI.setDockSide", &dock_side); + base::FundamentalValue docked(IsDocked()); + CallClientFunction("InspectorFrontendAPI.setAttachedWindow", &docked); +} + + +void DevToolsWindow::AddDevToolsExtensionsToClient() { + if (inspected_web_contents_) { + SessionTabHelper* session_tab_helper = + SessionTabHelper::FromWebContents(inspected_web_contents_); + if (session_tab_helper) { + base::FundamentalValue tabId(session_tab_helper->session_id().id()); + CallClientFunction("WebInspector.setInspectedTabId", &tabId); + } + } + ListValue results; + Profile* profile = + Profile::FromBrowserContext(web_contents_->GetBrowserContext()); + const ExtensionService* extension_service = extensions::ExtensionSystem::Get( + profile->GetOriginalProfile())->extension_service(); + if (!extension_service) + return; + + const ExtensionSet* extensions = extension_service->extensions(); + + for (ExtensionSet::const_iterator extension = extensions->begin(); + extension != extensions->end(); ++extension) { + if ((*extension)->devtools_url().is_empty()) + continue; + DictionaryValue* extension_info = new DictionaryValue(); + extension_info->Set("startPage", + new StringValue((*extension)->devtools_url().spec())); + extension_info->Set("name", new StringValue((*extension)->name())); + bool allow_experimental = (*extension)->HasAPIPermission( + extensions::APIPermission::kExperimental); + extension_info->Set("exposeExperimentalAPIs", + new base::FundamentalValue(allow_experimental)); + results.Append(extension_info); + } + CallClientFunction("WebInspector.addExtensions", &results); +} + +WebContents* DevToolsWindow::OpenURLFromTab(WebContents* source, + const OpenURLParams& params) { + if (inspected_web_contents_) + return inspected_web_contents_->OpenURL(params); + return NULL; +} + +void DevToolsWindow::CallClientFunction(const std::string& function_name, + const Value* arg) { + std::string json; + if (arg) + base::JSONWriter::Write(arg, &json); + + string16 javascript = + ASCIIToUTF16(function_name + "(" + json + ");"); + web_contents_->GetRenderViewHost()-> + ExecuteJavascriptInWebFrame(string16(), javascript); +} + +void DevToolsWindow::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == content::NOTIFICATION_LOAD_STOP && !is_loaded_) { + is_loaded_ = true; + UpdateTheme(); + DoAction(); + AddDevToolsExtensionsToClient(); + } else if (type == chrome::NOTIFICATION_TAB_CLOSING) { + if (content::Source<NavigationController>(source).ptr() == + &web_contents_->GetController()) { + // This happens when browser closes all of its tabs as a result + // of window.Close event. + // Notify manager that this DevToolsClientHost no longer exists and + // initiate self-destuct here. + DevToolsManager::GetInstance()->ClientHostClosing(frontend_host_); + UpdateBrowserToolbar(); + delete this; + } + } else if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) { + UpdateTheme(); + } +} + +void DevToolsWindow::ScheduleAction(DevToolsToggleAction action) { + action_on_load_ = action; + if (is_loaded_) + DoAction(); +} + +void DevToolsWindow::DoAction() { + UpdateFrontendDockSide(); + switch (action_on_load_) { + case DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE: + CallClientFunction("InspectorFrontendAPI.showConsole", NULL); + break; + case DEVTOOLS_TOGGLE_ACTION_INSPECT: + CallClientFunction("InspectorFrontendAPI.enterInspectElementMode", NULL); + case DEVTOOLS_TOGGLE_ACTION_SHOW: + case DEVTOOLS_TOGGLE_ACTION_TOGGLE: + // Do nothing. + break; + default: + NOTREACHED(); + } + action_on_load_ = DEVTOOLS_TOGGLE_ACTION_SHOW; +} + +std::string SkColorToRGBAString(SkColor color) { + // We convert the alpha using DoubleToString because StringPrintf will use + // locale specific formatters (e.g., use , instead of . in German). + return StringPrintf("rgba(%d,%d,%d,%s)", SkColorGetR(color), + SkColorGetG(color), SkColorGetB(color), + base::DoubleToString(SkColorGetA(color) / 255.0).c_str()); +} + +// static +GURL DevToolsWindow::GetDevToolsUrl(Profile* profile, + DevToolsDockSide dock_side, + bool shared_worker_frontend) { + ThemeService* tp = ThemeServiceFactory::GetForProfile(profile); + CHECK(tp); + + SkColor color_toolbar = + tp->GetColor(ThemeService::COLOR_TOOLBAR); + SkColor color_tab_text = + tp->GetColor(ThemeService::COLOR_BOOKMARK_TEXT); + + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + bool experiments_enabled = + command_line.HasSwitch(switches::kEnableDevToolsExperiments); + + std::string url_string = StringPrintf("%sdevtools.html?" + "dockSide=%s&toolbarColor=%s&textColor=%s%s%s", + chrome::kChromeUIDevToolsURL, + SideToString(dock_side).c_str(), + SkColorToRGBAString(color_toolbar).c_str(), + SkColorToRGBAString(color_tab_text).c_str(), + shared_worker_frontend ? "&isSharedWorker=true" : "", + experiments_enabled ? "&experiments=true" : ""); + return GURL(url_string); +} + +void DevToolsWindow::UpdateTheme() { + ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_); + CHECK(tp); + + SkColor color_toolbar = + tp->GetColor(ThemeService::COLOR_TOOLBAR); + SkColor color_tab_text = + tp->GetColor(ThemeService::COLOR_BOOKMARK_TEXT); + std::string command = StringPrintf( + "InspectorFrontendAPI.setToolbarColors(\"%s\", \"%s\")", + SkColorToRGBAString(color_toolbar).c_str(), + SkColorToRGBAString(color_tab_text).c_str()); + web_contents_->GetRenderViewHost()-> + ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(command)); +} + +void DevToolsWindow::AddNewContents(WebContents* source, + WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture, + bool* was_blocked) { + if (inspected_web_contents_) { + inspected_web_contents_->GetDelegate()->AddNewContents( + source, new_contents, disposition, initial_pos, user_gesture, + was_blocked); + } +} + +bool DevToolsWindow::PreHandleKeyboardEvent( + WebContents* source, + const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { + if (IsDocked()) { + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + return inspected_window->PreHandleKeyboardEvent( + event, is_keyboard_shortcut); + } + return false; +} + +void DevToolsWindow::HandleKeyboardEvent(WebContents* source, + const NativeWebKeyboardEvent& event) { + if (IsDocked()) { + if (event.windowsKeyCode == 0x08) { + // Do not navigate back in history on Windows (http://crbug.com/74156). + return; + } + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + inspected_window->HandleKeyboardEvent(event); + } +} + +// static +DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow( + RenderViewHost* inspected_rvh, + bool force_open, + DevToolsToggleAction action) { + DevToolsAgentHost* agent = DevToolsAgentHostRegistry::GetDevToolsAgentHost( + inspected_rvh); + DevToolsManager* manager = DevToolsManager::GetInstance(); + DevToolsClientHost* host = manager->GetDevToolsClientHostFor(agent); + DevToolsWindow* window = AsDevToolsWindow(host); + if (host && !window) { + // Break remote debugging / extension debugging session. + host->ReplacedWithAnotherClient(); + manager->UnregisterDevToolsClientHostFor(agent); + } + + bool do_open = force_open; + if (!window) { + Profile* profile = Profile::FromBrowserContext( + inspected_rvh->GetProcess()->GetBrowserContext()); + DevToolsDockSide dock_side = GetDockSideFromPrefs(profile); + window = Create(profile, inspected_rvh, dock_side, false); + manager->RegisterDevToolsClientHostFor(agent, window->frontend_host_); + do_open = true; + } + + // Update toolbar to reflect DevTools changes. + window->UpdateBrowserToolbar(); + + // If window is docked and visible, we hide it on toggle. If window is + // undocked, we show (activate) it. + if (!window->IsDocked() || do_open) + window->Show(action); + else + manager->UnregisterDevToolsClientHostFor(agent); + + return window; +} + +// static +DevToolsWindow* DevToolsWindow::AsDevToolsWindow( + DevToolsClientHost* client_host) { + if (!client_host || g_instances == NULL) + return NULL; + DevToolsWindowList& instances = g_instances.Get(); + for (DevToolsWindowList::iterator it = instances.begin(); + it != instances.end(); ++it) { + if ((*it)->frontend_host_ == client_host) + return *it; + } + return NULL; +} + +// static +DevToolsWindow* DevToolsWindow::AsDevToolsWindow(RenderViewHost* window_rvh) { + if (g_instances == NULL) + return NULL; + DevToolsWindowList& instances = g_instances.Get(); + for (DevToolsWindowList::iterator it = instances.begin(); + it != instances.end(); ++it) { + if ((*it)->web_contents_->GetRenderViewHost() == window_rvh) + return *it; + } + return NULL; +} + +void DevToolsWindow::ActivateWindow() { + if (!IsDocked()) { + if (!browser_->window()->IsActive()) { + browser_->window()->Activate(); + } + } else { + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + web_contents_->GetView()->Focus(); + } +} + +void DevToolsWindow::CloseWindow() { + DCHECK(IsDocked()); + DevToolsManager::GetInstance()->ClientHostClosing(frontend_host_); + InspectedContentsClosing(); +} + +void DevToolsWindow::MoveWindow(int x, int y) { + if (!IsDocked()) { + gfx::Rect bounds = browser_->window()->GetBounds(); + bounds.Offset(x, y); + browser_->window()->SetBounds(bounds); + } +} + +void DevToolsWindow::SetDockSide(const std::string& side) { + DevToolsDockSide requested_side = SideFromString(side); + bool dock_requested = requested_side != DEVTOOLS_DOCK_SIDE_UNDOCKED; + bool is_docked = IsDocked(); + + if (dock_requested && (!inspected_web_contents_ || + !GetInspectedBrowserWindow() || IsInspectedBrowserPopupOrPanel())) { + // Cannot dock, avoid window flashing due to close-reopen cycle. + return; + } + + dock_side_ = requested_side; + if (dock_requested) { + if (!is_docked) { + // Detach window from the external devtools browser. It will lead to + // the browser object's close and delete. Remove observer first. + TabStripModel* tab_strip_model = browser_->tab_strip_model(); + tab_strip_model->DetachWebContentsAt( + tab_strip_model->GetIndexOfWebContents(web_contents_)); + browser_ = NULL; + } + } else if (is_docked) { + // Update inspected window to hide split and reset it. + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + inspected_window->UpdateDevTools(); + } + + std::string pref_value = kPrefBottom; + switch (dock_side_) { + case DEVTOOLS_DOCK_SIDE_UNDOCKED: + pref_value = kPrefUndocked; + break; + case DEVTOOLS_DOCK_SIDE_RIGHT: + pref_value = kPrefRight; + break; + case DEVTOOLS_DOCK_SIDE_BOTTOM: + pref_value = kPrefBottom; + break; + } + profile_->GetPrefs()->SetString(prefs::kDevToolsDockSide, pref_value); + + Show(DEVTOOLS_TOGGLE_ACTION_SHOW); +} + +void DevToolsWindow::OpenInNewTab(const std::string& url) { + OpenURLParams params(GURL(url), + content::Referrer(), + NEW_FOREGROUND_TAB, + content::PAGE_TRANSITION_LINK, + false /* is_renderer_initiated */); + if (inspected_web_contents_) { + inspected_web_contents_->OpenURL(params); + } else { + for (BrowserList::const_iterator it = BrowserList::begin(); + it != BrowserList::end(); ++it) { + if ((*it)->type() == Browser::TYPE_TABBED) { + (*it)->OpenURL(params); + break; + } + } + } +} + +void DevToolsWindow::SaveToFile(const std::string& url, + const std::string& content, + bool save_as) { + file_helper_->Save(url, content, save_as); +} + +void DevToolsWindow::AppendToFile(const std::string& url, + const std::string& content) { + file_helper_->Append(url, content); +} + +void DevToolsWindow::FileSavedAs(const std::string& url) { + StringValue url_value(url); + CallClientFunction("InspectorFrontendAPI.savedURL", &url_value); +} + +void DevToolsWindow::AppendedTo(const std::string& url) { + StringValue url_value(url); + CallClientFunction("InspectorFrontendAPI.appendedToURL", &url_value); +} + +content::JavaScriptDialogCreator* DevToolsWindow::GetJavaScriptDialogCreator() { + if (inspected_web_contents_ && inspected_web_contents_->GetDelegate()) { + return inspected_web_contents_->GetDelegate()-> + GetJavaScriptDialogCreator(); + } + return content::WebContentsDelegate::GetJavaScriptDialogCreator(); +} + +void DevToolsWindow::RunFileChooser(WebContents* web_contents, + const FileChooserParams& params) { + FileSelectHelper::RunFileChooser(web_contents, params); +} + +void DevToolsWindow::WebContentsFocused(WebContents* contents) { + Browser* inspected_browser = NULL; + int inspected_tab_index = -1; + + if (IsDocked() && FindInspectedBrowserAndTabIndex(&inspected_browser, + &inspected_tab_index)) { + inspected_browser->window()->WebContentsFocused(contents); + } +} + +void DevToolsWindow::UpdateBrowserToolbar() { + if (!inspected_web_contents_) + return; + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + inspected_window->UpdateToolbar(inspected_web_contents_, false); +} + +bool DevToolsWindow::IsDocked() { + return dock_side_ != DEVTOOLS_DOCK_SIDE_UNDOCKED; +} + +// static +DevToolsDockSide DevToolsWindow::GetDockSideFromPrefs(Profile* profile) { + std::string dock_side = + profile->GetPrefs()->GetString(prefs::kDevToolsDockSide); + + // Migrate prefs + if (dock_side == kOldPrefBottom || dock_side == kOldPrefRight) { + bool docked = profile->GetPrefs()->GetBoolean(prefs::kDevToolsOpenDocked); + if (dock_side == kOldPrefBottom) + return docked ? DEVTOOLS_DOCK_SIDE_BOTTOM : DEVTOOLS_DOCK_SIDE_UNDOCKED; + else + return docked ? DEVTOOLS_DOCK_SIDE_RIGHT : DEVTOOLS_DOCK_SIDE_UNDOCKED; + } + + if (dock_side == kPrefUndocked) + return DEVTOOLS_DOCK_SIDE_UNDOCKED; + else if (dock_side == kPrefRight) + return DEVTOOLS_DOCK_SIDE_RIGHT; + // Default to docked to bottom + return DEVTOOLS_DOCK_SIDE_BOTTOM; +} + +// static +std::string DevToolsWindow::SideToString(DevToolsDockSide dock_side) { + std::string dock_side_string; + switch (dock_side) { + case DEVTOOLS_DOCK_SIDE_UNDOCKED: return kDockSideUndocked; + case DEVTOOLS_DOCK_SIDE_RIGHT: return kDockSideRight; + case DEVTOOLS_DOCK_SIDE_BOTTOM: return kDockSideBottom; + } + return kDockSideUndocked; +} + +// static +DevToolsDockSide DevToolsWindow::SideFromString( + const std::string& dock_side) { + if (dock_side == kDockSideRight) + return DEVTOOLS_DOCK_SIDE_RIGHT; + if (dock_side == kDockSideBottom) + return DEVTOOLS_DOCK_SIDE_BOTTOM; + return DEVTOOLS_DOCK_SIDE_UNDOCKED; +} diff --git a/chrome/browser/debugger/devtools_window.h b/chrome/browser/debugger/devtools_window.h new file mode 100644 index 0000000..b0b72a0 --- /dev/null +++ b/chrome/browser/debugger/devtools_window.h @@ -0,0 +1,214 @@ +// 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_DEBUGGER_DEVTOOLS_WINDOW_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_WINDOW_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/debugger/devtools_file_helper.h" +#include "chrome/browser/debugger/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 PrefService; +class Profile; + +namespace base { +class Value; +} + +namespace chrome { +class BrowserListImpl; +} + +namespace content { +class DevToolsAgentHost; +class DevToolsClientHost; +struct FileChooserParams; +class RenderViewHost; +class WebContents; +} + +namespace IPC { +class Message; +} + +enum DevToolsDockSide { + DEVTOOLS_DOCK_SIDE_UNDOCKED = 0, + DEVTOOLS_DOCK_SIDE_BOTTOM, + DEVTOOLS_DOCK_SIDE_RIGHT +}; + +class DevToolsWindow : private content::NotificationObserver, + private content::WebContentsDelegate, + private content::DevToolsFrontendHostDelegate, + private DevToolsFileHelper::Delegate { + public: + static const char kDevToolsApp[]; + static void RegisterUserPrefs(PrefService* prefs); + 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, + DevToolsToggleAction action); + + // Exposed for testing, normal clients should not use this method. + static DevToolsWindow* ToggleDevToolsWindow( + content::RenderViewHost* inspected_rvh, + bool force_open, + DevToolsToggleAction action); + static void InspectElement( + content::RenderViewHost* inspected_rvh, int x, int y); + + virtual ~DevToolsWindow(); + + // Overridden from DevToolsClientHost. + virtual void InspectedContentsClosing() OVERRIDE; + virtual void ContentsReplaced(content::WebContents* new_contents) OVERRIDE; + content::RenderViewHost* GetRenderViewHost(); + + void Show(DevToolsToggleAction action); + + content::WebContents* web_contents() { return web_contents_; } + Browser* browser() { return browser_; } // For tests. + DevToolsDockSide dock_side() { return dock_side_; } + content::DevToolsClientHost* devtools_client_host() { return frontend_host_; } + + // 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); + + private: + static DevToolsWindow* Create(Profile* profile, + content::RenderViewHost* inspected_rvh, + DevToolsDockSide dock_side, + bool shared_worker_frontend); + DevToolsWindow(content::WebContents* web_contents, + Profile* profile, + content::RenderViewHost* inspected_rvh, + DevToolsDockSide dock_side); + + void CreateDevToolsBrowser(); + bool FindInspectedBrowserAndTabIndex(Browser**, int* tab); + bool FindInspectedBrowserAndTabIndexFromBrowserList( + chrome::BrowserListImpl* browser_list, + Browser** browser, + int* tab); + BrowserWindow* GetInspectedBrowserWindow(); + bool IsInspectedBrowserPopupOrPanel(); + void UpdateFrontendDockSide(); + + // Overridden from content::NotificationObserver. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + void ScheduleAction(DevToolsToggleAction action); + void DoAction(); + static GURL GetDevToolsUrl(Profile* profile, + DevToolsDockSide dock_side, + bool shared_worker_frontend); + void UpdateTheme(); + void AddDevToolsExtensionsToClient(); + void CallClientFunction(const std::string& function_name, + const base::Value* arg); + // Overridden from 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 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::JavaScriptDialogCreator* + GetJavaScriptDialogCreator() OVERRIDE; + virtual void RunFileChooser( + content::WebContents* web_contents, + const content::FileChooserParams& params) OVERRIDE; + virtual void WebContentsFocused(content::WebContents* contents) OVERRIDE; + + virtual void FrameNavigating(const std::string& url) OVERRIDE {} + + static DevToolsWindow* AsDevToolsWindow(content::DevToolsClientHost*); + static DevToolsWindow* AsDevToolsWindow(content::RenderViewHost*); + + // content::DevToolsFrontendHostDelegate overrides. + virtual void ActivateWindow() OVERRIDE; + virtual void CloseWindow() 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; + + // Overridden from DevToolsFileHelper::Delegate + virtual void FileSavedAs(const std::string& url) OVERRIDE; + virtual void AppendedTo(const std::string& url) OVERRIDE; + + void UpdateBrowserToolbar(); + bool IsDocked(); + static DevToolsDockSide GetDockSideFromPrefs(Profile* profile); + static std::string SideToString(DevToolsDockSide dock_side); + static DevToolsDockSide SideFromString(const std::string& dock_side); + + Profile* profile_; + content::WebContents* inspected_web_contents_; + content::WebContents* web_contents_; + Browser* browser_; + DevToolsDockSide dock_side_; + bool is_loaded_; + DevToolsToggleAction action_on_load_; + content::NotificationRegistrar registrar_; + content::DevToolsClientHost* frontend_host_; + scoped_ptr<DevToolsFileHelper> file_helper_; + int width_; + int height_; + DISALLOW_COPY_AND_ASSIGN(DevToolsWindow); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_WINDOW_H_ diff --git a/chrome/browser/debugger/frontend/devtools_discovery_page.html b/chrome/browser/debugger/frontend/devtools_discovery_page.html new file mode 100644 index 0000000..85c03b8 --- /dev/null +++ b/chrome/browser/debugger/frontend/devtools_discovery_page.html @@ -0,0 +1,142 @@ +<html> +<head> +<title>Inspectable pages</title> +<style> +body { + background-color: rgb(245, 245, 245); + font-family: Helvetica, Arial, sans-serif; + text-shadow: rgba(255, 255, 255, 0.496094) 0px 1px 0px; +} + +#caption { + text-align: left; + color: black; + font-size: 16px; + margin-top: 30px; + margin-bottom: 0px; + margin-left: 70px; + height: 20px; +} + +#items { + display: -webkit-box; + -webkit-box-orient: horizontal; + -webkit-box-lines: multiple; + margin-left: 60px; + margin-right: 60px; +} + +.frontend_ref { + color: black; + text-decoration: initial; +} + +.thumbnail { + height: 132px; + width: 212px; + background-attachment: scroll; + background-origin: padding-box; + background-repeat: no-repeat; + border: 4px solid rgba(184, 184, 184, 1); + border-radius: 5px; + -webkit-transition-property: background-color, border-color; + -webkit-transition: background-color 0.15s, 0.15s; + -webkit-transition-delay: 0, 0; +} + +.thumbnail:hover { + background-color: rgba(242, 242, 242, 1); + border-color: rgba(110, 116, 128, 1); + color: black; +} + +.thumbnail.connected { + opacity: 0.5; +} + +.thumbnail.connected:hover { + border-color: rgba(184, 184, 184, 1); + color: rgb(110, 116, 128); +} + +.item { + display: inline-block; + margin: 5px; + margin-top: 15px; + height: 162px; + width: 222px; + vertical-align: top; +} + +.text { + text-align: left; + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + background: no-repeat 0; + background-size: 16px; + padding: 2px 0px 0px 20px; + margin: 4px 0px 0px 4px; +} +</style> + +<script> +function onLoad() { + var tabsListRequest = new XMLHttpRequest(); + tabsListRequest.open("GET", "/json", true); + tabsListRequest.onreadystatechange = onReady; + tabsListRequest.send(); +} + +function onReady() { + if(this.readyState == 4 && this.status == 200) { + if(this.response != null) + var responseJSON = JSON.parse(this.response); + for (var i = 0; i < responseJSON.length; ++i) + appendItem(responseJSON[i]); + } +} + +function appendItem(item_object) { + var frontend_ref; + if (item_object.devtoolsFrontendUrl) { + frontend_ref = document.createElement("a"); + frontend_ref.href = item_object.devtoolsFrontendUrl; + frontend_ref.title = item_object.title; + } else { + frontend_ref = document.createElement("div"); + frontend_ref.title = "The tab already has an active debug session"; + } + frontend_ref.className = "frontend_ref"; + + var thumbnail = document.createElement("div"); + thumbnail.className = item_object.devtoolsFrontendUrl ? + "thumbnail" : "thumbnail connected"; + thumbnail.style.cssText = "background-image:url(" + + item_object.thumbnailUrl + + ")"; + frontend_ref.appendChild(thumbnail); + + var text = document.createElement("div"); + text.className = "text"; + text.innerText = item_object.title; + text.style.cssText = "background-image:url(" + + item_object.faviconUrl + ")"; + frontend_ref.appendChild(text); + + var item = document.createElement("p"); + item.className = "item"; + item.appendChild(frontend_ref); + + document.getElementById("items").appendChild(item); +} +</script> +</head> +<body onload='onLoad()'> + <div id='caption'>Inspectable pages</div> + <div id='items'> + </div> + <hr> +</body> +</html> diff --git a/chrome/browser/debugger/frontend/devtools_discovery_page_resources.grd b/chrome/browser/debugger/frontend/devtools_discovery_page_resources.grd new file mode 100644 index 0000000..85ad2aa --- /dev/null +++ b/chrome/browser/debugger/frontend/devtools_discovery_page_resources.grd @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grit current_release="1" latest_public_release="0"> + <outputs> + <output filename="grit/devtools_discovery_page_resources.h" + type="rc_header"> + <emit emit_type="prepend"/> + </output> + <output filename="grit/devtools_discovery_page_resources_map.cc" + type="resource_file_map_source"/> + <output filename="grit/devtools_discovery_page_resources_map.h" + type="resource_map_header"/> + <output filename="devtools_discovery_page_resources.pak" + type="data_package"/> + </outputs> + <release seq="1"> + <includes> + <include file="devtools_discovery_page.html" + name="IDR_DEVTOOLS_DISCOVERY_PAGE_HTML" + type="BINDATA"/></includes> + </release> +</grit> diff --git a/chrome/browser/debugger/remote_debugging_server.cc b/chrome/browser/debugger/remote_debugging_server.cc new file mode 100644 index 0000000..b951206 --- /dev/null +++ b/chrome/browser/debugger/remote_debugging_server.cc @@ -0,0 +1,27 @@ +// 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. + +#include "chrome/browser/debugger/remote_debugging_server.h" + +#include "chrome/browser/debugger/browser_list_tabcontents_provider.h" +#include "chrome/browser/ui/webui/devtools_ui.h" +#include "content/public/browser/devtools_http_handler.h" +#include "net/base/tcp_listen_socket.h" + +RemoteDebuggingServer::RemoteDebuggingServer(Profile* profile, + const std::string& ip, + int port, + const std::string& frontend_url) { + // Initialize DevTools data source. + DevToolsUI::RegisterDevToolsDataSource(profile); + + devtools_http_handler_ = content::DevToolsHttpHandler::Start( + new net::TCPListenSocketFactory(ip, port), + frontend_url, + new BrowserListTabContentsProvider(profile)); +} + +RemoteDebuggingServer::~RemoteDebuggingServer() { + devtools_http_handler_->Stop(); +} diff --git a/chrome/browser/debugger/remote_debugging_server.h b/chrome/browser/debugger/remote_debugging_server.h new file mode 100644 index 0000000..e1c2785 --- /dev/null +++ b/chrome/browser/debugger/remote_debugging_server.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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_DEBUGGER_REMOTE_DEBUGGING_SERVER_H_ +#define CHROME_BROWSER_DEBUGGER_REMOTE_DEBUGGING_SERVER_H_ + +#include <string> + +#include "base/basictypes.h" + +class Profile; + +namespace content { +class DevToolsHttpHandler; +} + +class RemoteDebuggingServer { + public: + RemoteDebuggingServer(Profile* profile, + const std::string& ip, + int port, + const std::string& frontend_url); + + virtual ~RemoteDebuggingServer(); + + private: + content::DevToolsHttpHandler* devtools_http_handler_; + DISALLOW_COPY_AND_ASSIGN(RemoteDebuggingServer); +}; + +#endif // CHROME_BROWSER_DEBUGGER_REMOTE_DEBUGGING_SERVER_H_ |