blob: 0978deb2665219060b64667730e7e65a569c3e66 [file] [log] [blame]
[email protected]ac2f89372014-06-23 21:44:251// 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_injection.h"
6
7#include <vector>
8
9#include "base/lazy_instance.h"
10#include "base/metrics/histogram.h"
11#include "content/public/common/url_constants.h"
12#include "content/public/renderer/render_view.h"
13#include "extensions/common/extension.h"
14#include "extensions/common/extension_messages.h"
15#include "extensions/common/feature_switch.h"
16#include "extensions/common/permissions/permissions_data.h"
17#include "extensions/renderer/dom_activity_logger.h"
18#include "extensions/renderer/extension_groups.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 "third_party/WebKit/public/web/WebView.h"
24#include "ui/base/resource/resource_bundle.h"
25#include "url/gurl.h"
26
27namespace extensions {
28
29namespace {
30
31const int kInvalidRequestId = -1;
32
33// The id of the next pending injection.
34int64 g_next_pending_id = 0;
35
36// These two strings are injected before and after the Greasemonkey API and
37// user script to wrap it in an anonymous scope.
38const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
39const char kUserScriptTail[] = "\n})(window);";
40
41// Greasemonkey API source that is injected with the scripts.
42struct GreasemonkeyApiJsString {
43 GreasemonkeyApiJsString();
44 blink::WebScriptSource source;
45};
46
47// The below constructor, monstrous as it is, just makes a WebScriptSource from
48// the GreasemonkeyApiJs resource.
49GreasemonkeyApiJsString::GreasemonkeyApiJsString()
50 : source(blink::WebScriptSource(blink::WebString::fromUTF8(
51 ResourceBundle::GetSharedInstance()
52 .GetRawDataResource(IDR_GREASEMONKEY_API_JS)
53 .as_string()))) {
54}
55
56base::LazyInstance<GreasemonkeyApiJsString> g_greasemonkey_api =
57 LAZY_INSTANCE_INITIALIZER;
58
59} // namespace
60
61UserScriptInjection::UserScriptInjection(
62 blink::WebFrame* web_frame,
63 const std::string& extension_id,
64 UserScript::RunLocation run_location,
65 int tab_id,
66 UserScriptSet* script_list,
67 const UserScript* script)
68 : ScriptInjection(web_frame,
69 extension_id,
70 run_location,
71 tab_id),
72 script_(script),
73 script_id_(script_->id()),
74 user_script_set_observer_(this) {
75 user_script_set_observer_.Add(script_list);
76}
77
78UserScriptInjection::~UserScriptInjection() {
79}
80
81void UserScriptInjection::OnUserScriptsUpdated(
82 const std::set<std::string>& changed_extensions,
83 const std::vector<UserScript*>& scripts) {
84 // If the extension causing this injection changed, then this injection
85 // will be removed, and there's no guarantee the backing script still exists.
86 if (changed_extensions.count(extension_id()) > 0)
87 return;
88
89 for (std::vector<UserScript*>::const_iterator iter = scripts.begin();
90 iter != scripts.end();
91 ++iter) {
92 // We need to compare to |script_id_| (and not to script_->id()) because the
93 // old |script_| may be deleted by now.
94 if ((*iter)->id() == script_id_) {
95 script_ = *iter;
96 break;
97 }
98 }
99}
100
101bool UserScriptInjection::TryToInject(UserScript::RunLocation current_location,
102 const Extension* extension,
103 ScriptsRunInfo* scripts_run_info) {
104 // If the extension is removed, we return immediately and never inject.
105 if (!extension)
106 return true;
107
108 // If we are already waiting for permission, wait.
109 if (request_id() != -1)
110 return false;
111
112 // If the page is not to the proper load point, wait.
113 if (current_location < run_location())
114 return false;
115
116 // If we are not allowed to inject, then request permission and wait.
117 if (!Allowed(extension) && !RequestPermission(extension))
118 return false;
119
120 // Otherwise, we're good to inject!
121 Inject(extension, scripts_run_info);
122
123 return true;
124}
125
126bool UserScriptInjection::OnPermissionGranted(
127 const Extension* extension,
128 ScriptsRunInfo* scripts_run_info) {
129 if (!extension)
130 return false;
131
132 Inject(extension, scripts_run_info);
133 return true;
134}
135
136bool UserScriptInjection::Allowed(const Extension* extension) {
137 // If we don't have a tab id, we have no UI surface to ask for user consent.
138 // For now, we treat this as an automatic allow.
139 if (tab_id() == -1)
140 return true;
141
142 return !extension->permissions_data()->RequiresActionForScriptExecution(
143 extension, tab_id(), web_frame()->top()->document().url());
144}
145
146bool UserScriptInjection::RequestPermission(const Extension* extension) {
147 content::RenderView* render_view =
148 content::RenderView::FromWebView(web_frame()->top()->view());
149
150 // By default, we allow injection.
151 bool should_inject = true;
152 int64 request_id = kInvalidRequestId;
153 int page_id = render_view->GetPageId();
154
155 if (FeatureSwitch::scripts_require_action()->IsEnabled()) {
156 should_inject = false;
157 request_id = g_next_pending_id++;
158 set_request_id(request_id);
159 }
160
161 render_view->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
162 render_view->GetRoutingID(), extension->id(), page_id, request_id));
163
164 return should_inject;
165}
166
167void UserScriptInjection::Inject(const Extension* extension,
168 ScriptsRunInfo* scripts_run_info) {
169 if (!script_->css_scripts().empty() &&
170 run_location() == UserScript::DOCUMENT_START) {
171 InjectCSS(scripts_run_info);
172 }
173 if (!script_->js_scripts().empty() &&
174 script_->run_location() == run_location()) {
175 InjectJS(extension, scripts_run_info);
176 }
177}
178
179void UserScriptInjection::InjectJS(const Extension* extension,
180 ScriptsRunInfo* scripts_run_info) {
181 const UserScript::FileList& js_scripts = script_->js_scripts();
182 std::vector<blink::WebScriptSource> sources;
183 scripts_run_info->num_js += js_scripts.size();
184
185 bool is_standalone_or_emulate_greasemonkey =
186 script_->is_standalone() || script_->emulate_greasemonkey();
187 for (UserScript::FileList::const_iterator iter = js_scripts.begin();
188 iter != js_scripts.end();
189 ++iter) {
190 std::string content = iter->GetContent().as_string();
191
192 // We add this dumb function wrapper for standalone user script to
193 // emulate what Greasemonkey does.
194 // TODO(aa): I think that maybe "is_standalone" scripts don't exist
195 // anymore. Investigate.
196 if (is_standalone_or_emulate_greasemonkey) {
197 content.insert(0, kUserScriptHead);
198 content += kUserScriptTail;
199 }
200 sources.push_back(blink::WebScriptSource(
201 blink::WebString::fromUTF8(content), iter->url()));
202 }
203
204 // Emulate Greasemonkey API for scripts that were converted to extensions
205 // and "standalone" user scripts.
206 if (is_standalone_or_emulate_greasemonkey)
207 sources.insert(sources.begin(), g_greasemonkey_api.Get().source);
208
209 int isolated_world_id =
210 GetIsolatedWorldIdForExtension(extension, web_frame());
211 base::ElapsedTimer exec_timer;
212 DOMActivityLogger::AttachToWorld(isolated_world_id, script_->extension_id());
213 web_frame()->executeScriptInIsolatedWorld(isolated_world_id,
214 &sources.front(),
215 sources.size(),
216 EXTENSION_GROUP_CONTENT_SCRIPTS);
217 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
218
219 for (std::vector<blink::WebScriptSource>::const_iterator iter =
220 sources.begin();
221 iter != sources.end();
222 ++iter) {
223 scripts_run_info->executing_scripts[script_->extension_id()].insert(
224 GURL(iter->url).path());
225 }
226}
227
228void UserScriptInjection::InjectCSS(ScriptsRunInfo* scripts_run_info) {
229 const UserScript::FileList& css_scripts = script_->css_scripts();
230 scripts_run_info->num_css += css_scripts.size();
231 for (UserScript::FileList::const_iterator iter = css_scripts.begin();
232 iter != css_scripts.end();
233 ++iter) {
234 web_frame()->document().insertStyleSheet(
235 blink::WebString::fromUTF8(iter->GetContent().as_string()));
236 }
237}
238
239} // namespace extensions