Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / extensions / renderer / user_script_injector.cc
blob511fac34a329f743bcd4a2cc4b89592952faf90b
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/renderer/user_script_injector.h"
7 #include <vector>
9 #include "base/lazy_instance.h"
10 #include "content/public/common/url_constants.h"
11 #include "content/public/renderer/render_thread.h"
12 #include "content/public/renderer/render_frame.h"
13 #include "content/public/renderer/render_view.h"
14 #include "extensions/common/extension.h"
15 #include "extensions/common/guest_view/extensions_guest_view_messages.h"
16 #include "extensions/common/permissions/permissions_data.h"
17 #include "extensions/renderer/injection_host.h"
18 #include "extensions/renderer/script_context.h"
19 #include "extensions/renderer/scripts_run_info.h"
20 #include "grit/extensions_renderer_resources.h"
21 #include "third_party/WebKit/public/web/WebDocument.h"
22 #include "third_party/WebKit/public/web/WebLocalFrame.h"
23 #include "third_party/WebKit/public/web/WebScriptSource.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "url/gurl.h"
27 namespace extensions {
29 namespace {
31 struct RoutingInfoKey {
32 int routing_id;
33 int script_id;
35 RoutingInfoKey(int routing_id, int script_id)
36 : routing_id(routing_id), script_id(script_id) {}
38 bool operator<(const RoutingInfoKey& other) const {
39 if (routing_id != other.routing_id)
40 return routing_id < other.routing_id;
42 if (script_id != other.script_id)
43 return script_id < other.script_id;
44 return false; // keys are equal.
48 using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
50 // These two strings are injected before and after the Greasemonkey API and
51 // user script to wrap it in an anonymous scope.
52 const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
53 const char kUserScriptTail[] = "\n})(window);";
55 // A map records whether a given |script_id| from a webview-added user script
56 // is allowed to inject on the render of given |routing_id|.
57 // Once a script is added, the decision of whether or not allowed to inject
58 // won't be changed.
59 // After removed by the webview, the user scipt will also be removed
60 // from the render. Therefore, there won't be any query from the same
61 // |script_id| and |routing_id| pair.
62 base::LazyInstance<RoutingInfoMap> g_routing_info_map =
63 LAZY_INSTANCE_INITIALIZER;
65 // Greasemonkey API source that is injected with the scripts.
66 struct GreasemonkeyApiJsString {
67 GreasemonkeyApiJsString();
68 blink::WebScriptSource GetSource() const;
70 private:
71 std::string source_;
74 // The below constructor, monstrous as it is, just makes a WebScriptSource from
75 // the GreasemonkeyApiJs resource.
76 GreasemonkeyApiJsString::GreasemonkeyApiJsString()
77 : source_(ResourceBundle::GetSharedInstance()
78 .GetRawDataResource(IDR_GREASEMONKEY_API_JS)
79 .as_string()) {
82 blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
83 return blink::WebScriptSource(blink::WebString::fromUTF8(source_));
86 base::LazyInstance<GreasemonkeyApiJsString> g_greasemonkey_api =
87 LAZY_INSTANCE_INITIALIZER;
89 } // namespace
91 UserScriptInjector::UserScriptInjector(const UserScript* script,
92 UserScriptSet* script_list,
93 bool is_declarative)
94 : script_(script),
95 script_id_(script_->id()),
96 host_id_(script_->host_id()),
97 is_declarative_(is_declarative),
98 user_script_set_observer_(this) {
99 user_script_set_observer_.Add(script_list);
102 UserScriptInjector::~UserScriptInjector() {
105 void UserScriptInjector::OnUserScriptsUpdated(
106 const std::set<HostID>& changed_hosts,
107 const std::vector<UserScript*>& scripts) {
108 // If the host causing this injection changed, then this injection
109 // will be removed, and there's no guarantee the backing script still exists.
110 if (changed_hosts.count(host_id_) > 0)
111 return;
113 for (std::vector<UserScript*>::const_iterator iter = scripts.begin();
114 iter != scripts.end();
115 ++iter) {
116 // We need to compare to |script_id_| (and not to script_->id()) because the
117 // old |script_| may be deleted by now.
118 if ((*iter)->id() == script_id_) {
119 script_ = *iter;
120 break;
125 UserScript::InjectionType UserScriptInjector::script_type() const {
126 return UserScript::CONTENT_SCRIPT;
129 bool UserScriptInjector::ShouldExecuteInMainWorld() const {
130 return false;
133 bool UserScriptInjector::IsUserGesture() const {
134 return false;
137 bool UserScriptInjector::ExpectsResults() const {
138 return false;
141 bool UserScriptInjector::ShouldInjectJs(
142 UserScript::RunLocation run_location) const {
143 return script_->run_location() == run_location &&
144 !script_->js_scripts().empty();
147 bool UserScriptInjector::ShouldInjectCss(
148 UserScript::RunLocation run_location) const {
149 return run_location == UserScript::DOCUMENT_START &&
150 !script_->css_scripts().empty();
153 PermissionsData::AccessType UserScriptInjector::CanExecuteOnFrame(
154 const InjectionHost* injection_host,
155 blink::WebLocalFrame* web_frame,
156 int tab_id) const {
157 if (script_->consumer_instance_type() ==
158 UserScript::ConsumerInstanceType::WEBVIEW) {
159 int routing_id = content::RenderView::FromWebView(web_frame->top()->view())
160 ->GetRoutingID();
162 RoutingInfoKey key(routing_id, script_->id());
164 RoutingInfoMap& map = g_routing_info_map.Get();
165 auto iter = map.find(key);
167 bool allowed = false;
168 if (iter != map.end()) {
169 allowed = iter->second;
170 } else {
171 // Send a SYNC IPC message to the browser to check if this is allowed.
172 // This is not ideal, but is mitigated by the fact that this is only done
173 // for webviews, and then only once per host.
174 // TODO(hanxi): Find a more efficient way to do this.
175 content::RenderThread::Get()->Send(
176 new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync(
177 routing_id, script_->id(), &allowed));
178 map.insert(std::pair<RoutingInfoKey, bool>(key, allowed));
181 return allowed ? PermissionsData::ACCESS_ALLOWED
182 : PermissionsData::ACCESS_DENIED;
185 GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
186 web_frame, web_frame->document().url(), script_->match_about_blank());
188 return injection_host->CanExecuteOnFrame(
189 effective_document_url,
190 content::RenderFrame::FromWebFrame(web_frame),
191 tab_id,
192 is_declarative_);
195 std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
196 UserScript::RunLocation run_location) const {
197 DCHECK_EQ(script_->run_location(), run_location);
199 std::vector<blink::WebScriptSource> sources;
200 const UserScript::FileList& js_scripts = script_->js_scripts();
201 bool is_standalone_or_emulate_greasemonkey =
202 script_->is_standalone() || script_->emulate_greasemonkey();
204 for (UserScript::FileList::const_iterator iter = js_scripts.begin();
205 iter != js_scripts.end();
206 ++iter) {
207 std::string content = iter->GetContent().as_string();
209 // We add this dumb function wrapper for standalone user script to
210 // emulate what Greasemonkey does.
211 // TODO(aa): I think that maybe "is_standalone" scripts don't exist
212 // anymore. Investigate.
213 if (is_standalone_or_emulate_greasemonkey) {
214 content.insert(0, kUserScriptHead);
215 content += kUserScriptTail;
217 sources.push_back(blink::WebScriptSource(
218 blink::WebString::fromUTF8(content), iter->url()));
221 // Emulate Greasemonkey API for scripts that were converted to extensions
222 // and "standalone" user scripts.
223 if (is_standalone_or_emulate_greasemonkey)
224 sources.insert(sources.begin(), g_greasemonkey_api.Get().GetSource());
226 return sources;
229 std::vector<std::string> UserScriptInjector::GetCssSources(
230 UserScript::RunLocation run_location) const {
231 DCHECK_EQ(UserScript::DOCUMENT_START, run_location);
233 std::vector<std::string> sources;
234 const UserScript::FileList& css_scripts = script_->css_scripts();
235 for (UserScript::FileList::const_iterator iter = css_scripts.begin();
236 iter != css_scripts.end();
237 ++iter) {
238 sources.push_back(iter->GetContent().as_string());
240 return sources;
243 void UserScriptInjector::GetRunInfo(
244 ScriptsRunInfo* scripts_run_info,
245 UserScript::RunLocation run_location) const {
246 if (ShouldInjectJs(run_location)) {
247 const UserScript::FileList& js_scripts = script_->js_scripts();
248 scripts_run_info->num_js += js_scripts.size();
249 for (UserScript::FileList::const_iterator iter = js_scripts.begin();
250 iter != js_scripts.end();
251 ++iter) {
252 scripts_run_info->executing_scripts[host_id_.id()].insert(
253 iter->url().path());
257 if (ShouldInjectCss(run_location))
258 scripts_run_info->num_css += script_->css_scripts().size();
261 void UserScriptInjector::OnInjectionComplete(
262 scoped_ptr<base::Value> execution_result,
263 UserScript::RunLocation run_location) {
266 void UserScriptInjector::OnWillNotInject(InjectFailureReason reason) {
269 } // namespace extensions