blob: 11a4898f4b6441c67f8f9e623bd71e5d8c5759ac [file] [log] [blame]
// Copyright 2016 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 <memory>
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/lazy_background_page_test_util.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/api/file_system/file_system_api.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/switches.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
namespace extensions {
// And end-to-end test for extension APIs using native bindings.
class NativeBindingsApiTest : public ExtensionApiTest {
public:
NativeBindingsApiTest() {}
~NativeBindingsApiTest() override {}
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
extensions_features::kNativeCrxBindings);
ExtensionApiTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionApiTest::SetUpCommandLine(command_line);
// We whitelist the extension so that it can use the cast.streaming.* APIs,
// which are the only APIs that are prefixed twice.
command_line->AppendSwitchASCII(
switches::kWhitelistedExtensionID,
"ddchlicdkolnonkihahngkmmmjnjlkkf");
}
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(NativeBindingsApiTest);
};
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, SimpleEndToEndTest) {
embedded_test_server()->ServeFilesFromDirectory(test_data_dir_);
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionTest("native_bindings/extension")) << message_;
}
// A simplistic app test for app-specific APIs.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, SimpleAppTest) {
ExtensionTestMessageListener ready_listener("ready", true);
ASSERT_TRUE(RunPlatformAppTest("native_bindings/platform_app")) << message_;
ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
// On reply, the extension will try to close the app window and send a
// message.
ExtensionTestMessageListener close_listener(false);
ready_listener.Reply(std::string());
ASSERT_TRUE(close_listener.WaitUntilSatisfied());
EXPECT_EQ("success", close_listener.message());
}
// Tests the declarativeContent API and declarative events.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, DeclarativeEvents) {
embedded_test_server()->ServeFilesFromDirectory(test_data_dir_);
ASSERT_TRUE(StartEmbeddedTestServer());
// Load an extension. On load, this extension will a) run a few simple tests
// using chrome.test.runTests() and b) set up rules for declarative events for
// a browser-driven test. Wait for both the tests to finish and the extension
// to be ready.
ExtensionTestMessageListener listener("ready", false);
ResultCatcher catcher;
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("native_bindings/declarative_content"));
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
ASSERT_TRUE(extension);
ASSERT_TRUE(listener.WaitUntilSatisfied());
// The extension's page action should currently be hidden.
ExtensionAction* page_action =
ExtensionActionManager::Get(profile())->GetPageAction(*extension);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int tab_id = SessionTabHelper::IdForTab(web_contents).id();
EXPECT_FALSE(page_action->GetIsVisible(tab_id));
EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
// Navigating to example.com should show the page action.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"example.com", "/native_bindings/simple.html"));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(page_action->GetIsVisible(tab_id));
EXPECT_FALSE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
// And the extension should be notified of the click.
ExtensionTestMessageListener clicked_listener("clicked and removed", false);
ExtensionActionAPI::Get(profile())->DispatchExtensionActionClicked(
*page_action, web_contents, extension);
ASSERT_TRUE(clicked_listener.WaitUntilSatisfied());
}
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, LazyListeners) {
ProcessManager::SetEventPageIdleTimeForTesting(1);
ProcessManager::SetEventPageSuspendingTimeForTesting(1);
LazyBackgroundObserver background_page_done;
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("native_bindings/lazy_listeners"));
ASSERT_TRUE(extension);
background_page_done.Wait();
EventRouter* event_router = EventRouter::Get(profile());
EXPECT_TRUE(event_router->ExtensionHasEventListener(extension->id(),
"tabs.onCreated"));
}
// End-to-end test for the fileSystem API, which includes parameters with
// instance-of requirements and a post-validation argument updater that violates
// the schema.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, FileSystemApiGetDisplayPath) {
base::FilePath test_dir = test_data_dir_.AppendASCII("native_bindings");
FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
"test_root", test_dir);
base::FilePath test_file = test_dir.AppendASCII("text.txt");
FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
&test_file);
ASSERT_TRUE(RunPlatformAppTest("native_bindings/instance_of")) << message_;
}
// Tests the webRequest API, which requires IO thread requests and custom
// events.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, WebRequest) {
embedded_test_server()->ServeFilesFromDirectory(test_data_dir_);
ASSERT_TRUE(StartEmbeddedTestServer());
// Load an extension and wait for it to be ready.
ResultCatcher catcher;
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("native_bindings/web_request"));
ASSERT_TRUE(extension);
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"example.com", "/native_bindings/simple.html"));
GURL expected_url = embedded_test_server()->GetURL(
"example.com", "/native_bindings/simple2.html");
EXPECT_EQ(expected_url, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
}
// Tests the context menu API, which includes calling sendRequest with an
// different signature than specified and using functions as properties on an
// object.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, ContextMenusTest) {
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "Context menus",
"manifest_version": 2,
"version": "0.1",
"permissions": ["contextMenus"],
"background": {
"scripts": ["background.js"]
}
})");
test_dir.WriteFile(
FILE_PATH_LITERAL("background.js"),
R"(chrome.contextMenus.create(
{
title: 'Context Menu Item',
onclick: () => { chrome.test.sendMessage('clicked'); },
}, () => { chrome.test.sendMessage('registered'); });)");
const Extension* extension = nullptr;
{
ExtensionTestMessageListener listener("registered", false);
extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
EXPECT_TRUE(listener.WaitUntilSatisfied());
}
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::unique_ptr<TestRenderViewContextMenu> menu(
TestRenderViewContextMenu::Create(
web_contents, GURL("https://www.example.com"), GURL(), GURL()));
ExtensionTestMessageListener listener("clicked", false);
int command_id = ContextMenuMatcher::ConvertToExtensionsCustomCommandId(0);
EXPECT_TRUE(menu->IsCommandIdEnabled(command_id));
menu->ExecuteCommand(command_id, 0);
EXPECT_TRUE(listener.WaitUntilSatisfied());
}
// Tests that unchecked errors don't impede future calls.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, ErrorsInCallbackTest) {
embedded_test_server()->ServeFilesFromDirectory(test_data_dir_);
ASSERT_TRUE(StartEmbeddedTestServer());
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "Errors In Callback",
"manifest_version": 2,
"version": "0.1",
"permissions": ["contextMenus"],
"background": {
"scripts": ["background.js"]
}
})");
test_dir.WriteFile(
FILE_PATH_LITERAL("background.js"),
R"(chrome.tabs.query({}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {code: 'x'}, function() {
// There's an error here (we don't have permission to access the
// host), but we don't check it so that it gets surfaced as an
// unchecked runtime.lastError.
// We should still be able to invoke other APIs and get correct
// callbacks.
chrome.tabs.query({}, function(tabs) {
chrome.tabs.query({}, function(tabs) {
chrome.test.sendMessage('callback');
});
});
});
});)");
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"example.com", "/native_bindings/simple.html"));
ExtensionTestMessageListener listener("callback", false);
ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
EXPECT_TRUE(listener.WaitUntilSatisfied());
}
// Tests that bindings are available in WebUI pages.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, WebUIBindings) {
ui_test_utils::NavigateToURL(browser(), GURL("chrome://extensions"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto api_exists = [web_contents](const std::string& api_name) {
bool exists = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
web_contents,
base::StringPrintf("window.domAutomationController.send(!!%s);",
api_name.c_str()),
&exists));
return exists;
};
EXPECT_TRUE(api_exists("chrome.developerPrivate"));
EXPECT_TRUE(api_exists("chrome.developerPrivate.getProfileConfiguration"));
EXPECT_TRUE(api_exists("chrome.management"));
EXPECT_TRUE(api_exists("chrome.management.setEnabled"));
EXPECT_FALSE(api_exists("chrome.networkingPrivate"));
EXPECT_FALSE(api_exists("chrome.sockets"));
EXPECT_FALSE(api_exists("chrome.browserAction"));
}
// Tests creating an API from a context that hasn't been initialized yet
// by doing so in a parent frame. Regression test for https://crbug.com/819968.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, APICreationFromNewContext) {
embedded_test_server()->ServeFilesFromDirectory(test_data_dir_);
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionTest("native_bindings/context_initialization"))
<< message_;
}
} // namespace extensions