chromium/ash/webui/media_app_ui/media_app_ui.cc

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

#include "ash/webui/media_app_ui/media_app_ui.h"

#include <utility>

#include "ash/webui/grit/ash_media_app_resources.h"
#include "ash/webui/media_app_ui/buildflags.h"
#include "ash/webui/media_app_ui/media_app_page_handler.h"
#include "ash/webui/web_applications/webui_test_prod_util.h"
#include "chromeos/grit/chromeos_media_app_bundle_resources.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "content/public/browser/render_frame_host.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 "content/public/common/extra_mojo_js_features.mojom.h"
#include "content/public/common/url_constants.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/webui/webui_allowlist.h"

namespace ash {
namespace {

constexpr webui::LocalizedString kLocalizedStrings[] = {
    {"appTitle", IDS_MEDIA_APP_APP_NAME},
    {"fileFilterAudio", IDS_MEDIA_APP_FILE_FILTER_AUDIO},
    {"fileFilterImage", IDS_MEDIA_APP_FILE_FILTER_IMAGE},
    {"fileFilterVideo", IDS_MEDIA_APP_FILE_FILTER_VIDEO},
};

content::WebUIDataSource* CreateAndAddHostDataSource(
    content::BrowserContext* browser_context) {
  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
      browser_context, kChromeUIMediaAppHost);
  source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::ScriptSrc,
      "script-src chrome://resources chrome://webui-test 'self';");

  // Add resources from ash_media_app_resources.pak.
  source->SetDefaultResource(IDR_MEDIA_APP_INDEX_DARK_LIGHT_HTML);
  source->AddResourcePath("launch.js", IDR_MEDIA_APP_LAUNCH_JS);
  source->AddResourcePath("viewpdfhost.html", IDR_MEDIA_APP_VIEWPDFHOST_HTML);
  source->AddResourcePath("viewpdfhost.js", IDR_MEDIA_APP_VIEWPDFHOST_JS);
  source->AddResourcePath("first_message_received.js",
                          IDR_MEDIA_APP_FIRST_MESSAGE_RECEIVED_JS);

  source->AddLocalizedStrings(kLocalizedStrings);
  source->UseStringsJs();

  // Redirects "system_assets/*" (from manifest.json) to the icons for the
  // gallery app.
  source->AddResourcePath("system_assets/app_icon_16.png",
                          IDR_MEDIA_APP_APP_ICON_16_PNG);
  source->AddResourcePath("system_assets/app_icon_32.png",
                          IDR_MEDIA_APP_APP_ICON_32_PNG);
  source->AddResourcePath("system_assets/app_icon_48.png",
                          IDR_MEDIA_APP_APP_ICON_48_PNG);
  source->AddResourcePath("system_assets/app_icon_64.png",
                          IDR_MEDIA_APP_APP_ICON_64_PNG);
  source->AddResourcePath("system_assets/app_icon_96.png",
                          IDR_MEDIA_APP_APP_ICON_96_PNG);
  source->AddResourcePath("system_assets/app_icon_128.png",
                          IDR_MEDIA_APP_APP_ICON_128_PNG);
  source->AddResourcePath("system_assets/app_icon_192.png",
                          IDR_MEDIA_APP_APP_ICON_192_PNG);
  source->AddResourcePath("system_assets/app_icon_256.png",
                          IDR_MEDIA_APP_APP_ICON_256_PNG);
  source->AddResourcePath("system_assets/app_icon.svg",
                          IDR_MEDIA_APP_APP_ICON_192_SVG);  // App favicon.

