Commit 670a8b2a authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions] Add developerPrivate.installDroppedFile

Add a developerPrivate API function to install a file that was dragged
and dropped on the calling web contents, and add tests for the same.
This will allow us to move the MD extensions page away from using
chrome.send for this functionality.

Bug: None
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: Id2b4f5417a94c330c516d4eb1432c59c89c31e32
Reviewed-on: https://chromium-review.googlesource.com/981119Reviewed-by: default avatarIstiaque Ahmed <lazyboy@chromium.org>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547776}
parent 73b2804d
specific_include_rules = {
"developer_private_api_unittest.cc": [
# Allow the unittest to create a data_decoder service.
"+services/data_decoder"
],
}
......@@ -24,6 +24,8 @@
#include "chrome/browser/extensions/api/developer_private/entry_picker.h"
#include "chrome/browser/extensions/api/developer_private/extension_info_generator.h"
#include "chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.h"
#include "chrome/browser/extensions/chrome_zipfile_installer.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/devtools_util.h"
#include "chrome/browser/extensions/extension_commands_global_registry.h"
#include "chrome/browser/extensions/extension_service.h"
......@@ -58,6 +60,7 @@
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/drop_data.h"
#include "content/public/common/service_manager_connection.h"
#include "extensions/browser/api/file_handlers/app_file_handler_util.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
......@@ -73,6 +76,7 @@
#include "extensions/browser/notification_types.h"
#include "extensions/browser/path_util.h"
#include "extensions/browser/warning_service.h"
#include "extensions/browser/zipfile_installer.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/install_warning.h"
......@@ -80,6 +84,7 @@
#include "extensions/common/manifest_handlers/options_page_info.h"
#include "extensions/common/manifest_url_handlers.h"
#include "extensions/common/permissions/permissions_data.h"
#include "net/base/filename_util.h"
#include "storage/browser/blob/shareable_file_reference.h"
#include "storage/browser/fileapi/external_mount_points.h"
#include "storage/browser/fileapi/file_system_context.h"
......@@ -1089,6 +1094,52 @@ void DeveloperPrivateLoadUnpackedFunction::OnGotManifestError(
.ToValue()));
}
DeveloperPrivateInstallDroppedFileFunction::
DeveloperPrivateInstallDroppedFileFunction() = default;
DeveloperPrivateInstallDroppedFileFunction::
~DeveloperPrivateInstallDroppedFileFunction() = default;
ExtensionFunction::ResponseAction
DeveloperPrivateInstallDroppedFileFunction::Run() {
content::WebContents* web_contents = GetSenderWebContents();
if (!web_contents)
return RespondNow(Error(kCouldNotFindWebContentsError));
DeveloperPrivateAPI* api = DeveloperPrivateAPI::Get(browser_context());
base::FilePath path = api->GetDraggedPath(web_contents);
if (path.empty())
return RespondNow(Error("No dragged path"));
ExtensionService* service = GetExtensionService(browser_context());
if (path.MatchesExtension(FILE_PATH_LITERAL(".zip"))) {
ZipFileInstaller::Create(
content::ServiceManagerConnection::GetForProcess()->GetConnector(),
MakeRegisterInExtensionServiceCallback(service))
->LoadFromZipFile(path);
} else {
auto prompt = std::make_unique<ExtensionInstallPrompt>(web_contents);
scoped_refptr<CrxInstaller> crx_installer =
CrxInstaller::Create(service, std::move(prompt));
crx_installer->set_error_on_unsupported_requirements(true);
crx_installer->set_off_store_install_allow_reason(
CrxInstaller::OffStoreInstallAllowedFromSettingsPage);
crx_installer->set_install_immediately(true);
if (path.MatchesExtension(FILE_PATH_LITERAL(".user.js"))) {
crx_installer->InstallUserScript(path, net::FilePathToFileURL(path));
} else if (path.MatchesExtension(FILE_PATH_LITERAL(".crx"))) {
crx_installer->InstallCrx(path);
} else {
EXTENSION_FUNCTION_VALIDATE(false);
}
}
// TODO(devlin): We could optionally wait to return until we validate whether
// the load succeeded or failed. For now, that's unnecessary, and just adds
// complexity.
return RespondNow(NoArguments());
}
DeveloperPrivateNotifyDragInstallInProgressFunction::
DeveloperPrivateNotifyDragInstallInProgressFunction() = default;
DeveloperPrivateNotifyDragInstallInProgressFunction::
......
......@@ -512,6 +512,22 @@ class DeveloperPrivateLoadUnpackedFunction
DeveloperPrivateAPI::UnpackedRetryId retry_guid_;
};
class DeveloperPrivateInstallDroppedFileFunction
: public DeveloperPrivateAPIFunction {
public:
DECLARE_EXTENSION_FUNCTION("developerPrivate.installDroppedFile",
DEVELOPERPRIVATE_INSTALLDROPPEDFILE);
DeveloperPrivateInstallDroppedFileFunction();
private:
~DeveloperPrivateInstallDroppedFileFunction() override;
// ExtensionFunction:
ResponseAction Run() override;
DISALLOW_COPY_AND_ASSIGN(DeveloperPrivateInstallDroppedFileFunction);
};
class DeveloperPrivateNotifyDragInstallInProgressFunction
: public DeveloperPrivateAPIFunction {
public:
......
......@@ -35,17 +35,20 @@
#include "components/policy/core/common/policy_service_impl.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/services/unzip/unzip_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_dialog_auto_confirm.h"
#include "extensions/browser/extension_error_test_util.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/install/extension_install_ui.h"
#include "extensions/browser/mock_external_provider.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/extension.h"
......@@ -55,6 +58,9 @@
#include "extensions/common/manifest_constants.h"
#include "extensions/common/value_builder.h"
#include "extensions/test/test_extension_dir.h"
#include "services/data_decoder/data_decoder_service.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/cpp/test/test_connector_factory.h"
using testing::Return;
using testing::_;
......@@ -1220,6 +1226,120 @@ TEST_F(DeveloperPrivateApiUnitTest, LoadUnpackedFailsWithBlacklistingPolicy) {
EXPECT_THAT(error, testing::HasSubstr("policy"));
}
TEST_F(DeveloperPrivateApiUnitTest, InstallDroppedFileNoDraggedPath) {
extensions::ExtensionInstallUI::set_disable_ui_for_tests();
ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT);
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
scoped_refptr<UIThreadExtensionFunction> function =
base::MakeRefCounted<api::DeveloperPrivateInstallDroppedFileFunction>();
function->SetRenderFrameHost(web_contents->GetMainFrame());
TestExtensionRegistryObserver observer(registry());
EXPECT_EQ("No dragged path", api_test_utils::RunFunctionAndReturnError(
function.get(), "[]", profile()));
}
TEST_F(DeveloperPrivateApiUnitTest, InstallDroppedFileCrx) {
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "foo",
"version": "1.0",
"manifest_version": 2
})");
base::FilePath crx_path = test_dir.Pack();
extensions::ExtensionInstallUI::set_disable_ui_for_tests();
ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT);
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
DeveloperPrivateAPI::Get(profile())->SetDraggedPath(web_contents.get(),
crx_path);
scoped_refptr<UIThreadExtensionFunction> function =
base::MakeRefCounted<api::DeveloperPrivateInstallDroppedFileFunction>();
function->SetRenderFrameHost(web_contents->GetMainFrame());
TestExtensionRegistryObserver observer(registry());
ASSERT_TRUE(api_test_utils::RunFunction(function.get(), "[]", profile()))
<< function->GetError();
const Extension* extension = observer.WaitForExtensionInstalled();
ASSERT_TRUE(extension);
EXPECT_EQ("foo", extension->name());
}
TEST_F(DeveloperPrivateApiUnitTest, InstallDroppedFileUserScript) {
base::FilePath script_path =
data_dir().AppendASCII("user_script_basic.user.js");
extensions::ExtensionInstallUI::set_disable_ui_for_tests();
ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT);
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
DeveloperPrivateAPI::Get(profile())->SetDraggedPath(web_contents.get(),
script_path);
scoped_refptr<UIThreadExtensionFunction> function =
base::MakeRefCounted<api::DeveloperPrivateInstallDroppedFileFunction>();
function->SetRenderFrameHost(web_contents->GetMainFrame());
TestExtensionRegistryObserver observer(registry());
ASSERT_TRUE(api_test_utils::RunFunction(function.get(), "[]", profile()))
<< function->GetError();
const Extension* extension = observer.WaitForExtensionInstalled();
ASSERT_TRUE(extension);
EXPECT_EQ("My user script", extension->name());
}
class DeveloperPrivateZipInstallerUnitTest
: public DeveloperPrivateApiUnitTest {
public:
DeveloperPrivateZipInstallerUnitTest() {
service_manager::TestConnectorFactory::NameToServiceMap services;
services.insert(std::make_pair("data_decoder",
data_decoder::DataDecoderService::Create()));
services.insert(
std::make_pair("unzip_service", unzip::UnzipService::CreateService()));
test_connector_factory_ =
service_manager::TestConnectorFactory::CreateForServices(
std::move(services));
connector_ = test_connector_factory_->CreateConnector();
}
~DeveloperPrivateZipInstallerUnitTest() override {}
private:
std::unique_ptr<service_manager::TestConnectorFactory>
test_connector_factory_;
std::unique_ptr<service_manager::Connector> connector_;
DISALLOW_COPY_AND_ASSIGN(DeveloperPrivateZipInstallerUnitTest);
};
TEST_F(DeveloperPrivateZipInstallerUnitTest, InstallDroppedFileZip) {
base::FilePath zip_path = data_dir().AppendASCII("simple_empty.zip");
extensions::ExtensionInstallUI::set_disable_ui_for_tests();
ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT);
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
DeveloperPrivateAPI::Get(profile())->SetDraggedPath(web_contents.get(),
zip_path);
scoped_refptr<UIThreadExtensionFunction> function =
base::MakeRefCounted<api::DeveloperPrivateInstallDroppedFileFunction>();
function->SetRenderFrameHost(web_contents->GetMainFrame());
TestExtensionRegistryObserver observer(registry());
ASSERT_TRUE(api_test_utils::RunFunction(function.get(), "[]", profile()))
<< function->GetError();
const Extension* extension = observer.WaitForExtensionInstalled();
ASSERT_TRUE(extension);
EXPECT_EQ("Simple Empty Extension", extension->name());
}
class DeveloperPrivateApiSupervisedUserUnitTest
: public DeveloperPrivateApiUnitTest {
public:
......
......@@ -123,7 +123,7 @@ class ExtensionWebstorePrivateApiTest : public ExtensionApiTest {
// API functions.
host_resolver()->AddRule("www.example.com", "127.0.0.1");
ASSERT_TRUE(StartEmbeddedTestServer());
extensions::ExtensionInstallUI::set_disable_failure_ui_for_tests();
extensions::ExtensionInstallUI::set_disable_ui_for_tests();
auto_confirm_install_.reset(
new ScopedTestDialogAutoConfirm(ScopedTestDialogAutoConfirm::ACCEPT));
......
......@@ -38,4 +38,4 @@ ZipFileInstaller::DoneCallback MakeRegisterInExtensionServiceCallback(
service->AsWeakPtr());
}
} // namespace extensions
\ No newline at end of file
} // namespace extensions
......@@ -215,7 +215,7 @@ class WebstoreStartupInstallUnpackFailureTest
void SetUpInProcessBrowserTestFixture() override {
WebstoreStartupInstallerTest::SetUpInProcessBrowserTestFixture();
extensions::ExtensionInstallUI::set_disable_failure_ui_for_tests();
extensions::ExtensionInstallUI::set_disable_ui_for_tests();
}
};
......
......@@ -76,7 +76,7 @@ ExtensionInstallUIDefault::~ExtensionInstallUIDefault() {}
void ExtensionInstallUIDefault::OnInstallSuccess(const Extension* extension,
const SkBitmap* icon) {
if (skip_post_install_ui_ || extension->is_theme())
if (disable_ui_for_tests() || skip_post_install_ui_ || extension->is_theme())
return;
if (!profile_) {
......@@ -115,7 +115,7 @@ void ExtensionInstallUIDefault::OnInstallSuccess(const Extension* extension,
void ExtensionInstallUIDefault::OnInstallFailure(
const extensions::CrxInstallError& error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (disable_failure_ui_for_tests() || skip_post_install_ui_)
if (disable_ui_for_tests() || skip_post_install_ui_)
return;
Browser* browser = chrome::FindLastActiveWithProfile(profile_);
......
......@@ -562,6 +562,10 @@ namespace developerPrivate {
static void loadUnpacked(optional LoadUnpackedOptions options,
optional LoadErrorCallback callback);
// Installs the file that was dragged and dropped onto the associated
// page.
static void installDroppedFile(optional VoidCallback callback);
// Notifies the browser that a user began a drag in order to install an
// extension.
static void notifyDragInstallInProgress();
......
......@@ -1294,6 +1294,7 @@ enum HistogramValue {
DECLARATIVENETREQUEST_ADDWHITELISTEDPAGES,
DECLARATIVENETREQUEST_REMOVEWHITELISTEDPAGES,
DECLARATIVENETREQUEST_GETWHITELISTEDPAGES,
DEVELOPERPRIVATE_INSTALLDROPPEDFILE,
// Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
......
......@@ -7,7 +7,7 @@
namespace extensions {
// static
bool ExtensionInstallUI::disable_failure_ui_for_tests_ = false;
bool ExtensionInstallUI::disable_ui_for_tests_ = false;
ExtensionInstallUI::ExtensionInstallUI() {
}
......
......@@ -49,18 +49,14 @@ class ExtensionInstallUI {
virtual gfx::NativeWindow GetDefaultInstallDialogParent() = 0;
#if defined(UNIT_TEST)
static void set_disable_failure_ui_for_tests() {
disable_failure_ui_for_tests_ = true;
}
static void set_disable_ui_for_tests() { disable_ui_for_tests_ = true; }
#endif
protected:
static bool disable_failure_ui_for_tests() {
return disable_failure_ui_for_tests_;
}
static bool disable_ui_for_tests() { return disable_ui_for_tests_; }
private:
static bool disable_failure_ui_for_tests_;
static bool disable_ui_for_tests_;
DISALLOW_COPY_AND_ASSIGN(ExtensionInstallUI);
};
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Copyright 2018 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.
......@@ -679,6 +679,13 @@ chrome.developerPrivate.updateExtensionConfiguration = function(update, callback
*/
chrome.developerPrivate.loadUnpacked = function(options, callback) {};
/**
* Installs the file that was dragged and dropped onto the associated page.
* @param {function():void=} callback
* @see https://developer.chrome.com/extensions/developerPrivate#method-installDroppedFile
*/
chrome.developerPrivate.installDroppedFile = function(callback) {};
/**
* Notifies the browser that a user began a drag in order to install an
* extension.
......
......@@ -14882,6 +14882,7 @@ Called by update_net_error_codes.py.-->
<int value="1231" label="DECLARATIVENETREQUEST_ADDWHITELISTEDPAGES"/>
<int value="1232" label="DECLARATIVENETREQUEST_REMOVEWHITELISTEDPAGES"/>
<int value="1233" label="DECLARATIVENETREQUEST_GETWHITELISTEDPAGES"/>
<int value="1234" label="DEVELOPERPRIVATE_INSTALLDROPPEDFILE"/>
</enum>
<enum name="ExtensionIconState">
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment