diff options
author | mpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-24 22:11:07 +0000 |
---|---|---|
committer | mpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-24 22:11:07 +0000 |
commit | 89ebc7edb982ebf5f8b185aa6e3aae6904eed965 (patch) | |
tree | ba32c69980b01c7c7d8d3f9d3cabebcb9ed88711 /chrome | |
parent | 5101c0073bf723b6e6bc81273264eaf7ac124937 (diff) | |
download | chromium_src-89ebc7edb982ebf5f8b185aa6e3aae6904eed965.zip chromium_src-89ebc7edb982ebf5f8b185aa6e3aae6904eed965.tar.gz chromium_src-89ebc7edb982ebf5f8b185aa6e3aae6904eed965.tar.bz2 |
Exposes a chrome.devtools object to extensions. This allows extensions to call chrome.devtools.connect() to open up a Port by which it can receive devtools messages to implement the proposed perf trace extensions API documented here:
http://code.google.com/p/chromium/wiki/ExtensionsPerfTraceAPI
Review URL: http://codereview.chromium.org/159882
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24158 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
26 files changed, 818 insertions, 8 deletions
diff --git a/chrome/browser/automation/automation_profile_impl.h b/chrome/browser/automation/automation_profile_impl.h index ced38cd..9f4299d 100644 --- a/chrome/browser/automation/automation_profile_impl.h +++ b/chrome/browser/automation/automation_profile_impl.h @@ -54,6 +54,9 @@ class AutomationProfileImpl : public Profile { virtual UserScriptMaster* GetUserScriptMaster() { return original_profile_->GetUserScriptMaster(); } + virtual ExtensionDevToolsManager* GetExtensionDevToolsManager() { + return original_profile_->GetExtensionDevToolsManager(); + } virtual ExtensionProcessManager* GetExtensionProcessManager() { return original_profile_->GetExtensionProcessManager(); } diff --git a/chrome/browser/extensions/extension_devtools_bridge.cc b/chrome/browser/extensions/extension_devtools_bridge.cc new file mode 100644 index 0000000..2ac456f --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_bridge.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2009 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/extensions/extension_devtools_bridge.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/extensions/extension_devtools_events.h" +#include "chrome/browser/extensions/extension_devtools_manager.h" +#include "chrome/browser/extensions/extension_tabs_module.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/devtools_messages.h" + +ExtensionDevToolsBridge::ExtensionDevToolsBridge(int tab_id, + Profile* profile) + : tab_id_(tab_id), + inspected_rvh_(NULL), + profile_(profile), + on_page_event_name_( + ExtensionDevToolsEvents::OnPageEventNameForTab(tab_id)), + on_tab_url_change_event_name_( + ExtensionDevToolsEvents::OnTabUrlChangeEventNameForTab(tab_id)), + on_tab_close_event_name_( + ExtensionDevToolsEvents::OnTabCloseEventNameForTab(tab_id)) { + extension_devtools_manager_ = profile_->GetExtensionDevToolsManager(); + DCHECK(extension_devtools_manager_.get()); +} + +ExtensionDevToolsBridge::~ExtensionDevToolsBridge() { +} + +bool ExtensionDevToolsBridge::RegisterAsDevToolsClientHost() { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + + Browser* browser; + TabStripModel* tab_strip; + TabContents* contents; + int tab_index; + if (ExtensionTabUtil::GetTabById(tab_id_, profile_, &browser, &tab_strip, + &contents, &tab_index)) { + inspected_rvh_ = contents->render_view_host(); + DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( + inspected_rvh_, this); + return true; + } + return false; +} + +void ExtensionDevToolsBridge::UnregisterAsDevToolsClientHost() { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + + if (inspected_rvh_) { + DevToolsManager::GetInstance()->UnregisterDevToolsClientHostFor( + inspected_rvh_); + inspected_rvh_ = NULL; + } +} + +// If the tab we are looking at is going away then we fire a closing event at +// the extension. +void ExtensionDevToolsBridge::InspectedTabClosing() { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + + std::string json("[{}]"); + profile_->GetExtensionMessageService()-> + DispatchEventToRenderers(on_tab_close_event_name_, json); + + // This may result in this object being destroyed. + extension_devtools_manager_->BridgeClosingForTab(tab_id_); +} + +void ExtensionDevToolsBridge::SendMessageToClient(const IPC::Message& msg) { + IPC_BEGIN_MESSAGE_MAP(ExtensionDevToolsBridge, msg) + IPC_MESSAGE_HANDLER(DevToolsClientMsg_RpcMessage, OnRpcMessage); + IPC_MESSAGE_UNHANDLED_ERROR() + IPC_END_MESSAGE_MAP() +} + +static const char kTimelineAgentClassName[] = "TimelineAgentClass"; +static const char kPageEventMessageName[] = "PageEventMessage"; +static const char kTabUrlChangeEventMessageName[] = "TabUrlChangeEventMessage"; + +void ExtensionDevToolsBridge::OnRpcMessage(const std::string& class_name, + const std::string& message_name, + const std::string& msg) { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + // TODO(jamesr): Update the filtering and message creation logic once + // the TimelineAgent lands in WebKit. + if (class_name == kTimelineAgentClassName) { + if (message_name == kPageEventMessageName) { + std::string json = StringPrintf("[{\"record\": \"%s\"}]", msg.c_str()); + profile_->GetExtensionMessageService()-> + DispatchEventToRenderers(on_page_event_name_, json); + } else if (message_name == kTabUrlChangeEventMessageName) { + std::string json = StringPrintf("[{\"record\": \"%s\"}]", msg.c_str()); + profile_->GetExtensionMessageService()-> + DispatchEventToRenderers(on_tab_url_change_event_name_, json); + } + } +} + diff --git a/chrome/browser/extensions/extension_devtools_bridge.h b/chrome/browser/extensions/extension_devtools_bridge.h new file mode 100644 index 0000000..b4cb98d --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_bridge.h @@ -0,0 +1,60 @@ +// Copyright (c) 2009 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_EXTENSIONS_EXTENSION_DEVTOOLS_BRIDGE_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_BRIDGE_H_ + +#include <string> + +#include "base/ref_counted.h" +#include "chrome/browser/debugger/devtools_client_host.h" +#include "chrome/browser/extensions/extension_devtools_manager.h" +#include "chrome/browser/extensions/extension_message_service.h" + +class Profile; +class RenderViewHost; + +// This class is a DevToolsClientHost that fires extension events. +class ExtensionDevToolsBridge : public DevToolsClientHost { + public: + ExtensionDevToolsBridge(int tab_id, Profile* profile); + virtual ~ExtensionDevToolsBridge(); + + bool RegisterAsDevToolsClientHost(); + void UnregisterAsDevToolsClientHost(); + + // DevToolsClientHost, called when the tab inspected by this client is + // closing. + virtual void InspectedTabClosing(); + + // DevToolsClientHost, called to send a message to this host. + virtual void SendMessageToClient(const IPC::Message& msg); + + private: + void OnRpcMessage(const std::string& class_name, + const std::string& message_name, + const std::string& msg); + + // ID of the tab we are monitoring. + int tab_id_; + // Host of the tab we are monitoring, NULL if not monitoring anything. + RenderViewHost* inspected_rvh_; + + scoped_refptr<ExtensionDevToolsManager> extension_devtools_manager_; + scoped_refptr<ExtensionMessageService> extension_message_service_; + + // Profile that owns our tab + Profile* profile_; + + // The names of the events fired at extensions depend on the tab id, + // so we store the various event names in each bridge. + const std::string on_page_event_name_; + const std::string on_tab_url_change_event_name_; + const std::string on_tab_close_event_name_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsBridge); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_BRIDGE_H_ + diff --git a/chrome/browser/extensions/extension_devtools_browsertest.cc b/chrome/browser/extensions/extension_devtools_browsertest.cc new file mode 100644 index 0000000..85d34ac --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_browsertest.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2009 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/extensions/extension_devtools_browsertest.h" + +#include "chrome/common/chrome_switches.h" + +void ExtensionDevToolsBrowserTest::SetUpCommandLine(CommandLine* command_line) { + ExtensionBrowserTest::SetUpCommandLine(command_line); + + command_line->AppendSwitch(switches::kEnableExtensionTimelineApi); +} + + diff --git a/chrome/browser/extensions/extension_devtools_browsertest.h b/chrome/browser/extensions/extension_devtools_browsertest.h new file mode 100644 index 0000000..7fa6435 --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_browsertest.h @@ -0,0 +1,22 @@ +// Copyright (c) 2009 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_EXTENSIONS_EXTENSION_DEVTOOLS_BROWSERTEST_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_BROWSERTEST_H_ + +#include "base/command_line.h" +#include "chrome/browser/extensions/extension_browsertest.h" + +// Subclass of ExtensionBrowserTest that enables the devtools +// command line features. +class ExtensionDevToolsBrowserTest : public ExtensionBrowserTest { + protected: + virtual void SetUpCommandLine(CommandLine* command_line); + + private: + + NotificationRegistrar registrar_; +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_BROWSERTEST_H_ diff --git a/chrome/browser/extensions/extension_devtools_browsertests.cc b/chrome/browser/extensions/extension_devtools_browsertests.cc new file mode 100644 index 0000000..9aaa071 --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_browsertests.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2009 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/ref_counted.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/debugger/devtools_client_host.h" +#include "chrome/browser/extensions/extension_devtools_browsertest.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/extensions/extension_tabs_module.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/views/extensions/extension_shelf.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/devtools_messages.h" +#include "chrome/common/extensions/extension_error_reporter.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/ui_test_utils.h" +#include "net/base/net_util.h" + +// Looks for an ExtensionHost whose URL has the given path component (including +// leading slash). Also verifies that the expected number of hosts are loaded. +static ExtensionHost* FindHostWithPath(ExtensionProcessManager* manager, + const std::string& path, + int expected_hosts) { + ExtensionHost* host = NULL; + int num_hosts = 0; + for (ExtensionProcessManager::const_iterator iter = manager->begin(); + iter != manager->end(); ++iter) { + if ((*iter)->GetURL().path() == path) { + EXPECT_FALSE(host); + host = *iter; + } + num_hosts++; + } + EXPECT_EQ(expected_hosts, num_hosts); + EXPECT_TRUE(host); + return host; +} + +// Tests for the experimental timeline extensions API. +IN_PROC_BROWSER_TEST_F(ExtensionDevToolsBrowserTest, TimelineApi) { + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("devtools").AppendASCII("timeline_api"))); + + // Get the ExtensionHost that is hosting our background page. + ExtensionProcessManager* manager = + browser()->profile()->GetExtensionProcessManager(); + ExtensionHost* host = FindHostWithPath(manager, "/background.html", 1); + + // Grab a handle to the DevToolsManager so we can forward messages to it. + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + + // Grab the tab_id of whatever tab happens to be first. + TabContents* tab_contents = browser()->tabstrip_model()->GetTabContentsAt(0); + ASSERT_TRUE(tab_contents); + int tab_id = ExtensionTabUtil::GetTabId(tab_contents); + + // Test setup. + bool result = false; + std::wstring register_listeners_js = StringPrintf(L"setListenersOnTab(%d)", + tab_id); + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host->render_view_host(), L"", register_listeners_js, &result); + EXPECT_TRUE(result); + + // Setting the events should have caused an ExtensionDevToolsBridge to be + // registered for the tab's RenderViewHost. + DevToolsClientHost* devtools_client_host = + devtools_manager->GetDevToolsClientHostFor( + tab_contents->render_view_host()); + ASSERT_TRUE(devtools_client_host); + + // Test onTabUrlChange event. + DevToolsClientMsg_RpcMessage tabUrlChangeEventMessage( + "TimelineAgentClass", "TabUrlChangeEventMessage", "{}"); + devtools_client_host->SendMessageToClient(tabUrlChangeEventMessage); + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host->render_view_host(), + L"", + L"testReceiveTabUrlChangeEvent()", + &result); + EXPECT_TRUE(result); + + // Test onPageEvent event. + result = false; + DevToolsClientMsg_RpcMessage pageEventMessage( + "TimelineAgentClass", "PageEventMessage", "{}"); + devtools_client_host->SendMessageToClient(pageEventMessage); + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host->render_view_host(), L"", L"testReceivePageEvent()", &result); + EXPECT_TRUE(result); + + // Test onTabClose event. + result = false; + devtools_manager->UnregisterDevToolsClientHostFor( + tab_contents->render_view_host()); + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host->render_view_host(), L"", L"testReceiveTabCloseEvent()", &result); + EXPECT_TRUE(result); +} + + +// Tests that ref counting of listeners from multiple processes works. +IN_PROC_BROWSER_TEST_F(ExtensionDevToolsBrowserTest, ProcessRefCounting) { + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("devtools").AppendASCII("timeline_api"))); + + // Get the ExtensionHost that is hosting our background page. + ExtensionProcessManager* manager = + browser()->profile()->GetExtensionProcessManager(); + ExtensionHost* host_one = FindHostWithPath(manager, "/background.html", 1); + + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("devtools").AppendASCII("timeline_api_two"))); + ExtensionHost* host_two = FindHostWithPath(manager, + "/background_two.html", 2); + + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + + // Grab the tab_id of whatever tab happens to be first. + TabContents* tab_contents = browser()->tabstrip_model()->GetTabContentsAt(0); + ASSERT_TRUE(tab_contents); + int tab_id = ExtensionTabUtil::GetTabId(tab_contents); + + // Test setup. + bool result = false; + std::wstring register_listeners_js = StringPrintf(L"setListenersOnTab(%d)", + tab_id); + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host_one->render_view_host(), L"", register_listeners_js, &result); + EXPECT_TRUE(result); + + // Setting the event listeners should have caused an ExtensionDevToolsBridge + // to be registered for the tab's RenderViewHost. + ASSERT_TRUE(devtools_manager->GetDevToolsClientHostFor( + tab_contents->render_view_host())); + + // Register listeners from the second extension as well. + std::wstring script = StringPrintf(L"registerListenersForTab(%d)", tab_id); + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host_two->render_view_host(), L"", script, &result); + EXPECT_TRUE(result); + + // Removing the listeners from the first extension should leave the bridge + // alive. + result = false; + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host_one->render_view_host(), L"", L"unregisterListeners()", &result); + EXPECT_TRUE(result); + ASSERT_TRUE(devtools_manager->GetDevToolsClientHostFor( + tab_contents->render_view_host())); + + // Removing the listeners from the second extension should tear the bridge + // down. + result = false; + ui_test_utils::ExecuteJavaScriptAndExtractBool( + host_two->render_view_host(), L"", L"unregisterListeners()", &result); + EXPECT_TRUE(result); + ASSERT_FALSE(devtools_manager->GetDevToolsClientHostFor( + tab_contents->render_view_host())); +} diff --git a/chrome/browser/extensions/extension_devtools_events.cc b/chrome/browser/extensions/extension_devtools_events.cc new file mode 100644 index 0000000..979726c --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_events.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2009 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/extensions/extension_devtools_events.h" + +#include <vector> + +#include "base/string_util.h" + +// These string constants and the formats used in this file must stay +// in sync with chrome/renderer/resources/extension_process_bindings.js +static const char kDevToolsEventPrefix[] = "devtools."; +static const char kOnPageEventName[] = "onPageEvent"; +static const char kOnTabUrlChangeEventName[] = "onTabUrlChange"; +static const char kOnTabCloseEventName[] = "onTabClose"; + +// static +bool ExtensionDevToolsEvents::IsDevToolsEventName( + const std::string& event_name, int* tab_id) { + // We only care about events of the form "devtools.34.*", where 34 is + // a tab id. + if (IsStringASCII(event_name) && + StartsWithASCII(event_name, + kDevToolsEventPrefix, + true /* case_sensitive */)) { + // At this point we want something like "4.onPageEvent" + std::vector<std::string> parts; + SplitString(event_name.substr(strlen(kDevToolsEventPrefix)), '.', &parts); + if (parts.size() == 2 && StringToInt(parts[0], tab_id)) { + return true; + } + } + return false; +} + +// static +std::string ExtensionDevToolsEvents::OnPageEventNameForTab(int tab_id) { + return StringPrintf("%s%d.%s", + kDevToolsEventPrefix, + tab_id, + kOnPageEventName); +} + +// static +std::string ExtensionDevToolsEvents::OnTabUrlChangeEventNameForTab(int tab_id) { + return StringPrintf("%s%d.%s", + kDevToolsEventPrefix, + tab_id, + kOnTabUrlChangeEventName); +} + +// static +std::string ExtensionDevToolsEvents::OnTabCloseEventNameForTab(int tab_id) { + return StringPrintf("%s%d.%s", + kDevToolsEventPrefix, + tab_id, + kOnTabCloseEventName); +} + diff --git a/chrome/browser/extensions/extension_devtools_events.h b/chrome/browser/extensions/extension_devtools_events.h new file mode 100644 index 0000000..ff985b2 --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_events.h @@ -0,0 +1,37 @@ +// Copyright (c) 2009 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_EXTENSIONS_EXTENSION_DEVTOOLS_EVENTS_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_EVENTS_H_ + +#include <string> + +#include "base/basictypes.h" + +// Static utility functions for dealing with extension devtools event names. +// The format of the event names is <prefix>.<tab id>.<event name> +// Equivalent name munging is done in the extension process in JavaScript +// by chrome/renderer/resources/extension_process_bindings.js +class ExtensionDevToolsEvents { + public: + // Checks if an event name is a magic devtools event name. If so, + // the tab id of the event is put in *tab_id. + static bool IsDevToolsEventName(const std::string& event_name, int* tab_id); + + // Generates the event string for an onPageEvent for a given tab. + static std::string OnPageEventNameForTab(int tab_id); + + // Generates the event string for an onPageEvent for a given tab. + static std::string OnTabUrlChangeEventNameForTab(int tab_id); + + // Generates the event string for an onTabCloseEvent for a given tab. + static std::string OnTabCloseEventNameForTab(int tab_id); + + private: + + DISALLOW_IMPLICIT_CONSTRUCTORS(ExtensionDevToolsEvents); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_EVENTS_H_ + diff --git a/chrome/browser/extensions/extension_devtools_manager.cc b/chrome/browser/extensions/extension_devtools_manager.cc new file mode 100644 index 0000000..20f4eb0 --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_manager.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2009 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/extensions/extension_devtools_manager.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/task.h" +#include "chrome/browser/extensions/extension_devtools_bridge.h" +#include "chrome/browser/extensions/extension_devtools_events.h" + +ExtensionDevToolsManager::ExtensionDevToolsManager(Profile* profile) + : profile_(profile), + ui_loop_(NULL) { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + ui_loop_ = MessageLoop::current(); +} + +ExtensionDevToolsManager::~ExtensionDevToolsManager() { +} + +void ExtensionDevToolsManager::AddEventListener(const std::string& event_name, + int render_process_id) { + int tab_id; + if (ExtensionDevToolsEvents::IsDevToolsEventName(event_name, &tab_id)) { + // Add the renderer process ID to the set of processes interested + // in this tab. + tab_id_to_render_process_ids_[tab_id].insert(render_process_id); + if (tab_id_to_bridge_.count(tab_id) == 0) { + // Create a new bridge for this tab if there isn't one already. + linked_ptr<ExtensionDevToolsBridge> bridge( + new ExtensionDevToolsBridge(tab_id, profile_)); + if (bridge->RegisterAsDevToolsClientHost()) { + tab_id_to_bridge_[tab_id] = bridge; + } + } + } +} + +void ExtensionDevToolsManager::RemoveEventListener( + const std::string& event_name, + int render_process_id) { + int tab_id; + if (ExtensionDevToolsEvents::IsDevToolsEventName(event_name, &tab_id)) { + std::map<int, std::set<int> >::iterator it = + tab_id_to_render_process_ids_.find(tab_id); + if (it != tab_id_to_render_process_ids_.end()) { + // Remove the process from the set of processes interested in this tab. + it->second.erase(render_process_id); + if (it->second.empty()) { + // No renderers have registered listeners for this tab, so kill the + // bridge if there is one. + if (tab_id_to_bridge_.count(tab_id) != 0) { + linked_ptr<ExtensionDevToolsBridge> bridge(tab_id_to_bridge_[tab_id]); + bridge->UnregisterAsDevToolsClientHost(); + tab_id_to_bridge_.erase(tab_id); + } + } + } + } +} + +void ExtensionDevToolsManager::BridgeClosingForTab(int tab_id) { + if (tab_id_to_bridge_.count(tab_id) != 0) { + linked_ptr<ExtensionDevToolsBridge> bridge(tab_id_to_bridge_[tab_id]); + bridge->UnregisterAsDevToolsClientHost(); + tab_id_to_bridge_.erase(tab_id); + } + tab_id_to_render_process_ids_.erase(tab_id); +} + diff --git a/chrome/browser/extensions/extension_devtools_manager.h b/chrome/browser/extensions/extension_devtools_manager.h new file mode 100644 index 0000000..35e7cec --- /dev/null +++ b/chrome/browser/extensions/extension_devtools_manager.h @@ -0,0 +1,63 @@ +// Copyright (c) 2009 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_EXTENSIONS_EXTENSION_DEVTOOLS_MANAGER_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_MANAGER_H_ + +#include <map> +#include <set> +#include <string> +#include "base/linked_ptr.h" +#include "base/ref_counted.h" + +class ExtensionDevToolsBridge; +class MessageLoop; +class Profile; + +// This class manages the lifetimes of ExtensionDevToolsBridge objects. +// The manager is owned by the Profile. +// +// The lifetime of an ExtensionDevToolsBridge object is determined by: +// * the existence of registered event handlers for the bridge's tab +// * the lifetime of the inspected tab +// +// The manager is alerted whenever an event listener is added or removed and +// keeps track of the set of renderers with event listeners registered for each +// tab. A new bridge object is created for a tab when the first event listener +// is registered on that tab. A bridge object is destroyed when all event +// listeners are removed, the inspected tab closes, or when the manager itself +// is destroyed. + +class ExtensionDevToolsManager + : public base::RefCountedThreadSafe<ExtensionDevToolsManager> { + public: + // UI thread only: + explicit ExtensionDevToolsManager(Profile* profile); + ~ExtensionDevToolsManager(); + + void AddEventListener(const std::string& event_name, + int render_process_id); + + void RemoveEventListener(const std::string& event_name, + int render_process_id); + + void BridgeClosingForTab(int tab_id); + + private: + + // Map of tab IDs to the ExtensionDevToolsBridge connected to the tab + std::map<int, linked_ptr<ExtensionDevToolsBridge> > tab_id_to_bridge_; + + // Map of tab IDs to the set of render_process_ids that have registered + // event handlers for the tab. + std::map<int, std::set<int> > tab_id_to_render_process_ids_; + + Profile* profile_; + MessageLoop* ui_loop_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsManager); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_DEVTOOLS_MANAGER_H_ + diff --git a/chrome/browser/extensions/extension_message_service.cc b/chrome/browser/extensions/extension_message_service.cc index a4b1fd6d..edc2cf9 100644 --- a/chrome/browser/extensions/extension_message_service.cc +++ b/chrome/browser/extensions/extension_message_service.cc @@ -105,7 +105,10 @@ const char ExtensionMessageService::kDispatchEvent[] = "Event.dispatchJSON"; ExtensionMessageService::ExtensionMessageService(Profile* profile) - : ui_loop_(MessageLoop::current()), profile_(profile), next_port_id_(0) { + : ui_loop_(MessageLoop::current()), + profile_(profile), + extension_devtools_manager_(NULL), + next_port_id_(0) { DCHECK_EQ(ui_loop_->type(), MessageLoop::TYPE_UI); registrar_.Add(this, NotificationType::RENDERER_PROCESS_TERMINATED, @@ -114,6 +117,8 @@ ExtensionMessageService::ExtensionMessageService(Profile* profile) NotificationService::AllSources()); registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_DELETED, NotificationService::AllSources()); + + extension_devtools_manager_ = profile_->GetExtensionDevToolsManager(); } ExtensionMessageService::~ExtensionMessageService() { @@ -136,6 +141,11 @@ void ExtensionMessageService::AddEventListener(const std::string& event_name, DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); DCHECK_EQ(listeners_[event_name].count(render_process_id), 0u) << event_name; listeners_[event_name].insert(render_process_id); + + if (extension_devtools_manager_.get()) { + extension_devtools_manager_->AddEventListener(event_name, + render_process_id); + } } void ExtensionMessageService::RemoveEventListener(const std::string& event_name, @@ -149,6 +159,11 @@ void ExtensionMessageService::RemoveEventListener(const std::string& event_name, DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); DCHECK_EQ(listeners_[event_name].count(render_process_id), 1u) << event_name; listeners_[event_name].erase(render_process_id); + + if (extension_devtools_manager_.get()) { + extension_devtools_manager_->RemoveEventListener(event_name, + render_process_id); + } } void ExtensionMessageService::AllocatePortIdPair(int* port1, int* port2) { @@ -345,6 +360,7 @@ void ExtensionMessageService::CloseChannelImpl( channels_.erase(channel_iter); } + void ExtensionMessageService::PostMessageFromRenderer( int source_port_id, const std::string& message) { DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); @@ -394,13 +410,12 @@ void ExtensionMessageService::Observe(NotificationType type, RenderProcessHost* renderer = Source<RenderProcessHost>(source).ptr(); OnSenderClosed(renderer); - // Remove this renderer from our listener maps. + // Remove all event listeners associated with this renderer for (ListenerMap::iterator it = listeners_.begin(); it != listeners_.end(); ) { ListenerMap::iterator current = it++; - current->second.erase(renderer->pid()); - if (current->second.empty()) - listeners_.erase(current); + if (current->second.count(renderer->pid()) != 0) + RemoveEventListener(current->first, renderer->pid()); } break; } diff --git a/chrome/browser/extensions/extension_message_service.h b/chrome/browser/extensions/extension_message_service.h index a6b1cd5..32ed945 100644 --- a/chrome/browser/extensions/extension_message_service.h +++ b/chrome/browser/extensions/extension_message_service.h @@ -13,6 +13,7 @@ #include "base/lock.h" #include "base/ref_counted.h" #include "chrome/common/notification_registrar.h" +#include "chrome/browser/extensions/extension_devtools_manager.h" #include "ipc/ipc_message.h" class MessageLoop; @@ -157,6 +158,8 @@ class ExtensionMessageService MessageChannelMap channels_; + scoped_refptr<ExtensionDevToolsManager> extension_devtools_manager_; + // A map between an event name and a set of process id's that are listening // to that event. typedef std::map<std::string, std::set<int> > ListenerMap; diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index 0e473315..77d4ff1 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -14,6 +14,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/browser_theme_provider.h" #include "chrome/browser/download/download_manager.h" +#include "chrome/browser/extensions/extension_devtools_manager.h" #include "chrome/browser/extensions/extension_message_service.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extensions_service.h" @@ -220,6 +221,10 @@ class OffTheRecordProfileImpl : public Profile, return profile_->GetUserScriptMaster(); } + virtual ExtensionDevToolsManager* GetExtensionDevToolsManager() { + return NULL; + } + virtual ExtensionProcessManager* GetExtensionProcessManager() { return NULL; } @@ -494,6 +499,7 @@ class OffTheRecordProfileImpl : public Profile, ProfileImpl::ProfileImpl(const FilePath& path) : path_(path), visited_link_event_listener_(new VisitedLinkEventListener()), + extension_devtools_manager_(NULL), request_context_(NULL), media_request_context_(NULL), extensions_request_context_(NULL), @@ -512,6 +518,11 @@ ProfileImpl::ProfileImpl(const FilePath& path) TimeDelta::FromMilliseconds(kCreateSessionServiceDelayMS), this, &ProfileImpl::EnsureSessionServiceCreated); + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExtensionTimelineApi)) { + extension_devtools_manager_ = new ExtensionDevToolsManager(this); + } + extension_process_manager_.reset(new ExtensionProcessManager(this)); extension_message_service_ = new ExtensionMessageService(this); @@ -744,6 +755,10 @@ UserScriptMaster* ProfileImpl::GetUserScriptMaster() { return user_script_master_.get(); } +ExtensionDevToolsManager* ProfileImpl::GetExtensionDevToolsManager() { + return extension_devtools_manager_.get(); +} + ExtensionProcessManager* ProfileImpl::GetExtensionProcessManager() { return extension_process_manager_.get(); } diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index bc541d7..6ff28e8 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -28,6 +28,7 @@ class BrowserThemeProvider; class ChromeURLRequestContext; class DownloadManager; class Extension; +class ExtensionDevToolsManager; class ExtensionProcessManager; class ExtensionMessageService; class ExtensionsService; @@ -129,6 +130,10 @@ class Profile { // that this method is called. virtual UserScriptMaster* GetUserScriptMaster() = 0; + // Retrieves a pointer to the ExtensionDevToolsManager associated with this + // profile. The instance is created at startup. + virtual ExtensionDevToolsManager* GetExtensionDevToolsManager() = 0; + // Retrieves a pointer to the ExtensionProcessManager associated with this // profile. The instance is created at startup. virtual ExtensionProcessManager* GetExtensionProcessManager() = 0; @@ -345,6 +350,7 @@ class ProfileImpl : public Profile, virtual SSLHostState* GetSSLHostState(); virtual net::ForceTLSState* GetForceTLSState(); virtual ExtensionsService* GetExtensionsService(); + virtual ExtensionDevToolsManager* GetExtensionDevToolsManager(); virtual ExtensionProcessManager* GetExtensionProcessManager(); virtual ExtensionMessageService* GetExtensionMessageService(); virtual HistoryService* GetHistoryService(ServiceAccessType sat); @@ -427,6 +433,7 @@ class ProfileImpl : public Profile, scoped_ptr<VisitedLinkMaster> visited_link_master_; scoped_refptr<ExtensionsService> extensions_service_; scoped_refptr<UserScriptMaster> user_script_master_; + scoped_refptr<ExtensionDevToolsManager> extension_devtools_manager_; scoped_ptr<ExtensionProcessManager> extension_process_manager_; scoped_refptr<ExtensionMessageService> extension_message_service_; scoped_ptr<SSLHostState> ssl_host_state_; diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 39992b4..c368821 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -45,6 +45,9 @@ 'browser/extensions/extension_apitest.cc', 'browser/extensions/extension_apitest.h', 'browser/extensions/extension_bookmarks_apitest.cc', + 'browser/extensions/extension_devtools_browsertest.cc', + 'browser/extensions/extension_devtools_browsertest.h', + 'browser/extensions/extension_devtools_browsertests.cc', 'browser/extensions/extension_browsertest.cc', 'browser/extensions/extension_browsertest.h', 'browser/extensions/extension_browsertests_misc.cc', @@ -1049,6 +1052,12 @@ 'browser/extensions/extension_creator.h', 'browser/extensions/extension_disabled_infobar_delegate.cc', 'browser/extensions/extension_disabled_infobar_delegate.h', + 'browser/extensions/extension_devtools_bridge.cc', + 'browser/extensions/extension_devtools_bridge.h', + 'browser/extensions/extension_devtools_events.cc', + 'browser/extensions/extension_devtools_events.h', + 'browser/extensions/extension_devtools_manager.cc', + 'browser/extensions/extension_devtools_manager.h', 'browser/extensions/extension_dom_ui.cc', 'browser/extensions/extension_dom_ui.h', 'browser/extensions/extension_event_names.cc', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index a232be0..8fc249e 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -279,6 +279,10 @@ const wchar_t kDisableDevTools[] = L"disable-dev-tools"; // Allows us to use our dev tools to debug browser windows itself. const wchar_t kAlwaysEnableDevTools[] = L"always-enable-dev-tools"; +// Enable experimental timeline API. +const wchar_t kEnableExtensionTimelineApi[] = + L"enable-extension-timeline-api"; + // Used to set the value of SessionRestore::num_tabs_to_load_. See // session_restore.h for details. const wchar_t kTabCountToLoadOnSessionRestore[] = diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index fce1b69..ef9a465 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -95,6 +95,7 @@ extern const wchar_t kAllowAllActiveX[]; extern const wchar_t kDisableDevTools[]; extern const wchar_t kAlwaysEnableDevTools[]; +extern const wchar_t kEnableExtensionTimelineApi[]; extern const wchar_t kTabCountToLoadOnSessionRestore[]; diff --git a/chrome/common/common_resources.grd b/chrome/common/common_resources.grd index d9aae0d..9b7356c 100644 --- a/chrome/common/common_resources.grd +++ b/chrome/common/common_resources.grd @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This comment is only here because changes to resources are not picked up -without changes to the corresponding grd file. rw4 --> +without changes to the corresponding grd file. jr1 --> <grit latest_public_release="0" current_release="1"> <outputs> <output filename="grit/common_resources.h" type="rc_header"> diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index 8f0c94d..2ea4962 100755 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -865,6 +865,29 @@ "events": [] }, { + "namespace": "devtools", + "types": [ + ], + "functions": [ + { + "name": "getTabEvents", + "type": "function", + "description": "EXPERIMENTAL support for timeline API", + "nodocs": "true", + "parameters": [ + { + "name": "tab_id", + "type": "integer" + } + ], + "returns": { + "type": "object", + "description": "DevTools tab events object" + } + } + ] + }, + { "namespace": "test", "types": [], "functions": [ diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index e5c7fcf..5640a3c 100644 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This comment is only here because changes to resources are not picked up -without changes to the corresponding grd file. rw2 --> +without changes to the corresponding grd file. rw2b --> <grit latest_public_release="0" current_release="1"> <outputs> <output filename="grit/renderer_resources.h" type="rc_header"> diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index 66872f9..63d99cc 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -281,6 +281,18 @@ var chrome = chrome || {}; return GetExtensionViews(windowId, "TAB"); } + apiFunctions["devtools.getTabEvents"].handleRequest = function(tabId) { + var tabIdProxy = {}; + forEach(["onPageEvent", "onTabUrlChange", "onTabClose"], + function(name) { + // Event disambiguation is handled by name munging. See + // chrome/browser/extensions/extension_devtools_events.h for the C++ + // equivalent of this logic. + tabIdProxy[name] = new chrome.Event("devtools." + tabId + "." + name); + }); + return tabIdProxy; + } + setupPageActionEvents(extensionId); }); - })(); +})(); diff --git a/chrome/test/data/extensions/devtools/timeline_api/background.html b/chrome/test/data/extensions/devtools/timeline_api/background.html new file mode 100644 index 0000000..1b98882 --- /dev/null +++ b/chrome/test/data/extensions/devtools/timeline_api/background.html @@ -0,0 +1,67 @@ +<script> + +var receivedEvents = []; +var devtoolsTabEvents = undefined; + +function tabUrlChangeListener() { + receivedEvents.push("onTabUrlChange"); +} + +function pageEventListener() { + receivedEvents.push("onPageEvent"); +} + +function tabCloseListener() { + receivedEvents.push("onTabClose"); +} + +function setListenersOnTab(tabId) { + try { + devtoolsTabEvents = chrome.devtools.getTabEvents(tabId); + devtoolsTabEvents.onPageEvent.addListener(pageEventListener); + devtoolsTabEvents.onTabUrlChange.addListener(tabUrlChangeListener); + devtoolsTabEvents.onTabClose.addListener(tabCloseListener); + window.domAutomationController.send(true); + } catch(e) { + window.domAutomationController.send(false); + } +} + +function testReceiveTabUrlChangeEvent() { + if (receivedEvents.length == 1) { + var eventName = receivedEvents.pop(); + window.domAutomationController.send(eventName === "onTabUrlChange"); + } else { + receivedEvents = []; + window.domAutomationController.send(false); + } +} + +function testReceivePageEvent() { + if (receivedEvents.length == 1) { + var eventName = receivedEvents.pop(); + window.domAutomationController.send(eventName === "onPageEvent"); + } else { + receivedEvents = []; + window.domAutomationController.send(false); + } +} + +function testReceiveTabCloseEvent() { + if (receivedEvents.length == 1) { + var eventName = receivedEvents.pop(); + window.domAutomationController.send(eventName === "onTabClose"); + } else { + receivedEvents = []; + window.domAutomationController.send(false); + } +} + +function unregisterListeners() { + devtoolsTabEvents.onPageEvent.removeListener(pageEventListener); + devtoolsTabEvents.onTabUrlChange.removeListener(tabUrlChangeListener); + devtoolsTabEvents.onTabClose.removeListener(tabCloseListener); + window.domAutomationController.send(true); +} + +</script> diff --git a/chrome/test/data/extensions/devtools/timeline_api/manifest.json b/chrome/test/data/extensions/devtools/timeline_api/manifest.json new file mode 100644 index 0000000..e456c57 --- /dev/null +++ b/chrome/test/data/extensions/devtools/timeline_api/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "DevTools test", + "version": "0.1", + "description": "", + "background_page": "background.html", + "permissions": [ "tabs" ] +} diff --git a/chrome/test/data/extensions/devtools/timeline_api_two/background_two.html b/chrome/test/data/extensions/devtools/timeline_api_two/background_two.html new file mode 100644 index 0000000..fa0422e --- /dev/null +++ b/chrome/test/data/extensions/devtools/timeline_api_two/background_two.html @@ -0,0 +1,32 @@ +<script> + +var devtoolsTabEvents = undefined; + +function tabUrlChangeListener() { + receivedEvents.push("onTabUrlChange"); +} + +function pageEventListener() { + receivedEvents.push("onPageEvent"); +} + +function tabCloseListener() { + receivedEvents.push("onTabClose"); +} + +function registerListenersForTab(tabId) { + devtoolsTabEvents = chrome.devtools.getTabEvents(tabId); + devtoolsTabEvents.onPageEvent.addListener(pageEventListener); + devtoolsTabEvents.onTabUrlChange.addListener(tabUrlChangeListener); + devtoolsTabEvents.onTabClose.addListener(tabCloseListener); + window.domAutomationController.send(true); +} + +function unregisterListeners() { + devtoolsTabEvents.onPageEvent.removeListener(pageEventListener); + devtoolsTabEvents.onTabUrlChange.removeListener(tabUrlChangeListener); + devtoolsTabEvents.onTabClose.removeListener(tabCloseListener); + window.domAutomationController.send(true); +} + +</script> diff --git a/chrome/test/data/extensions/devtools/timeline_api_two/manifest.json b/chrome/test/data/extensions/devtools/timeline_api_two/manifest.json new file mode 100644 index 0000000..04245a8 --- /dev/null +++ b/chrome/test/data/extensions/devtools/timeline_api_two/manifest.json @@ -0,0 +1,6 @@ +{ + "name": "DevTools test (two)", + "version": "0.1", + "description": "", + "background_page": "background_two.html" +} diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h index eb58bf4..ecfec35 100644 --- a/chrome/test/testing_profile.h +++ b/chrome/test/testing_profile.h @@ -83,6 +83,9 @@ class TestingProfile : public Profile { virtual UserScriptMaster* GetUserScriptMaster() { return NULL; } + virtual ExtensionDevToolsManager* GetExtensionDevToolsManager() { + return NULL; + } virtual ExtensionProcessManager* GetExtensionProcessManager() { return NULL; } |