  // File-type favicons.
  source->AddResourcePath("system_assets/pdf_icon.svg",
                          IDR_MEDIA_APP_PDF_ICON_SVG);
  source->AddResourcePath("system_assets/pdf_icon_dark.svg",
                          IDR_MEDIA_APP_PDF_ICON_DARK_SVG);
  source->AddResourcePath("system_assets/video_icon.svg",
                          IDR_MEDIA_APP_VIDEO_ICON_SVG);
  source->AddResourcePath("system_assets/video_icon_dark.svg",
                          IDR_MEDIA_APP_VIDEO_ICON_DARK_SVG);
  source->AddResourcePath("system_assets/image_icon.svg",
                          IDR_MEDIA_APP_IMAGE_ICON_SVG);
  source->AddResourcePath("system_assets/image_icon_dark.svg",
                          IDR_MEDIA_APP_IMAGE_ICON_DARK_SVG);
  source->AddResourcePath("system_assets/audio_icon.svg",
                          IDR_MEDIA_APP_AUDIO_ICON_SVG);
  source->AddResourcePath("system_assets/audio_icon_dark.svg",
                          IDR_MEDIA_APP_AUDIO_ICON_DARK_SVG);
  source->AddResourcePath("system_assets/file_icon.svg",
                          IDR_MEDIA_APP_FILE_ICON_SVG);
  source->AddResourcePath("system_assets/file_icon_dark.svg",
                          IDR_MEDIA_APP_FILE_ICON_DARK_SVG);
  return source;
}

}  // namespace

MediaAppUI::MediaAppUI(content::WebUI* web_ui,
                       std::unique_ptr<MediaAppUIDelegate> delegate)
    : MojoWebUIController(web_ui), delegate_(std::move(delegate)) {
  content::BrowserContext* browser_context =
      web_ui->GetWebContents()->GetBrowserContext();
  content::WebUIDataSource* host_source =
      CreateAndAddHostDataSource(browser_context);

  // The guest is in an <iframe>. Add it to CSP.
  std::string csp = std::string("frame-src ") + kChromeUIMediaAppGuestURL + ";";
  host_source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::FrameSrc, csp);
  // Allow use of SharedArrayBuffer (required by wasm code in the iframe guest).
  host_source->OverrideCrossOriginOpenerPolicy("same-origin");
  host_source->OverrideCrossOriginEmbedderPolicy("require-corp");

  if (MaybeConfigureTestableDataSource(host_source)) {
    host_source->OverrideContentSecurityPolicy(
        network::mojom::CSPDirectiveName::TrustedTypes,
        std::string("trusted-types test-harness;"));
  }

  // Register auto-granted permissions.
  auto* allowlist = WebUIAllowlist::GetOrCreate(browser_context);
  const url::Origin host_origin =
      url::Origin::Create(GURL(kChromeUIMediaAppURL));
  allowlist->RegisterAutoGrantedPermissions(
      host_origin, {
                       ContentSettingsType::COOKIES,
                       ContentSettingsType::FILE_SYSTEM_READ_GUARD,
                       ContentSettingsType::FILE_SYSTEM_WRITE_GUARD,
                       ContentSettingsType::IMAGES,
                       ContentSettingsType::JAVASCRIPT,
                       ContentSettingsType::SOUND,
                   });
  const url::Origin guest_origin =
      url::Origin::Create(GURL(kChromeUIMediaAppGuestURL));
  allowlist->RegisterAutoGrantedPermissions(guest_origin,
                                            {
                                                ContentSettingsType::COOKIES,
                                            });
  // Add ability to request chrome-untrusted: URLs.
  web_ui->AddRequestableScheme(content::kChromeUIUntrustedScheme);
}

MediaAppUI::~MediaAppUI() = default;

void MediaAppUI::BindInterface(
    mojo::PendingReceiver<media_app_ui::mojom::PageHandlerFactory> receiver) {
  page_factory_receiver_.reset();
  page_factory_receiver_.Bind(std::move(receiver));
}

void MediaAppUI::WebUIRenderFrameCreated(
    content::RenderFrameHost* render_frame_host) {
  // Allow the render frame to get a MojoHandle from a FileSystemFileHandle.
  auto features = content::mojom::ExtraMojoJsFeatures::New();
  features->file_system_access = true;
  render_frame_host->EnableMojoJsBindings(std::move(features));
}

bool MediaAppUI::IsJavascriptErrorReportingEnabled() {
  // JavaScript errors are reported via CrashReportPrivate.reportError. Don't
  // send duplicate reports via WebUI.
  return false;
}

void MediaAppUI::CreatePageHandler(
    mojo::PendingReceiver<media_app_ui::mojom::PageHandler> receiver) {
  page_handler_ =
      std::make_unique<MediaAppPageHandler>(this, std::move(receiver));
}

WEB_UI_CONTROLLER_TYPE_IMPL(MediaAppUI)

}  // namespace ash