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_slave.h"
6
7 #include <map>
8
9 #include "base/logging.h"
10 #include "base/memory/shared_memory.h"
11 #include "base/metrics/histogram.h"
12 #include "base/pickle.h"
13 #include "base/timer/elapsed_timer.h"
14 #include "content/public/renderer/render_thread.h"
15 #include "content/public/renderer/render_view.h"
16 #include "extensions/common/extension.h"
17 #include "extensions/common/extension_messages.h"
18 #include "extensions/common/extension_set.h"
19 #include "extensions/common/manifest_handlers/csp_info.h"
20 #include "extensions/common/permissions/permissions_data.h"
21 #include "extensions/renderer/extension_helper.h"
22 #include "extensions/renderer/extensions_renderer_client.h"
23 #include "extensions/renderer/script_context.h"
24 #include "third_party/WebKit/public/web/WebFrame.h"
25 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
26 #include "third_party/WebKit/public/web/WebSecurityPolicy.h"
27 #include "third_party/WebKit/public/web/WebView.h"
28 #include "url/gurl.h"
29
30 using blink::WebFrame;
31 using blink::WebSecurityOrigin;
32 using blink::WebSecurityPolicy;
33 using blink::WebString;
34 using content::RenderThread;
35
36 namespace extensions {
37
GetIsolatedWorldIdForExtension(const Extension * extension,WebFrame * frame)38 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension,
39 WebFrame* frame) {
40 static int g_next_isolated_world_id =
41 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
42
43 int id = 0;
44 IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id());
45 if (iter != isolated_world_ids_.end()) {
46 id = iter->second;
47 } else {
48 id = g_next_isolated_world_id++;
49 // This map will tend to pile up over time, but realistically, you're never
50 // going to have enough extensions for it to matter.
51 isolated_world_ids_[extension->id()] = id;
52 }
53
54 // We need to set the isolated world origin and CSP even if it's not a new
55 // world since these are stored per frame, and we might not have used this
56 // isolated world in this frame before.
57 frame->setIsolatedWorldSecurityOrigin(
58 id, WebSecurityOrigin::create(extension->url()));
59 frame->setIsolatedWorldContentSecurityPolicy(
60 id, WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
61
62 return id;
63 }
64
GetExtensionIdForIsolatedWorld(int isolated_world_id)65 std::string UserScriptSlave::GetExtensionIdForIsolatedWorld(
66 int isolated_world_id) {
67 for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin();
68 iter != isolated_world_ids_.end();
69 ++iter) {
70 if (iter->second == isolated_world_id)
71 return iter->first;
72 }
73 return std::string();
74 }
75
RemoveIsolatedWorld(const std::string & extension_id)76 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
77 isolated_world_ids_.erase(extension_id);
78 }
79
UserScriptSlave(const ExtensionSet * extensions)80 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
81 : extensions_(extensions) {
82 }
83
~UserScriptSlave()84 UserScriptSlave::~UserScriptSlave() {
85 }
86
GetActiveExtensions(std::set<std::string> * extension_ids)87 void UserScriptSlave::GetActiveExtensions(
88 std::set<std::string>* extension_ids) {
89 DCHECK(extension_ids);
90 for (ScopedVector<ScriptInjection>::const_iterator iter =
91 script_injections_.begin();
92 iter != script_injections_.end();
93 ++iter) {
94 DCHECK(!(*iter)->extension_id().empty());
95 extension_ids->insert((*iter)->extension_id());
96 }
97 }
98
GetExtension(const std::string & extension_id)99 const Extension* UserScriptSlave::GetExtension(
100 const std::string& extension_id) {
101 return extensions_->GetByID(extension_id);
102 }
103
UpdateScripts(base::SharedMemoryHandle shared_memory,const std::set<std::string> & changed_extensions)104 bool UserScriptSlave::UpdateScripts(
105 base::SharedMemoryHandle shared_memory,
106 const std::set<std::string>& changed_extensions) {
107 bool only_inject_incognito =
108 ExtensionsRendererClient::Get()->IsIncognitoProcess();
109
110 // Create the shared memory object (read only).
111 shared_memory_.reset(new base::SharedMemory(shared_memory, true));
112 if (!shared_memory_.get())
113 return false;
114
115 // First get the size of the memory block.
116 if (!shared_memory_->Map(sizeof(Pickle::Header)))
117 return false;
118 Pickle::Header* pickle_header =
119 reinterpret_cast<Pickle::Header*>(shared_memory_->memory());
120
121 // Now map in the rest of the block.
122 int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size;
123 shared_memory_->Unmap();
124 if (!shared_memory_->Map(pickle_size))
125 return false;
126
127 // Unpickle scripts.
128 uint64 num_scripts = 0;
129 Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size);
130 PickleIterator iter(pickle);
131 CHECK(pickle.ReadUInt64(&iter, &num_scripts));
132
133 // If we pass no explicit extension ids, we should refresh all extensions.
134 bool include_all_extensions = changed_extensions.empty();
135
136 // If we include all extensions, then we clear the script injections and
137 // start from scratch. If not, then clear only the scripts for extension ids
138 // that we are updating. This is important to maintain pending script
139 // injection state for each ScriptInjection.
140 if (include_all_extensions) {
141 script_injections_.clear();
142 } else {
143 for (ScopedVector<ScriptInjection>::iterator iter =
144 script_injections_.begin();
145 iter != script_injections_.end();) {
146 if (changed_extensions.count((*iter)->extension_id()) > 0)
147 iter = script_injections_.erase(iter);
148 else
149 ++iter;
150 }
151 }
152
153 script_injections_.reserve(num_scripts);
154 for (uint64 i = 0; i < num_scripts; ++i) {
155 scoped_ptr<UserScript> script(new UserScript());
156 script->Unpickle(pickle, &iter);
157
158 // Note that this is a pointer into shared memory. We don't own it. It gets
159 // cleared up when the last renderer or browser process drops their
160 // reference to the shared memory.
161 for (size_t j = 0; j < script->js_scripts().size(); ++j) {
162 const char* body = NULL;
163 int body_length = 0;
164 CHECK(pickle.ReadData(&iter, &body, &body_length));
165 script->js_scripts()[j].set_external_content(
166 base::StringPiece(body, body_length));
167 }
168 for (size_t j = 0; j < script->css_scripts().size(); ++j) {
169 const char* body = NULL;
170 int body_length = 0;
171 CHECK(pickle.ReadData(&iter, &body, &body_length));
172 script->css_scripts()[j].set_external_content(
173 base::StringPiece(body, body_length));
174 }
175
176 if (only_inject_incognito && !script->is_incognito_enabled())
177 continue; // This script shouldn't run in an incognito tab.
178
179 // If we include all extensions or the given extension changed, we add a
180 // new script injection.
181 if (include_all_extensions ||
182 changed_extensions.count(script->extension_id()) > 0) {
183 script_injections_.push_back(new ScriptInjection(script.Pass(), this));
184 } else {
185 // Otherwise, we need to update the existing script injection with the
186 // new user script (since the old content was invalidated).
187 //
188 // Note: Yes, this is O(n^2). But vectors are faster than maps for
189 // relatively few elements, and less than 1% of our users actually have
190 // enough content scripts for it to matter. If this changes, or if
191 // std::maps get a much faster implementation, we should look into
192 // making a map for script injections.
193 for (ScopedVector<ScriptInjection>::iterator iter =
194 script_injections_.begin();
195 iter != script_injections_.end();
196 ++iter) {
197 if ((*iter)->script()->id() == script->id()) {
198 (*iter)->SetScript(script.Pass());
199 break;
200 }
201 }
202 }
203 }
204 return true;
205 }
206
InjectScripts(WebFrame * frame,UserScript::RunLocation location)207 void UserScriptSlave::InjectScripts(WebFrame* frame,
208 UserScript::RunLocation location) {
209 GURL document_url = ScriptInjection::GetDocumentUrlForFrame(frame);
210 if (document_url.is_empty())
211 return;
212
213 ScriptInjection::ScriptsRunInfo scripts_run_info;
214 for (ScopedVector<ScriptInjection>::const_iterator iter =
215 script_injections_.begin();
216 iter != script_injections_.end();
217 ++iter) {
218 (*iter)->InjectIfAllowed(frame, location, document_url, &scripts_run_info);
219 }
220
221 LogScriptsRun(frame, location, scripts_run_info);
222 }
223
OnContentScriptGrantedPermission(content::RenderView * render_view,int request_id)224 void UserScriptSlave::OnContentScriptGrantedPermission(
225 content::RenderView* render_view, int request_id) {
226 ScriptInjection::ScriptsRunInfo run_info;
227 blink::WebFrame* frame = NULL;
228 // Notify the injections that a request to inject has been granted.
229 for (ScopedVector<ScriptInjection>::iterator iter =
230 script_injections_.begin();
231 iter != script_injections_.end();
232 ++iter) {
233 if ((*iter)->NotifyScriptPermitted(request_id,
234 render_view,
235 &run_info,
236 &frame)) {
237 DCHECK(frame);
238 LogScriptsRun(frame, UserScript::RUN_DEFERRED, run_info);
239 break;
240 }
241 }
242 }
243
FrameDetached(blink::WebFrame * frame)244 void UserScriptSlave::FrameDetached(blink::WebFrame* frame) {
245 for (ScopedVector<ScriptInjection>::iterator iter =
246 script_injections_.begin();
247 iter != script_injections_.end();
248 ++iter) {
249 (*iter)->FrameDetached(frame);
250 }
251 }
252
LogScriptsRun(blink::WebFrame * frame,UserScript::RunLocation location,const ScriptInjection::ScriptsRunInfo & info)253 void UserScriptSlave::LogScriptsRun(
254 blink::WebFrame* frame,
255 UserScript::RunLocation location,
256 const ScriptInjection::ScriptsRunInfo& info) {
257 // Notify the browser if any extensions are now executing scripts.
258 if (!info.executing_scripts.empty()) {
259 content::RenderView* render_view =
260 content::RenderView::FromWebView(frame->view());
261 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
262 render_view->GetRoutingID(),
263 info.executing_scripts,
264 render_view->GetPageId(),
265 ScriptContext::GetDataSourceURLForFrame(frame)));
266 }
267
268 switch (location) {
269 case UserScript::DOCUMENT_START:
270 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount",
271 info.num_css);
272 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount",
273 info.num_js);
274 if (info.num_css || info.num_js)
275 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time",
276 info.timer.Elapsed());
277 break;
278 case UserScript::DOCUMENT_END:
279 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js);
280 if (info.num_js)
281 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed());
282 break;
283 case UserScript::DOCUMENT_IDLE:
284 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount",
285 info.num_js);
286 if (info.num_js)
287 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed());
288 break;
289 case UserScript::RUN_DEFERRED:
290 // TODO(rdevlin.cronin): Add histograms.
291 break;
292 case UserScript::UNDEFINED:
293 case UserScript::RUN_LOCATION_LAST:
294 NOTREACHED();
295 }
296 }
297
298 } // namespace extensions
299