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.
4
5 #include "extensions/renderer/user_script_injector.h"
6
7 #include <tuple>
8 #include <vector>
9
10 #include "base/lazy_instance.h"
11 #include "content/public/common/url_constants.h"
12 #include "content/public/renderer/render_frame.h"
13 #include "content/public/renderer/render_thread.h"
14 #include "content/public/renderer/render_view.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/guest_view/extensions_guest_view_messages.h"
17 #include "extensions/common/permissions/permissions_data.h"
18 #include "extensions/grit/extensions_renderer_resources.h"
19 #include "extensions/renderer/injection_host.h"
20 #include "extensions/renderer/script_context.h"
21 #include "extensions/renderer/scripts_run_info.h"
22 #include "third_party/blink/public/web/web_document.h"
23 #include "third_party/blink/public/web/web_local_frame.h"
24 #include "third_party/blink/public/web/web_script_source.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "url/gurl.h"
27
28 namespace extensions {
29
30 namespace {
31
32 struct RoutingInfoKey {
33 int routing_id;
34 int script_id;
35
RoutingInfoKeyextensions::__anon8112fdc90111::RoutingInfoKey36 RoutingInfoKey(int routing_id, int script_id)
37 : routing_id(routing_id), script_id(script_id) {}
38
operator <extensions::__anon8112fdc90111::RoutingInfoKey39 bool operator<(const RoutingInfoKey& other) const {
40 return std::tie(routing_id, script_id) <
41 std::tie(other.routing_id, other.script_id);
42 }
43 };
44
45 using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
46
47 // A map records whether a given |script_id| from a webview-added user script
48 // is allowed to inject on the render of given |routing_id|.
49 // Once a script is added, the decision of whether or not allowed to inject
50 // won't be changed.
51 // After removed by the webview, the user scipt will also be removed
52 // from the render. Therefore, there won't be any query from the same
53 // |script_id| and |routing_id| pair.
54 base::LazyInstance<RoutingInfoMap>::DestructorAtExit g_routing_info_map =
55 LAZY_INSTANCE_INITIALIZER;
56
57 // Greasemonkey API source that is injected with the scripts.
58 struct GreasemonkeyApiJsString {
59 GreasemonkeyApiJsString();
60 blink::WebScriptSource GetSource() const;
61
62 private:
63 blink::WebString source_;
64 };
65
66 // The below constructor, monstrous as it is, just makes a WebScriptSource from
67 // the GreasemonkeyApiJs resource.
GreasemonkeyApiJsString()68 GreasemonkeyApiJsString::GreasemonkeyApiJsString() {
69 base::StringPiece source_piece =
70 ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
71 IDR_GREASEMONKEY_API_JS);
72 source_ =
73 blink::WebString::FromUTF8(source_piece.data(), source_piece.length());
74 }
75
GetSource() const76 blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
77 return blink::WebScriptSource(source_);
78 }
79
80 base::LazyInstance<GreasemonkeyApiJsString>::Leaky g_greasemonkey_api =
81 LAZY_INSTANCE_INITIALIZER;
82
ShouldInjectScripts(const UserScript::FileList & scripts,const std::set<std::string> & injected_files)83 bool ShouldInjectScripts(const UserScript::FileList& scripts,
84 const std::set<std::string>& injected_files) {
85 for (const std::unique_ptr<UserScript::File>& file : scripts) {
86 // Check if the script is already injected.
87 if (injected_files.count(file->url().path()) == 0) {
88 return true;
89 }
90 }
91 return false;
92 }
93
94 } // namespace
95
UserScriptInjector(const UserScript * script,UserScriptSet * script_list,bool is_declarative)96 UserScriptInjector::UserScriptInjector(const UserScript* script,
97 UserScriptSet* script_list,
98 bool is_declarative)
99 : script_(script),
100 user_script_set_(script_list),
101 script_id_(script_->id()),
102 host_id_(script_->host_id()),
103 is_declarative_(is_declarative),
104 user_script_set_observer_(this) {
105 user_script_set_observer_.Add(script_list);
106 }
107
~UserScriptInjector()108 UserScriptInjector::~UserScriptInjector() {
109 }
110
OnUserScriptsUpdated(const std::set<HostID> & changed_hosts,const UserScriptList & scripts)111 void UserScriptInjector::OnUserScriptsUpdated(
112 const std::set<HostID>& changed_hosts,
113 const UserScriptList& scripts) {
114 // When user scripts are updated, all the old script pointers are invalidated.
115 script_ = nullptr;
116 // If the host causing this injection changed, then this injection
117 // will be removed, and there's no guarantee the backing script still exists.
118 if (changed_hosts.count(host_id_) > 0)
119 return;
120
121 for (const std::unique_ptr<UserScript>& script : scripts) {
122 if (script->id() == script_id_) {
123 script_ = script.get();
124 break;
125 }
126 }
127 // If |host_id_| wasn't in |changed_hosts|, then the script for this injection
128 // should be guaranteed to exist.
129 DCHECK(script_);
130 }
131
script_type() const132 UserScript::InjectionType UserScriptInjector::script_type() const {
133 return UserScript::CONTENT_SCRIPT;
134 }
135
IsUserGesture() const136 bool UserScriptInjector::IsUserGesture() const {
137 return false;
138 }
139
ExpectsResults() const140 bool UserScriptInjector::ExpectsResults() const {
141 return false;
142 }
143
GetCssOrigin() const144 base::Optional<CSSOrigin> UserScriptInjector::GetCssOrigin() const {
145 return base::nullopt;
146 }
147
GetInjectionKey() const148 const base::Optional<std::string> UserScriptInjector::GetInjectionKey() const {
149 return base::nullopt;
150 }
151
ShouldInjectJs(UserScript::RunLocation run_location,const std::set<std::string> & executing_scripts) const152 bool UserScriptInjector::ShouldInjectJs(
153 UserScript::RunLocation run_location,
154 const std::set<std::string>& executing_scripts) const {
155 return script_ && script_->run_location() == run_location &&
156 !script_->js_scripts().empty() &&
157 ShouldInjectScripts(script_->js_scripts(), executing_scripts);
158 }
159
ShouldInjectCss(UserScript::RunLocation run_location,const std::set<std::string> & injected_stylesheets) const160 bool UserScriptInjector::ShouldInjectCss(
161 UserScript::RunLocation run_location,
162 const std::set<std::string>& injected_stylesheets) const {
163 return script_ && run_location == UserScript::DOCUMENT_START &&
164 !script_->css_scripts().empty() &&
165 ShouldInjectScripts(script_->css_scripts(), injected_stylesheets);
166 }
167
CanExecuteOnFrame(const InjectionHost * injection_host,blink::WebLocalFrame * web_frame,int tab_id)168 PermissionsData::PageAccess UserScriptInjector::CanExecuteOnFrame(
169 const InjectionHost* injection_host,
170 blink::WebLocalFrame* web_frame,
171 int tab_id) {
172 // There is no harm in allowing the injection when the script is gone,
173 // because there is nothing to inject.
174 if (!script_)
175 return PermissionsData::PageAccess::kAllowed;
176
177 if (script_->consumer_instance_type() ==
178 UserScript::ConsumerInstanceType::WEBVIEW) {
179 int routing_id = content::RenderView::FromWebView(web_frame->Top()->View())
180 ->GetRoutingID();
181
182 RoutingInfoKey key(routing_id, script_->id());
183
184 RoutingInfoMap& map = g_routing_info_map.Get();
185 auto iter = map.find(key);
186
187 bool allowed = false;
188 if (iter != map.end()) {
189 allowed = iter->second;
190 } else {
191 // Send a SYNC IPC message to the browser to check if this is allowed.
192 // This is not ideal, but is mitigated by the fact that this is only done
193 // for webviews, and then only once per host.
194 // TODO(hanxi): Find a more efficient way to do this.
195 content::RenderThread::Get()->Send(
196 new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync(
197 routing_id, script_->id(), &allowed));
198 map.insert(std::pair<RoutingInfoKey, bool>(key, allowed));
199 }
200
201 return allowed ? PermissionsData::PageAccess::kAllowed
202 : PermissionsData::PageAccess::kDenied;
203 }
204
205 GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
206 web_frame, web_frame->GetDocument().Url(), script_->match_about_blank());
207
208 return injection_host->CanExecuteOnFrame(
209 effective_document_url,
210 content::RenderFrame::FromWebFrame(web_frame),
211 tab_id,
212 is_declarative_);
213 }
214
GetJsSources(UserScript::RunLocation run_location,std::set<std::string> * executing_scripts,size_t * num_injected_js_scripts) const215 std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
216 UserScript::RunLocation run_location,
217 std::set<std::string>* executing_scripts,
218 size_t* num_injected_js_scripts) const {
219 DCHECK(script_);
220 std::vector<blink::WebScriptSource> sources;
221
222 DCHECK_EQ(script_->run_location(), run_location);
223
224 const UserScript::FileList& js_scripts = script_->js_scripts();
225 sources.reserve(js_scripts.size() +
226 (script_->emulate_greasemonkey() ? 1 : 0));
227 // Emulate Greasemonkey API for scripts that were converted to extension
228 // user scripts.
229 if (script_->emulate_greasemonkey())
230 sources.push_back(g_greasemonkey_api.Get().GetSource());
231 for (const std::unique_ptr<UserScript::File>& file : js_scripts) {
232 const GURL& script_url = file->url();
233 // Check if the script is already injected.
234 if (executing_scripts->count(script_url.path()) != 0)
235 continue;
236
237 sources.push_back(blink::WebScriptSource(
238 user_script_set_->GetJsSource(*file, script_->emulate_greasemonkey()),
239 script_url));
240
241 (*num_injected_js_scripts) += 1;
242 executing_scripts->insert(script_url.path());
243 }
244
245 return sources;
246 }
247
GetCssSources(UserScript::RunLocation run_location,std::set<std::string> * injected_stylesheets,size_t * num_injected_stylesheets) const248 std::vector<blink::WebString> UserScriptInjector::GetCssSources(
249 UserScript::RunLocation run_location,
250 std::set<std::string>* injected_stylesheets,
251 size_t* num_injected_stylesheets) const {
252 DCHECK(script_);
253 DCHECK_EQ(UserScript::DOCUMENT_START, run_location);
254
255 std::vector<blink::WebString> sources;
256
257 const UserScript::FileList& css_scripts = script_->css_scripts();
258 sources.reserve(css_scripts.size());
259 for (const std::unique_ptr<UserScript::File>& file : script_->css_scripts()) {
260 const std::string& stylesheet_path = file->url().path();
261 // Check if the stylesheet is already injected.
262 if (injected_stylesheets->count(stylesheet_path) != 0)
263 continue;
264
265 sources.push_back(user_script_set_->GetCssSource(*file));
266 (*num_injected_stylesheets) += 1;
267 injected_stylesheets->insert(stylesheet_path);
268 }
269 return sources;
270 }
271
OnInjectionComplete(std::unique_ptr<base::Value> execution_result,UserScript::RunLocation run_location,content::RenderFrame * render_frame)272 void UserScriptInjector::OnInjectionComplete(
273 std::unique_ptr<base::Value> execution_result,
274 UserScript::RunLocation run_location,
275 content::RenderFrame* render_frame) {}
276
OnWillNotInject(InjectFailureReason reason,content::RenderFrame * render_frame)277 void UserScriptInjector::OnWillNotInject(InjectFailureReason reason,
278 content::RenderFrame* render_frame) {
279 }
280
281 } // namespace extensions
282