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"
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"
27 namespace extensions
{
31 struct RoutingInfoKey
{
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
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;
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
)
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
;
91 UserScriptInjector::UserScriptInjector(const UserScript
* script
,
92 UserScriptSet
* script_list
,
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)
113 for (std::vector
<UserScript
*>::const_iterator iter
= scripts
.begin();
114 iter
!= scripts
.end();
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_
) {
125 UserScript::InjectionType
UserScriptInjector::script_type() const {
126 return UserScript::CONTENT_SCRIPT
;
129 bool UserScriptInjector::ShouldExecuteInMainWorld() const {
133 bool UserScriptInjector::IsUserGesture() const {
137 bool UserScriptInjector::ExpectsResults() const {
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
,
157 GURL effective_document_url
= ScriptContext::GetEffectiveDocumentURL(
158 web_frame
, web_frame
->document().url(), script_
->match_about_blank());
159 PermissionsData::AccessType can_execute
= injection_host
->CanExecuteOnFrame(
160 effective_document_url
,
161 content::RenderFrame::FromWebFrame(web_frame
),
164 if (script_
->consumer_instance_type() !=
165 UserScript::ConsumerInstanceType::WEBVIEW
||
166 can_execute
== PermissionsData::ACCESS_DENIED
)
169 int routing_id
= content::RenderView::FromWebView(web_frame
->top()->view())
172 RoutingInfoKey
key(routing_id
, script_
->id());
174 RoutingInfoMap
& map
= g_routing_info_map
.Get();
175 auto iter
= map
.find(key
);
177 bool allowed
= false;
178 if (iter
!= map
.end()) {
179 allowed
= iter
->second
;
181 // Send a SYNC IPC message to the browser to check if this is allowed. This
182 // is not ideal, but is mitigated by the fact that this is only done for
183 // webviews, and then only once per host.
184 // TODO(hanxi): Find a more efficient way to do this.
185 content::RenderThread::Get()->Send(
186 new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync(
187 routing_id
, script_
->id(), &allowed
));
188 map
.insert(std::pair
<RoutingInfoKey
, bool>(key
, allowed
));
191 return allowed
? PermissionsData::ACCESS_ALLOWED
192 : PermissionsData::ACCESS_DENIED
;
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();
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());
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();
238 sources
.push_back(iter
->GetContent().as_string());
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();
252 scripts_run_info
->executing_scripts
[host_id_
.id()].insert(
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