| // 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 "base/task_scheduler/post_task.h" |
| #include "base/threading/thread_restrictions.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::GetFileURLAndMaybeLocalizeInBackground( |
| const std::string& extension_id, |
| const base::FilePath& extension_path, |
| const std::string& extension_default_locale, |
| bool might_require_localization, |
| std::string* data) { |
| base::AssertBlockingAllowed(); |
| |
| // TODO(devlin): FilePathToFileURL() doesn't need to be done on a blocking |
| // task runner, so we could do that on the UI thread and then avoid the hop |
| // if we don't need localization. |
| file_url_ = net::FilePathToFileURL(resource_.GetFilePath()); |
| |
| 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)); |
| |
| std::string error; |
| MessageBundle::ReplaceMessagesWithExternalDictionary(*localization_messages, |
| data, &error); |
| } |
| |
| std::unique_ptr<std::string> |
| ExecuteCodeFunction::GetFileURLAndLocalizeComponentResourceInBackground( |
| std::unique_ptr<std::string> data, |
| const std::string& extension_id, |
| const base::FilePath& extension_path, |
| const std::string& extension_default_locale, |
| bool might_require_localization) { |
| base::AssertBlockingAllowed(); |
| GetFileURLAndMaybeLocalizeInBackground( |
| extension_id, extension_path, extension_default_locale, |
| 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(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; |
| |
| ScriptExecutor::ScriptType script_type = ScriptExecutor::JAVASCRIPT; |
| if (ShouldInsertCSS()) |
| script_type = ScriptExecutor::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_, script_type, code_string, frame_scope, frame_id, |
| match_about_blank, run_at, ScriptExecutor::ISOLATED_WORLD, |
| IsWebView() ? ScriptExecutor::WEB_VIEW_PROCESS |
| : ScriptExecutor::DEFAULT_PROCESS, |
| GetWebViewSrc(), file_url_, user_gesture(), css_origin, |
| has_callback() ? ScriptExecutor::JSON_SERIALIZED_RESULT |
| : ScriptExecutor::NO_RESULT, |
| base::Bind(&ExecuteCodeFunction::OnExecuteCodeFinished, this)); |
| return true; |
| } |
| |
| bool ExecuteCodeFunction::HasPermission() { |
| 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()) { |
| return RespondNow(Error(kCSSOriginForNonCSSError)); |
| } |
| |
| std::string error; |
| if (!CanExecuteScriptOnPage(&error)) |
| return RespondNow(Error(error)); |
| |
| if (details_->code) { |
| if (!Execute(*details_->code, &error)) |
| return RespondNow(Error(error)); |
| return did_respond() ? AlreadyResponded() : RespondLater(); |
| } |
| |
| DCHECK(details_->file); |
| if (!LoadFile(*details_->file, &error)) |
| return RespondNow(Error(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; |
| } |
| |
| 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); |
| // TODO(lazyboy): |extension_id| should not be empty(), turn this into a |
| // DCHECK. |
| bool might_require_localization = ShouldInsertCSS() && !extension_id.empty(); |
| |
| int resource_id; |
| 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)) { |
| base::StringPiece resource = |
| ui::ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id); |
| std::unique_ptr<std::string> data( |
| new std::string(resource.data(), resource.size())); |
| |
| base::PostTaskWithTraitsAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, |
| base::BindOnce(&ExecuteCodeFunction:: |
| GetFileURLAndLocalizeComponentResourceInBackground, |
| this, base::Passed(std::move(data)), extension_id, |
| extension_path, extension_default_locale, |
| 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::GetFileURLAndMaybeLocalizeInBackground, this, |
| extension_id, extension_path, extension_default_locale, |
| 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 doesn't have a result argument. |
| Respond(ShouldInsertCSS() ? NoArguments() |
| : OneArgument(result.CreateDeepCopy())); |
| } |
| |
| } // namespace extensions |
| |
| #endif // EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_ |