chromium/third_party/blink/renderer/extensions/webview/web_view.cc

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

#include "third_party/blink/renderer/extensions/webview/web_view.h"

#include "base/task/single_thread_task_runner.h"
#include "third_party/blink/public/mojom/webview/webview_media_integrity.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/extensions_webview/v8/v8_get_media_integrity_token_provider_params.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/extensions/webview/media_integrity/media_integrity_error.h"
#include "third_party/blink/renderer/extensions/webview/media_integrity/media_integrity_token_provider.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/supplementable.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"

namespace {
const char kInvalidContext[] = "Invalid context";
}  // namespace

namespace blink {

const char WebView::kSupplementName[] = "WebView";

WebView& WebView::From(ExecutionContext& execution_context) {
  CHECK(!execution_context.IsContextDestroyed());

  auto* supplement =
      Supplement<ExecutionContext>::From<WebView>(execution_context);

  if (!supplement) {
    supplement = MakeGarbageCollected<WebView>(execution_context);
    ProvideTo(execution_context, supplement);
  }
  return *supplement;
}

WebView::WebView(ExecutionContext& execution_context)
    : Supplement<ExecutionContext>(execution_context),
      ExecutionContextClient(&execution_context),
      media_integrity_service_remote_(&execution_context) {}

void WebView::EnsureServiceConnection(ExecutionContext* execution_context) {
  if (media_integrity_service_remote_.is_bound()) {
    return;
  }
  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      execution_context->GetTaskRunner(TaskType::kInternalDefault);
  execution_context->GetBrowserInterfaceBroker().GetInterface(
      media_integrity_service_remote_.BindNewPipeAndPassReceiver(task_runner));
  media_integrity_service_remote_.set_disconnect_handler(WTF::BindOnce(
      &WebView::OnServiceConnectionError, WrapWeakPersistent(this)));
}

void WebView::OnServiceConnectionError() {
  media_integrity_service_remote_.reset();
  for (auto& resolver : provider_resolvers_) {
    resolver->Reject(MediaIntegrityError::CreateForName(
        V8MediaIntegrityErrorName::Enum::kInternalError));
  }
  provider_resolvers_.clear();
}

ScriptPromise<MediaIntegrityTokenProvider>
WebView::getExperimentalMediaIntegrityTokenProvider(
    ScriptState* script_state,
    GetMediaIntegrityTokenProviderParams* params,
    ExceptionState& exception_state) {
  if (!script_state->ContextIsValid()) {
    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                      kInvalidContext);
    return EmptyPromise();
  }

  ExecutionContext* execution_context = ExecutionContext::From(script_state);
  const SecurityOrigin* origin = execution_context->GetSecurityOrigin();
  if ((origin->Protocol() != url::kHttpScheme &&
       origin->Protocol() != url::kHttpsScheme) ||
      !origin->IsPotentiallyTrustworthy()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotSupportedError,
        "getExperimentalMediaIntegrityTokenProvider: "
        "can only be used from trustworthy http/https origins");
    return EmptyPromise();
  }

  ScriptPromiseResolver<MediaIntegrityTokenProvider>* resolver =
      MakeGarbageCollected<ScriptPromiseResolver<MediaIntegrityTokenProvider>>(
          script_state, exception_state.GetContext());
  ScriptPromise<MediaIntegrityTokenProvider> promise = resolver->Promise();

  if (!params->hasCloudProjectNumber()) {
    resolver->Reject(MediaIntegrityError::CreateForName(
        V8MediaIntegrityErrorName::Enum::kInvalidArgument));
    return promise;
  }

  const uint64_t cloud_project_number = params->cloudProjectNumber();

  // This is checked in the browser also, but the browser will consider it a bad
  // message (and has the right to ignore or kill the renderer). We want to
  // report an error to the script instead.
  if (cloud_project_number >
      mojom::blink::WebViewMediaIntegrityService::kMaxCloudProjectNumber) {
    resolver->Reject(MediaIntegrityError::CreateForName(
        V8MediaIntegrityErrorName::Enum::kInvalidArgument));
    return promise;
  }

  EnsureServiceConnection(execution_context);
  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      execution_context->GetTaskRunner(TaskType::kInternalDefault);
  mojo::PendingRemote<mojom::blink::WebViewMediaIntegrityProvider>
      provider_pending_remote;
  mojo::PendingReceiver<mojom::blink::WebViewMediaIntegrityProvider>
      provider_pending_receiver =
          provider_pending_remote.InitWithNewPipeAndPassReceiver();

  provider_resolvers_.insert(resolver);
  media_integrity_service_remote_->GetIntegrityProvider(
      std::move(provider_pending_receiver), cloud_project_number,
      WTF::BindOnce(&WebView::OnGetIntegrityProviderResponse,
                    WrapPersistent(this), WrapPersistent(script_state),
                    std::move(provider_pending_remote), cloud_project_number,
                    WrapPersistent(resolver)));

  return promise;
}

void WebView::OnGetIntegrityProviderResponse(
    ScriptState* script_state,
    mojo::PendingRemote<mojom::blink::WebViewMediaIntegrityProvider>
        provider_pending_remote,
    const uint64_t cloud_project_number,
    ScriptPromiseResolver<MediaIntegrityTokenProvider>* resolver,
    const std::optional<mojom::blink::WebViewMediaIntegrityErrorCode> error) {
  provider_resolvers_.erase(resolver);

  if (!script_state->ContextIsValid()) {
    resolver->Reject(MakeGarbageCollected<DOMException>(
        DOMExceptionCode::kInvalidStateError, kInvalidContext));
    return;
  }

  if (error.has_value()) {
    resolver->Reject(MediaIntegrityError::CreateFromMojomEnum(*error));
    return;
  }

  MediaIntegrityTokenProvider* provider =
      MakeGarbageCollected<MediaIntegrityTokenProvider>(
          ExecutionContext::From(script_state),
          std::move(provider_pending_remote), cloud_project_number);

  resolver->Resolve(provider);
}

void WebView::Trace(Visitor* visitor) const {
  visitor->Trace(provider_resolvers_);
  visitor->Trace(media_integrity_service_remote_);
  Supplement<ExecutionContext>::Trace(visitor);
  ExecutionContextClient::Trace(visitor);
  ScriptWrappable::Trace(visitor);
}

}  // namespace blink