chromium/ash/webui/scanning/scanning_ui.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/webui/scanning/scanning_ui.h"

#include <memory>
#include <string>
#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/webui/common/backend/accessibility_features.h"
#include "ash/webui/common/mojom/accessibility_features.mojom.h"
#include "ash/webui/common/trusted_types_util.h"
#include "ash/webui/grit/ash_scanning_app_resources.h"
#include "ash/webui/grit/ash_scanning_app_resources_map.h"
#include "ash/webui/scanning/mojom/scanning.mojom.h"
#include "ash/webui/scanning/scanning_app_delegate.h"
#include "ash/webui/scanning/scanning_metrics_handler.h"
#include "ash/webui/scanning/url_constants.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/resources/grit/webui_resources.h"
#include "ui/webui/color_change_listener/color_change_handler.h"

namespace ash {

namespace {

void SetUpWebUIDataSource(content::WebUIDataSource* source,
                          base::span<const webui::ResourcePath> resources,
                          int default_resource) {
  for (const auto& resource : resources) {
    source->AddResourcePath(resource.path, resource.id);
  }

  source->SetDefaultResource(default_resource);
  source->AddResourcePath("test_loader.html", IDR_WEBUI_TEST_LOADER_HTML);
  source->AddResourcePath("test_loader.js", IDR_WEBUI_JS_TEST_LOADER_JS);
  source->AddResourcePath("test_loader_util.js",
                          IDR_WEBUI_JS_TEST_LOADER_UTIL_JS);
}

void AddScanningAppStrings(content::WebUIDataSource* html_source) {
  static constexpr webui::LocalizedString kLocalizedStrings[] = {
      {"a3OptionText", IDS_SCANNING_APP_A3_OPTION_TEXT},
      {"a4OptionText", IDS_SCANNING_APP_A4_OPTION_TEXT},
      {"actionToolbarPageCountText",
       IDS_SCANNING_APP_ACTION_TOOLBAR_PAGE_COUNT_TEXT},
      {"appTitle", IDS_SCANNING_APP_TITLE},
      {"b4OptionText", IDS_SCANNING_APP_B4_OPTION_TEXT},
      {"blackAndWhiteOptionText", IDS_SCANNING_APP_BLACK_AND_WHITE_OPTION_TEXT},
      {"cancelButtonText", IDS_SCANNING_APP_CANCEL_BUTTON_TEXT},
      {"cancelFailedToastText", IDS_SCANNING_APP_CANCEL_FAILED_TOAST_TEXT},
      {"cancelingScanningText", IDS_SCANNING_APP_CANCELING_SCANNING_TEXT},
      {"colorModeDropdownLabel", IDS_SCANNING_APP_COLOR_MODE_DROPDOWN_LABEL},
      {"colorOptionText", IDS_SCANNING_APP_COLOR_OPTION_TEXT},
      {"defaultSourceOptionText", IDS_SCANNING_APP_DEFAULT_SOURCE_OPTION_TEXT},
      {"doneButtonText", IDS_SCANNING_APP_DONE_BUTTON_TEXT},
      {"editButtonLabel", IDS_SCANNING_APP_EDIT_BUTTON_LABEL},
      {"fileNotFoundToastText", IDS_SCANNING_APP_FILE_NOT_FOUND_TOAST_TEXT},
      {"fileTypeDropdownLabel", IDS_SCANNING_APP_FILE_TYPE_DROPDOWN_LABEL},
      {"fitToScanAreaOptionText",
       IDS_SCANNING_APP_FIT_TO_SCAN_AREA_OPTION_TEXT},
      {"flatbedOptionText", IDS_SCANNING_APP_FLATBED_OPTION_TEXT},
      {"getHelpLinkText", IDS_SCANNING_APP_GET_HELP_LINK_TEXT},
      {"grayscaleOptionText", IDS_SCANNING_APP_GRAYSCALE_OPTION_TEXT},
      {"jpgOptionText", IDS_SCANNING_APP_JPG_OPTION_TEXT},
      {"learnMoreButtonLabel", IDS_SCANNING_APP_LEARN_MORE_BUTTON_LABEL},
      {"legalOptionText", IDS_SCANNING_APP_LEGAL_OPTION_TEXT},
      {"letterOptionText", IDS_SCANNING_APP_LETTER_OPTION_TEXT},
      {"moreSettings", IDS_SCANNING_APP_MORE_SETTINGS},
      {"multiPageCancelingScanningText",
       IDS_SCANNING_APP_MULTI_PAGE_CANCELING_SCANNING_TEXT},
      {"multiPageCheckboxAriaLabel",
       IDS_SCANNING_APP_MULTI_PAGE_CHECKBOX_ARIA_LABEL},
      {"multiPageCheckboxText", IDS_SCANNING_APP_MULTI_PAGE_CHECKBOX_TEXT},
      {"multiPageImageAriaLabel", IDS_SCANNING_APP_MULTI_PAGE_IMAGE_ARIA_LABEL},
      {"multiPageScanInstructionsText",
       IDS_SCANNING_APP_MULTI_PAGE_SCAN_INSTRUCTIONS_TEXT},
      {"multiPageScanProgressText",
       IDS_SCANNING_APP_MULTI_PAGE_SCAN_PROGRESS_TEXT},
      {"myFilesSelectOption", IDS_SCANNING_APP_MY_FILES_SELECT_OPTION},
      {"noScannersText", IDS_SCANNING_APP_NO_SCANNERS_TEXT},
      {"noScannersSubtext", IDS_SCANNING_APP_NO_SCANNERS_SUBTEXT},
      {"okButtonLabel", IDS_SCANNING_APP_OK_BUTTON_LABEL},
      {"oneSidedDocFeederOptionText",
       IDS_SCANNING_APP_ONE_SIDED_DOC_FEEDER_OPTION_TEXT},
      {"pdfOptionText", IDS_SCANNING_APP_PDF_OPTION_TEXT},
      {"pngOptionText", IDS_SCANNING_APP_PNG_OPTION_TEXT},
      {"pageSizeDropdownLabel", IDS_SCANNING_APP_PAGE_SIZE_DROPDOWN_LABEL},
      {"removePageButtonLabel", IDS_SCANNING_APP_REMOVE_PAGE_BUTTON_LABEL},
      {"removePageConfirmationText",
       IDS_SCANNING_APP_REMOVE_PAGE_CONFIRMATION_TEXT},
      {"rescanPageButtonLabel", IDS_SCANNING_APP_RESCAN_PAGE_BUTTON_LABEL},
      {"rescanPageConfirmationText",
       IDS_SCANNING_APP_RESCAN_PAGE_CONFIRMATION_TEXT},
      {"resolutionDropdownLabel", IDS_SCANNING_APP_RESOLUTION_DROPDOWN_LABEL},
      {"resolutionOptionText", IDS_SCANNING_APP_RESOLUTION_OPTION_TEXT},
      {"retryButtonLabel", IDS_SCANNING_APP_RETRY_BUTTON_LABEL},
      {"saveButtonLabel", IDS_SCANNING_APP_SAVE_BUTTON_LABEL},
      {"scanCanceledToastText", IDS_SCANNING_APP_SCAN_CANCELED_TOAST_TEXT},
      {"scanFailedDialogAdfEmptyText",
       IDS_SCANNING_APP_SCAN_FAILED_DIALOG_ADF_EMPTY_TEXT},
      {"scanFailedDialogAdfJammedText",
       IDS_SCANNING_APP_SCAN_FAILED_DIALOG_ADF_JAMMED_TEXT},
      {"scanFailedDialogDeviceBusyText",
       IDS_SCANNING_APP_SCAN_FAILED_DIALOG_DEVICE_BUSY_TEXT},
      {"scanFailedDialogFlatbedOpenText",
       IDS_SCANNING_APP_SCAN_FAILED_DIALOG_FLATBED_OPEN_TEXT},
      {"scanFailedDialogIoErrorText",
       IDS_SCANNING_APP_SCAN_FAILED_DIALOG_IO_ERROR_TEXT},
      {"scanFailedDialogTitleText",
       IDS_SCANNING_APP_SCAN_FAILED_DIALOG_TITLE_TEXT},
      {"scanFailedDialogUnknownErrorText",
       IDS_SCANNING_APP_SCAN_FAILED_DIALOG_UNKNOWN_ERROR_TEXT},
      {"scanPreviewHelperText", IDS_SCANNING_APP_SCAN_PREVIEW_HELPER_TEXT},
      {"scanPreviewProgressText", IDS_SCANNING_APP_SCAN_PREVIEW_PROGRESS_TEXT},
      {"scanToDropdownLabel", IDS_SCANNING_APP_SCAN_TO_DROPDOWN_LABEL},
      {"scannerDropdownLabel", IDS_SCANNING_APP_SCANNER_DROPDOWN_LABEL},
      {"scannersLoadingText", IDS_SCANNING_APP_SCANNERS_LOADING_TEXT},
      {"scanningImagesAriaLabel", IDS_SCANNING_APP_SCANNING_IMAGES_ARIA_LABEL},
      {"selectFolderOption", IDS_SCANNING_APP_SELECT_FOLDER_OPTION},
      {"showInFolderButtonLabel", IDS_SCANNING_APP_SHOW_IN_FOLDER_BUTTON_LABEL},
      {"sourceDropdownLabel", IDS_SCANNING_APP_SOURCE_DROPDOWN_LABEL},
      {"startScanFailedToast", IDS_SCANNING_APP_START_SCAN_FAILED_TOAST},
      {"tabloidOptionText", IDS_SCANNING_APP_TABLOID_OPTION_TEXT},
      {"twoSidedDocFeederOptionText",
       IDS_SCANNING_APP_TWO_SIDED_DOC_FEEDER_OPTION_TEXT}};

  html_source->AddLocalizedStrings(kLocalizedStrings);
  html_source->UseStringsJs();
}

void AddScanningAppPluralStrings(ScanningHandler* handler) {
  static constexpr webui::LocalizedString kLocalizedStrings[] = {
      {"editButtonLabel", IDS_SCANNING_APP_EDIT_BUTTON_LABEL},
      {"fileSavedText", IDS_SCANNING_APP_FILE_SAVED_TEXT},
      {"removePageDialogTitle", IDS_SCANNING_APP_REMOVE_PAGE_DIALOG_TITLE},
      {"rescanPageDialogTitle", IDS_SCANNING_APP_RESCAN_PAGE_DIALOG_TITLE},
      {"scanButtonText", IDS_SCANNING_APP_SCAN_BUTTON_TEXT},
      {"scannedImagesAriaLabel", IDS_SCANNING_APP_SCANNED_IMAGES_ARIA_LABEL}};

  for (const auto& str : kLocalizedStrings)
    handler->AddStringToPluralMap(str.name, str.id);
}

}  // namespace

ScanningUI::ScanningUI(
    content::WebUI* web_ui,
    std::unique_ptr<ScanningAppDelegate> scanning_app_delegate)
    : ui::MojoWebUIController(web_ui, true /* enable_chrome_send */),
      bind_pending_receiver_callback_(
          scanning_app_delegate->GetBindScanServiceCallback(web_ui)) {
  content::WebUIDataSource* html_source =
      content::WebUIDataSource::CreateAndAdd(
          web_ui->GetWebContents()->GetBrowserContext(),
          kChromeUIScanningAppHost);
  html_source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::ScriptSrc,
      "script-src chrome://resources chrome://webui-test 'self';");
  ash::EnableTrustedTypesCSP(html_source);

