chromium/chromecast/renderer/feature_manager.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 "chromecast/renderer/feature_manager.h"

#include <iostream>
#include <map>
#include <string>
#include <vector>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/not_fatal_until.h"
#include "base/values.h"
#include "chromecast/base/cast_features.h"
#include "chromecast/common/feature_constants.h"
#include "chromecast/renderer/assistant_bindings.h"
#include "chromecast/renderer/cast_demo_bindings.h"
#include "chromecast/renderer/cast_window_manager_bindings.h"
#include "chromecast/renderer/settings_ui_bindings.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_media_playback_options.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/platform/web_runtime_features.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_security_policy.h"

namespace chromecast {

FeatureManager::FeatureManager(content::RenderFrame* render_frame)
    : content::RenderFrameObserver(render_frame),
      configured_(false),
      can_install_bindings_(false),
      dev_origin_(GURL()),
      secure_origin_set_(false) {
  registry_.AddInterface(base::BindRepeating(
      &FeatureManager::OnFeatureManagerRequest, base::Unretained(this)));
}

FeatureManager::~FeatureManager() {}

void FeatureManager::OnInterfaceRequestForFrame(
    const std::string& interface_name,
    mojo::ScopedMessagePipeHandle* interface_pipe) {
  registry_.TryBindInterface(interface_name, interface_pipe);
}

void FeatureManager::OnDestruct() {
  delete this;
}

void FeatureManager::DidClearWindowObject() {
  can_install_bindings_ = true;
  if (!configured_)
    return;

  EnableBindings();
}

void FeatureManager::ConfigureFeatures(
    std::vector<chromecast::shell::mojom::FeaturePtr> features) {
  if (configured_)
    return;
  configured_ = true;
  for (auto& feature : features) {
    // If we want to add enabled/disabled status to FeaturePtr, we can overlap
    // previous setting via [] operator
    features_map_[feature->name] = std::move(feature);
  }

  ConfigureFeaturesInternal();

  if (!can_install_bindings_)
    return;
  EnableBindings();
}

void FeatureManager::ConfigureFeaturesInternal() {
  if (FeatureEnabled(feature::kEnableDevMode)) {
    const base::Value::Dict& dev_mode_config =
        (features_map_.find(feature::kEnableDevMode)->second)->config;
    const std::string* dev_mode_origin =
        dev_mode_config.FindString(feature::kDevModeOrigin);
    DCHECK(dev_mode_origin);
    dev_origin_ = GURL(*dev_mode_origin);
    DCHECK(dev_origin_.is_valid());
  }

  if (FeatureEnabled(feature::kDisableBackgroundSuspend)) {
    auto options = render_frame()->GetRenderFrameMediaPlaybackOptions();
    options.is_background_suspend_enabled = false;
    render_frame()->SetRenderFrameMediaPlaybackOptions(options);
  }

  // Call feature-specific functions.
  SetupAdditionalSecureOrigin();

  // Disable timer throttling for background tabs before the frame is painted.
  if (FeatureEnabled(feature::kDisableBackgroundTabTimerThrottle)) {
    blink::WebRuntimeFeatures::EnableTimerThrottlingForBackgroundTabs(false);
  }
  if (FeatureEnabled(feature::kEnableSettingsUiMojo)) {
    v8_bindings_.insert(new shell::SettingsUiBindings(render_frame()));
  }

  // Window manager bindings will install themselves depending on the specific
  // feature flags enabled, so we pass the feature manager through to let it
  // decide.
  v8_bindings_.insert(
      new shell::CastWindowManagerBindings(render_frame(), this));
  if (FeatureEnabled(feature::kEnableDemoStandaloneMode)) {
    v8_bindings_.insert(new shell::CastDemoBindings(render_frame()));
  }

  if (FeatureEnabled(feature::kEnableAssistantMessagePipe)) {
    auto& feature = GetFeature(feature::kEnableAssistantMessagePipe);
    v8_bindings_.insert(
        new shell::AssistantBindings(render_frame(), feature->config));
  }
}

void FeatureManager::EnableBindings() {
  LOG(INFO) << "Enabling bindings: " << *this;
  for (auto* binding : v8_bindings_) {
    binding->TryInstall();
  }
}

void FeatureManager::OnFeatureManagerRequest(
    mojo::PendingReceiver<shell::mojom::FeatureManager> request) {
  bindings_.Add(this, std::move(request));
}

bool FeatureManager::FeatureEnabled(const std::string& feature) const {
  return base::Contains(features_map_, feature);
}

const chromecast::shell::mojom::FeaturePtr& FeatureManager::GetFeature(
    const std::string& feature) const {
  auto itor = features_map_.find(feature);
  CHECK(itor != features_map_.end(), base::NotFatalUntil::M130);
  return itor->second;
}

void FeatureManager::SetupAdditionalSecureOrigin() {
  if (!dev_origin_.is_valid()) {
    return;
  }

  // Secure origin should be only set once, otherwise it will cause CHECK
  // failure when race between origin safelist changing and thread creation
  // happens (b/63583734).
  if (secure_origin_set_) {
    return;
  }

  secure_origin_set_ = true;

  LOG(INFO) << "Treat origin " << dev_origin_ << " as secure origin";

  blink::WebSecurityPolicy::AddSchemeToSecureContextSafelist(
      blink::WebString::FromASCII(dev_origin_.scheme()));

  network::SecureOriginAllowlist::GetInstance().SetAuxiliaryAllowlist(
      dev_origin_.spec(), nullptr);
}

std::ostream& operator<<(std::ostream& os, const FeatureManager& features) {
  for (auto& feature : features.features_map_) {
    os << feature.first << " ";
  }
  return os;
}

}  // namespace chromecast