910e62b5创建于 1月15日历史提交
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/renderer/programmatic_script_injector.h"

#include <utility>
#include <vector>

#include "base/values.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_frame.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/renderer/injection_host.h"
#include "extensions/renderer/renderer_extension_registry.h"
#include "extensions/renderer/script_context.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_script_source.h"

namespace extensions {

ProgrammaticScriptInjector::ProgrammaticScriptInjector(
    mojom::ExecuteCodeParamsPtr params,
    mojom::LocalFrame::ExecuteCodeCallback callback)
    : params_(std::move(params)), callback_(std::move(callback)) {}

ProgrammaticScriptInjector::~ProgrammaticScriptInjector() {
}

mojom::InjectionType ProgrammaticScriptInjector::script_type() const {
  return mojom::InjectionType::kProgrammaticScript;
}

blink::mojom::UserActivationOption ProgrammaticScriptInjector::IsUserGesture()
    const {
  DCHECK(params_->injection->is_js());
  return params_->injection->get_js()->user_gesture;
}

mojom::ExecutionWorld ProgrammaticScriptInjector::GetExecutionWorld() const {
  DCHECK(params_->injection->is_js());
  return params_->injection->get_js()->world;
}

const std::optional<std::string>&
ProgrammaticScriptInjector::GetExecutionWorldId() const {
  return params_->injection->get_js()->world_id;
}

mojom::CSSOrigin ProgrammaticScriptInjector::GetCssOrigin() const {
  DCHECK(params_->injection->is_css());
  return params_->injection->get_css()->css_origin;
}

mojom::CSSInjection::Operation
ProgrammaticScriptInjector::GetCSSInjectionOperation() const {
  DCHECK(params_->injection->is_css());
  return params_->injection->get_css()->operation;
}

blink::mojom::WantResultOption ProgrammaticScriptInjector::ExpectsResults()
    const {
  DCHECK(params_->injection->is_js());
  return params_->injection->get_js()->wants_result;
}

blink::mojom::PromiseResultOption
ProgrammaticScriptInjector::ShouldWaitForPromise() const {
  DCHECK(params_->injection->is_js());
  return params_->injection->get_js()->wait_for_promise;
}

bool ProgrammaticScriptInjector::ShouldInjectJs(
    mojom::RunLocation run_location,
    const std::set<std::string>& executing_scripts) const {
  return params_->run_at == run_location && params_->injection->is_js();
}

bool ProgrammaticScriptInjector::ShouldInjectOrRemoveCss(
    mojom::RunLocation run_location,
    const std::set<std::string>& injected_stylesheets) const {
  return params_->run_at == run_location && params_->injection->is_css();
}

PermissionsData::PageAccess ProgrammaticScriptInjector::CanExecuteOnFrame(
    const InjectionHost* injection_host,
    blink::WebLocalFrame* frame,
    int tab_id) {
  // Note: we calculate url_ now and not in the constructor because we don't
  // have the URL at that point when loads start. The browser issues the request
  // and only when it has a response does the renderer see the provisional data
  // source which the method below uses.
  url_ = ScriptContext::GetDocumentLoaderURLForFrame(frame);
  if (url_.SchemeIs(url::kAboutScheme)) {
    origin_for_about_error_ = frame->GetSecurityOrigin().ToString().Utf8();
  }
  GURL effective_document_url =
      ScriptContext::GetEffectiveDocumentURLForInjection(
          frame, frame->GetDocument().Url(),
          params_->match_origin_as_fallback_behavior);
  if (params_->is_web_view) {
    if (!frame->IsOutermostMainFrame()) {
      // This is a subframe inside <webview>, so allow it.
      return PermissionsData::PageAccess::kAllowed;
    }

    return effective_document_url == params_->webview_src
               ? PermissionsData::PageAccess::kAllowed
               : PermissionsData::PageAccess::kDenied;
  }
  DCHECK_EQ(injection_host->id().type, mojom::HostID::HostType::kExtensions);

  return injection_host->CanExecuteOnFrame(
      effective_document_url,
      content::RenderFrame::FromWebFrame(frame),
      tab_id,
      true /* is_declarative */);
}

std::vector<blink::WebScriptSource> ProgrammaticScriptInjector::GetJsSources(
    mojom::RunLocation run_location,
    std::set<std::string>* executing_scripts,
    size_t* num_injected_js_scripts) const {
  DCHECK_EQ(params_->run_at, run_location);
  DCHECK(params_->injection->is_js());

  auto& js_injection = params_->injection->get_js();
  std::vector<blink::WebScriptSource> sources;
  sources.reserve(js_injection->sources.size());
  for (const auto& source : js_injection->sources) {
    sources.emplace_back(blink::WebString::FromUTF8(source->code),
                         source->script_url);
  }

  return sources;
}

std::vector<ScriptInjector::CSSSource>
ProgrammaticScriptInjector::GetCssSources(
    mojom::RunLocation run_location,
    std::set<std::string>* injected_stylesheets,
    size_t* num_injected_stylesheets) const {
  DCHECK_EQ(params_->run_at, run_location);
  DCHECK(params_->injection->is_css());

  auto& css_injection = params_->injection->get_css();
  std::vector<CSSSource> sources;
  sources.reserve(css_injection->sources.size());
  for (const auto& source : css_injection->sources) {
    blink::WebStyleSheetKey style_sheet_key;
    if (source->key)
      style_sheet_key = blink::WebString::FromASCII(*source->key);
    sources.push_back(
        CSSSource{blink::WebString::FromUTF8(source->code), style_sheet_key});
  }

  return sources;
}

void ProgrammaticScriptInjector::OnInjectionComplete(
    std::optional<base::Value> execution_result,
    mojom::RunLocation run_location) {
  DCHECK(!result_.has_value());
  result_ = std::move(execution_result);
  Finish(std::string());
}

void ProgrammaticScriptInjector::OnWillNotInject(InjectFailureReason reason) {
  std::string error;
  switch (reason) {
    case NOT_ALLOWED:
      if (!CanShowUrlInError()) {
        error = manifest_errors::kCannotAccessPage;
      } else if (!origin_for_about_error_.empty()) {
        error = ErrorUtils::FormatErrorMessage(
            manifest_errors::kCannotAccessAboutUrl, url_.spec(),
            origin_for_about_error_);
      } else {
        error = ErrorUtils::FormatErrorMessage(
            manifest_errors::kCannotAccessPageWithUrl, url_.spec());
      }
      break;
    case EXTENSION_REMOVED:  // no special error here.
    case WONT_INJECT:
      break;
  }
  Finish(error);
}

bool ProgrammaticScriptInjector::CanShowUrlInError() const {
  if (params_->host_id->type != mojom::HostID::HostType::kExtensions)
    return false;
  const Extension* extension =
      RendererExtensionRegistry::Get()->GetByID(params_->host_id->id);
  if (!extension)
    return false;
  return extension->permissions_data()->active_permissions().HasAPIPermission(
      mojom::APIPermissionID::kTab);
}

void ProgrammaticScriptInjector::Finish(const std::string& error) {
  DCHECK(!finished_);
  finished_ = true;

  if (callback_)
    std::move(callback_).Run(error, url_, std::move(result_));
}

}  // namespace extensions