Add some instrumentation for jank in URLRequest::Start.
[chromium-blink-merge.git] / extensions / renderer / user_script_injector.cc
blob9a5a26d95ad917790b31f4db942297d05ff98d23
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_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"
24 #include "url/gurl.h"
26 namespace extensions {
28 namespace {
30 struct RoutingInfoKey {
31 int routing_id;
32 int script_id;
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
57 // won't be changed.
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;
69 private:
70 std::string source_;
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)
78 .as_string()) {
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;
88 } // namespace
90 UserScriptInjector::UserScriptInjector(const UserScript* script,
91 UserScriptSet* script_list,
92 bool is_declarative)
93 : script_(script),
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)
110 return;
112 for (std::vector<UserScript*>::const_iterator iter = scripts.begin();
113 iter != scripts.end();
114 ++iter) {
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_) {
118 script_ = *iter;
119 break;
124 UserScript::InjectionType UserScriptInjector::script_type() const {
125 return UserScript::CONTENT_SCRIPT;
128 bool UserScriptInjector::ShouldExecuteInChildFrames() const {
129 return false;
132 bool UserScriptInjector::ShouldExecuteInMainWorld() const {
133 return false;
136 bool UserScriptInjector::IsUserGesture() const {
137 return false;
140 bool UserScriptInjector::ExpectsResults() const {
141 return false;
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,
159 int tab_id,
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)
168 return can_execute;
170 int routing_id = content::RenderView::FromWebView(web_frame->top()->view())
171 ->GetRoutingID();
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;
181 } else {
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();
207 ++iter) {
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());
227 return sources;
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();
238 ++iter) {
239 sources.push_back(iter->GetContent().as_string());
241 return sources;
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();
252 ++iter) {
253 scripts_run_info->executing_scripts[host_id_.id()].insert(
254 iter->url().path());
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