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_view.h"
13 #include "extensions/common/extension.h"
14 #include "extensions/common/guest_view/extensions_guest_view_messages.h"
15 #include "extensions/common/permissions/permissions_data.h"
16 #include "extensions/renderer/injection_host.h"
17 #include "extensions/renderer/script_context.h"
18 #include "extensions/renderer/scripts_run_info.h"
19 #include "grit/extensions_renderer_resources.h"
20 #include "third_party/WebKit/public/web/WebDocument.h"
21 #include "third_party/WebKit/public/web/WebFrame.h"
22 #include "third_party/WebKit/public/web/WebScriptSource.h"
23 #include "ui/base/resource/resource_bundle.h"
26 namespace extensions
{
30 struct RoutingInfoKey
{
34 RoutingInfoKey(int routing_id
, int script_id
)
35 : routing_id(routing_id
), script_id(script_id
) {}
37 bool operator<(const RoutingInfoKey
& other
) const {
38 if (routing_id
!= other
.routing_id
)
39 return routing_id
< other
.routing_id
;
41 if (script_id
!= other
.script_id
)
42 return script_id
< other
.script_id
;
43 return false; // keys are equal.
47 using RoutingInfoMap
= std::map
<RoutingInfoKey
, bool>;
49 // These two strings are injected before and after the Greasemonkey API and
50 // user script to wrap it in an anonymous scope.
51 const char kUserScriptHead
[] = "(function (unsafeWindow) {\n";
52 const char kUserScriptTail
[] = "\n})(window);";
54 // A map records whether a given |script_id| from a webview-added user script
55 // is allowed to inject on the render of given |routing_id|.
56 // Once a script is added, the decision of whether or not allowed to inject
58 // After removed by the webview, the user scipt will also be removed
59 // from the render. Therefore, there won't be any query from the same
60 // |script_id| and |routing_id| pair.
61 base::LazyInstance
<RoutingInfoMap
> g_routing_info_map
=
62 LAZY_INSTANCE_INITIALIZER
;
64 // Greasemonkey API source that is injected with the scripts.
65 struct GreasemonkeyApiJsString
{
66 GreasemonkeyApiJsString();
67 blink::WebScriptSource
GetSource() const;
73 // The below constructor, monstrous as it is, just makes a WebScriptSource from
74 // the GreasemonkeyApiJs resource.
75 GreasemonkeyApiJsString::GreasemonkeyApiJsString()
76 : source_(ResourceBundle::GetSharedInstance()
77 .GetRawDataResource(IDR_GREASEMONKEY_API_JS
)
81 blink::WebScriptSource
GreasemonkeyApiJsString::GetSource() const {
82 return blink::WebScriptSource(blink::WebString::fromUTF8(source_
));
85 base::LazyInstance
<GreasemonkeyApiJsString
> g_greasemonkey_api
=
86 LAZY_INSTANCE_INITIALIZER
;
90 UserScriptInjector::UserScriptInjector(const UserScript
* script
,
91 UserScriptSet
* script_list
,
94 script_id_(script_
->id()),
95 host_id_(script_
->host_id()),
96 is_declarative_(is_declarative
),
97 user_script_set_observer_(this) {
98 user_script_set_observer_
.Add(script_list
);
101 UserScriptInjector::~UserScriptInjector() {
104 void UserScriptInjector::OnUserScriptsUpdated(
105 const std::set
<HostID
>& changed_hosts
,
106 const std::vector
<UserScript
*>& scripts
) {
107 // If the host causing this injection changed, then this injection
108 // will be removed, and there's no guarantee the backing script still exists.
109 if (changed_hosts
.count(host_id_
) > 0)
112 for (std::vector
<UserScript
*>::const_iterator iter
= scripts
.begin();
113 iter
!= scripts
.end();
115 // We need to compare to |script_id_| (and not to script_->id()) because the
116 // old |script_| may be deleted by now.
117 if ((*iter
)->id() == script_id_
) {
124 UserScript::InjectionType
UserScriptInjector::script_type() const {
125 return UserScript::CONTENT_SCRIPT
;
128 bool UserScriptInjector::ShouldExecuteInChildFrames() const {
132 bool UserScriptInjector::ShouldExecuteInMainWorld() const {
136 bool UserScriptInjector::IsUserGesture() const {
140 bool UserScriptInjector::ExpectsResults() const {
144 bool UserScriptInjector::ShouldInjectJs(
145 UserScript::RunLocation run_location
) const {
146 return script_
->run_location() == run_location
&&
147 !script_
->js_scripts().empty();
150 bool UserScriptInjector::ShouldInjectCss(
151 UserScript::RunLocation run_location
) const {
152 return run_location
== UserScript::DOCUMENT_START
&&
153 !script_
->css_scripts().empty();
156 PermissionsData::AccessType
UserScriptInjector::CanExecuteOnFrame(
157 const InjectionHost
* injection_host
,
158 blink::WebFrame
* web_frame
,
160 const GURL
& top_url
) const {
161 GURL effective_document_url
= ScriptContext::GetEffectiveDocumentURL(
162 web_frame
, web_frame
->document().url(), script_
->match_about_blank());
163 PermissionsData::AccessType can_execute
= injection_host
->CanExecuteOnFrame(
164 effective_document_url
, top_url
, tab_id
, is_declarative_
);
165 if (script_
->consumer_instance_type() !=
166 UserScript::ConsumerInstanceType::WEBVIEW
||
167 can_execute
== PermissionsData::ACCESS_DENIED
)
170 int routing_id
= content::RenderView::FromWebView(web_frame
->top()->view())
173 RoutingInfoKey
key(routing_id
, script_
->id());
175 RoutingInfoMap
& map
= g_routing_info_map
.Get();
176 auto iter
= map
.find(key
);
178 bool allowed
= false;
179 if (iter
!= map
.end()) {
180 allowed
= iter
->second
;
182 // Send a SYNC IPC message to the browser to check if this is allowed. This
183 // is not ideal, but is mitigated by the fact that this is only done for
184 // webviews, and then only once per host.
185 // TODO(hanxi): Find a more efficient way to do this.
186 content::RenderThread::Get()->Send(
187 new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync(
188 routing_id
, script_
->id(), &allowed
));
189 map
.insert(std::pair
<RoutingInfoKey
, bool>(key
, allowed
));
192 return allowed
? PermissionsData::ACCESS_ALLOWED
193 : PermissionsData::ACCESS_DENIED
;
196 std::vector
<blink::WebScriptSource
> UserScriptInjector::GetJsSources(
197 UserScript::RunLocation run_location
) const {
198 DCHECK_EQ(script_
->run_location(), run_location
);
200 std::vector
<blink::WebScriptSource
> sources
;
201 const UserScript::FileList
& js_scripts
= script_
->js_scripts();
202 bool is_standalone_or_emulate_greasemonkey
=
203 script_
->is_standalone() || script_
->emulate_greasemonkey();
205 for (UserScript::FileList::const_iterator iter
= js_scripts
.begin();
206 iter
!= js_scripts
.end();
208 std::string content
= iter
->GetContent().as_string();
210 // We add this dumb function wrapper for standalone user script to
211 // emulate what Greasemonkey does.
212 // TODO(aa): I think that maybe "is_standalone" scripts don't exist
213 // anymore. Investigate.
214 if (is_standalone_or_emulate_greasemonkey
) {
215 content
.insert(0, kUserScriptHead
);
216 content
+= kUserScriptTail
;
218 sources
.push_back(blink::WebScriptSource(
219 blink::WebString::fromUTF8(content
), iter
->url()));
222 // Emulate Greasemonkey API for scripts that were converted to extensions
223 // and "standalone" user scripts.
224 if (is_standalone_or_emulate_greasemonkey
)
225 sources
.insert(sources
.begin(), g_greasemonkey_api
.Get().GetSource());
230 std::vector
<std::string
> UserScriptInjector::GetCssSources(
231 UserScript::RunLocation run_location
) const {
232 DCHECK_EQ(UserScript::DOCUMENT_START
, run_location
);
234 std::vector
<std::string
> sources
;
235 const UserScript::FileList
& css_scripts
= script_
->css_scripts();
236 for (UserScript::FileList::const_iterator iter
= css_scripts
.begin();
237 iter
!= css_scripts
.end();
239 sources
.push_back(iter
->GetContent().as_string());
244 void UserScriptInjector::GetRunInfo(
245 ScriptsRunInfo
* scripts_run_info
,
246 UserScript::RunLocation run_location
) const {
247 if (ShouldInjectJs(run_location
)) {
248 const UserScript::FileList
& js_scripts
= script_
->js_scripts();
249 scripts_run_info
->num_js
+= js_scripts
.size();
250 for (UserScript::FileList::const_iterator iter
= js_scripts
.begin();
251 iter
!= js_scripts
.end();
253 scripts_run_info
->executing_scripts
[host_id_
.id()].insert(
258 if (ShouldInjectCss(run_location
))
259 scripts_run_info
->num_css
+= script_
->css_scripts().size();
262 void UserScriptInjector::OnInjectionComplete(
263 scoped_ptr
<base::ListValue
> execution_results
,
264 UserScript::RunLocation run_location
) {
267 void UserScriptInjector::OnWillNotInject(InjectFailureReason reason
) {
270 } // namespace extensions