summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/wake_event_page_apitest.cc242
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--extensions/browser/extension_message_filter.cc141
-rw-r--r--extensions/browser/extension_message_filter.h16
-rw-r--r--extensions/browser/process_manager_observer.h9
-rw-r--r--extensions/common/api/test.json11
-rw-r--r--extensions/common/extension_messages.h11
-rw-r--r--extensions/extensions.gypi4
-rw-r--r--extensions/renderer/dispatcher.cc6
-rw-r--r--extensions/renderer/resources/test_custom_bindings.js5
-rw-r--r--extensions/renderer/test_native_handler.cc24
-rw-r--r--extensions/renderer/test_native_handler.h28
-rw-r--r--extensions/renderer/wake_event_page.cc173
-rw-r--r--extensions/renderer/wake_event_page.h108
14 files changed, 739 insertions, 40 deletions
diff --git a/chrome/browser/extensions/wake_event_page_apitest.cc b/chrome/browser/extensions/wake_event_page_apitest.cc
new file mode 100644
index 0000000..456155e
--- /dev/null
+++ b/chrome/browser/extensions/wake_event_page_apitest.cc
@@ -0,0 +1,242 @@
+// Copyright 2015 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 <string>
+
+#include "base/auto_reset.h"
+#include "base/run_loop.h"
+#include "base/scoped_observer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/extensions/test_extension_dir.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_observer.h"
+#include "extensions/common/extension.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace extensions {
+namespace {
+
+// manifest.json:
+//
+// This uses single quotes for brevity, which will be replaced by double quotes
+// when installing the extension.
+//
+// Expects a single string replacement of the "background" property, including
+// trailing comma, or nothing if there is no background page.
+const char* kManifestJson =
+ "{\n"
+ " %s\n"
+ " 'content_scripts': [{\n"
+ " 'js': ['content_script.js'],\n"
+ " 'matches': ['<all_urls>'],\n"
+ " 'run_at': 'document_start'\n"
+ " }],\n"
+ " 'manifest_version': 2,\n"
+ " 'name': 'wake_event_page_apitest',\n"
+ " 'version': '1'\n"
+ "}\n";
+
+// content_script.js:
+//
+// This content script just wakes the event page whenever it runs, then sends a
+// chrome.test message with the result.
+//
+// Note: The wake-event-page function is exposed to content scripts via the
+// chrome.test API for testing purposes only. In production its intended use
+// case is from workers.
+const char* kContentScriptJs =
+ "chrome.test.getWakeEventPage()(function(success) {\n"
+ " chrome.test.sendMessage(success ? 'success' : 'failure');\n"
+ "});\n";
+
+class BackgroundPageWatcher : public ProcessManagerObserver {
+ public:
+ BackgroundPageWatcher(ProcessManager* process_manager,
+ const Extension* extension)
+ : process_manager_(process_manager),
+ extension_id_(extension->id()),
+ is_waiting_for_open_(false),
+ is_waiting_for_close_(false) {}
+
+ // Returns when the background page is open. If the background page is
+ // already open, returns immediately.
+ void WaitForOpen() { WaitForOpenState(true); }
+
+ // Returns when the background page is closed. If the background page is
+ // already closed, returns immediately.
+ void WaitForClose() { WaitForOpenState(false); }
+
+ private:
+ // Returns when the background page has open state of |wait_for_open|. If the
+ // background page is already in that state, returns immediately.
+ void WaitForOpenState(bool wait_for_open) {
+ if (IsBackgroundPageOpen() == wait_for_open)
+ return;
+ ScopedObserver<ProcessManager, ProcessManagerObserver> observer(this);
+ observer.Add(process_manager_);
+ bool* flag = wait_for_open ? &is_waiting_for_open_ : &is_waiting_for_close_;
+ base::AutoReset<bool> set_flag(flag, true);
+ base::RunLoop run_loop;
+ base::AutoReset<base::Closure> set_quit_run_loop(&quit_run_loop_,
+ run_loop.QuitClosure());
+ CHECK_EQ(wait_for_open, IsBackgroundPageOpen());
+ }
+
+ bool IsBackgroundPageOpen() {
+ return process_manager_->GetBackgroundHostForExtension(extension_id_) !=
+ nullptr;
+ }
+
+ void OnBackgroundHostCreated(ExtensionHost* host) override {
+ if (is_waiting_for_open_ && host->extension()->id() == extension_id_)
+ quit_run_loop_.Run();
+ }
+
+ void OnBackgroundHostClose(const std::string& extension_id) override {
+ if (is_waiting_for_close_ && extension_id == extension_id_)
+ quit_run_loop_.Run();
+ }
+
+ ProcessManager* const process_manager_;
+ const std::string extension_id_;
+
+ base::Closure quit_run_loop_;
+ bool is_waiting_for_open_;
+ bool is_waiting_for_close_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundPageWatcher);
+};
+
+class WakeEventPageTest : public ExtensionBrowserTest {
+ public:
+ WakeEventPageTest() {}
+
+ protected:
+ enum BackgroundPageConfiguration { EVENT, PERSISTENT, NONE };
+
+ void RunTest(bool expect_success,
+ BackgroundPageConfiguration bg_config,
+ bool should_close,
+ bool will_be_open) {
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ GURL web_url = embedded_test_server()->GetURL("example.com", "/empty.html");
+ host_resolver()->AddRule(web_url.host(), "127.0.0.1");
+
+ TestExtensionDir extension_dir;
+ {
+ std::string manifest_json;
+ switch (bg_config) {
+ case EVENT:
+ manifest_json =
+ base::StringPrintf(kManifestJson,
+ " 'background': {\n"
+ " 'persistent': false,\n"
+ " 'scripts': ['background.js']\n"
+ " },");
+ break;
+ case PERSISTENT:
+ manifest_json =
+ base::StringPrintf(kManifestJson,
+ " 'background': {\n"
+ " 'persistent': true,\n"
+ " 'scripts': ['background.js']\n"
+ " },");
+ break;
+ case NONE:
+ manifest_json = base::StringPrintf(kManifestJson, "");
+ break;
+ }
+ base::ReplaceChars(manifest_json, "'", "\"", &manifest_json);
+ extension_dir.WriteManifest(manifest_json);
+ // Empty background page. Closing/opening it is driven by this test.
+ extension_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "");
+ extension_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
+ kContentScriptJs);
+ }
+
+ // Install the extension, then close its background page if desired..
+ const Extension* extension = LoadExtension(extension_dir.unpacked_path());
+ CHECK(extension);
+
+ // Regardless of |will_be_open|, we haven't closed the background page yet,
+ // so it should always open if it exists.
+ if (bg_config != NONE)
+ BackgroundPageWatcher(process_manager(), extension).WaitForOpen();
+
+ if (should_close) {
+ GetBackgroundPage(extension->id())->Close();
+ BackgroundPageWatcher(process_manager(), extension).WaitForClose();
+ EXPECT_FALSE(GetBackgroundPage(extension->id()));
+ }
+
+ // Start a content script to wake up the background page, if it's closed.
+ {
+ ExtensionTestMessageListener listener(false /* will_reply */);
+ ui_test_utils::NavigateToURL(browser(), web_url);
+ ASSERT_TRUE(listener.WaitUntilSatisfied());
+ EXPECT_EQ(expect_success ? "success" : "failure", listener.message());
+ }
+
+ EXPECT_EQ(will_be_open, GetBackgroundPage(extension->id()) != nullptr);
+
+ // Run the content script again. The background page will be awaken iff
+ // |will_be_open| is true, but if not, this is a harmless no-op.
+ {
+ ExtensionTestMessageListener listener(false /* will_reply */);
+ ui_test_utils::NavigateToURL(browser(), web_url);
+ ASSERT_TRUE(listener.WaitUntilSatisfied());
+ EXPECT_EQ(expect_success ? "success" : "failure", listener.message());
+ }
+
+ EXPECT_EQ(will_be_open, GetBackgroundPage(extension->id()) != nullptr);
+ }
+
+ private:
+ ExtensionHost* GetBackgroundPage(const std::string& extension_id) {
+ return process_manager()->GetBackgroundHostForExtension(extension_id);
+ }
+
+ ProcessManager* process_manager() { return ProcessManager::Get(profile()); }
+
+ DISALLOW_COPY_AND_ASSIGN(WakeEventPageTest);
+};
+
+IN_PROC_BROWSER_TEST_F(WakeEventPageTest, ClosedEventPage) {
+ RunTest(true /* expect_success */, EVENT, true /* should_close */,
+ true /* will_be_open */);
+}
+
+IN_PROC_BROWSER_TEST_F(WakeEventPageTest, OpenEventPage) {
+ RunTest(true /* expect_success */, EVENT, false /* should_close */,
+ true /* will_be_open */);
+}
+
+IN_PROC_BROWSER_TEST_F(WakeEventPageTest, ClosedPersistentBackgroundPage) {
+ // Note: this is an odd test, because persistent background pages aren't
+ // supposed to close. Extensions can close them with window.close() but why
+ // would they do that? Test it anyway.
+ RunTest(false /* expect_success */, PERSISTENT, true /* should_close */,
+ false /* will_be_open */);
+}
+
+IN_PROC_BROWSER_TEST_F(WakeEventPageTest, OpenPersistentBackgroundPage) {
+ RunTest(true /* expect_success */, PERSISTENT, false /* should_close */,
+ true /* will_be_open */);
+}
+
+IN_PROC_BROWSER_TEST_F(WakeEventPageTest, NoBackgroundPage) {
+ RunTest(false /* expect_success */, NONE, false /* should_close */,
+ false /* will_be_open */);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index e6133d3..b1116e7 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -279,6 +279,7 @@
'browser/extensions/startup_helper_browsertest.cc',
'browser/extensions/stubs_apitest.cc',
'browser/extensions/subscribe_page_action_browsertest.cc',
+ 'browser/extensions/wake_event_page_apitest.cc',
'browser/extensions/web_contents_browsertest.cc',
'browser/extensions/webstore_inline_installer_browsertest.cc',
'browser/extensions/webstore_installer_browsertest.cc',
diff --git a/extensions/browser/extension_message_filter.cc b/extensions/browser/extension_message_filter.cc
index 053d8ee..d35f255 100644
--- a/extensions/browser/extension_message_filter.cc
+++ b/extensions/browser/extension_message_filter.cc
@@ -12,10 +12,13 @@
#include "extensions/browser/blob_holder.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_factory.h"
+#include "extensions/browser/process_map.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/background_info.h"
#include "ipc/ipc_message_macros.h"
using content::BrowserThread;
@@ -52,8 +55,7 @@ ExtensionMessageFilter::ExtensionMessageFilter(int render_process_id,
content::BrowserContext* context)
: BrowserMessageFilter(ExtensionMsgStart),
render_process_id_(render_process_id),
- event_router_(EventRouter::Get(context)),
- process_manager_(ProcessManager::Get(context)) {
+ browser_context_(context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
shutdown_notifier_ =
ShutdownNotifierFactory::GetInstance()->Get(context)->Subscribe(
@@ -69,9 +71,13 @@ ExtensionMessageFilter::~ExtensionMessageFilter() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
+EventRouter* ExtensionMessageFilter::GetEventRouter() {
+ DCHECK(browser_context_);
+ return EventRouter::Get(browser_context_);
+}
+
void ExtensionMessageFilter::ShutdownOnUIThread() {
- event_router_ = nullptr;
- process_manager_ = nullptr;
+ browser_context_ = nullptr;
shutdown_notifier_.reset();
}
@@ -88,6 +94,7 @@ void ExtensionMessageFilter::OverrideThreadForMessage(
case ExtensionHostMsg_ShouldSuspendAck::ID:
case ExtensionHostMsg_SuspendAck::ID:
case ExtensionHostMsg_TransferBlobsAck::ID:
+ case ExtensionHostMsg_WakeEventPage::ID:
*thread = BrowserThread::UI;
break;
default:
@@ -101,7 +108,7 @@ void ExtensionMessageFilter::OnDestruct() const {
bool ExtensionMessageFilter::OnMessageReceived(const IPC::Message& message) {
// If we have been shut down already, return.
- if (!event_router_)
+ if (!browser_context_)
return true;
bool handled = true;
@@ -124,6 +131,8 @@ bool ExtensionMessageFilter::OnMessageReceived(const IPC::Message& message) {
OnExtensionSuspendAck)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_TransferBlobsAck,
OnExtensionTransferBlobsAck)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_WakeEventPage,
+ OnExtensionWakeEventPage)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
@@ -133,17 +142,18 @@ void ExtensionMessageFilter::OnExtensionAddListener(
const std::string& extension_id,
const GURL& listener_url,
const std::string& event_name) {
- RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
- if (!process)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
return;
- if (!event_router_)
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
return;
if (crx_file::id_util::IdIsValid(extension_id)) {
- event_router_->AddEventListener(event_name, process, extension_id);
+ GetEventRouter()->AddEventListener(event_name, process, extension_id);
} else if (listener_url.is_valid()) {
- event_router_->AddEventListenerForURL(event_name, process, listener_url);
+ GetEventRouter()->AddEventListenerForURL(event_name, process, listener_url);
} else {
NOTREACHED() << "Tried to add an event listener without a valid "
<< "extension ID nor listener URL";
@@ -154,17 +164,19 @@ void ExtensionMessageFilter::OnExtensionRemoveListener(
const std::string& extension_id,
const GURL& listener_url,
const std::string& event_name) {
- RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
- if (!process)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
return;
- if (!event_router_)
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
return;
if (crx_file::id_util::IdIsValid(extension_id)) {
- event_router_->RemoveEventListener(event_name, process, extension_id);
+ GetEventRouter()->RemoveEventListener(event_name, process, extension_id);
} else if (listener_url.is_valid()) {
- event_router_->RemoveEventListenerForURL(event_name, process, listener_url);
+ GetEventRouter()->RemoveEventListenerForURL(event_name, process,
+ listener_url);
} else {
NOTREACHED() << "Tried to remove an event listener without a valid "
<< "extension ID nor listener URL";
@@ -173,18 +185,20 @@ void ExtensionMessageFilter::OnExtensionRemoveListener(
void ExtensionMessageFilter::OnExtensionAddLazyListener(
const std::string& extension_id, const std::string& event_name) {
- if (!event_router_)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
return;
- event_router_->AddLazyEventListener(event_name, extension_id);
+ GetEventRouter()->AddLazyEventListener(event_name, extension_id);
}
void ExtensionMessageFilter::OnExtensionRemoveLazyListener(
const std::string& extension_id, const std::string& event_name) {
- if (!event_router_)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
return;
- event_router_->RemoveLazyEventListener(event_name, extension_id);
+ GetEventRouter()->RemoveLazyEventListener(event_name, extension_id);
}
void ExtensionMessageFilter::OnExtensionAddFilteredListener(
@@ -192,15 +206,16 @@ void ExtensionMessageFilter::OnExtensionAddFilteredListener(
const std::string& event_name,
const base::DictionaryValue& filter,
bool lazy) {
- RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
- if (!process)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
return;
- if (!event_router_)
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
return;
- event_router_->AddFilteredEventListener(event_name, process, extension_id,
- filter, lazy);
+ GetEventRouter()->AddFilteredEventListener(event_name, process, extension_id,
+ filter, lazy);
}
void ExtensionMessageFilter::OnExtensionRemoveFilteredListener(
@@ -208,29 +223,43 @@ void ExtensionMessageFilter::OnExtensionRemoveFilteredListener(
const std::string& event_name,
const base::DictionaryValue& filter,
bool lazy) {
- RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
- if (!process)
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
return;
- if (!event_router_)
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
return;
- event_router_->RemoveFilteredEventListener(event_name, process, extension_id,
- filter, lazy);
+ GetEventRouter()->RemoveFilteredEventListener(event_name, process,
+ extension_id, filter, lazy);
}
void ExtensionMessageFilter::OnExtensionShouldSuspendAck(
const std::string& extension_id, int sequence_id) {
- process_manager_->OnShouldSuspendAck(extension_id, sequence_id);
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ ProcessManager::Get(browser_context_)
+ ->OnShouldSuspendAck(extension_id, sequence_id);
}
void ExtensionMessageFilter::OnExtensionSuspendAck(
const std::string& extension_id) {
- process_manager_->OnSuspendAck(extension_id);
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ ProcessManager::Get(browser_context_)->OnSuspendAck(extension_id);
}
void ExtensionMessageFilter::OnExtensionTransferBlobsAck(
const std::vector<std::string>& blob_uuids) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
if (!process)
return;
@@ -238,4 +267,54 @@ void ExtensionMessageFilter::OnExtensionTransferBlobsAck(
BlobHolder::FromRenderProcessHost(process)->DropBlobs(blob_uuids);
}
+void ExtensionMessageFilter::OnExtensionWakeEventPage(
+ int request_id,
+ const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ const Extension* extension = ExtensionRegistry::Get(browser_context_)
+ ->enabled_extensions()
+ .GetByID(extension_id);
+ if (!extension) {
+ // Don't kill the renderer, it might just be some context which hasn't
+ // caught up to extension having been uninstalled.
+ return;
+ }
+
+ ProcessManager* process_manager = ProcessManager::Get(browser_context_);
+
+ if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ // Wake the event page if it's asleep, or immediately repond with success
+ // if it's already awake.
+ if (process_manager->IsEventPageSuspended(extension_id)) {
+ process_manager->WakeEventPage(
+ extension_id,
+ base::Bind(&ExtensionMessageFilter::SendWakeEventPageResponse, this,
+ request_id));
+ } else {
+ SendWakeEventPageResponse(request_id, true);
+ }
+ return;
+ }
+
+ if (BackgroundInfo::HasPersistentBackgroundPage(extension)) {
+ // No point in trying to wake a persistent background page. If it's open,
+ // immediately return and call it a success. If it's closed, fail.
+ SendWakeEventPageResponse(request_id,
+ process_manager->GetBackgroundHostForExtension(
+ extension_id) != nullptr);
+ return;
+ }
+
+ // The extension has no background page, so there is nothing to wake.
+ SendWakeEventPageResponse(request_id, false);
+}
+
+void ExtensionMessageFilter::SendWakeEventPageResponse(int request_id,
+ bool success) {
+ Send(new ExtensionMsg_WakeEventPageResponse(request_id, success));
+}
+
} // namespace extensions
diff --git a/extensions/browser/extension_message_filter.h b/extensions/browser/extension_message_filter.h
index e0c0e0c..65900d1 100644
--- a/extensions/browser/extension_message_filter.h
+++ b/extensions/browser/extension_message_filter.h
@@ -25,9 +25,7 @@ class Size;
}
namespace extensions {
-
class EventRouter;
-class ProcessManager;
// This class filters out incoming extension-specific IPC messages from the
// renderer process. It is created and destroyed on the UI thread and handles
@@ -47,9 +45,11 @@ class ExtensionMessageFilter : public content::BrowserMessageFilter {
~ExtensionMessageFilter() override;
+ EventRouter* GetEventRouter();
+
void ShutdownOnUIThread();
- // content::BrowserMessageFilter implementation.
+ // content::BrowserMessageFilter implementation:
void OverrideThreadForMessage(const IPC::Message& message,
content::BrowserThread::ID* thread) override;
void OnDestruct() const override;
@@ -78,14 +78,18 @@ class ExtensionMessageFilter : public content::BrowserMessageFilter {
int sequence_id);
void OnExtensionSuspendAck(const std::string& extension_id);
void OnExtensionTransferBlobsAck(const std::vector<std::string>& blob_uuids);
+ void OnExtensionWakeEventPage(int request_id,
+ const std::string& extension_id);
+
+ // Responds to the ExtensionHostMsg_WakeEventPage message.
+ void SendWakeEventPageResponse(int request_id, bool success);
const int render_process_id_;
scoped_ptr<KeyedServiceShutdownNotifier::Subscription> shutdown_notifier_;
- // Owned by the browser context; should only be accessed on the UI thread.
- EventRouter* event_router_;
- ProcessManager* process_manager_;
+ // Only access from the UI thread.
+ content::BrowserContext* browser_context_;
DISALLOW_COPY_AND_ASSIGN(ExtensionMessageFilter);
};
diff --git a/extensions/browser/process_manager_observer.h b/extensions/browser/process_manager_observer.h
index 93c119f..668b3df 100644
--- a/extensions/browser/process_manager_observer.h
+++ b/extensions/browser/process_manager_observer.h
@@ -17,11 +17,14 @@ class ExtensionHost;
class ProcessManagerObserver {
public:
- // Called immediately after an extension background host is started.
+ // Called immediately after an extension background host is started. This
+ // corresponds with the loading of background hosts immediately after profile
+ // startup.
virtual void OnBackgroundHostStartup(const Extension* extension) {}
- // Called immediately after an ExtensionHost for an extension with a lazy
- // background page is created.
+ // Called immediately after an ExtensionHost for an extension is created.
+ // This corresponds with any time ProcessManager::OnBackgroundHostCreated is
+ // called.
virtual void OnBackgroundHostCreated(ExtensionHost* host) {}
// Called immediately after the extension background host is destroyed.
diff --git a/extensions/common/api/test.json b/extensions/common/api/test.json
index 594709c..f4e193f 100644
--- a/extensions/common/api/test.json
+++ b/extensions/common/api/test.json
@@ -416,6 +416,17 @@
]
}
]
+ },
+ {
+ "name": "getWakeEventPage",
+ "type": "function",
+ "description": "Returns the wake-event-page API function, which can be called to wake up the extension's event page.",
+ "nocompile": true,
+ "parameters": [],
+ "returns": {
+ "type": "function",
+ "description": "The API function which wakes the extension's event page"
+ }
}
],
"events": [
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index 49aee48..d799781 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -506,6 +506,11 @@ IPC_MESSAGE_ROUTED1(ExtensionMsg_NotifyRenderViewType,
IPC_MESSAGE_CONTROL1(ExtensionMsg_UsingWebRequestAPI,
bool /* webrequest_used */)
+// The browser's response to the ExtensionMsg_WakeEventPage IPC.
+IPC_MESSAGE_CONTROL2(ExtensionMsg_WakeEventPageResponse,
+ int /* request_id */,
+ bool /* success */)
+
// Ask the lazy background page if it is ready to be suspended. This is sent
// when the page is considered idle. The renderer will reply with the same
// sequence_id so that we can tell which message it is responding to.
@@ -769,6 +774,12 @@ IPC_MESSAGE_ROUTED1(ExtensionHostMsg_OnWatchedPageChange,
IPC_MESSAGE_CONTROL1(ExtensionHostMsg_TransferBlobsAck,
std::vector<std::string> /* blob_uuids */)
+// Asks the browser to wake the event page of an extension.
+// The browser will reply with ExtensionHostMsg_WakeEventPageResponse.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_WakeEventPage,
+ int /* request_id */,
+ std::string /* extension_id */)
+
// Tells listeners that a detailed message was reported to the console by
// WebKit.
IPC_MESSAGE_ROUTED4(ExtensionHostMsg_DetailedConsoleMessageAdded,
diff --git a/extensions/extensions.gypi b/extensions/extensions.gypi
index 06b5a87..46e920f 100644
--- a/extensions/extensions.gypi
+++ b/extensions/extensions.gypi
@@ -992,6 +992,8 @@
'renderer/static_v8_external_one_byte_string_resource.h',
'renderer/test_features_native_handler.cc',
'renderer/test_features_native_handler.h',
+ 'renderer/test_native_handler.cc',
+ 'renderer/test_native_handler.h',
'renderer/user_gestures_native_handler.cc',
'renderer/user_gestures_native_handler.h',
'renderer/user_script_injector.cc',
@@ -1007,6 +1009,8 @@
'renderer/v8_helpers.h',
'renderer/v8_schema_registry.cc',
'renderer/v8_schema_registry.h',
+ 'renderer/wake_event_page.cc',
+ 'renderer/wake_event_page.h',
'renderer/web_ui_injection_host.cc',
'renderer/web_ui_injection_host.h',
],
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index ed64dfd..636a2bf 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -81,10 +81,12 @@
#include "extensions/renderer/send_request_natives.h"
#include "extensions/renderer/set_icon_natives.h"
#include "extensions/renderer/test_features_native_handler.h"
+#include "extensions/renderer/test_native_handler.h"
#include "extensions/renderer/user_gestures_native_handler.h"
#include "extensions/renderer/utils_native_handler.h"
#include "extensions/renderer/v8_context_native_handler.h"
#include "extensions/renderer/v8_helpers.h"
+#include "extensions/renderer/wake_event_page.h"
#include "grit/extensions_renderer_resources.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
@@ -210,6 +212,7 @@ Dispatcher::Dispatcher(DispatcherDelegate* delegate)
user_script_set_manager_observer_.Add(user_script_set_manager_.get());
request_sender_.reset(new RequestSender(this));
PopulateSourceMap();
+ WakeEventPage::Get()->Init(content::RenderThread::Get());
// chrome-extensions: and chrome-extensions-resource: schemes should be
// treated as secure because communication with them is entirely in the
@@ -668,6 +671,9 @@ void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system,
"test_features",
scoped_ptr<NativeHandler>(new TestFeaturesNativeHandler(context)));
module_system->RegisterNativeHandler(
+ "test_native_handler",
+ scoped_ptr<NativeHandler>(new TestNativeHandler(context)));
+ module_system->RegisterNativeHandler(
"user_gestures",
scoped_ptr<NativeHandler>(new UserGesturesNativeHandler(context)));
module_system->RegisterNativeHandler(
diff --git a/extensions/renderer/resources/test_custom_bindings.js b/extensions/renderer/resources/test_custom_bindings.js
index ebb3f1b..7310f06 100644
--- a/extensions/renderer/resources/test_custom_bindings.js
+++ b/extensions/renderer/resources/test_custom_bindings.js
@@ -11,6 +11,7 @@ var environmentSpecificBindings = require('test_environment_specific_bindings');
var GetExtensionAPIDefinitionsForTest =
requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest;
var GetAPIFeatures = requireNative('test_features').GetAPIFeatures;
+var natives = requireNative('test_native_handler');
var uncaughtExceptionHandler = require('uncaught_exception_handler');
var userGestures = requireNative('user_gestures');
@@ -353,6 +354,10 @@ binding.registerCustomHook(function(api) {
uncaughtExceptionHandler.setHandler(callback);
});
+ apiFunctions.setHandleRequest('getWakeEventPage', function() {
+ return natives.GetWakeEventPage();
+ });
+
environmentSpecificBindings.registerHooks(api);
});
diff --git a/extensions/renderer/test_native_handler.cc b/extensions/renderer/test_native_handler.cc
new file mode 100644
index 0000000..31e7dcb
--- /dev/null
+++ b/extensions/renderer/test_native_handler.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 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 "extensions/renderer/test_native_handler.h"
+
+#include "extensions/renderer/wake_event_page.h"
+
+namespace extensions {
+
+TestNativeHandler::TestNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetWakeEventPage",
+ base::Bind(&TestNativeHandler::GetWakeEventPage, base::Unretained(this)));
+}
+
+void TestNativeHandler::GetWakeEventPage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(0, args.Length());
+ args.GetReturnValue().Set(WakeEventPage::Get()->GetForContext(context()));
+}
+
+} // namespace extensions
diff --git a/extensions/renderer/test_native_handler.h b/extensions/renderer/test_native_handler.h
new file mode 100644
index 0000000..057329d
--- /dev/null
+++ b/extensions/renderer/test_native_handler.h
@@ -0,0 +1,28 @@
+// Copyright 2015 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 EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// NativeHandler for the chrome.test API.
+class TestNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit TestNativeHandler(ScriptContext* context);
+
+ private:
+ void GetWakeEventPage(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(TestNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
diff --git a/extensions/renderer/wake_event_page.cc b/extensions/renderer/wake_event_page.cc
new file mode 100644
index 0000000..1bc4304
--- /dev/null
+++ b/extensions/renderer/wake_event_page.cc
@@ -0,0 +1,173 @@
+// Copyright 2015 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 "extensions/renderer/wake_event_page.h"
+
+#include "base/atomic_sequence_num.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+base::LazyInstance<WakeEventPage> g_instance = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+class WakeEventPage::WakeEventPageNativeHandler
+ : public ObjectBackedNativeHandler {
+ public:
+ // Handles own lifetime.
+ WakeEventPageNativeHandler(ScriptContext* context,
+ const std::string& name,
+ const MakeRequestCallback& make_request)
+ : ObjectBackedNativeHandler(context),
+ make_request_(make_request),
+ weak_ptr_factory_(this) {
+ // Use Unretained not a WeakPtr because RouteFunction is tied to the
+ // lifetime of this, so there is no way for DoWakeEventPage to be called
+ // after destruction.
+ RouteFunction(name, base::Bind(&WakeEventPageNativeHandler::DoWakeEventPage,
+ base::Unretained(this)));
+ // Delete self on invalidation. base::Unretained because by definition this
+ // can't be deleted before it's deleted.
+ context->AddInvalidationObserver(base::Bind(
+ &WakeEventPageNativeHandler::DeleteSelf, base::Unretained(this)));
+ };
+
+ ~WakeEventPageNativeHandler() override {}
+
+ private:
+ void DeleteSelf() {
+ Invalidate();
+ delete this;
+ }
+
+ // Called by JavaScript with a single argument, the function to call when the
+ // event page has been woken.
+ void DoWakeEventPage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsFunction());
+ v8::Global<v8::Function> callback(args.GetIsolate(),
+ args[0].As<v8::Function>());
+
+ const std::string& extension_id = context()->GetExtensionID();
+ CHECK(!extension_id.empty());
+
+ make_request_.Run(
+ extension_id,
+ base::Bind(&WakeEventPageNativeHandler::OnEventPageIsAwake,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
+ }
+
+ void OnEventPageIsAwake(v8::Global<v8::Function> callback, bool success) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Value> args[] = {
+ v8::Boolean::New(isolate, success),
+ };
+ context()->CallFunction(v8::Local<v8::Function>::New(isolate, callback),
+ arraysize(args), args);
+ }
+
+ MakeRequestCallback make_request_;
+ base::WeakPtrFactory<WakeEventPageNativeHandler> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WakeEventPageNativeHandler);
+};
+
+// static
+WakeEventPage* WakeEventPage::Get() {
+ return g_instance.Pointer();
+}
+
+void WakeEventPage::Init(content::RenderThread* render_thread) {
+ DCHECK(render_thread);
+ DCHECK_EQ(content::RenderThread::Get(), render_thread);
+ DCHECK(!message_filter_);
+
+ message_filter_ = render_thread->GetSyncMessageFilter();
+ render_thread->AddObserver(this);
+}
+
+v8::Local<v8::Function> WakeEventPage::GetForContext(ScriptContext* context) {
+ DCHECK(message_filter_);
+
+ v8::Isolate* isolate = context->isolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Handle<v8::Context> v8_context = context->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ // Cache the imported function as a hidden property on the global object of
+ // |v8_context|. Creating it isn't free.
+ v8::Local<v8::String> kWakeEventPageKey =
+ ToV8StringUnsafe(isolate, "WakeEventPage");
+ v8::Local<v8::Value> wake_event_page =
+ v8_context->Global()->GetHiddenValue(kWakeEventPageKey);
+
+ if (wake_event_page.IsEmpty()) {
+ // Implement this using a NativeHandler, which requires a function name
+ // (arbitrary in this case). Handles own lifetime.
+ const char* kFunctionName = "WakeEventPage";
+ WakeEventPageNativeHandler* native_handler = new WakeEventPageNativeHandler(
+ context, kFunctionName, base::Bind(&WakeEventPage::MakeRequest,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ // Extract and cache the wake-event-page function from the native handler.
+ wake_event_page = GetPropertyUnsafe(
+ v8_context, native_handler->NewInstance(), kFunctionName);
+ v8_context->Global()->SetHiddenValue(kWakeEventPageKey, wake_event_page);
+ }
+
+ CHECK(wake_event_page->IsFunction());
+ return handle_scope.Escape(wake_event_page.As<v8::Function>());
+}
+
+WakeEventPage::RequestData::RequestData(const OnResponseCallback& on_response)
+ : on_response(on_response) {}
+
+WakeEventPage::RequestData::~RequestData() {}
+
+WakeEventPage::WakeEventPage() : weak_ptr_factory_(this) {}
+
+WakeEventPage::~WakeEventPage() {}
+
+void WakeEventPage::MakeRequest(const std::string& extension_id,
+ const OnResponseCallback& on_response) {
+ static base::AtomicSequenceNumber sequence_number;
+ int request_id = sequence_number.GetNext();
+ requests_.set(request_id, make_scoped_ptr(new RequestData(on_response)));
+ message_filter_->Send(
+ new ExtensionHostMsg_WakeEventPage(request_id, extension_id));
+}
+
+bool WakeEventPage::OnControlMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(WakeEventPage, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_WakeEventPageResponse,
+ OnWakeEventPageResponse)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void WakeEventPage::OnWakeEventPageResponse(int request_id, bool success) {
+ scoped_ptr<RequestData> request_data = requests_.take(request_id);
+ CHECK(request_data);
+ request_data->on_response.Run(success);
+}
+
+} // namespace extensions
diff --git a/extensions/renderer/wake_event_page.h b/extensions/renderer/wake_event_page.h
new file mode 100644
index 0000000..38ceda4f
--- /dev/null
+++ b/extensions/renderer/wake_event_page.h
@@ -0,0 +1,108 @@
+// Copyright 2015 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 EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
+#define EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/scoped_ptr_hash_map.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "ipc/ipc_sync_message_filter.h"
+#include "v8/include/v8.h"
+
+namespace content {
+class RenderThread;
+}
+
+namespace extensions {
+class ScriptContext;
+
+// This class implements the wake-event-page JavaScript function, which wakes
+// an event page and runs a callback when done.
+//
+// Note, the function will do a round trip to the browser even if event page is
+// open. Any optimisation to prevent this must be at the JavaScript level.
+class WakeEventPage : public content::RenderProcessObserver {
+ public:
+ WakeEventPage();
+ ~WakeEventPage() override;
+
+ // Returns the single instance of the WakeEventPage object.
+ //
+ // Thread safe.
+ static WakeEventPage* Get();
+
+ // Initializes the WakeEventPage.
+ //
+ // This must be called before any bindings are installed, and must be called
+ // on the render thread.
+ void Init(content::RenderThread* render_thread);
+
+ // Returns the wake-event-page function bound to a given context. The
+ // function will be cached as a hidden value in the context's global object.
+ //
+ // To mix C++ and JavaScript, example usage might be:
+ //
+ // WakeEventPage::Get().GetForContext(context)(function() {
+ // ...
+ // });
+ //
+ // Thread safe.
+ v8::Local<v8::Function> GetForContext(ScriptContext* context);
+
+ private:
+ class WakeEventPageNativeHandler;
+
+ // The response from an ExtensionHostMsg_WakeEvent call, passed true if the
+ // call was successful, false on failure.
+ using OnResponseCallback = base::Callback<void(bool)>;
+
+ // Makes an ExtensionHostMsg_WakeEvent request for an extension ID. The
+ // second argument is a callback to run when the request has completed.
+ using MakeRequestCallback =
+ base::Callback<void(const std::string&, const OnResponseCallback&)>;
+
+ // For |requests_|.
+ struct RequestData {
+ explicit RequestData(const OnResponseCallback& on_response);
+ ~RequestData();
+ OnResponseCallback on_response;
+ };
+
+ // Runs |on_response|, passing it |success|.
+ static void RunOnResponseWithResult(const OnResponseCallback& on_response,
+ bool success);
+
+ // Sends the ExtensionHostMsg_WakeEvent IPC for |extension_id|, and
+ // updates |requests_| bookkeeping.
+ void MakeRequest(const std::string& extension_id,
+ const OnResponseCallback& on_response);
+
+ // content::RenderProcessObserver:
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+
+ // OnControlMessageReceived handlers:
+ void OnWakeEventPageResponse(int request_id, bool success);
+
+ // IPC sender. Belongs to the render thread, but thread safe.
+ scoped_refptr<IPC::SyncMessageFilter> message_filter_;
+
+ // All in-flight requests, keyed by request ID.
+ base::ScopedPtrHashMap<int, scoped_ptr<RequestData>> requests_;
+
+ base::WeakPtrFactory<WakeEventPage> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WakeEventPage);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_