Commit f581c262 authored by Gavin Williams's avatar Gavin Williams Committed by Commit Bot

scanning: Create WebUI handler for opening select dialog

Handler takes requests from the Scanning UI to open the select dialog.

Bug: 1059779
Change-Id: Id98cdf013a4e020b3055061aac7c9dd945fe9b83
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2497423Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarJesse Schettler <jschettler@chromium.org>
Reviewed-by: default avatarZentaro Kavanagh <zentaro@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Commit-Queue: Gavin Williams <gavinwill@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821753}
parent b8cd37f0
......@@ -26,6 +26,7 @@
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/search/ntp_features.h"
#include "chrome/browser/search/suggestions/suggestions_ui.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/browser/ui/webui/about_ui.h"
#include "chrome/browser/ui/webui/autofill_and_password_manager_internals/autofill_internals_ui.h"
#include "chrome/browser/ui/webui/autofill_and_password_manager_internals/password_manager_internals_ui.h"
......@@ -385,12 +386,17 @@ void BindScanService(
service->BindInterface(std::move(pending_receiver));
}
std::unique_ptr<ui::SelectFilePolicy> CreateChromeSelectFilePolicy(
content::WebContents* web_contents) {
return std::make_unique<ChromeSelectFilePolicy>(web_contents);
}
template <>
WebUIController* NewWebUI<chromeos::ScanningUI>(WebUI* web_ui,
const GURL& url) {
return new chromeos::ScanningUI(
web_ui,
base::BindRepeating(&BindScanService, Profile::FromWebUI(web_ui)));
web_ui, base::BindRepeating(&BindScanService, Profile::FromWebUI(web_ui)),
base::BindRepeating(&CreateChromeSelectFilePolicy));
}
void BindMultiDeviceSetup(
......
......@@ -28,6 +28,7 @@ test("chromeos_components_unittests") {
"//chromeos/components/power:unit_tests",
"//chromeos/components/proximity_auth:unit_tests",
"//chromeos/components/quick_answers:unit_tests",
"//chromeos/components/scanning:unit_tests",
"//chromeos/components/security_token_pin:unit_tests",
"//chromeos/components/sensors:unit_tests",
"//chromeos/components/smbfs:unit_tests",
......
......@@ -2,10 +2,14 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//testing/test.gni")
assert(is_chromeos, "Scanning App is Chrome OS only")
static_library("scanning") {
sources = [
"scanning_handler.cc",
"scanning_handler.h",
"scanning_ui.cc",
"scanning_ui.h",
"url_constants.cc",
......@@ -19,7 +23,9 @@ static_library("scanning") {
"//chromeos/strings/",
"//content/public/browser",
"//ui/base",
"//ui/gfx",
"//ui/resources",
"//ui/shell_dialogs",
"//ui/webui",
]
}
......@@ -27,3 +33,17 @@ static_library("scanning") {
group("closure_compile") {
deps = [ "resources:closure_compile_module" ]
}
source_set("unit_tests") {
testonly = true
sources = [ "scanning_handler_unittest.cc" ]
deps = [
":scanning",
"//base",
"//content/test:test_support",
"//testing/gtest",
"//ui/shell_dialogs",
]
}
......@@ -3,7 +3,10 @@ include_rules = [
"+chromeos/grit/chromeos_scanning_app_resources.h",
"+chromeos/strings/grit/chromeos_strings.h",
"+content/public/browser",
"+content/public/test",
"+ui/base",
"+ui/gfx",
"+ui/resources",
"+ui/shell_dialogs",
"+ui/webui",
]
// Copyright 2020 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 "chromeos/components/scanning/scanning_handler.h"
#include "base/strings/string16.h"
#include "base/values.h"
#include "content/public/browser/web_contents.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/shell_dialogs/select_file_policy.h"
namespace {
constexpr char kBaseName[] = "baseName";
constexpr char kFilePath[] = "filePath";
// Uses the full filepath and the base directory (lowest level directory in the
// filepath, used to display in the UI) to create a Value object to return to
// the Scanning UI.
base::Value CreateSelectedPathValue(const base::FilePath& path) {
base::Value selected_path(base::Value::Type::DICTIONARY);
selected_path.SetStringKey(kBaseName, path.BaseName().value());
selected_path.SetStringKey(kFilePath, path.value());
return selected_path;
}
} // namespace
namespace chromeos {
ScanningHandler::ScanningHandler(
const SelectFilePolicyCreator& select_file_policy_creator)
: select_file_policy_creator_(select_file_policy_creator) {}
ScanningHandler::~ScanningHandler() = default;
void ScanningHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"initialize", base::BindRepeating(&ScanningHandler::HandleInitialize,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"requestScanToLocation",
base::BindRepeating(&ScanningHandler::HandleRequestScanToLocation,
base::Unretained(this)));
}
void ScanningHandler::HandleInitialize(const base::ListValue* args) {
DCHECK(args && args->empty());
AllowJavascript();
}
void ScanningHandler::HandleRequestScanToLocation(const base::ListValue* args) {
CHECK_EQ(1U, args->GetSize());
scan_location_callback_id_ = args->GetList()[0].GetString();
content::WebContents* web_contents = web_ui()->GetWebContents();
gfx::NativeWindow owning_window =
web_contents ? web_contents->GetTopLevelNativeWindow()
: gfx::kNullNativeWindow;
select_file_dialog_ = ui::SelectFileDialog::Create(
this, select_file_policy_creator_.Run(web_contents));
select_file_dialog_->SelectFile(
ui::SelectFileDialog::SELECT_FOLDER, base::string16() /* title */,
base::FilePath() /* default_path */, nullptr /* file_types */,
0 /* file_type_index */,
base::FilePath::StringType() /* default_extension */, owning_window,
nullptr /* params */);
}
void ScanningHandler::FileSelected(const base::FilePath& path,
int index,
void* params) {
if (IsJavascriptAllowed()) {
ResolveJavascriptCallback(base::Value(scan_location_callback_id_),
CreateSelectedPathValue(path));
}
}
void ScanningHandler::FileSelectionCanceled(void* params) {
if (IsJavascriptAllowed()) {
ResolveJavascriptCallback(base::Value(scan_location_callback_id_),
CreateSelectedPathValue(base::FilePath()));
}
}
void ScanningHandler::SetWebUIForTest(content::WebUI* web_ui) {
set_web_ui(web_ui);
}
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROMEOS_COMPONENTS_SCANNING_SCANNING_HANDLER_H_
#define CHROMEOS_COMPONENTS_SCANNING_SCANNING_HANDLER_H_
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_policy.h"
namespace base {
class ListValue;
} // namespace base
namespace content {
class WebContents;
} // namespace content
namespace chromeos {
// ChromeOS Scanning app UI handler.
class ScanningHandler : public content::WebUIMessageHandler,
public ui::SelectFileDialog::Listener {
public:
using SelectFilePolicyCreator =
base::RepeatingCallback<std::unique_ptr<ui::SelectFilePolicy>(
content::WebContents*)>;
explicit ScanningHandler(
const SelectFilePolicyCreator& select_file_policy_creator);
~ScanningHandler() override;
ScanningHandler(const ScanningHandler&) = delete;
ScanningHandler& operator=(const ScanningHandler&) = delete;
// WebUIMessageHandler:
void RegisterMessages() override;
// SelectFileDialog::Listener:
void FileSelected(const base::FilePath& path,
int index,
void* params) override;
void FileSelectionCanceled(void* params) override;
void SetWebUIForTest(content::WebUI* web_ui);
private:
// Initializes Javascript.
void HandleInitialize(const base::ListValue* args);
// Opens the select dialog for the user to choose the directory to save
// completed scans.
void HandleRequestScanToLocation(const base::ListValue* args);
SelectFilePolicyCreator select_file_policy_creator_;
std::string scan_location_callback_id_;
scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
};
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_SCANNING_SCANNING_HANDLER_H_
// Copyright 2020 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 "chromeos/components/scanning/scanning_handler.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/strings/string16.h"
#include "base/values.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_web_ui.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_dialog_factory.h"
#include "ui/shell_dialogs/select_file_policy.h"
namespace {
constexpr char kTestDirectory[] = "/this/is/a/test/directory/Base Name";
} // namespace
namespace chromeos {
class TestSelectFilePolicy : public ui::SelectFilePolicy {
public:
TestSelectFilePolicy& operator=(const TestSelectFilePolicy&) = delete;
bool CanOpenSelectFileDialog() override { return true; }
void SelectFileDenied() override {}
};
std::unique_ptr<ui::SelectFilePolicy> CreateTestSelectFilePolicy(
content::WebContents* web_contents) {
return std::make_unique<TestSelectFilePolicy>();
}
// A fake ui::SelectFileDialog.
class FakeSelectFileDialog : public ui::SelectFileDialog {
public:
FakeSelectFileDialog(Listener* listener,
std::unique_ptr<ui::SelectFilePolicy> policy,
bool is_cancel)
: ui::SelectFileDialog(listener, std::move(policy)),
is_cancel_(is_cancel) {}
FakeSelectFileDialog(const FakeSelectFileDialog&) = delete;
FakeSelectFileDialog& operator=(const FakeSelectFileDialog&) = delete;
protected:
void SelectFileImpl(Type type,
const base::string16& title,
const base::FilePath& default_path,
const FileTypeInfo* file_types,
int file_type_index,
const base::FilePath::StringType& default_extension,
gfx::NativeWindow owning_window,
void* params) override {
if (is_cancel_) {
listener_->FileSelectionCanceled(params);
return;
}
const base::FilePath file_path(FILE_PATH_LITERAL(kTestDirectory));
listener_->FileSelected(file_path, 0 /* index */, nullptr /* params */);
}
bool IsRunning(gfx::NativeWindow owning_window) const override {
return true;
}
void ListenerDestroyed() override {}
bool HasMultipleFileTypeChoicesImpl() override { return false; }
private:
~FakeSelectFileDialog() override = default;
// Determines if directory is chosen or dialog is canceled.
bool is_cancel_;
};
// A factory associated with the artificial file picker.
class TestSelectFileDialogFactory : public ui::SelectFileDialogFactory {
public:
explicit TestSelectFileDialogFactory(bool is_cancel)
: is_cancel_(is_cancel) {}
ui::SelectFileDialog* Create(
ui::SelectFileDialog::Listener* listener,
std::unique_ptr<ui::SelectFilePolicy> policy) override {
return new FakeSelectFileDialog(listener, std::move(policy), is_cancel_);
}
TestSelectFileDialogFactory(const TestSelectFileDialogFactory&) = delete;
TestSelectFileDialogFactory& operator=(const TestSelectFileDialogFactory&) =
delete;
private:
// Determines if directory is chosen or dialog is canceled.
bool is_cancel_;
};
class ScanningHandlerTest : public testing::Test {
public:
ScanningHandlerTest()
: task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
web_ui_(),
scanning_handler_() {}
~ScanningHandlerTest() override = default;
void SetUp() override {
scanning_handler_ = std::make_unique<ScanningHandler>(
base::BindRepeating(&CreateTestSelectFilePolicy));
scanning_handler_->SetWebUIForTest(&web_ui_);
scanning_handler_->RegisterMessages();
base::ListValue args;
web_ui_.HandleReceivedMessage("initialize", &args);
}
void TearDown() override { ui::SelectFileDialog::SetFactory(nullptr); }
const content::TestWebUI::CallData& CallDataAtIndex(size_t index) {
return *web_ui_.call_data()[index];
}
protected:
content::BrowserTaskEnvironment task_environment_;
content::TestWebUI web_ui_;
std::unique_ptr<ScanningHandler> scanning_handler_;
};
// Validates that invoking the requestScanToLocation Web UI event opens the
// select dialog, and if a directory is chosen, returns the selected file path
// and base name.
TEST_F(ScanningHandlerTest, SelectDirectory) {
ui::SelectFileDialog::SetFactory(
new TestSelectFileDialogFactory(false /* is_cancel */));
const size_t call_data_count_before_call = web_ui_.call_data().size();
base::ListValue args;
args.Append("handlerFunctionName");
web_ui_.HandleReceivedMessage("requestScanToLocation", &args);
EXPECT_EQ(call_data_count_before_call + 1u, web_ui_.call_data().size());
const content::TestWebUI::CallData& call_data =
CallDataAtIndex(call_data_count_before_call);
EXPECT_EQ("cr.webUIResponse", call_data.function_name());
EXPECT_EQ("handlerFunctionName", call_data.arg1()->GetString());
EXPECT_TRUE(call_data.arg2()->GetBool());
const base::DictionaryValue* selected_path_dict;
EXPECT_TRUE(call_data.arg3()->GetAsDictionary(&selected_path_dict));
EXPECT_EQ(kTestDirectory, *selected_path_dict->FindStringPath("filePath"));
EXPECT_EQ("Base Name", *selected_path_dict->FindStringPath("baseName"));
}
// Validates that invoking the requestScanToLocation Web UI event opens the
// select dialog, and if the dialog is canceled, returns an empty file path and
// base name.
TEST_F(ScanningHandlerTest, CancelDialog) {
ui::SelectFileDialog::SetFactory(
new TestSelectFileDialogFactory(true /* is_cancel */));
const size_t call_data_count_before_call = web_ui_.call_data().size();
base::ListValue args;
args.Append("handlerFunctionName");
web_ui_.HandleReceivedMessage("requestScanToLocation", &args);
EXPECT_EQ(call_data_count_before_call + 1u, web_ui_.call_data().size());
const content::TestWebUI::CallData& call_data =
CallDataAtIndex(call_data_count_before_call);
EXPECT_EQ("cr.webUIResponse", call_data.function_name());
EXPECT_EQ("handlerFunctionName", call_data.arg1()->GetString());
EXPECT_TRUE(call_data.arg2()->GetBool());
const base::DictionaryValue* selected_path_dict;
EXPECT_TRUE(call_data.arg3()->GetAsDictionary(&selected_path_dict));
EXPECT_EQ("", *selected_path_dict->FindStringPath("filePath"));
EXPECT_EQ("", *selected_path_dict->FindStringPath("baseName"));
}
} // namespace chromeos.
......@@ -4,6 +4,7 @@
#include "chromeos/components/scanning/scanning_ui.h"
#include <memory>
#include <string>
#include "base/containers/span.h"
......@@ -69,8 +70,11 @@ void AddScanningAppStrings(content::WebUIDataSource* html_source) {
} // namespace
ScanningUI::ScanningUI(content::WebUI* web_ui, BindScanServiceCallback callback)
: ui::MojoWebUIController(web_ui),
ScanningUI::ScanningUI(
content::WebUI* web_ui,
BindScanServiceCallback callback,
const ScanningHandler::SelectFilePolicyCreator& select_file_policy_creator)
: ui::MojoWebUIController(web_ui, true /* enable_chrome_send */),
bind_pending_receiver_callback_(std::move(callback)) {
auto html_source = base::WrapUnique(
content::WebUIDataSource::Create(kChromeUIScanningAppHost));
......@@ -89,6 +93,8 @@ ScanningUI::ScanningUI(content::WebUI* web_ui, BindScanServiceCallback callback)
AddScanningAppStrings(html_source.get());
web_ui->AddMessageHandler(
std::make_unique<ScanningHandler>(select_file_policy_creator));
content::WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
html_source.release());
}
......
......@@ -7,6 +7,7 @@
#include "base/callback.h"
#include "chromeos/components/scanning/mojom/scanning.mojom-forward.h"
#include "chromeos/components/scanning/scanning_handler.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "ui/webui/mojo_web_ui_controller.h"
......@@ -24,7 +25,10 @@ class ScanningUI : public ui::MojoWebUIController {
// |callback| should bind the pending receiver to an implementation of
// chromeos::scanning::mojom::ScanService.
ScanningUI(content::WebUI* web_ui, BindScanServiceCallback callback);
ScanningUI(content::WebUI* web_ui,
BindScanServiceCallback callback,
const ScanningHandler::SelectFilePolicyCreator&
select_file_policy_creator);
~ScanningUI() override;
ScanningUI(const ScanningUI&) = delete;
......
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