// 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. #include "extensions/renderer/user_script_injector.h" #include #include #include "base/check.h" #include "base/lazy_instance.h" #include "base/no_destructor.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/mojom/guest_view.mojom.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/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" namespace extensions { namespace { struct RoutingInfoKey { int routing_id; std::string script_id; RoutingInfoKey(int routing_id, std::string script_id) : routing_id(routing_id), script_id(std::move(script_id)) {} bool operator<(const RoutingInfoKey& other) const { return std::tie(routing_id, script_id) < std::tie(other.routing_id, other.script_id); } }; using RoutingInfoMap = std::map; // 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::DestructorAtExit g_routing_info_map = LAZY_INSTANCE_INITIALIZER; // 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::Leaky g_greasemonkey_api = LAZY_INSTANCE_INITIALIZER; bool ShouldInjectScripts(const UserScript::FileList& scripts, const std::set& injected_files) { for (const std::unique_ptr& file : scripts) { // Check if the script is already injected. if (injected_files.count(file->url().path()) == 0) { return true; } } return false; } mojom::GuestView* GetGuestView() { static base::NoDestructor> guest_view; if (!*guest_view) { content::RenderThread::Get()->GetChannel()->GetRemoteAssociatedInterface( guest_view.get()); } return guest_view->get(); } } // 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(); } 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& 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& 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 (script_->consumer_instance_type() == UserScript::ConsumerInstanceType::WEBVIEW) { int routing_id = content::RenderFrame::FromWebFrame(web_frame)->GetRoutingID(); RoutingInfoKey key(routing_id, 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 { // 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. auto* guest_view = GetGuestView(); if (guest_view) { guest_view->CanExecuteContentScript(routing_id, script_->id(), &allowed); } map.insert(std::pair(key, allowed)); } return allowed ? PermissionsData::PageAccess::kAllowed : PermissionsData::PageAccess::kDenied; } 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 UserScriptInjector::GetJsSources( mojom::RunLocation run_location, std::set* executing_scripts, size_t* num_injected_js_scripts) const { DCHECK(script_); std::vector sources; DCHECK_EQ(script_->run_location(), run_location); const UserScript::FileList& 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& 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 UserScriptInjector::GetCssSources( mojom::RunLocation run_location, std::set* injected_stylesheets, size_t* num_injected_stylesheets) const { DCHECK(script_); DCHECK_EQ(mojom::RunLocation::kDocumentStart, run_location); std::vector sources; const UserScript::FileList& css_scripts = script_->css_scripts(); sources.reserve(css_scripts.size()); for (const std::unique_ptr& 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::unique_ptr execution_result, mojom::RunLocation run_location) {} void UserScriptInjector::OnWillNotInject(InjectFailureReason reason) {} } // namespace extensions