chromium/components/cast_receiver/browser/runtime_application_base.cc

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

#include "components/cast_receiver/browser/runtime_application_base.h"

#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/task/sequenced_task_runner.h"
#include "components/cast_receiver/browser/permissions_manager_impl.h"
#include "components/media_control/browser/media_blocker.h"
#include "components/url_rewrite/browser/url_request_rewrite_rules_manager.h"
#include "content/public/browser/web_contents.h"

namespace cast_receiver {

RuntimeApplicationBase::RuntimeApplicationBase(
    std::string cast_session_id,
    ApplicationConfig app_config,
    ApplicationClient& application_client)
    : cast_session_id_(std::move(cast_session_id)),
      app_config_(std::move(app_config)),
      task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      application_client_(application_client) {
  DCHECK(task_runner_);
}

RuntimeApplicationBase::~RuntimeApplicationBase() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(!is_application_running_);
}

void RuntimeApplicationBase::SetEmbedderApplication(
    EmbedderApplication& embedder_application) {
  DCHECK(!embedder_application_);
  embedder_application_ = &embedder_application;
}

const std::string& RuntimeApplicationBase::GetDisplayName() const {
  return config().display_name;
}

const std::string& RuntimeApplicationBase::GetAppId() const {
  return config().app_id;
}

const std::string& RuntimeApplicationBase::GetCastSessionId() const {
  return cast_session_id_;
}

void RuntimeApplicationBase::Load(StatusCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(embedder_application().GetWebContents());

  is_application_running_ = true;
  if (cached_mojom_rules_) {
    // Apply cached URL rewrite rules before anything is done with the page.
    SetUrlRewriteRules(std::move(cached_mojom_rules_));
  }

  DLOG(INFO) << "Loaded application: " << *this;
  std::move(callback).Run(OkStatus());
}

void RuntimeApplicationBase::Stop(StatusCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  StopApplication(EmbedderApplication::ApplicationStopReason::kUserRequest,
                  net::ERR_ABORTED);
  std::move(callback).Run(OkStatus());
}

ApplicationClient::ApplicationControls&
RuntimeApplicationBase::GetApplicationControls() {
  DCHECK(embedder_application().GetWebContents());

  return application_client_->GetApplicationControls(
      *embedder_application().GetWebContents());
}

void RuntimeApplicationBase::NavigateToPage(const GURL& url) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto* window_controls = embedder_application().GetContentWindowControls();
  DCHECK(window_controls);
  window_controls->AddVisibilityChangeObserver(*this);

  embedder_application().NavigateToPage(url);

  SetWebVisibilityAndPaint(is_visible_);
}

void RuntimeApplicationBase::SetContentPermissions(
    content::WebContents& web_contents) {
  PermissionsManagerImpl* permissions_manager =
      PermissionsManagerImpl::CreateInstance(web_contents, GetAppId());
  if (config().url.has_value()) {
    auto app_url_origin = url::Origin::Create(config().url.value());
    if (!app_url_origin.opaque()) {
      permissions_manager->AddOrigin(app_url_origin);
    }
  }
  for (blink::PermissionType permission : config().permissions.permissions) {
    permissions_manager->AddPermission(permission);
  }
  for (auto& origin : config().permissions.additional_origins) {
    DCHECK(!origin.opaque());
    permissions_manager->AddOrigin(origin);
  }
}

void RuntimeApplicationBase::OnPageNavigationComplete() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DLOG(INFO) << "Page loaded: " << *this;

  embedder_application().NotifyApplicationStarted();

  SetWebVisibilityAndPaint(is_visible_);
  SetTouchInputEnabled(is_touch_input_enabled_);
  SetMediaBlocking(is_media_load_blocked_, is_media_start_blocked_);
}

void RuntimeApplicationBase::SetUrlRewriteRules(
    url_rewrite::mojom::UrlRequestRewriteRulesPtr mojom_rules) {
  if (!embedder_application().GetWebContents()) {
    cached_mojom_rules_ = std::move(mojom_rules);
    return;
  }

  url_rewrite::UrlRequestRewriteRulesManager&
      url_request_rewrite_rules_manager =
          GetApplicationControls().GetUrlRequestRewriteRulesManager();
  if (!url_request_rewrite_rules_manager.OnRulesUpdated(
          std::move(mojom_rules))) {
    LOG(ERROR) << "URL rewrite rules update failed.";
    StopApplication(EmbedderApplication::ApplicationStopReason::kRuntimeError,
                    net::Error::ERR_UNEXPECTED);
  }
}

void RuntimeApplicationBase::SetMediaBlocking(bool load_blocked,
                                              bool start_blocked) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  is_media_load_blocked_ = load_blocked;
  is_media_start_blocked_ = start_blocked;
  DLOG(INFO) << "Media state updated: is_load_blocked=" << load_blocked
             << ", is_start_blocked=" << start_blocked << ", " << *this;

  if (!embedder_application().GetWebContents()) {
    return;
  }

  media_control::MediaBlocker& media_blocker =
      GetApplicationControls().GetMediaBlocker();

  media_blocker.BlockMediaLoading(is_media_load_blocked_);

  // TODO(crbug.com/1359584): Block media starting.
}

void RuntimeApplicationBase::SetVisibility(bool is_visible) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  is_visible_ = is_visible;
  DLOG(INFO) << "Visibility updated: is_visible_=" << is_visible_ << ", "
             << *this;

  auto* window_controls = embedder_application().GetContentWindowControls();
  if (!window_controls) {
    return;
  }

  if (is_visible_) {
    window_controls->ShowWindow();
  } else {
    window_controls->HideWindow();
  }
}

void RuntimeApplicationBase::SetTouchInputEnabled(bool enabled) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  is_touch_input_enabled_ = enabled;
  DLOG(INFO) << "Touch input updated: is_touch_input_enabled_= "
             << is_touch_input_enabled_ << ", " << *this;

  auto* window_controls = embedder_application().GetContentWindowControls();
  if (!window_controls) {
    return;
  }

  if (is_touch_input_enabled_) {
    window_controls->EnableTouchInput();
  } else {
    window_controls->DisableTouchInput();
  }
}

bool RuntimeApplicationBase::IsApplicationRunning() const {
  return is_application_running_;
}

void RuntimeApplicationBase::StopApplication(
    EmbedderApplication::ApplicationStopReason stop_reason,
    net::Error net_error_code) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!is_application_running_) {
    return;
  }
  is_application_running_ = false;

  auto* web_contents = embedder_application().GetWebContents();
  if (web_contents) {
    web_contents->DispatchBeforeUnload(false /* auto_cancel */);
    web_contents->ClosePage();

    // Check if window is still available as page might have been closed before.
    auto* window_controls = embedder_application().GetContentWindowControls();
    if (window_controls) {
      window_controls->RemoveVisibilityChangeObserver(*this);
    }
  }

  embedder_application().NotifyApplicationStopped(stop_reason, net_error_code);

  DLOG(INFO) << "Application is stopped: stop_reason=" << stop_reason << ", "
             << *this;
}

void RuntimeApplicationBase::SetWebVisibilityAndPaint(bool is_visible) {
  auto* web_contents = embedder_application().GetWebContents();
  if (!web_contents) {
    return;
  }

  if (is_visible) {
    web_contents->WasShown();
  } else {
    // NOTE: Calling WasHidden() and later WasShown() does not behave properly
    // on some platforms (e.g. Linux devices using X11 platform for Ozone). In
    // such cases, the WasShown() call will execute, and the browser-side code
    // associated with this call will run, but it will never reach the Renderer
    // process, so the LayerTreeHost will never draw the surface assocaited with
    // this WebContents.
    DLOG(WARNING)
        << "WebContents hidden. NOTE: Changing from hidden to visible does not "
           "work in all cases, and such calls may not be respected.";
    web_contents->WasHidden();
  }

  if (web_contents->GetVisibility() != content::Visibility::VISIBLE) {
    // Since we are managing the visibility, we need to ensure pages are
    // unfrozen in the event this occurred while in the background.
    web_contents->SetPageFrozen(false);
  }
}

void RuntimeApplicationBase::OnWindowShown() {
  SetWebVisibilityAndPaint(true);
}

void RuntimeApplicationBase::OnWindowHidden() {
  SetWebVisibilityAndPaint(false);
}

}  // namespace cast_receiver