blob: 3d5e4ac1e4dc09b367a108b9b9633a4e00ec1b7f [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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 <utility>
#include "base/bind.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "extensions/browser/component_extension_resource_manager.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/file_reader.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/message_bundle.h"
#include "net/base/filename_util.h"
#include "ui/base/resource/resource_bundle.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 kLoadFileError[] = "Failed to load file: \"*\". ";
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::MaybeLocalizeInBackground(
const std::string& extension_id,
const base::FilePath& extension_path,
const std::string& extension_default_locale,
extension_l10n_util::GzippedMessagesPermission gzip_permission,
bool might_require_localization,
std::string* data) {
// TODO(karandeepb): Limit scope of ScopedBlockingCall.
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
// TODO(devlin): Don't call the localization function if no localization is
// potentially required.
if (!might_require_localization)
return;
bool needs_message_substituion =
data->find(extensions::MessageBundle::kMessageBegin) != std::string::npos;
if (!needs_message_substituion)
return;
std::unique_ptr<SubstitutionMap> localization_messages(
file_util::LoadMessageBundleSubstitutionMap(extension_path, extension_id,
extension_default_locale,
gzip_permission));
std::string error;
MessageBundle::ReplaceMessagesWithExternalDictionary(*localization_messages,
data, &error);
}
std::unique_ptr<std::string>
ExecuteCodeFunction::LocalizeComponentResourceInBackground(
std::unique_ptr<std::string> data,
const std::string& extension_id,
const base::FilePath& extension_path,
const std::string& extension_default_locale,
extension_l10n_util::GzippedMessagesPermission gzip_permission,
bool might_require_localization) {
MaybeLocalizeInBackground(extension_id, extension_path,
extension_default_locale, gzip_permission,
might_require_localization, data.get());
return data;
}
void ExecuteCodeFunction::DidLoadAndLocalizeFile(
const std::string& file,
bool success,
std::unique_ptr<std::string> data) {
if (!success) {
// TODO(viettrungluu): bug: there's no particular reason the path should be
// UTF-8, in which case this may fail.
Respond(Error(ErrorUtils::FormatErrorMessage(kLoadFileError, file)));
return;
}
if (!base::IsStringUTF8(*data)) {
Respond(Error(ErrorUtils::FormatErrorMessage(kBadFileEncodingError, file)));
return;
}
std::string error;
if (!Execute(*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()));
auto action_type = UserScript::ActionType::ADD_JAVASCRIPT;
if (ShouldInsertCSS())
action_type = UserScript::ActionType::ADD_CSS;
else if (ShouldRemoveCSS())
action_type = UserScript::ActionType::REMOVE_CSS;
ScriptExecutor::FrameScope frame_scope =
details_->all_frames.get() && *details_->all_frames
? ScriptExecutor::INCLUDE_SUB_FRAMES
: ScriptExecutor::SINGLE_FRAME;
int frame_id = details_->frame_id.get() ? *details_->frame_id
: ExtensionApiFrameIdMap::kTopFrameId;
ScriptExecutor::MatchAboutBlank match_about_blank =
details_->match_about_blank.get() && *details_->match_about_blank
? ScriptExecutor::MATCH_ABOUT_BLANK
: ScriptExecutor::DONT_MATCH_ABOUT_BLANK;
UserScript::RunLocation run_at = UserScript::UNDEFINED;
switch (details_->run_at) {
case api::extension_types::RUN_AT_NONE:
case api::extension_types::RUN_AT_DOCUMENT_IDLE:
run_at = UserScript::DOCUMENT_IDLE;
break;
case api::extension_types::RUN_AT_DOCUMENT_START:
run_at = UserScript::DOCUMENT_START;
break;
case api::extension_types::RUN_AT_DOCUMENT_END:
run_at = UserScript::DOCUMENT_END;
break;
}
CHECK_NE(UserScript::UNDEFINED, run_at);
base::Optional<CSSOrigin> css_origin;
if (details_->css_origin == api::extension_types::CSS_ORIGIN_USER)
css_origin = CSS_ORIGIN_USER;
else if (details_->css_origin == api::extension_types::CSS_ORIGIN_AUTHOR)
css_origin = CSS_ORIGIN_AUTHOR;
executor->ExecuteScript(
host_id_, action_type, code_string, frame_scope, frame_id,
match_about_blank, run_at,
IsWebView() ? ScriptExecutor::WEB_VIEW_PROCESS
: ScriptExecutor::DEFAULT_PROCESS,
GetWebViewSrc(), script_url_, user_gesture(), css_origin,
has_callback() ? ScriptExecutor::JSON_SERIALIZED_RESULT
: ScriptExecutor::NO_RESULT,
base::Bind(&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::CSS_ORIGIN_NONE &&
!ShouldInsertCSS() && !ShouldRemoveCSS()) {
return RespondNow(Error(kCSSOriginForNonCSSError));
}
std::string error;
if (!CanExecuteScriptOnPage(&error))
return RespondNow(Error(std::move(error)));
if (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) {
resource_ = extension()->GetResource(file);
if (resource_.extension_root().empty() || resource_.relative_path().empty()) {
*error = kNoCodeOrFileToExecuteError;
return false;
}
script_url_ = extension()->GetResourceURL(file);
const std::string& extension_id = extension()->id();
base::FilePath extension_path = extension()->path();
std::string extension_default_locale;
extension()->manifest()->GetString(manifest_keys::kDefaultLocale,
&extension_default_locale);
auto gzip_permission =
extension_l10n_util::GetGzippedMessagesPermissionForExtension(
extension());
// TODO(lazyboy): |extension_id| should not be empty(), turn this into a
// DCHECK.
bool might_require_localization =
(ShouldInsertCSS() || ShouldRemoveCSS()) && !extension_id.empty();
int resource_id = 0;
const ComponentExtensionResourceManager*
component_extension_resource_manager =
ExtensionsBrowserClient::Get()
->GetComponentExtensionResourceManager();
if (component_extension_resource_manager &&
component_extension_resource_manager->IsComponentExtensionResource(
resource_.extension_root(), resource_.relative_path(),
&resource_id)) {
auto data = std::make_unique<std::string>(
ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
resource_id));
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(
&ExecuteCodeFunction::LocalizeComponentResourceInBackground, this,
std::move(data), extension_id, extension_path,
extension_default_locale, gzip_permission,
might_require_localization),
base::BindOnce(&ExecuteCodeFunction::DidLoadAndLocalizeFile, this,
resource_.relative_path().AsUTF8Unsafe(),
true /* We assume this call always succeeds */));
} else {
FileReader::OptionalFileSequenceTask get_file_and_l10n_callback =
base::BindOnce(&ExecuteCodeFunction::MaybeLocalizeInBackground, this,
extension_id, extension_path, extension_default_locale,
gzip_permission, might_require_localization);
auto file_reader = base::MakeRefCounted<FileReader>(
resource_, std::move(get_file_and_l10n_callback),
base::BindOnce(&ExecuteCodeFunction::DidLoadAndLocalizeFile, this,
resource_.relative_path().AsUTF8Unsafe()));
file_reader->Start();
}
return true;
}
void ExecuteCodeFunction::OnExecuteCodeFinished(const std::string& error,
const GURL& on_url,
const base::ListValue& result) {
if (!error.empty()) {
Respond(Error(error));
return;
}
// insertCSS and removeCSS don't have a result argument.
Respond(ShouldInsertCSS() || ShouldRemoveCSS()
? NoArguments()
: OneArgument(result.CreateDeepCopy()));
}
} // namespace extensions
#endif // EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_