|  | // 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 <vector> | 
|  |  | 
|  | #include "base/lazy_instance.h" | 
|  | #include "content/public/common/url_constants.h" | 
|  | #include "content/public/renderer/render_thread.h" | 
|  | #include "content/public/renderer/render_frame.h" | 
|  | #include "content/public/renderer/render_view.h" | 
|  | #include "extensions/common/extension.h" | 
|  | #include "extensions/common/guest_view/extensions_guest_view_messages.h" | 
|  | #include "extensions/common/permissions/permissions_data.h" | 
|  | #include "extensions/renderer/injection_host.h" | 
|  | #include "extensions/renderer/script_context.h" | 
|  | #include "extensions/renderer/scripts_run_info.h" | 
|  | #include "grit/extensions_renderer_resources.h" | 
|  | #include "third_party/WebKit/public/web/WebDocument.h" | 
|  | #include "third_party/WebKit/public/web/WebLocalFrame.h" | 
|  | #include "third_party/WebKit/public/web/WebScriptSource.h" | 
|  | #include "ui/base/resource/resource_bundle.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | struct RoutingInfoKey { | 
|  | int routing_id; | 
|  | int script_id; | 
|  |  | 
|  | RoutingInfoKey(int routing_id, int script_id) | 
|  | : routing_id(routing_id), script_id(script_id) {} | 
|  |  | 
|  | bool operator<(const RoutingInfoKey& other) const { | 
|  | if (routing_id != other.routing_id) | 
|  | return routing_id < other.routing_id; | 
|  |  | 
|  | if (script_id != other.script_id) | 
|  | return script_id < other.script_id; | 
|  | return false;  // keys are equal. | 
|  | } | 
|  | }; | 
|  |  | 
|  | using RoutingInfoMap = std::map<RoutingInfoKey, bool>; | 
|  |  | 
|  | // These two strings are injected before and after the Greasemonkey API and | 
|  | // user script to wrap it in an anonymous scope. | 
|  | const char kUserScriptHead[] = "(function (unsafeWindow) {\n"; | 
|  | const char kUserScriptTail[] = "\n})(window);"; | 
|  |  | 
|  | // 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> g_routing_info_map = | 
|  | LAZY_INSTANCE_INITIALIZER; | 
|  |  | 
|  | // Greasemonkey API source that is injected with the scripts. | 
|  | struct GreasemonkeyApiJsString { | 
|  | GreasemonkeyApiJsString(); | 
|  | blink::WebScriptSource GetSource() const; | 
|  |  | 
|  | private: | 
|  | std::string source_; | 
|  | }; | 
|  |  | 
|  | // The below constructor, monstrous as it is, just makes a WebScriptSource from | 
|  | // the GreasemonkeyApiJs resource. | 
|  | GreasemonkeyApiJsString::GreasemonkeyApiJsString() | 
|  | : source_(ResourceBundle::GetSharedInstance() | 
|  | .GetRawDataResource(IDR_GREASEMONKEY_API_JS) | 
|  | .as_string()) { | 
|  | } | 
|  |  | 
|  | blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const { | 
|  | return blink::WebScriptSource(blink::WebString::fromUTF8(source_)); | 
|  | } | 
|  |  | 
|  | base::LazyInstance<GreasemonkeyApiJsString> g_greasemonkey_api = | 
|  | LAZY_INSTANCE_INITIALIZER; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | UserScriptInjector::UserScriptInjector(const UserScript* script, | 
|  | UserScriptSet* script_list, | 
|  | bool is_declarative) | 
|  | : script_(script), | 
|  | script_id_(script_->id()), | 
|  | host_id_(script_->host_id()), | 
|  | is_declarative_(is_declarative), | 
|  | user_script_set_observer_(this) { | 
|  | user_script_set_observer_.Add(script_list); | 
|  | } | 
|  |  | 
|  | UserScriptInjector::~UserScriptInjector() { | 
|  | } | 
|  |  | 
|  | void UserScriptInjector::OnUserScriptsUpdated( | 
|  | const std::set<HostID>& changed_hosts, | 
|  | const std::vector<UserScript*>& scripts) { | 
|  | // If the host causing this injection changed, then this injection | 
|  | // will be removed, and there's no guarantee the backing script still exists. | 
|  | if (changed_hosts.count(host_id_) > 0) | 
|  | return; | 
|  |  | 
|  | for (std::vector<UserScript*>::const_iterator iter = scripts.begin(); | 
|  | iter != scripts.end(); | 
|  | ++iter) { | 
|  | // We need to compare to |script_id_| (and not to script_->id()) because the | 
|  | // old |script_| may be deleted by now. | 
|  | if ((*iter)->id() == script_id_) { | 
|  | script_ = *iter; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | UserScript::InjectionType UserScriptInjector::script_type() const { | 
|  | return UserScript::CONTENT_SCRIPT; | 
|  | } | 
|  |  | 
|  | bool UserScriptInjector::ShouldExecuteInMainWorld() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool UserScriptInjector::IsUserGesture() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool UserScriptInjector::ExpectsResults() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool UserScriptInjector::ShouldInjectJs( | 
|  | UserScript::RunLocation run_location) const { | 
|  | return script_->run_location() == run_location && | 
|  | !script_->js_scripts().empty(); | 
|  | } | 
|  |  | 
|  | bool UserScriptInjector::ShouldInjectCss( | 
|  | UserScript::RunLocation run_location) const { | 
|  | return run_location == UserScript::DOCUMENT_START && | 
|  | !script_->css_scripts().empty(); | 
|  | } | 
|  |  | 
|  | PermissionsData::AccessType UserScriptInjector::CanExecuteOnFrame( | 
|  | const InjectionHost* injection_host, | 
|  | blink::WebLocalFrame* web_frame, | 
|  | int tab_id) const { | 
|  | GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL( | 
|  | web_frame, web_frame->document().url(), script_->match_about_blank()); | 
|  | PermissionsData::AccessType can_execute = injection_host->CanExecuteOnFrame( | 
|  | effective_document_url, | 
|  | content::RenderFrame::FromWebFrame(web_frame), | 
|  | tab_id, | 
|  | is_declarative_); | 
|  | if (script_->consumer_instance_type() != | 
|  | UserScript::ConsumerInstanceType::WEBVIEW || | 
|  | can_execute == PermissionsData::ACCESS_DENIED) | 
|  | return can_execute; | 
|  |  | 
|  | int routing_id = content::RenderView::FromWebView(web_frame->top()->view()) | 
|  | ->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 { | 
|  | // Send a SYNC IPC message 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. | 
|  | content::RenderThread::Get()->Send( | 
|  | new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync( | 
|  | routing_id, script_->id(), &allowed)); | 
|  | map.insert(std::pair<RoutingInfoKey, bool>(key, allowed)); | 
|  | } | 
|  |  | 
|  | return allowed ? PermissionsData::ACCESS_ALLOWED | 
|  | : PermissionsData::ACCESS_DENIED; | 
|  | } | 
|  |  | 
|  | std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources( | 
|  | UserScript::RunLocation run_location) const { | 
|  | DCHECK_EQ(script_->run_location(), run_location); | 
|  |  | 
|  | std::vector<blink::WebScriptSource> sources; | 
|  | const UserScript::FileList& js_scripts = script_->js_scripts(); | 
|  | bool is_standalone_or_emulate_greasemonkey = | 
|  | script_->is_standalone() || script_->emulate_greasemonkey(); | 
|  |  | 
|  | for (UserScript::FileList::const_iterator iter = js_scripts.begin(); | 
|  | iter != js_scripts.end(); | 
|  | ++iter) { | 
|  | std::string content = iter->GetContent().as_string(); | 
|  |  | 
|  | // We add this dumb function wrapper for standalone user script to | 
|  | // emulate what Greasemonkey does. | 
|  | // TODO(aa): I think that maybe "is_standalone" scripts don't exist | 
|  | // anymore. Investigate. | 
|  | if (is_standalone_or_emulate_greasemonkey) { | 
|  | content.insert(0, kUserScriptHead); | 
|  | content += kUserScriptTail; | 
|  | } | 
|  | sources.push_back(blink::WebScriptSource( | 
|  | blink::WebString::fromUTF8(content), iter->url())); | 
|  | } | 
|  |  | 
|  | // Emulate Greasemonkey API for scripts that were converted to extensions | 
|  | // and "standalone" user scripts. | 
|  | if (is_standalone_or_emulate_greasemonkey) | 
|  | sources.insert(sources.begin(), g_greasemonkey_api.Get().GetSource()); | 
|  |  | 
|  | return sources; | 
|  | } | 
|  |  | 
|  | std::vector<std::string> UserScriptInjector::GetCssSources( | 
|  | UserScript::RunLocation run_location) const { | 
|  | DCHECK_EQ(UserScript::DOCUMENT_START, run_location); | 
|  |  | 
|  | std::vector<std::string> sources; | 
|  | const UserScript::FileList& css_scripts = script_->css_scripts(); | 
|  | for (UserScript::FileList::const_iterator iter = css_scripts.begin(); | 
|  | iter != css_scripts.end(); | 
|  | ++iter) { | 
|  | sources.push_back(iter->GetContent().as_string()); | 
|  | } | 
|  | return sources; | 
|  | } | 
|  |  | 
|  | void UserScriptInjector::GetRunInfo( | 
|  | ScriptsRunInfo* scripts_run_info, | 
|  | UserScript::RunLocation run_location) const { | 
|  | if (ShouldInjectJs(run_location)) { | 
|  | const UserScript::FileList& js_scripts = script_->js_scripts(); | 
|  | scripts_run_info->num_js += js_scripts.size(); | 
|  | for (UserScript::FileList::const_iterator iter = js_scripts.begin(); | 
|  | iter != js_scripts.end(); | 
|  | ++iter) { | 
|  | scripts_run_info->executing_scripts[host_id_.id()].insert( | 
|  | iter->url().path()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (ShouldInjectCss(run_location)) | 
|  | scripts_run_info->num_css += script_->css_scripts().size(); | 
|  | } | 
|  |  | 
|  | void UserScriptInjector::OnInjectionComplete( | 
|  | scoped_ptr<base::Value> execution_result, | 
|  | UserScript::RunLocation run_location) { | 
|  | } | 
|  |  | 
|  | void UserScriptInjector::OnWillNotInject(InjectFailureReason reason) { | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |