summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authormpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-24 22:11:07 +0000
committermpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-24 22:11:07 +0000
commit89ebc7edb982ebf5f8b185aa6e3aae6904eed965 (patch)
treeba32c69980b01c7c7d8d3f9d3cabebcb9ed88711 /chrome
parent5101c0073bf723b6e6bc81273264eaf7ac124937 (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/automation/automation_profile_impl.h3
-rw-r--r--chrome/browser/extensions/extension_devtools_bridge.cc104
-rw-r--r--chrome/browser/extensions/extension_devtools_bridge.h60
-rw-r--r--chrome/browser/extensions/extension_devtools_browsertest.cc15
-rw-r--r--chrome/browser/extensions/extension_devtools_browsertest.h22
-rw-r--r--chrome/browser/extensions/extension_devtools_browsertests.cc170
-rw-r--r--chrome/browser/extensions/extension_devtools_events.cc60
-rw-r--r--chrome/browser/extensions/extension_devtools_events.h37
-rw-r--r--chrome/browser/extensions/extension_devtools_manager.cc72
-rw-r--r--chrome/browser/extensions/extension_devtools_manager.h63
-rw-r--r--chrome/browser/extensions/extension_message_service.cc25
-rw-r--r--chrome/browser/extensions/extension_message_service.h3
-rw-r--r--chrome/browser/profile.cc15
-rw-r--r--chrome/browser/profile.h7
-rw-r--r--chrome/chrome.gyp9
-rw-r--r--chrome/common/chrome_switches.cc4
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--chrome/common/common_resources.grd2
-rwxr-xr-xchrome/common/extensions/api/extension_api.json23
-rw-r--r--chrome/renderer/renderer_resources.grd2
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js14
-rw-r--r--chrome/test/data/extensions/devtools/timeline_api/background.html67
-rw-r--r--chrome/test/data/extensions/devtools/timeline_api/manifest.json7
-rw-r--r--chrome/test/data/extensions/devtools/timeline_api_two/background_two.html32
-rw-r--r--chrome/test/data/extensions/devtools/timeline_api_two/manifest.json6
-rw-r--r--chrome/test/testing_profile.h3
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;
}