[go: nahoru, domu]

blob: cc88864b0bd0274a620238a67f6a7aa70b7e2513 [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/strings/stringprintf.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.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/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/test/browser_test.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/extension_action.h"
#include "extensions/browser/extension_action_manager.h"
#include "extensions/browser/process_manager.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 SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionApiTest::SetUpCommandLine(command_line);
// We allowlist the extension so that it can use the cast.streaming.* APIs,
// which are the only APIs that are prefixed twice.
command_line->AppendSwitchASCII(switches::kAllowlistedExtensionID,
"ddchlicdkolnonkihahngkmmmjnjlkkf");
}
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
private:
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(RunExtensionTest("native_bindings/platform_app",
{.launch_as_platform_app = true}))
<< 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* action =
ExtensionActionManager::Get(profile())->GetExtensionAction(*extension);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
EXPECT_FALSE(action->GetIsVisible(tab_id));
EXPECT_TRUE(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(action->GetIsVisible(tab_id));
EXPECT_FALSE(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(
*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 picker(
test_file);
ASSERT_TRUE(RunExtensionTest("native_bindings/instance_of",
{.launch_as_platform_app = true}))
<< 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_;
}
// End-to-end test for promise support on bindings for MV3 extensions, using a
// few tabs APIs. Also ensures callbacks still work for the API as expected.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, PromiseBasedAPI) {
ASSERT_TRUE(StartEmbeddedTestServer());
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "Promises",
"manifest_version": 3,
"version": "0.1",
"background": {
"service_worker": "background.js"
},
"permissions": ["tabs"]
})");
constexpr char kBackgroundJs[] =
R"(let tabIdExample;
let tabIdGoogle;
chrome.test.getConfig((config) => {
let exampleUrl = `https://example.com:${config.testServer.port}/`;
let googleUrl = `https://google.com:${config.testServer.port}/`
chrome.test.runTests([
function createNewTabPromise() {
let promise = chrome.tabs.create({url: exampleUrl});
chrome.test.assertNoLastError();
chrome.test.assertTrue(promise instanceof Promise);
promise.then((tab) => {
let url = tab.pendingUrl;
chrome.test.assertEq(exampleUrl, url);
tabIdExample = tab.id;
chrome.test.assertNoLastError();
chrome.test.succeed();
});
},
function queryTabPromise() {
let promise = chrome.tabs.query({url: exampleUrl});
chrome.test.assertNoLastError();
chrome.test.assertTrue(promise instanceof Promise);
promise.then((tabs) => {
chrome.test.assertTrue(tabs instanceof Array);
chrome.test.assertEq(1, tabs.length);
chrome.test.assertEq(tabIdExample, tabs[0].id);
chrome.test.assertNoLastError();
chrome.test.succeed();
});
},
function createNewTabCallback() {
chrome.tabs.create({url: googleUrl}, (tab) => {
let url = tab.pendingUrl;
chrome.test.assertEq(googleUrl, url);
tabIdGoogle = tab.id;
chrome.test.assertNoLastError();
chrome.test.succeed();
});
},
function queryTabCallback() {
chrome.tabs.query({url: googleUrl}, (tabs) => {
chrome.test.assertTrue(tabs instanceof Array);
chrome.test.assertEq(1, tabs.length);
chrome.test.assertEq(tabIdGoogle, tabs[0].id);
chrome.test.assertNoLastError();
chrome.test.succeed();
});
}
]);
});)";
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
ResultCatcher catcher;
ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
}
// Tests that calling an API which supports promises using an MV2 extension does
// not get a promise based return and still needs to use callbacks when
// required.
IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, MV2PromisesNotSupported) {
ASSERT_TRUE(StartEmbeddedTestServer());
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "Promises",
"manifest_version": 2,
"version": "0.1",
"background": {
"scripts": ["background.js"]
},
"permissions": ["tabs"]
})");
constexpr char kBackgroundJs[] =
R"(let tabIdGooge;
chrome.test.getConfig((config) => {
let exampleUrl = `https://example.com:${config.testServer.port}/`;
let googleUrl = `https://google.com:${config.testServer.port}/`
chrome.test.runTests([
function createNewTabPromise() {
let result = chrome.tabs.create({url: exampleUrl});
chrome.test.assertEq(undefined, result);
chrome.test.assertNoLastError();
chrome.test.succeed();
},
function queryTabPromise() {
let expectedError = 'Error in invocation of tabs.query(object ' +
'queryInfo, function callback): No matching signature.';
chrome.test.assertThrows(chrome.tabs.query,
[{url: exampleUrl}],
expectedError);
chrome.test.succeed();
},
function createNewTabCallback() {
chrome.tabs.create({url: googleUrl}, (tab) => {
let url = tab.pendingUrl;
chrome.test.assertEq(googleUrl, url);
tabIdGoogle = tab.id;
chrome.test.assertNoLastError();
chrome.test.succeed();
});
},
function queryTabCallback() {
chrome.tabs.query({url: googleUrl}, (tabs) => {
chrome.test.assertTrue(tabs instanceof Array);
chrome.test.assertEq(1, tabs.length);
chrome.test.assertEq(tabIdGoogle, tabs[0].id);
chrome.test.assertNoLastError();
chrome.test.succeed();
});
}
]);
});)";
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
ResultCatcher catcher;
ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
}
} // namespace extensions