blob: 538cabd35fc89e200ec013d3aba8c97dfc53d29c [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.
#include "extensions/renderer/user_script_injector.h"
#include <tuple>
#include <vector>
#include "base/check.h"
#include "base/lazy_instance.h"
#include "components/guest_view/buildflags/buildflags.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/grit/extensions_renderer_resources.h"
#include "extensions/renderer/extension_frame_helper.h"
#include "extensions/renderer/injection_host.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/scripts_run_info.h"
#include "ipc/ipc_sync_channel.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.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"
#include "ui/base/resource/resource_bundle.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_GUEST_VIEW)
#include "extensions/common/mojom/guest_view.mojom.h"
#endif
namespace extensions {
namespace {
#if BUILDFLAG(ENABLE_GUEST_VIEW)
struct RoutingInfoKey {
blink::LocalFrameToken frame_token;
std::string script_id;
RoutingInfoKey(const blink::LocalFrameToken& frame_token,
std::string script_id)
: frame_token(frame_token), script_id(std::move(script_id)) {}
bool operator<(const RoutingInfoKey& other) const {
return std::tie(frame_token, script_id) <
std::tie(other.frame_token, other.script_id);
}
};
using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
// A map records whether a given |script_id| from a webview-added user script
// is allowed to inject on the render of given |routing_id|.
// Once a script is added, the decision of whether or not allowed to inject
// won't be changed.
// After removed by the webview, the user scipt will also be removed
// from the render. Therefore, there won't be any query from the same
// |script_id| and |routing_id| pair.
base::LazyInstance<RoutingInfoMap>::DestructorAtExit g_routing_info_map =
LAZY_INSTANCE_INITIALIZER;
#endif
// Greasemonkey API source that is injected with the scripts.
struct GreasemonkeyApiJsString {
GreasemonkeyApiJsString();
blink::WebScriptSource GetSource() const;
private:
blink::WebString source_;
};
// The below constructor, monstrous as it is, just makes a WebScriptSource from
// the GreasemonkeyApiJs resource.
GreasemonkeyApiJsString::GreasemonkeyApiJsString() {
std::string greasemonky_api_js(
ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
IDR_GREASEMONKEY_API_JS));
source_ = blink::WebString::FromUTF8(greasemonky_api_js);
}
blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
return blink::WebScriptSource(source_);
}
base::LazyInstance<GreasemonkeyApiJsString>::Leaky g_greasemonkey_api =
LAZY_INSTANCE_INITIALIZER;
bool ShouldInjectScripts(const UserScript::ContentList& script_contents,
const std::set<std::string>& injected_files) {
for (const std::unique_ptr<UserScript::Content>& content : script_contents) {
// Check if the script is already injected.
if (injected_files.count(content->url().path()) == 0) {
return true;
}
}
return false;
}
} // namespace
UserScriptInjector::UserScriptInjector(const UserScript* script,
UserScriptSet* script_list,
bool is_declarative)
: script_(script),
user_script_set_(script_list),
script_id_(script_->id()),
host_id_(script_->host_id()),
is_declarative_(is_declarative) {
user_script_set_observation_.Observe(script_list);
}
UserScriptInjector::~UserScriptInjector() {
}
void UserScriptInjector::OnUserScriptsUpdated() {
// When user scripts are updated, this means the host causing this injection
// has changed. All old script pointers are invalidated and this injection
// will be removed as there's no guarantee the backing script still exists.
script_ = nullptr;
}
void UserScriptInjector::OnUserScriptSetDestroyed() {
user_script_set_observation_.Reset();
// Invalidate the script pointer as the UserScriptSet which this script
// belongs to has been destroyed.
script_ = nullptr;
}
mojom::InjectionType UserScriptInjector::script_type() const {
return mojom::InjectionType::kContentScript;
}
blink::mojom::UserActivationOption UserScriptInjector::IsUserGesture() const {
return blink::mojom::UserActivationOption::kDoNotActivate;
}
mojom::ExecutionWorld UserScriptInjector::GetExecutionWorld() const {
return script_->execution_world();
}
const std::optional<std::string>& UserScriptInjector::GetExecutionWorldId()
const {
return script_->world_id();
}
blink::mojom::WantResultOption UserScriptInjector::ExpectsResults() const {
return blink::mojom::WantResultOption::kNoResult;
}
blink::mojom::PromiseResultOption UserScriptInjector::ShouldWaitForPromise()
const {
return blink::mojom::PromiseResultOption::kDoNotWait;
}
mojom::CSSOrigin UserScriptInjector::GetCssOrigin() const {
return mojom::CSSOrigin::kAuthor;
}
mojom::CSSInjection::Operation UserScriptInjector::GetCSSInjectionOperation()
const {
DCHECK(script_);
DCHECK(!script_->css_scripts().empty());
return mojom::CSSInjection::Operation::kAdd;
}
bool UserScriptInjector::ShouldInjectJs(
mojom::RunLocation run_location,
const std::set<std::string>& executing_scripts) const {
return script_ && script_->run_location() == run_location &&
!script_->js_scripts().empty() &&
ShouldInjectScripts(script_->js_scripts(), executing_scripts);
}
bool UserScriptInjector::ShouldInjectOrRemoveCss(
mojom::RunLocation run_location,
const std::set<std::string>& injected_stylesheets) const {
return script_ && run_location == mojom::RunLocation::kDocumentStart &&
!script_->css_scripts().empty() &&
ShouldInjectScripts(script_->css_scripts(), injected_stylesheets);
}
PermissionsData::PageAccess UserScriptInjector::CanExecuteOnFrame(
const InjectionHost* injection_host,
blink::WebLocalFrame* web_frame,
int tab_id) {
// There is no harm in allowing the injection when the script is gone,
// because there is nothing to inject.
if (!script_)
return PermissionsData::PageAccess::kAllowed;
#if BUILDFLAG(ENABLE_GUEST_VIEW)
if (script_->consumer_instance_type() ==
UserScript::ConsumerInstanceType::WEBVIEW) {
auto* render_frame = content::RenderFrame::FromWebFrame(web_frame);
auto token = web_frame->GetLocalFrameToken();
RoutingInfoKey key(token, script_->id());
RoutingInfoMap& map = g_routing_info_map.Get();
auto iter = map.find(key);
bool allowed = false;
if (iter != map.end()) {
allowed = iter->second;
} else {
mojo::AssociatedRemote<mojom::GuestView> remote;
render_frame->GetRemoteAssociatedInterfaces()->GetInterface(&remote);
// Perform a sync mojo call to the browser to check if this is allowed.
// This is not ideal, but is mitigated by the fact that this is only done
// for webviews, and then only once per host.
// TODO(hanxi): Find a more efficient way to do this.
remote->CanExecuteContentScript(script_->id(), &allowed);
map.insert(std::pair<RoutingInfoKey, bool>(key, allowed));
}
return allowed ? PermissionsData::PageAccess::kAllowed
: PermissionsData::PageAccess::kDenied;
}
#endif
GURL effective_document_url =
ScriptContext::GetEffectiveDocumentURLForInjection(
web_frame, web_frame->GetDocument().Url(),
script_->match_origin_as_fallback());
return injection_host->CanExecuteOnFrame(
effective_document_url,
content::RenderFrame::FromWebFrame(web_frame),
tab_id,
is_declarative_);
}
std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
mojom::RunLocation run_location,
std::set<std::string>* executing_scripts,
size_t* num_injected_js_scripts) const {
DCHECK(script_);
std::vector<blink::WebScriptSource> sources;
DCHECK_EQ(script_->run_location(), run_location);
const UserScript::ContentList& js_scripts = script_->js_scripts();
sources.reserve(js_scripts.size() +
(script_->emulate_greasemonkey() ? 1 : 0));
// Emulate Greasemonkey API for scripts that were converted to extension
// user scripts.
if (script_->emulate_greasemonkey())
sources.push_back(g_greasemonkey_api.Get().GetSource());
for (const std::unique_ptr<UserScript::Content>& file : js_scripts) {
const GURL& script_url = file->url();
// Check if the script is already injected.
if (executing_scripts->count(script_url.path()) != 0)
continue;
sources.push_back(blink::WebScriptSource(
user_script_set_->GetJsSource(*file, script_->emulate_greasemonkey()),
script_url));
++*num_injected_js_scripts;
executing_scripts->insert(script_url.path());
}
return sources;
}
std::vector<ScriptInjector::CSSSource> UserScriptInjector::GetCssSources(
mojom::RunLocation run_location,
std::set<std::string>* injected_stylesheets,
size_t* num_injected_stylesheets) const {
DCHECK(script_);
DCHECK_EQ(mojom::RunLocation::kDocumentStart, run_location);
std::vector<CSSSource> sources;
const UserScript::ContentList& css_scripts = script_->css_scripts();
sources.reserve(css_scripts.size());
for (const std::unique_ptr<UserScript::Content>& file :
script_->css_scripts()) {
const std::string& stylesheet_path = file->url().path();
// Check if the stylesheet is already injected.
if (injected_stylesheets->count(stylesheet_path) != 0)
continue;
sources.push_back(CSSSource{user_script_set_->GetCssSource(*file),
blink::WebStyleSheetKey()});
injected_stylesheets->insert(stylesheet_path);
}
*num_injected_stylesheets += sources.size();
return sources;
}
void UserScriptInjector::OnInjectionComplete(
std::optional<base::Value> execution_result,
mojom::RunLocation run_location) {}
void UserScriptInjector::OnWillNotInject(InjectFailureReason reason) {}
} // namespace extensions