From dddfce456c3be3015972d48e7bc973f3f9580730 Mon Sep 17 00:00:00 2001 From: Ryan Shetley Date: Wed, 30 Aug 2017 11:46:21 +0000 Subject: [PATCH] Add support for Chrome Alarms Extension API (issue #1947) --- cef_paths2.gypi | 1 + ...browser_context_keyed_service_factories.cc | 2 + .../extensions/chrome_api_registration.cc | 12 + libcef/browser/extensions/extension_system.cc | 3 + .../extensions/chrome_alarms_unittest.cc | 340 ++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100644 tests/ceftests/extensions/chrome_alarms_unittest.cc diff --git a/cef_paths2.gypi b/cef_paths2.gypi index e08af4bfd..4f792576e 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -430,6 +430,7 @@ 'tests/ceftests/download_unittest.cc', 'tests/ceftests/draggable_regions_unittest.cc', 'tests/ceftests/extensions/background_unittest.cc', + 'tests/ceftests/extensions/chrome_alarms_unittest.cc', 'tests/ceftests/extensions/chrome_tabs_unittest.cc', 'tests/ceftests/extensions/extension_test_handler.cc', 'tests/ceftests/extensions/extension_test_handler.h', diff --git a/libcef/browser/extensions/browser_context_keyed_service_factories.cc b/libcef/browser/extensions/browser_context_keyed_service_factories.cc index ab275b694..3e91c3853 100644 --- a/libcef/browser/extensions/browser_context_keyed_service_factories.cc +++ b/libcef/browser/extensions/browser_context_keyed_service_factories.cc @@ -7,12 +7,14 @@ #include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/extensions/api/streams_private/streams_private_api.h" #include "chrome/browser/ui/prefs/prefs_tab_helper.h" +#include "extensions/browser/api/alarms/alarm_manager.h" #include "extensions/browser/renderer_startup_helper.h" namespace extensions { namespace cef { void EnsureBrowserContextKeyedServiceFactoriesBuilt() { + AlarmManager::GetFactoryInstance(); CookieSettingsFactory::GetInstance(); PrefsTabHelper::GetServiceInstance(); RendererStartupHelperFactory::GetInstance(); diff --git a/libcef/browser/extensions/chrome_api_registration.cc b/libcef/browser/extensions/chrome_api_registration.cc index 7a4174808..e5a8d60a1 100644 --- a/libcef/browser/extensions/chrome_api_registration.cc +++ b/libcef/browser/extensions/chrome_api_registration.cc @@ -12,6 +12,7 @@ #include "chrome/browser/extensions/api/resources_private/resources_private_api.h" #include "chrome/browser/extensions/api/streams_private/streams_private_api.h" +#include "extensions/browser/api/alarms/alarms_api.h" #include "extensions/browser/extension_function_registry.h" namespace extensions { @@ -29,6 +30,12 @@ const char* const kSupportedAPIs[] = { EXTENSION_FUNCTION_NAME(ResourcesPrivateGetStringsFunction), "streamsPrivate", EXTENSION_FUNCTION_NAME(StreamsPrivateAbortFunction), + "alarms", + EXTENSION_FUNCTION_NAME(AlarmsCreateFunction), + EXTENSION_FUNCTION_NAME(AlarmsGetFunction), + EXTENSION_FUNCTION_NAME(AlarmsGetAllFunction), + EXTENSION_FUNCTION_NAME(AlarmsClearFunction), + EXTENSION_FUNCTION_NAME(AlarmsClearAllFunction), "tabs", EXTENSION_FUNCTION_NAME(cefimpl::TabsGetFunction), EXTENSION_FUNCTION_NAME(cefimpl::TabsExecuteScriptFunction), @@ -55,6 +62,11 @@ bool ChromeFunctionRegistry::IsSupported(const std::string& name) { void ChromeFunctionRegistry::RegisterAll(ExtensionFunctionRegistry* registry) { registry->RegisterFunction(); registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); diff --git a/libcef/browser/extensions/extension_system.cc b/libcef/browser/extensions/extension_system.cc index be14dd3ef..60e890eda 100644 --- a/libcef/browser/extensions/extension_system.cc +++ b/libcef/browser/extensions/extension_system.cc @@ -480,6 +480,9 @@ scoped_refptr CefExtensionSystem::CreateExtension( } return Extension::Create( info.root_directory, + // Tests should continue to use the Manifest::COMMAND_LINE value here + // Some Chrome APIs will cause undesired effects if this is incorrect + // e.g.: alarms API has 1 minute minimum applied to Packed Extensions info.internal ? Manifest::COMPONENT : Manifest::COMMAND_LINE, *info.manifest, flags, utf8_error); } diff --git a/tests/ceftests/extensions/chrome_alarms_unittest.cc b/tests/ceftests/extensions/chrome_alarms_unittest.cc new file mode 100644 index 000000000..42397a66d --- /dev/null +++ b/tests/ceftests/extensions/chrome_alarms_unittest.cc @@ -0,0 +1,340 @@ +// Copyright (c) 2017 The Chromium Embedded Framework 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 "tests/ceftests/extensions/extension_test_handler.h" +#include "tests/ceftests/test_util.h" +#include "tests/shared/browser/extension_util.h" + +#define ALARMS_TEST_GROUP_ALL(name, test_class) \ + EXTENSION_TEST_GROUP_ALL(ChromeAlarms##name, test_class) +#define ALARMS_TEST_GROUP_MINIMAL(name, test_class) \ + EXTENSION_TEST_GROUP_MINIMAL(ChromeAlarms##name, test_class) + +namespace { + +const char kExtensionPath[] = "alarms-extension"; +const char kSuccessMessage[] = "success"; + +// Base class for testing chrome.alarms methods. +// See https://developer.chrome.com/extensions/alarms +class AlarmsTestHandler : public ExtensionTestHandler { + public: + explicit AlarmsTestHandler(RequestContextType request_context_type) + : ExtensionTestHandler(request_context_type) { + // Only creating the extension browser. + set_create_main_browser(false); + } + + // CefExtensionHandler methods: + void OnExtensionLoaded(CefRefPtr extension) override { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_FALSE(got_loaded_); + got_loaded_.yes(); + + // Verify |extension| contents. + EXPECT_FALSE(extension->GetIdentifier().empty()); + EXPECT_STREQ(("extensions/" + std::string(kExtensionPath)).c_str(), + client::extension_util::GetInternalExtensionResourcePath( + extension->GetPath()) + .c_str()); + TestDictionaryEqual(CreateManifest(), extension->GetManifest()); + + EXPECT_FALSE(extension_); + extension_ = extension; + + CreateBrowserForExtension(); + } + + void OnExtensionUnloaded(CefRefPtr extension) override { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_TRUE(extension_); + EXPECT_TRUE(extension_->IsSame(extension)); + EXPECT_FALSE(got_unloaded_); + got_unloaded_.yes(); + extension_ = NULL; + + // Execute asynchronously so call stacks have a chance to unwind. + // Will close the browser windows. + CefPostTask(TID_UI, base::Bind(&AlarmsTestHandler::DestroyTest, this)); + } + + // CefLoadHandler methods: + void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override { + CefRefPtr extension = browser->GetHost()->GetExtension(); + EXPECT_TRUE(extension); + EXPECT_TRUE(extension_->IsSame(extension)); + + if (isLoading) { + EXPECT_FALSE(extension_browser_); + extension_browser_ = browser; + } else { + EXPECT_TRUE(browser->IsSame(extension_browser_)); + + const std::string& url = browser->GetMainFrame()->GetURL(); + EXPECT_STREQ(extension_url_.c_str(), url.c_str()); + } + } + + // CefRequestHandler methods: + CefRefPtr GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override { + EXPECT_TRUE(browser->IsSame(extension_browser_)); + + CefRefPtr extension = browser->GetHost()->GetExtension(); + EXPECT_TRUE(extension); + EXPECT_TRUE(extension_->IsSame(extension)); + + const std::string& url = request->GetURL(); + EXPECT_STREQ(extension_url_.c_str(), url.c_str()); + + EXPECT_FALSE(got_url_request_); + got_url_request_.yes(); + + // Handle the resource request. + return RoutingTestHandler::GetResourceHandler(browser, frame, request); + } + + protected: + void OnLoadExtensions() override { + LoadExtension(kExtensionPath, CreateManifest()); + } + + bool OnMessage(CefRefPtr browser, + const std::string& message) override { + if (message == "extension_onload") { + // From body onLoad in the extension browser. + EXPECT_TRUE(browser->IsSame(extension_browser_)); + EXPECT_FALSE(got_body_onload_); + got_body_onload_.yes(); + TriggerAlarmsApiJSFunction(); + return true; + } + EXPECT_TRUE(browser->IsSame(extension_browser_)); + EXPECT_FALSE(got_success_message_); + got_success_message_.yes(); + EXPECT_STREQ(kSuccessMessage, message.c_str()); + TriggerDestroyTest(); + return true; + } + + void OnDestroyTest() override { + extension_browser_ = NULL; + + EXPECT_TRUE(got_loaded_); + EXPECT_TRUE(got_url_request_); + EXPECT_TRUE(got_body_onload_); + EXPECT_TRUE(got_trigger_api_function_); + EXPECT_TRUE(got_success_message_); + EXPECT_TRUE(got_unloaded_); + } + + // Create a manifest that grants access to the alarms API. + virtual CefRefPtr CreateManifest() const { + ApiPermissionsList api_permissions; + api_permissions.push_back("alarms"); + return CreateDefaultManifest(api_permissions); + } + + // Add resources in the extension browser. + virtual void OnAddExtensionResources(const std::string& origin) { + extension_url_ = origin + "extension.html"; + AddResource(extension_url_, GetExtensionHTML(), "text/html"); + } + + // Returns the chrome.alarms.* JS that is executed in the extension browser + // when the triggerAlarmsApi() JS function is called. + virtual std::string GetAlarmsApiJS() const = 0; + + // Returns the JS that will be loaded in the extension browser. This + // implements the triggerAlarmsApi() JS function called from + // TriggerAlarmsApiJSFunction(). + virtual std::string GetExtensionJS() const { + return "function triggerAlarmsApi() {" + GetAlarmsApiJS() + "}"; + } + + // Returns the HTML that will be loaded in the extension browser. + virtual std::string GetExtensionHTML() const { + return "Extension"; + } + + virtual void TriggerDestroyTest() { + // Execute asynchronously so call stacks have a chance to unwind. + CefPostTask(TID_UI, base::Bind(&AlarmsTestHandler::UnloadExtension, this, + extension_)); + } + + CefRefPtr extension() const { return extension_; } + std::string extension_url() const { return extension_url_; } + CefRefPtr extension_browser() const { return extension_browser_; } + + bool got_success_message() const { return got_success_message_; } + + private: + void CreateBrowserForExtension() { + const std::string& identifier = extension_->GetIdentifier(); + EXPECT_FALSE(identifier.empty()); + const std::string& origin = + client::extension_util::GetExtensionOrigin(identifier); + EXPECT_FALSE(origin.empty()); + + // Add extension resources. + OnAddExtensionResources(origin); + + // Create a browser to host the extension. + CreateBrowser(extension_url_, request_context()); + } + + void TriggerAlarmsApiJSFunction() { + EXPECT_FALSE(got_trigger_api_function_); + got_trigger_api_function_.yes(); + + extension_browser_->GetMainFrame()->ExecuteJavaScript("triggerAlarmsApi();", + extension_url_, 0); + } + + CefRefPtr extension_; + std::string extension_url_; + CefRefPtr extension_browser_; + + TrackCallback got_loaded_; + TrackCallback got_url_request_; + TrackCallback got_body_onload_; + TrackCallback got_trigger_api_function_; + TrackCallback got_success_message_; + TrackCallback got_unloaded_; +}; +} // namespace + +namespace { + +// Test for chrome.alarms.create(string name, object alarmInfo) +// and chrome.alarms.onAlarm.addListener(function callback) +class CreateAlarmTestHandler : public AlarmsTestHandler { + public: + explicit CreateAlarmTestHandler(RequestContextType request_context_type) + : AlarmsTestHandler(request_context_type) {} + + protected: + std::string GetAlarmsApiJS() const override { + return "chrome.alarms.onAlarm.addListener(function (alarm) {" + + GetMessageJS(kSuccessMessage) + + "});" + "chrome.alarms.create(\"test\", {delayInMinutes:0.01})"; + } + + private: + IMPLEMENT_REFCOUNTING(CreateAlarmTestHandler); + DISALLOW_COPY_AND_ASSIGN(CreateAlarmTestHandler); +}; +} // namespace + +ALARMS_TEST_GROUP_ALL(CreateAlarm, CreateAlarmTestHandler); + +namespace { + +// Test for chrome.alarms.get(string name, function callback) +class GetAlarmTestHandler : public AlarmsTestHandler { + public: + explicit GetAlarmTestHandler(RequestContextType request_context_type) + : AlarmsTestHandler(request_context_type) {} + + protected: + std::string GetAlarmsApiJS() const override { + return "chrome.alarms.create(\"test\", {delayInMinutes:1});" + "setTimeout(function() {" + "chrome.alarms.get(\"test\", function (alarm) {" + + GetMessageJS(kSuccessMessage) + "})}, 100)"; + } + + private: + IMPLEMENT_REFCOUNTING(GetAlarmTestHandler); + DISALLOW_COPY_AND_ASSIGN(GetAlarmTestHandler); +}; +} // namespace + +ALARMS_TEST_GROUP_MINIMAL(GetAlarm, GetAlarmTestHandler); + +namespace { + +// Test for chrome.alarms.getAll(function callback) +class GetAllAlarmsTestHandler : public AlarmsTestHandler { + public: + explicit GetAllAlarmsTestHandler(RequestContextType request_context_type) + : AlarmsTestHandler(request_context_type) {} + + protected: + std::string GetAlarmsApiJS() const override { + return "chrome.alarms.create(\"alarm1\", {delayInMinutes:1});" + "chrome.alarms.create(\"alarm2\", {delayInMinutes:1});" + "setTimeout(function() {" + "chrome.alarms.getAll(function (alarms) {" + "if (alarms.length == 2) {" + + GetMessageJS(kSuccessMessage) + "}})}, 100)"; + } + + private: + IMPLEMENT_REFCOUNTING(GetAllAlarmsTestHandler); + DISALLOW_COPY_AND_ASSIGN(GetAllAlarmsTestHandler); +}; +} // namespace + +ALARMS_TEST_GROUP_MINIMAL(GetAllAlarms, GetAllAlarmsTestHandler); + +namespace { + +// Test for chrome.alarms.clear(string name, function callback) +class ClearAlarmTestHandler : public AlarmsTestHandler { + public: + explicit ClearAlarmTestHandler(RequestContextType request_context_type) + : AlarmsTestHandler(request_context_type) {} + + protected: + std::string GetAlarmsApiJS() const override { + return "chrome.alarms.create(\"test\", {delayInMinutes:1});" + "setTimeout(function() {" + "chrome.alarms.clear(\"test\", function (wasCleared) {" + "if (wasCleared) {" + + GetMessageJS(kSuccessMessage) + "}})}, 100)"; + } + + private: + IMPLEMENT_REFCOUNTING(ClearAlarmTestHandler); + DISALLOW_COPY_AND_ASSIGN(ClearAlarmTestHandler); +}; +} // namespace + +ALARMS_TEST_GROUP_MINIMAL(ClearAlarm, ClearAlarmTestHandler); + +namespace { + +// Test for chrome.alarms.clearAll(function callback) +class ClearAllAlarmsTestHandler : public AlarmsTestHandler { + public: + explicit ClearAllAlarmsTestHandler(RequestContextType request_context_type) + : AlarmsTestHandler(request_context_type) {} + + protected: + std::string GetAlarmsApiJS() const override { + return "chrome.alarms.create(\"alarm1\", {delayInMinutes:1});" + "chrome.alarms.create(\"alarm2\", {delayInMinutes:1});" + "setTimeout(function() {" + "chrome.alarms.clearAll(function (wasCleared) {" + "if (wasCleared) {" + + GetMessageJS(kSuccessMessage) + "}})}, 100)"; + } + + private: + IMPLEMENT_REFCOUNTING(ClearAllAlarmsTestHandler); + DISALLOW_COPY_AND_ASSIGN(ClearAllAlarmsTestHandler); +}; +} // namespace + +ALARMS_TEST_GROUP_MINIMAL(ClearAllAlarms, ClearAllAlarmsTestHandler);