diff options
author | kalman <kalman@chromium.org> | 2015-08-21 12:30:13 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-21 19:31:00 +0000 |
commit | 12033f1290753504a3c324293689313b890e4ef2 (patch) | |
tree | 84493e36fd5ef8f215bcde3cd82aa638dac52d06 | |
parent | b3ad146d98e8e67187e4f072676afbe210e73042 (diff) | |
download | chromium_src-12033f1290753504a3c324293689313b890e4ef2.zip chromium_src-12033f1290753504a3c324293689313b890e4ef2.tar.gz chromium_src-12033f1290753504a3c324293689313b890e4ef2.tar.bz2 |
Implement the wake-event-page internal extension API.
This is implemented purely using IPC because it will be used from worker
contexts, which don't have access to extension bindings. The next step will be
to add worker thread support so the renderer side is structured to easily
accommodate for that, once the public content API is chaned to allow it.
It will be used in the public chrome.runtime.getBackgroundClient() exposed to
service workers.
BUG=501569
R=rdevlin.cronin@chromium.org, palmer@chromium.org
Review URL: https://codereview.chromium.org/1294993004
Cr-Commit-Position: refs/heads/master@{#344834}
-rw-r--r-- | chrome/browser/extensions/wake_event_page_apitest.cc | 242 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | extensions/browser/extension_message_filter.cc | 141 | ||||
-rw-r--r-- | extensions/browser/extension_message_filter.h | 16 | ||||
-rw-r--r-- | extensions/browser/process_manager_observer.h | 9 | ||||
-rw-r--r-- | extensions/common/api/test.json | 11 | ||||
-rw-r--r-- | extensions/common/extension_messages.h | 11 | ||||
-rw-r--r-- | extensions/extensions.gypi | 4 | ||||
-rw-r--r-- | extensions/renderer/dispatcher.cc | 6 | ||||
-rw-r--r-- | extensions/renderer/resources/test_custom_bindings.js | 5 | ||||
-rw-r--r-- | extensions/renderer/test_native_handler.cc | 24 | ||||
-rw-r--r-- | extensions/renderer/test_native_handler.h | 28 | ||||
-rw-r--r-- | extensions/renderer/wake_event_page.cc | 173 | ||||
-rw-r--r-- | extensions/renderer/wake_event_page.h | 108 |
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_ |