diff options
-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_ |