// 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 #include "include/cef_parser.h" #include "tests/ceftests/test_util.h" #include "tests/shared/browser/extension_util.h" #define TABS_TEST_GROUP_ALL(name, test_class) \ EXTENSION_TEST_GROUP_ALL(ChromeTabs##name, test_class) #define TABS_TEST_GROUP_MINIMAL(name, test_class) \ EXTENSION_TEST_GROUP_MINIMAL(ChromeTabs##name, test_class) namespace { const char kMainBrowserURL[] = "https://test-extensions.com/chrome-tabs"; const char kExtensionPath[] = "tabs-extension"; const char kSuccessMessage[] = "success"; // Base class for testing chrome.tabs methods. // See https://developer.chrome.com/extensions/tabs class TabsTestHandler : public ExtensionTestHandler { public: explicit TabsTestHandler(RequestContextType request_context_type) : ExtensionTestHandler(request_context_type), create_main_browser_first_(false), expect_get_active_browser_(true), expect_success_in_main_browser_(true), expected_api_call_count_(1), got_get_active_browser_count_(0), got_can_access_browser_count_(0) {} // CefExtensionHandler methods: void OnExtensionLoaded(CefRefPtr extension) override { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); EXPECT_FALSE(got_extension_loaded_); got_extension_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; if (create_main_browser_first_) CreateBrowserForExtensionIfReady(); else CreateBrowserForExtension(); } void OnExtensionUnloaded(CefRefPtr extension) override { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); EXPECT_TRUE(extension_); EXPECT_TRUE(extension_->IsSame(extension)); EXPECT_FALSE(got_extension_unloaded_); got_extension_unloaded_.yes(); extension_ = nullptr; // Execute asynchronously so call stacks have a chance to unwind. // Will close the browser windows. CefPostTask(TID_UI, base::Bind(&TabsTestHandler::DestroyTest, this)); } CefRefPtr GetActiveBrowser(CefRefPtr extension, CefRefPtr browser, bool include_incognito) override { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); EXPECT_TRUE(extension_); EXPECT_TRUE(extension_->IsSame(extension)); EXPECT_TRUE(main_browser_); EXPECT_LE(got_get_active_browser_count_, expected_api_call_count_); got_get_active_browser_count_++; // Tabs APIs will operate on the main browser. return main_browser_; } bool CanAccessBrowser(CefRefPtr extension, CefRefPtr browser, bool include_incognito, CefRefPtr target_browser) override { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); EXPECT_TRUE(extension_); EXPECT_TRUE(extension_->IsSame(extension)); EXPECT_TRUE(main_browser_); EXPECT_TRUE(main_browser_->IsSame(target_browser)); EXPECT_LE(got_can_access_browser_count_, expected_api_call_count_); got_can_access_browser_count_++; return true; } // CefLoadHandler methods: void OnLoadingStateChange(CefRefPtr browser, bool isLoading, bool canGoBack, bool canGoForward) override { if (isLoading) { // Keep a reference to both browsers. if (browser->GetHost()->GetExtension()) { EXPECT_FALSE(extension_browser_); extension_browser_ = browser; } else { EXPECT_FALSE(main_browser_); main_browser_ = browser; } } else { const std::string& url = browser->GetMainFrame()->GetURL(); if (browser->GetHost()->GetExtension()) { EXPECT_TRUE(browser->IsSame(extension_browser_)); EXPECT_STREQ(extension_url_.c_str(), url.c_str()); } else { EXPECT_TRUE(browser->IsSame(main_browser_)); EXPECT_STREQ(kMainBrowserURL, url.c_str()); } } } // CefResourceRequestHandler methods: CefRefPtr GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override { const std::string& url = request->GetURL(); if (url == kMainBrowserURL) { EXPECT_TRUE(browser->IsSame(main_browser_)); EXPECT_FALSE(got_main_url_request_); got_main_url_request_.yes(); } else if (url == extension_url_) { EXPECT_TRUE(browser->IsSame(extension_browser_)); EXPECT_FALSE(got_extension_url_request_); got_extension_url_request_.yes(); } // Handle the resource request. return RoutingTestHandler::GetResourceHandler(browser, frame, request); } protected: void OnAddMainBrowserResources() override { AddResource(kMainBrowserURL, GetMainBrowserHTML(), "text/html"); } void OnCreateMainBrowser() override { CreateBrowser(kMainBrowserURL, request_context()); } void OnLoadExtensions() override { LoadExtension(kExtensionPath, CreateManifest()); } bool OnMessage(CefRefPtr browser, const std::string& message) override { if (message == "main_onload") { // From body onLoad in the main browser. EXPECT_TRUE(browser->IsSame(main_browser_)); EXPECT_FALSE(got_main_body_onload_); got_main_body_onload_.yes(); if (create_main_browser_first_) CreateBrowserForExtensionIfReady(); TriggerTabsApiJSFunctionIfReady(); return true; } if (message == "extension_onload") { // From body onLoad in the extension browser. EXPECT_TRUE(browser->IsSame(extension_browser_)); EXPECT_FALSE(got_extension_body_onload_); got_extension_body_onload_.yes(); TriggerTabsApiJSFunctionIfReady(); return true; } // The success message usually orginates from the logic in // GetMainBrowserSuccessHEAD/BODY(). It may occasionally originate from the // extension browser if we don't know how to detect success in the main // browser. if (expect_success_in_main_browser_) { EXPECT_TRUE(browser->IsSame(main_browser_)); } else { 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 { main_browser_ = nullptr; extension_browser_ = nullptr; EXPECT_TRUE(got_extension_loaded_); EXPECT_TRUE(got_main_url_request_); EXPECT_TRUE(got_extension_url_request_); EXPECT_TRUE(got_main_body_onload_); EXPECT_TRUE(got_extension_body_onload_); EXPECT_TRUE(got_trigger_api_function_); EXPECT_TRUE(got_success_message_); EXPECT_TRUE(got_extension_unloaded_); if (expect_get_active_browser_) { EXPECT_EQ(expected_api_call_count_, got_get_active_browser_count_); EXPECT_EQ(0, got_can_access_browser_count_); } else { EXPECT_EQ(0, got_get_active_browser_count_); EXPECT_EQ(expected_api_call_count_, got_can_access_browser_count_); } } // Create a manifest that grants access to the tabs API. virtual CefRefPtr CreateManifest() const { ApiPermissionsList api_permissions; api_permissions.push_back("tabs"); 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 target tabId (null, or value >= 0). virtual std::string GetTargetTabId() const { return "null"; } // Returns the logic in the main browser that triggers on success. It should // execute GetMessageJS(kSuccessMessage). virtual std::string GetMainBrowserSuccessHEAD() const { return std::string(); } virtual std::string GetMainBrowserSuccessBODY() const { return std::string(); } // Returns the HTML that will be loaded in the main browser. virtual std::string GetMainBrowserHTML() const { return "" + GetMainBrowserSuccessHEAD() + "Main" + GetMainBrowserSuccessBODY() + ""; } // Returns the chrome.tabs.* JS that is executed in the extension browser // when the triggerTabsApi() JS function is called. virtual std::string GetTabsApiJS() const = 0; // Returns the JS that will be loaded in the extension browser. This // implements the triggerTabsApi() JS function called from // TriggerTabsApiJSFunction(). virtual std::string GetExtensionJS() const { return "function triggerTabsApi() {" + GetTabsApiJS() + "}"; } // 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(&TabsTestHandler::UnloadExtension, this, extension_)); } CefRefPtr extension() const { return extension_; } std::string extension_url() const { return extension_url_; } CefRefPtr main_browser() const { return main_browser_; } CefRefPtr extension_browser() const { return extension_browser_; } void set_create_main_browser_first(bool val) { create_main_browser_first_ = val; } void set_expect_get_active_browser(bool val) { expect_get_active_browser_ = val; } void set_expect_success_in_main_browser(bool val) { expect_success_in_main_browser_ = val; } void set_expected_api_call_count(int val) { expected_api_call_count_ = val; } bool got_success_message() const { return got_success_message_; } void set_got_success_message() { got_success_message_.yes(); } private: void CreateBrowserForExtensionIfReady() { DCHECK(create_main_browser_first_); if (extension_ && main_browser_) CreateBrowserForExtension(); } 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 TriggerTabsApiJSFunctionIfReady() { if (got_main_body_onload_ && got_extension_body_onload_) TriggerTabsApiJSFunction(); } void TriggerTabsApiJSFunction() { EXPECT_FALSE(got_trigger_api_function_); got_trigger_api_function_.yes(); extension_browser_->GetMainFrame()->ExecuteJavaScript("triggerTabsApi();", extension_url_, 0); } // If true the main browser will be created before the extension browser. // Otherwise the creation order is undefined. bool create_main_browser_first_; // If true we expect GetActiveBrowser() but not CanAccessBrowser() to be // called. Else visa-versa. bool expect_get_active_browser_; // If true we expect the success message to be delivered in the main browser. // Else expect it in the extension browser. bool expect_success_in_main_browser_; // Number of expected calls to GetActiveBrowser or CanAccessBrowser. This // should match the number of calls to chrome.tabs.* API functions in the // test. int expected_api_call_count_; CefRefPtr extension_; std::string extension_url_; CefRefPtr main_browser_; CefRefPtr extension_browser_; TrackCallback got_extension_loaded_; TrackCallback got_main_url_request_; TrackCallback got_extension_url_request_; TrackCallback got_main_body_onload_; TrackCallback got_extension_body_onload_; TrackCallback got_trigger_api_function_; TrackCallback got_success_message_; TrackCallback got_extension_unloaded_; int got_get_active_browser_count_; int got_can_access_browser_count_; }; // // chrome.tabs.create tests. // const char kCreateBrowserURL[] = "https://test-extensions.com/chrome-tabs-create"; const char kTabCallbackMessage[] = "tab-callback"; const int kCreateTabIndex = 2; // Class for chrome.tabs.create tests. class CreateTestHandler : public TabsTestHandler { public: explicit CreateTestHandler(RequestContextType request_context_type) : TabsTestHandler(request_context_type) {} bool OnBeforeBrowser(CefRefPtr extension, CefRefPtr browser, CefRefPtr active_browser, int index, const CefString& url, bool foreground, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings) override { EXPECT_TRUE(extension->IsSame(this->extension())); EXPECT_TRUE(browser->IsSame(extension_browser())); EXPECT_TRUE(active_browser->IsSame(main_browser())); EXPECT_EQ(kCreateTabIndex, index); EXPECT_STREQ(kCreateBrowserURL, url.ToString().c_str()); EXPECT_TRUE(foreground); EXPECT_TRUE(client); EXPECT_FALSE(got_on_before_browser_); got_on_before_browser_.yes(); return false; } void OnAddMainBrowserResources() override { AddResource(kCreateBrowserURL, GetCreatedBrowserHTML(), "text/html"); TabsTestHandler::OnAddMainBrowserResources(); } void OnLoadingStateChange(CefRefPtr browser, bool isLoading, bool canGoBack, bool canGoForward) override { if (extension_browser() && main_browser()) { if (isLoading) { // Keep a reference to the newly created browser. EXPECT_FALSE(created_browser_); created_browser_ = browser; return; } else { const std::string& url = browser->GetMainFrame()->GetURL(); if (url == kCreateBrowserURL) { EXPECT_TRUE(browser->IsSame(created_browser_)); return; } } } TabsTestHandler::OnLoadingStateChange(browser, isLoading, canGoBack, canGoForward); } CefRefPtr GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override { const std::string& url = request->GetURL(); if (url == kCreateBrowserURL) { EXPECT_TRUE(browser->IsSame(created_browser_)); EXPECT_FALSE(got_create_browser_url_request_); got_create_browser_url_request_.yes(); } return TabsTestHandler::GetResourceHandler(browser, frame, request); } protected: std::string GetTabsApiJS() const override { std::stringstream ss; ss << kCreateTabIndex; return "chrome.tabs.create({url: \"" + std::string(kCreateBrowserURL) + "\", index: " + ss.str() + "}, function(tab) { window.testQuery({request:'" + kTabCallbackMessage + ":' + JSON.stringify(tab)}); });"; } bool OnMessage(CefRefPtr browser, const std::string& message) override { if (message.find(kTabCallbackMessage) != std::string::npos) { EXPECT_TRUE(browser->IsSame(extension_browser())); EXPECT_FALSE(got_tab_callback_message_); got_tab_callback_message_.yes(); // Verify the contents of the Tab object. const std::string& json_str = message.substr(strlen(kTabCallbackMessage) + 1); CefRefPtr obj = CefParseJSON(json_str, JSON_PARSER_RFC); EXPECT_TRUE(obj); EXPECT_EQ(VTYPE_DICTIONARY, obj->GetType()); CefRefPtr dict = obj->GetDictionary(); int index = dict->GetInt("index"); EXPECT_EQ(kCreateTabIndex, index); int id = dict->GetInt("id"); int windowId = dict->GetInt("windowId"); EXPECT_EQ(created_browser_->GetIdentifier(), id); EXPECT_EQ(created_browser_->GetIdentifier(), windowId); const std::string& url = dict->GetString("url"); EXPECT_STREQ(kCreateBrowserURL, url.c_str()); TriggerDestroyTestIfReady(); return true; } else if (message == kSuccessMessage) { // Overriding default kSuccessMessage handling. EXPECT_TRUE(browser->IsSame(created_browser_)); EXPECT_FALSE(got_success_message()); set_got_success_message(); TriggerDestroyTestIfReady(); return true; } return TabsTestHandler::OnMessage(browser, message); } void OnDestroyTest() override { created_browser_ = nullptr; EXPECT_TRUE(got_on_before_browser_); EXPECT_TRUE(got_create_browser_url_request_); EXPECT_TRUE(got_tab_callback_message_); TabsTestHandler::OnDestroyTest(); } private: std::string GetCreatedBrowserHTML() { return "Created"; } void TriggerDestroyTestIfReady() { if (got_tab_callback_message_ && got_success_message()) TriggerDestroyTest(); } CefRefPtr created_browser_; TrackCallback got_on_before_browser_; TrackCallback got_create_browser_url_request_; TrackCallback got_tab_callback_message_; IMPLEMENT_REFCOUNTING(CreateTestHandler); DISALLOW_COPY_AND_ASSIGN(CreateTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(Create, CreateTestHandler) namespace { // // chrome.tabs.executeScript tests. // // Base class for chrome.tabs.executeScript tests. class ExecuteScriptTestHandler : public TabsTestHandler { public: explicit ExecuteScriptTestHandler(RequestContextType request_context_type) : TabsTestHandler(request_context_type) {} protected: std::string GetMainBrowserSuccessHEAD() const override { return ""; } // Returns the code that will be injected as a content script. virtual std::string GetContentScriptJS() const { // Execute the onTrigger() JS function. return "var s = document.createElement('script');" "s.textContent = 'onTrigger();';" "document.head.appendChild(s);"; } std::string GetTabsApiJS() const override { return "chrome.tabs.executeScript(" + GetTargetTabId() + ", {code:\"" + GetContentScriptJS() + "\"});"; } }; // Test for chrome.tabs.executeScript with a null tabId value. class ExecuteScriptNullTabTestHandler : public ExecuteScriptTestHandler { public: explicit ExecuteScriptNullTabTestHandler( RequestContextType request_context_type) : ExecuteScriptTestHandler(request_context_type) {} private: IMPLEMENT_REFCOUNTING(ExecuteScriptNullTabTestHandler); DISALLOW_COPY_AND_ASSIGN(ExecuteScriptNullTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(ExecuteScriptNullTab, ExecuteScriptNullTabTestHandler) namespace { // Test for chrome.tabs.executeScript with an explicit tabId value. class ExecuteScriptExplicitTabTestHandler : public ExecuteScriptTestHandler { public: explicit ExecuteScriptExplicitTabTestHandler( RequestContextType request_context_type) : ExecuteScriptTestHandler(request_context_type) { // Create the main browser first so we can retrieve the id. set_create_main_browser_first(true); // When a tabId is specified we should get a call to CanAccessBrowser // instead of GetActiveBrowser. set_expect_get_active_browser(false); } protected: std::string GetTargetTabId() const override { DCHECK(main_browser()); std::stringstream ss; ss << main_browser()->GetIdentifier(); return ss.str(); } private: IMPLEMENT_REFCOUNTING(ExecuteScriptExplicitTabTestHandler); DISALLOW_COPY_AND_ASSIGN(ExecuteScriptExplicitTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(ExecuteScriptExplicitTab, ExecuteScriptExplicitTabTestHandler) namespace { // Test for chrome.tabs.executeScript with a file argument loading a content // script. class ExecuteScriptFileTestHandler : public ExecuteScriptTestHandler { public: explicit ExecuteScriptFileTestHandler(RequestContextType request_context_type) : ExecuteScriptTestHandler(request_context_type) {} // CefExtensionHandler methods: bool GetExtensionResource( CefRefPtr extension, CefRefPtr browser, const CefString& file, CefRefPtr callback) override { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); EXPECT_TRUE(this->extension()); EXPECT_TRUE(this->extension()->IsSame(extension)); if (file == "script.js") { EXPECT_FALSE(got_get_extension_resource_); got_get_extension_resource_.yes(); const std::string& content = GetContentScriptJS(); CefRefPtr stream = CefStreamReader::CreateForData( const_cast(content.data()), content.size()); callback->Continue(stream); return true; } EXPECT_FALSE(true); // Not reached. return false; } protected: std::string GetTabsApiJS() const override { return "chrome.tabs.executeScript(" + GetTargetTabId() + ", {file:\"script.js\"});"; } void OnDestroyTest() override { ExecuteScriptTestHandler::OnDestroyTest(); EXPECT_TRUE(got_get_extension_resource_); } private: TrackCallback got_get_extension_resource_; IMPLEMENT_REFCOUNTING(ExecuteScriptFileTestHandler); DISALLOW_COPY_AND_ASSIGN(ExecuteScriptFileTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(ExecuteScriptFile, ExecuteScriptFileTestHandler) namespace { // Test for chrome.tabs.executeScript with a callback argument. class ExecuteScriptCallbackTestHandler : public ExecuteScriptTestHandler { public: explicit ExecuteScriptCallbackTestHandler( RequestContextType request_context_type) : ExecuteScriptTestHandler(request_context_type) {} protected: bool OnMessage(CefRefPtr browser, const std::string& message) override { if (message == "callback") { EXPECT_FALSE(got_callback_message_); got_callback_message_.yes(); EXPECT_TRUE(browser->IsSame(extension_browser())); TriggerDestroyTest(); return true; } return ExecuteScriptTestHandler::OnMessage(browser, message); } std::string GetTabsApiJS() const override { return "chrome.tabs.executeScript(" + GetTargetTabId() + ", {code:\"" + GetContentScriptJS() + "\"}, function(results) {" + GetMessageJS("callback") + "});"; } void TriggerDestroyTest() override { // Only destroy the test if we got both callbacks. if (got_callback_message_ && got_success_message()) { ExecuteScriptTestHandler::TriggerDestroyTest(); } } void OnDestroyTest() override { ExecuteScriptTestHandler::OnDestroyTest(); EXPECT_TRUE(got_callback_message_); } private: TrackCallback got_callback_message_; IMPLEMENT_REFCOUNTING(ExecuteScriptCallbackTestHandler); DISALLOW_COPY_AND_ASSIGN(ExecuteScriptCallbackTestHandler); }; } // namespace TABS_TEST_GROUP_MINIMAL(ExecuteScriptCallback, ExecuteScriptCallbackTestHandler) namespace { // Test for chrome.tabs.executeScript with execution occuring from a separate // resource script. class ExecuteScriptResourceTabTestHandler : public ExecuteScriptTestHandler { public: explicit ExecuteScriptResourceTabTestHandler( RequestContextType request_context_type) : ExecuteScriptTestHandler(request_context_type) {} // CefResourceRequestHandler methods: CefRefPtr GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override { const std::string& url = request->GetURL(); if (url == resource_url_) { EXPECT_TRUE(browser->IsSame(extension_browser())); EXPECT_FALSE(got_resource_url_request_); got_resource_url_request_.yes(); } return ExecuteScriptTestHandler::GetResourceHandler(browser, frame, request); } protected: void OnAddExtensionResources(const std::string& origin) override { ExecuteScriptTestHandler::OnAddExtensionResources(origin); resource_url_ = origin + "resource.js"; AddResource(resource_url_, GetExtensionJS(), "text/javascript"); } std::string GetExtensionHTML() const override { return "Extension"; } void OnDestroyTest() override { ExecuteScriptTestHandler::OnDestroyTest(); EXPECT_TRUE(got_resource_url_request_); } private: std::string resource_url_; TrackCallback got_resource_url_request_; IMPLEMENT_REFCOUNTING(ExecuteScriptResourceTabTestHandler); DISALLOW_COPY_AND_ASSIGN(ExecuteScriptResourceTabTestHandler); }; } // namespace TABS_TEST_GROUP_MINIMAL(ExecuteScriptResource, ExecuteScriptResourceTabTestHandler) // // chrome.tabs.insertCSS tests. // namespace { // Base class for chrome.tabs.insertCSS tests. class InsertCSSTestHandler : public TabsTestHandler { public: explicit InsertCSSTestHandler(RequestContextType request_context_type) : TabsTestHandler(request_context_type) {} protected: std::string GetMainBrowserSuccessBODY() const override { // We can't use a MutationObserver here because insertCSS does not modify // the style attribute. We could detect the change by tracking modifications // to document.styleSheets but that's complicated. Use a simple timer loop // implementation calling getComputedStyle instead. return ""; } // Returns the code that will be injected as a content script. virtual std::string GetContentScriptCSS() const { return "body{background-color:red}"; } std::string GetTabsApiJS() const override { return "chrome.tabs.insertCSS(" + GetTargetTabId() + ", {code:\"" + GetContentScriptCSS() + "\"});"; } }; // Test for chrome.tabs.insertCSS with a null tabId value. class InsertCSSNullTabTestHandler : public InsertCSSTestHandler { public: explicit InsertCSSNullTabTestHandler(RequestContextType request_context_type) : InsertCSSTestHandler(request_context_type) {} private: IMPLEMENT_REFCOUNTING(InsertCSSNullTabTestHandler); DISALLOW_COPY_AND_ASSIGN(InsertCSSNullTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(InsertCSSNullTab, InsertCSSNullTabTestHandler) namespace { // Test for chrome.tabs.insertCSS with an explicit tabId value. class InsertCSSExplicitTabTestHandler : public InsertCSSTestHandler { public: explicit InsertCSSExplicitTabTestHandler( RequestContextType request_context_type) : InsertCSSTestHandler(request_context_type) { // Create the main browser first so we can retrieve the id. set_create_main_browser_first(true); // When a tabId is specified we should get a call to CanAccessBrowser // instead of GetActiveBrowser. set_expect_get_active_browser(false); } protected: std::string GetTargetTabId() const override { DCHECK(main_browser()); std::stringstream ss; ss << main_browser()->GetIdentifier(); return ss.str(); } private: IMPLEMENT_REFCOUNTING(InsertCSSExplicitTabTestHandler); DISALLOW_COPY_AND_ASSIGN(InsertCSSExplicitTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(InsertCSSExplicitTab, InsertCSSExplicitTabTestHandler) namespace { // Test for chrome.tabs.insertCSS with a file argument loading a content // script. class InsertCSSFileTestHandler : public InsertCSSTestHandler { public: explicit InsertCSSFileTestHandler(RequestContextType request_context_type) : InsertCSSTestHandler(request_context_type) {} // CefExtensionHandler methods: bool GetExtensionResource( CefRefPtr extension, CefRefPtr browser, const CefString& file, CefRefPtr callback) override { EXPECT_TRUE(CefCurrentlyOn(TID_UI)); EXPECT_TRUE(this->extension()); EXPECT_TRUE(this->extension()->IsSame(extension)); if (file == "script.css") { EXPECT_FALSE(got_get_extension_resource_); got_get_extension_resource_.yes(); const std::string& content = GetContentScriptCSS(); CefRefPtr stream = CefStreamReader::CreateForData( const_cast(content.data()), content.size()); callback->Continue(stream); return true; } EXPECT_FALSE(true); // Not reached. return false; } protected: std::string GetTabsApiJS() const override { return "chrome.tabs.insertCSS(" + GetTargetTabId() + ", {file:\"script.css\"});"; } void OnDestroyTest() override { InsertCSSTestHandler::OnDestroyTest(); EXPECT_TRUE(got_get_extension_resource_); } private: TrackCallback got_get_extension_resource_; IMPLEMENT_REFCOUNTING(InsertCSSFileTestHandler); DISALLOW_COPY_AND_ASSIGN(InsertCSSFileTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(InsertCSSFile, InsertCSSFileTestHandler) namespace { // Test for chrome.tabs.insertCSS with a callback argument. class InsertCSSCallbackTestHandler : public InsertCSSTestHandler { public: explicit InsertCSSCallbackTestHandler(RequestContextType request_context_type) : InsertCSSTestHandler(request_context_type) {} protected: bool OnMessage(CefRefPtr browser, const std::string& message) override { if (message == "callback") { EXPECT_FALSE(got_callback_message_); got_callback_message_.yes(); EXPECT_TRUE(browser->IsSame(extension_browser())); TriggerDestroyTest(); return true; } return InsertCSSTestHandler::OnMessage(browser, message); } std::string GetTabsApiJS() const override { return "chrome.tabs.insertCSS(" + GetTargetTabId() + ", {code:\"" + GetContentScriptCSS() + "\"}, function(results) {" + GetMessageJS("callback") + "});"; } void TriggerDestroyTest() override { // Only destroy the test if we got both callbacks. if (got_callback_message_ && got_success_message()) { InsertCSSTestHandler::TriggerDestroyTest(); } } void OnDestroyTest() override { InsertCSSTestHandler::OnDestroyTest(); EXPECT_TRUE(got_callback_message_); } private: TrackCallback got_callback_message_; IMPLEMENT_REFCOUNTING(InsertCSSCallbackTestHandler); DISALLOW_COPY_AND_ASSIGN(InsertCSSCallbackTestHandler); }; } // namespace TABS_TEST_GROUP_MINIMAL(InsertCSSCallback, InsertCSSCallbackTestHandler) namespace { // Test for chrome.tabs.insertCSS with execution occuring from a separate // resource script. class InsertCSSResourceTabTestHandler : public InsertCSSTestHandler { public: explicit InsertCSSResourceTabTestHandler( RequestContextType request_context_type) : InsertCSSTestHandler(request_context_type) {} // CefResourceRequestHandler methods: CefRefPtr GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override { const std::string& url = request->GetURL(); if (url == resource_url_) { EXPECT_TRUE(browser->IsSame(extension_browser())); EXPECT_FALSE(got_resource_url_request_); got_resource_url_request_.yes(); } return InsertCSSTestHandler::GetResourceHandler(browser, frame, request); } protected: void OnAddExtensionResources(const std::string& origin) override { InsertCSSTestHandler::OnAddExtensionResources(origin); resource_url_ = origin + "resource.js"; AddResource(resource_url_, GetExtensionJS(), "text/javascript"); } std::string GetExtensionHTML() const override { return "Extension"; } void OnDestroyTest() override { InsertCSSTestHandler::OnDestroyTest(); EXPECT_TRUE(got_resource_url_request_); } private: std::string resource_url_; TrackCallback got_resource_url_request_; IMPLEMENT_REFCOUNTING(InsertCSSResourceTabTestHandler); DISALLOW_COPY_AND_ASSIGN(InsertCSSResourceTabTestHandler); }; } // namespace TABS_TEST_GROUP_MINIMAL(InsertCSSResource, InsertCSSResourceTabTestHandler) // // chrome.tabs.setZoom/getZoom tests. // namespace { // Base class for chrome.tabs.setZoom/getZoom tests. class ZoomTestHandler : public TabsTestHandler { public: explicit ZoomTestHandler(RequestContextType request_context_type) : TabsTestHandler(request_context_type) { // We call API functions three times in this handler. set_expected_api_call_count(3); } protected: std::string GetMainBrowserSuccessHEAD() const override { return ""; } std::string GetMainBrowserSuccessBODY() const override { // We can't directly detect zoom changes, so instead we look for changes // in window.innerWidth. return ""; } std::string GetTabsApiJS() const override { // Results in a change to window.innerWidth. return "chrome.tabs.setZoom(" + GetTargetTabId() + ", 2.0);"; } bool OnMessage(CefRefPtr browser, const std::string& message) override { if (message == "restored") { EXPECT_TRUE(browser->IsSame(extension_browser())); EXPECT_FALSE(got_restored_message_); got_restored_message_.yes(); // Destroy the test for real. TabsTestHandler::TriggerDestroyTest(); return true; } return TabsTestHandler::OnMessage(browser, message); } void TriggerDestroyTest() override { // Before destroying the test we need to restore the zoom factor so that // it doesn't persist in the RequestContext. This also tests the callback // argument and the getZoom function so there's no need to do that // separately. extension_browser()->GetMainFrame()->ExecuteJavaScript( "chrome.tabs.setZoom(" + GetTargetTabId() + ", 1.0, function() {" + "chrome.tabs.getZoom(" + GetTargetTabId() + ", function(zoomFactor) { if (zoomFactor == 1.0) {" + GetMessageJS("restored") + "}})});", extension_url(), 0); } void OnDestroyTest() override { TabsTestHandler::OnDestroyTest(); EXPECT_TRUE(got_restored_message_); } private: TrackCallback got_restored_message_; }; // Test for chrome.tabs.setZoom/getZoom with a null tabId value. class ZoomNullTabTestHandler : public ZoomTestHandler { public: explicit ZoomNullTabTestHandler(RequestContextType request_context_type) : ZoomTestHandler(request_context_type) {} private: IMPLEMENT_REFCOUNTING(ZoomNullTabTestHandler); DISALLOW_COPY_AND_ASSIGN(ZoomNullTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(ZoomNullTab, ZoomNullTabTestHandler) namespace { // Test for chrome.tabs.setZoom/getZoom with an explicit tabId value. class ZoomExplicitTabTestHandler : public ZoomTestHandler { public: explicit ZoomExplicitTabTestHandler(RequestContextType request_context_type) : ZoomTestHandler(request_context_type) { // Create the main browser first so we can retrieve the id. set_create_main_browser_first(true); // When a tabId is specified we should get a call to CanAccessBrowser // instead of GetActiveBrowser. set_expect_get_active_browser(false); } protected: std::string GetTargetTabId() const override { DCHECK(main_browser()); std::stringstream ss; ss << main_browser()->GetIdentifier(); return ss.str(); } private: IMPLEMENT_REFCOUNTING(ZoomExplicitTabTestHandler); DISALLOW_COPY_AND_ASSIGN(ZoomExplicitTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(ZoomExplicitTab, ZoomExplicitTabTestHandler) // // chrome.tabs.setZoomSettings/getZoomSettings tests. // namespace { // Base class for chrome.tabs.setZoomSettings/getZoomSettings tests. class ZoomSettingsTestHandler : public TabsTestHandler { public: explicit ZoomSettingsTestHandler(RequestContextType request_context_type) : TabsTestHandler(request_context_type) { // We call API functions two times in this handler. set_expected_api_call_count(2); // Success message will be delivered in the extension browser because we // don't know how to detect zoom settings changes in the main browser. set_expect_success_in_main_browser(false); } protected: std::string GetTabsApiJS() const override { // Set and restore the zoom settings. This also tests the callback argument // and the getZoomSettings function so there's no need to do that // separately. This is safe because zoom settings are not persisted in the // RequestContext across navigations. return "chrome.tabs.setZoomSettings(" + GetTargetTabId() + ", {mode: 'manual', scope: 'per-tab'}, function() {" + "chrome.tabs.getZoomSettings(" + GetTargetTabId() + ", function(zoomSettings) { if (zoomSettings.mode == 'manual' " "&& zoomSettings.scope == 'per-tab') {" + GetMessageJS(kSuccessMessage) + "}})});"; } }; // Test for chrome.tabs.setZoomSettings/getZoomSettings with a null tabId value. class ZoomSettingsNullTabTestHandler : public ZoomSettingsTestHandler { public: explicit ZoomSettingsNullTabTestHandler( RequestContextType request_context_type) : ZoomSettingsTestHandler(request_context_type) {} private: IMPLEMENT_REFCOUNTING(ZoomSettingsNullTabTestHandler); DISALLOW_COPY_AND_ASSIGN(ZoomSettingsNullTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(ZoomSettingsNullTab, ZoomSettingsNullTabTestHandler) namespace { // Test for chrome.tabs.setZoomSettings/getZoomSettings with an explicit tabId // value. class ZoomSettingsExplicitTabTestHandler : public ZoomSettingsTestHandler { public: explicit ZoomSettingsExplicitTabTestHandler( RequestContextType request_context_type) : ZoomSettingsTestHandler(request_context_type) { // Create the main browser first so we can retrieve the id. set_create_main_browser_first(true); // When a tabId is specified we should get a call to CanAccessBrowser // instead of GetActiveBrowser. set_expect_get_active_browser(false); } protected: std::string GetTargetTabId() const override { DCHECK(main_browser()); std::stringstream ss; ss << main_browser()->GetIdentifier(); return ss.str(); } private: IMPLEMENT_REFCOUNTING(ZoomSettingsExplicitTabTestHandler); DISALLOW_COPY_AND_ASSIGN(ZoomSettingsExplicitTabTestHandler); }; } // namespace TABS_TEST_GROUP_ALL(ZoomSettingsExplicitTab, ZoomSettingsExplicitTabTestHandler)