blob: 85af470dd20459e9eb812e77d63045ff3d339145 [file] [log] [blame]
// 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.
#ifndef EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_
#define EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_
#include "extensions/browser/api/execute_code_function.h"
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/load_and_localize_file.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/mojom/css_origin.mojom-shared.h"
#include "extensions/common/mojom/run_location.mojom-shared.h"
#include "extensions/common/utils/content_script_utils.h"
#include "extensions/common/utils/extension_types_utils.h"
namespace {
// Error messages
const char kNoCodeOrFileToExecuteError[] = "No source code or file specified.";
const char kMoreThanOneValuesError[] =
"Code and file should not be specified "
"at the same time in the second argument.";
const char kBadFileEncodingError[] =
"Could not load file '*' for content script. It isn't UTF-8 encoded.";
const char kCSSOriginForNonCSSError[] =
"CSS origin should be specified only for CSS code.";
}
namespace extensions {
using api::extension_types::InjectDetails;
ExecuteCodeFunction::ExecuteCodeFunction() {
}
ExecuteCodeFunction::~ExecuteCodeFunction() {
}
void ExecuteCodeFunction::DidLoadAndLocalizeFile(
const std::string& file,
std::vector<std::unique_ptr<std::string>> data,
std::optional<std::string> load_error) {
if (load_error) {
// TODO(viettrungluu): bug: there's no particular reason the path should be
// UTF-8, in which case this may fail.
Respond(Error(std::move(*load_error)));
return;
}
DCHECK_EQ(1u, data.size());
auto& file_data = data.front();
if (!base::IsStringUTF8(*file_data)) {
Respond(Error(ErrorUtils::FormatErrorMessage(kBadFileEncodingError, file)));
return;
}
std::string error;
if (!Execute(*file_data, &error))
Respond(Error(std::move(error)));
// If Execute() succeeds, the function will respond in
// OnExecuteCodeFinished().
}
bool ExecuteCodeFunction::Execute(const std::string& code_string,
std::string* error) {
ScriptExecutor* executor = GetScriptExecutor(error);
if (!executor)
return false;
// TODO(lazyboy): Set |error|?
if (!extension() && !IsWebView())
return false;
DCHECK(!(ShouldInsertCSS() && ShouldRemoveCSS()));
ScriptExecutor::FrameScope frame_scope =
details_->all_frames.value_or(false) ? ScriptExecutor::INCLUDE_SUB_FRAMES
: ScriptExecutor::SPECIFIED_FRAMES;
root_frame_id_ =
details_->frame_id.value_or(ExtensionApiFrameIdMap::kTopFrameId);
ScriptExecutor::MatchAboutBlank match_about_blank =
details_->match_about_blank.value_or(false)
? ScriptExecutor::MATCH_ABOUT_BLANK
: ScriptExecutor::DONT_MATCH_ABOUT_BLANK;
mojom::RunLocation run_at = ConvertRunLocation(details_->run_at);
mojom::CSSOrigin css_origin = mojom::CSSOrigin::kAuthor;
switch (details_->css_origin) {
case api::extension_types::CSSOrigin::kNone:
case api::extension_types::CSSOrigin::kAuthor:
css_origin = mojom::CSSOrigin::kAuthor;
break;
case api::extension_types::CSSOrigin::kUser:
css_origin = mojom::CSSOrigin::kUser;
break;
}
mojom::CodeInjectionPtr injection;
bool is_css_injection = ShouldInsertCSS() || ShouldRemoveCSS();
if (is_css_injection) {
std::optional<std::string> injection_key;
if (host_id_.type == mojom::HostID::HostType::kExtensions) {
injection_key = ScriptExecutor::GenerateInjectionKey(
host_id_, script_url_, code_string);
}
mojom::CSSInjection::Operation operation =
ShouldInsertCSS() ? mojom::CSSInjection::Operation::kAdd
: mojom::CSSInjection::Operation::kRemove;
std::vector<mojom::CSSSourcePtr> sources;
sources.push_back(
mojom::CSSSource::New(code_string, std::move(injection_key)));
injection = mojom::CodeInjection::NewCss(
mojom::CSSInjection::New(std::move(sources), css_origin, operation));
} else {
bool wants_result = has_callback();
std::vector<mojom::JSSourcePtr> sources;
sources.push_back(mojom::JSSource::New(code_string, script_url_));
// tabs.executeScript does not support waiting for promises (only
// scripting.executeScript does).
injection = mojom::CodeInjection::NewJs(mojom::JSInjection::New(
std::move(sources), mojom::ExecutionWorld::kIsolated,
wants_result ? blink::mojom::WantResultOption::kWantResult
: blink::mojom::WantResultOption::kNoResult,
user_gesture() ? blink::mojom::UserActivationOption::kActivate
: blink::mojom::UserActivationOption::kDoNotActivate,
blink::mojom::PromiseResultOption::kDoNotWait));
}
executor->ExecuteScript(
host_id_, std::move(injection), frame_scope, {root_frame_id_},
match_about_blank, run_at,
IsWebView() ? ScriptExecutor::WEB_VIEW_PROCESS
: ScriptExecutor::DEFAULT_PROCESS,
GetWebViewSrc(),
base::BindOnce(&ExecuteCodeFunction::OnExecuteCodeFinished, this));
return true;
}
ExtensionFunction::ResponseAction ExecuteCodeFunction::Run() {
InitResult init_result = Init();
EXTENSION_FUNCTION_VALIDATE(init_result != VALIDATION_FAILURE);
if (init_result == FAILURE)
return RespondNow(Error(init_error_.value_or(kUnknownErrorDoNotUse)));
if (!details_->code && !details_->file)
return RespondNow(Error(kNoCodeOrFileToExecuteError));
if (details_->code && details_->file)
return RespondNow(Error(kMoreThanOneValuesError));
if (details_->css_origin != api::extension_types::CSSOrigin::kNone &&
!ShouldInsertCSS() && !ShouldRemoveCSS()) {
return RespondNow(Error(kCSSOriginForNonCSSError));
}
std::string error;
if (!CanExecuteScriptOnPage(&error))
return RespondNow(Error(std::move(error)));
if (details_->code) {
if (!IsWebView() && extension()) {
ExtensionsBrowserClient::Get()->NotifyExtensionApiTabExecuteScript(
browser_context(), extension_id(), *details_->code);
}
if (!Execute(*details_->code, &error))
return RespondNow(Error(std::move(error)));
return did_respond() ? AlreadyResponded() : RespondLater();
}
DCHECK(details_->file);
if (!LoadFile(*details_->file, &error))
return RespondNow(Error(std::move(error)));
// LoadFile will respond asynchronously later.
return RespondLater();
}
bool ExecuteCodeFunction::LoadFile(const std::string& file,
std::string* error) {
ExtensionResource resource = extension()->GetResource(file);
if (resource.extension_root().empty() || resource.relative_path().empty()) {
*error = kNoCodeOrFileToExecuteError;
return false;
}
script_url_ = extension()->GetResourceURL(file);
bool might_require_localization = ShouldInsertCSS() || ShouldRemoveCSS();
std::string relative_path = resource.relative_path().AsUTF8Unsafe();
LoadAndLocalizeResources(
*extension(), {std::move(resource)}, might_require_localization,
script_parsing::GetMaxScriptLength(),
base::BindOnce(&ExecuteCodeFunction::DidLoadAndLocalizeFile, this,
relative_path));
return true;
}
void ExecuteCodeFunction::OnExecuteCodeFinished(
std::vector<ScriptExecutor::FrameResult> results) {
DCHECK(!results.empty());
auto root_frame_result = base::ranges::find(
results, root_frame_id_, &ScriptExecutor::FrameResult::frame_id);
DCHECK(root_frame_result != results.end());
// We just error out if we never injected in the root frame.
// TODO(devlin): That's a bit odd, because other injections may have
// succeeded. It seems like it might be worth passing back the values
// anyway.
if (!root_frame_result->error.empty()) {
// If the frame never responded (e.g. the frame was removed or didn't
// exist), we provide a different error message for backwards
// compatibility.
if (!root_frame_result->frame_responded) {
root_frame_result->error =
root_frame_id_ == ExtensionApiFrameIdMap::kTopFrameId
? "The tab was closed."
: "The frame was removed.";
}
Respond(Error(std::move(root_frame_result->error)));
return;
}
if (ShouldInsertCSS() || ShouldRemoveCSS()) {
// insertCSS and removeCSS don't have a result argument.
Respond(NoArguments());
return;
}
// Place the root frame result at the beginning.
std::iter_swap(root_frame_result, results.begin());
base::Value::List result_list;
for (auto& result : results) {
if (result.error.empty())
result_list.Append(std::move(result.value));
}
Respond(WithArguments(std::move(result_list)));
}
} // namespace extensions
#endif // EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_