Skip to content

Commit

Permalink
documentScan: Add DocumentScanAPIHandler
Browse files Browse the repository at this point in the history
The existing documentScan.scan function is implemented directly in the
API function class because there was only a single function in the
documentScan API.  New functions are going to be added that need to talk
to the same DocumentScan service, so introduce a separate
DocumentScanAPIHandler class and move the existing logic into it.

Because the additional scanning functions are going to have requirements
similar to the chrome.printing API, the new class is modeled closely
after PrintingAPIHandler.  It provides more functionality than is needed
for the existing scan function, but will be appropriate once the new API
functions start being added.

The only behavior change in the new version is that MIME types are
checked before fetching the list of available scanners.  This means that
passing an invalid MIME type with no scanners attached will now return
"Invalid MIME type" instead of "No scanners available".  Scanner names
are not exposed in the API and both checks have to pass before a scan is
started, so the only real effect is to make the invalid MIME case fail
faster.

Bug: b:297435442
Test: tast run documentscanapi.Scan
Test: Sample extension with physical scanners on a Chromebook
Change-Id: If85f32e2a402af1b90fcffaff593f4f883bebf9b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4916906
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Commit-Queue: Benjamin Gordon <bmgordon@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1212664}
  • Loading branch information
yetamrra authored and Chromium LUCI CQ committed Oct 20, 2023
1 parent c230886 commit 4133517
Show file tree
Hide file tree
Showing 10 changed files with 517 additions and 252 deletions.
2 changes: 2 additions & 0 deletions chrome/browser/extensions/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,8 @@ static_library("extensions") {
"api/certificate_provider/certificate_provider_api.h",
"api/document_scan/document_scan_api.cc",
"api/document_scan/document_scan_api.h",
"api/document_scan/document_scan_api_handler.cc",
"api/document_scan/document_scan_api_handler.h",
"api/enterprise_device_attributes/enterprise_device_attributes_api.cc",
"api/enterprise_device_attributes/enterprise_device_attributes_api.h",
"api/enterprise_networking_attributes/enterprise_networking_attributes_api.cc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/chromeos/extensions/wm/wm_desks_private_events.h"
#include "chrome/browser/extensions/api/document_scan/document_scan_api_handler.h"
#endif

#if BUILDFLAG(IS_CHROMEOS_ASH)
Expand Down Expand Up @@ -81,6 +82,9 @@ void EnsureApiBrowserContextKeyedServiceFactoriesBuilt() {
extensions::CommandService::GetFactoryInstance();
extensions::CookiesAPI::GetFactoryInstance();
extensions::DeveloperPrivateAPI::GetFactoryInstance();
#if BUILDFLAG(IS_CHROMEOS)
extensions::DocumentScanAPIHandler::GetFactoryInstance();
#endif
extensions::ExtensionActionAPI::GetFactoryInstance();
extensions::FontSettingsAPI::GetFactoryInstance();
extensions::HistoryAPI::GetFactoryInstance();
Expand Down
1 change: 1 addition & 0 deletions chrome/browser/extensions/api/document_scan/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bmgordon@chromium.org
127 changes: 21 additions & 106 deletions chrome/browser/extensions/api/document_scan/document_scan_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "chromeos/crosapi/mojom/document_scan.mojom.h"
#include "chrome/browser/extensions/api/document_scan/document_scan_api_handler.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
Expand All @@ -22,137 +20,54 @@
#include "chromeos/lacros/lacros_service.h"
#endif

namespace extensions::api {
namespace extensions {

namespace {

// Error messages that can be included in a response when scanning fails.
constexpr char kUserGestureRequiredError[] =
"User gesture required to perform scan";
constexpr char kNoScannersAvailableError[] = "No scanners available";
constexpr char kUnsupportedMimeTypesError[] = "Unsupported MIME types";
constexpr char kScanImageError[] = "Failed to scan image";
constexpr char kVirtualPrinterUnavailableError[] =
"Virtual USB printer unavailable";

// The name of the virtual USB printer used for testing.
constexpr char kVirtualUSBPrinter[] = "DavieV Virtual USB Printer (USB)";

// The testing MIME type.
constexpr char kTestingMimeType[] = "testing";

// The PNG MIME type.
constexpr char kScannerImageMimeTypePng[] = "image/png";

// The PNG image data URL prefix of a scanned image.
constexpr char kPngImageDataUrlPrefix[] = "data:image/png;base64,";

} // namespace

DocumentScanScanFunction::DocumentScanScanFunction() = default;

DocumentScanScanFunction::~DocumentScanScanFunction() = default;

void DocumentScanScanFunction::SetMojoInterfaceForTesting(
crosapi::mojom::DocumentScan* document_scan) {
document_scan_ = document_scan;
}

ExtensionFunction::ResponseAction DocumentScanScanFunction::Run() {
params_ = document_scan::Scan::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params_);
auto params = api::document_scan::Scan::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);

if (!user_gesture())
return RespondNow(Error(kUserGestureRequiredError));

MaybeInitializeMojoInterface();
if (!document_scan_)
return RespondNow(Error(kScanImageError));

document_scan_->GetScannerNames(
base::BindOnce(&DocumentScanScanFunction::OnNamesReceived, this));
return did_respond() ? AlreadyResponded() : RespondLater();
}
std::vector<std::string> mime_types;
if (params->options.mime_types) {
mime_types = std::move(*params->options.mime_types);
}

void DocumentScanScanFunction::MaybeInitializeMojoInterface() {
// Check if SetMojoInterfaceForTesting() already initialized `document_scan_`.
if (document_scan_)
return;
DocumentScanAPIHandler::Get(browser_context())
->SimpleScan(
mime_types,
base::BindOnce(&DocumentScanScanFunction::OnScanCompleted, this));

#if BUILDFLAG(IS_CHROMEOS_ASH)
DCHECK(crosapi::CrosapiManager::IsInitialized());
document_scan_ =
crosapi::CrosapiManager::Get()->crosapi_ash()->document_scan_ash();
#else
auto* service = chromeos::LacrosService::Get();
if (service->IsAvailable<crosapi::mojom::DocumentScan>()) {
document_scan_ = service->GetRemote<crosapi::mojom::DocumentScan>().get();
} else {
LOG(ERROR) << "Document scan not available";
}
#endif
return RespondLater();
}

void DocumentScanScanFunction::OnNamesReceived(
const std::vector<std::string>& scanner_names) {
if (scanner_names.empty()) {
Respond(Error(kNoScannersAvailableError));
void DocumentScanScanFunction::OnScanCompleted(
absl::optional<api::document_scan::ScanResults> scan_results,
absl::optional<std::string> error) {
if (error) {
Respond(Error(*error));
return;
}

bool should_use_virtual_usb_printer = false;
if (params_->options.mime_types) {
std::vector<std::string>& mime_types = *params_->options.mime_types;
if (base::Contains(mime_types, kTestingMimeType)) {
should_use_virtual_usb_printer = true;
} else if (!base::Contains(mime_types, kScannerImageMimeTypePng)) {
Respond(Error(kUnsupportedMimeTypesError));
return;
}
}

// TODO(pstew): Call a delegate method here to select a scanner and options.
// The first scanner supporting one of the requested MIME types used to be
// selected. The testing MIME type dictates that the virtual USB printer
// should be used if available. Otherwise, since all of the scanners only
// support PNG, select the first scanner in the list.

std::string scanner_name;
if (should_use_virtual_usb_printer) {
if (!base::Contains(scanner_names, kVirtualUSBPrinter)) {
Respond(Error(kVirtualPrinterUnavailableError));
return;
}

scanner_name = kVirtualUSBPrinter;
} else {
scanner_name = scanner_names[0];
}

document_scan_->ScanFirstPage(
scanner_name,
base::BindOnce(&DocumentScanScanFunction::OnScanCompleted, this));
}

void DocumentScanScanFunction::OnScanCompleted(
crosapi::mojom::ScanFailureMode failure_mode,
const absl::optional<std::string>& scan_data) {
// TODO(pstew): Enlist a delegate to display received scan in the UI and
// confirm that this scan should be sent to the caller. If this is a
// multi-page scan, provide a means for adding additional scanned images up to
// the requested limit.
if (!scan_data.has_value() ||
failure_mode != crosapi::mojom::ScanFailureMode::kNoFailure) {
if (!scan_results) {
Respond(Error(kScanImageError));
return;
}

std::string image_base64;
base::Base64Encode(scan_data.value(), &image_base64);
document_scan::ScanResults scan_results;
scan_results.data_urls.push_back(kPngImageDataUrlPrefix + image_base64);
scan_results.mime_type = kScannerImageMimeTypePng;
Respond(ArgumentList(document_scan::Scan::Results::Create(scan_results)));
Respond(WithArguments(scan_results->ToValue()));
}

} // namespace extensions::api
} // namespace extensions
33 changes: 5 additions & 28 deletions chrome/browser/extensions/api/document_scan/document_scan_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,32 @@
#ifndef CHROME_BROWSER_EXTENSIONS_API_DOCUMENT_SCAN_DOCUMENT_SCAN_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_DOCUMENT_SCAN_DOCUMENT_SCAN_API_H_

#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "chrome/common/extensions/api/document_scan.h"
#include "chromeos/crosapi/mojom/document_scan.mojom-forward.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/extension_function_histogram_value.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace crosapi::mojom {
class DocumentScan;
} // namespace crosapi::mojom

namespace extensions {

namespace api {

class DocumentScanScanFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("documentScan.scan", DOCUMENT_SCAN_SCAN)
DocumentScanScanFunction();
DocumentScanScanFunction(const DocumentScanScanFunction&) = delete;
DocumentScanScanFunction& operator=(const DocumentScanScanFunction&) = delete;

void SetMojoInterfaceForTesting(crosapi::mojom::DocumentScan* document_scan);

protected:
~DocumentScanScanFunction() override;

// ExtensionFunction:
ResponseAction Run() override;

private:
void MaybeInitializeMojoInterface();
void OnNamesReceived(const std::vector<std::string>& scanner_names);
void OnScanCompleted(crosapi::mojom::ScanFailureMode failure_mode,
const absl::optional<std::string>& scan_data);

absl::optional<document_scan::Scan::Params> params_;

// Used to transmit mojo interface method calls to ash chrome.
// Null if the interface is unavailable.
// The pointer is constant - if Ash crashes and the mojo connection is lost,
// Lacros will automatically be restarted.
raw_ptr<crosapi::mojom::DocumentScan> document_scan_ = nullptr;
void OnScanCompleted(
absl::optional<api::document_scan::ScanResults> scan_result,
absl::optional<std::string> error);
DECLARE_EXTENSION_FUNCTION("documentScan.scan", DOCUMENT_SCAN_SCAN)
};

} // namespace api

} // namespace extensions

#endif // CHROME_BROWSER_EXTENSIONS_API_DOCUMENT_SCAN_DOCUMENT_SCAN_API_H_

0 comments on commit 4133517

Please sign in to comment.