  accessibility_features_ = std::make_unique<AccessibilityFeatures>();

  const auto resources =
      base::make_span(kAshScanningAppResources, kAshScanningAppResourcesSize);
  SetUpWebUIDataSource(html_source, resources, IDR_ASH_SCANNING_APP_INDEX_HTML);

  AddScanningAppStrings(html_source);

  auto handler =
      std::make_unique<ScanningHandler>(std::move(scanning_app_delegate));
  AddScanningAppPluralStrings(handler.get());

  web_ui->AddMessageHandler(std::move(handler));
  web_ui->AddMessageHandler(std::make_unique<ScanningMetricsHandler>());
}

ScanningUI::~ScanningUI() = default;

void ScanningUI::BindInterface(
    mojo::PendingReceiver<scanning::mojom::ScanService> pending_receiver) {
  bind_pending_receiver_callback_.Run(std::move(pending_receiver));
}

void ScanningUI::BindInterface(
    mojo::PendingReceiver<common::mojom::AccessibilityFeatures>
        pending_receiver) {
  accessibility_features_->BindInterface(std::move(pending_receiver));
}

void ScanningUI::BindInterface(
    mojo::PendingReceiver<color_change_listener::mojom::PageHandler> receiver) {
  color_provider_handler_ = std::make_unique<ui::ColorChangeHandler>(
      web_ui()->GetWebContents(), std::move(receiver));
}

WEB_UI_CONTROLLER_TYPE_IMPL(ScanningUI)

}  // namespace ash