Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(296)

Side by Side Diff: extensions/renderer/user_script_slave.cc

Issue 288053002: Block content scripts from executing until user grants permission (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "extensions/renderer/user_script_slave.h" 5 #include "extensions/renderer/user_script_slave.h"
6 6
7 #include <algorithm> // std::max
not at google - send to devlin 2014/05/15 00:31:54 grepping through Chromium, the comment is almost n
Devlin 2014/05/20 16:48:09 Looks like you're right. I must have seen one of
7 #include <map> 8 #include <map>
8 9
9 #include "base/command_line.h" 10 #include "base/command_line.h"
10 #include "base/logging.h" 11 #include "base/logging.h"
11 #include "base/memory/shared_memory.h" 12 #include "base/memory/shared_memory.h"
12 #include "base/metrics/histogram.h" 13 #include "base/metrics/histogram.h"
13 #include "base/pickle.h" 14 #include "base/pickle.h"
14 #include "base/strings/stringprintf.h" 15 #include "base/strings/stringprintf.h"
15 #include "base/timer/elapsed_timer.h" 16 #include "base/timer/elapsed_timer.h"
16 #include "content/public/common/url_constants.h" 17 #include "content/public/common/url_constants.h"
(...skipping 22 matching lines...) Expand all
39 using blink::WebFrame; 40 using blink::WebFrame;
40 using blink::WebSecurityOrigin; 41 using blink::WebSecurityOrigin;
41 using blink::WebSecurityPolicy; 42 using blink::WebSecurityPolicy;
42 using blink::WebString; 43 using blink::WebString;
43 using blink::WebVector; 44 using blink::WebVector;
44 using blink::WebView; 45 using blink::WebView;
45 using content::RenderThread; 46 using content::RenderThread;
46 47
47 namespace extensions { 48 namespace extensions {
48 49
50 namespace {
51
49 // These two strings are injected before and after the Greasemonkey API and 52 // These two strings are injected before and after the Greasemonkey API and
50 // user script to wrap it in an anonymous scope. 53 // user script to wrap it in an anonymous scope.
51 static const char kUserScriptHead[] = "(function (unsafeWindow) {\n"; 54 const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
52 static const char kUserScriptTail[] = "\n})(window);"; 55 const char kUserScriptTail[] = "\n})(window);";
56
57 int64 g_next_pending_id = 0;
58
59 } // namespace
60
61 struct UserScriptSlave::ScriptsRunInfo {
62 ScriptsRunInfo();
63
64 size_t num_css;
65 size_t num_js;
66 ExecutingScriptsMap executing_scripts;
67 base::ElapsedTimer timer;
68
69 private:
70 // Technically, this is safe to copy, but it's slow because of
71 // |executing_scripts|. Disallow it until we need it.
72 DISALLOW_COPY_AND_ASSIGN(ScriptsRunInfo);
73 };
74
75 UserScriptSlave::ScriptsRunInfo::ScriptsRunInfo() : num_css(0u), num_js(0u) {
76 }
77
78 struct UserScriptSlave::PendingInjection {
79 PendingInjection(UserScript* user_script,
80 UserScript::RunLocation run_location,
81 const blink::WebString& web_frame_name);
82 ~PendingInjection();
83
84 // The user scripts to run. Weak, but guaranteed to be alive because they
85 // have the same parent (UserScriptSlave).
86 std::set<UserScript*> user_scripts;
87
88 // The extension id relating to these user scripts.
89 const std::string extension_id;
90
91 // The pending injection's latest run location. Scripts with any RunLocation
92 // before or equal to this are run when the injection is executed.
93 UserScript::RunLocation latest_run_location;
94
95 // The unique name of the web frame to inject into.
96 blink::WebString web_frame_name;
97
98 // The id of this PendingInjection, guaranteed to be unique.
99 int64 id;
100 };
not at google - send to devlin 2014/05/15 00:31:54 this factoring into objects makes sense. could you
Devlin 2014/05/20 16:48:09 Semi-massive refactor done. Woohoo!
101
102 UserScriptSlave::PendingInjection::PendingInjection(
103 UserScript* user_script,
104 UserScript::RunLocation run_location,
105 const blink::WebString& web_frame_name)
106 : extension_id(user_script->extension_id()),
107 latest_run_location(run_location),
108 web_frame_name(web_frame_name),
109 id(g_next_pending_id++) {
110 user_scripts.insert(user_script);
111 }
112
113 UserScriptSlave::PendingInjection::~PendingInjection() {
114 }
53 115
54 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension, 116 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension,
55 WebFrame* frame) { 117 WebFrame* frame) {
56 static int g_next_isolated_world_id = 118 static int g_next_isolated_world_id =
57 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId(); 119 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
58 120
59 IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id()); 121 IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id());
60 if (iter != isolated_world_ids_.end()) { 122 if (iter != isolated_world_ids_.end()) {
61 // We need to set the isolated world origin and CSP even if it's not a new 123 // We need to set the isolated world origin and CSP even if it's not a new
62 // world since these are stored per frame, and we might not have used this 124 // world since these are stored per frame, and we might not have used this
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
181 void UserScriptSlave::InjectScripts(WebFrame* frame, 243 void UserScriptSlave::InjectScripts(WebFrame* frame,
182 UserScript::RunLocation location) { 244 UserScript::RunLocation location) {
183 GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame); 245 GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame);
184 if (data_source_url.is_empty()) 246 if (data_source_url.is_empty())
185 return; 247 return;
186 248
187 if (frame->isViewSourceModeEnabled()) 249 if (frame->isViewSourceModeEnabled())
188 data_source_url = GURL(content::kViewSourceScheme + std::string(":") + 250 data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
189 data_source_url.spec()); 251 data_source_url.spec());
190 252
191 base::ElapsedTimer timer;
192 int num_css = 0;
193 int num_scripts = 0;
194
195 ExecutingScriptsMap extensions_executing_scripts;
196
197 blink::WebFrame* top_frame = frame->top(); 253 blink::WebFrame* top_frame = frame->top();
198 content::RenderView* top_render_view = 254 content::RenderView* top_render_view =
199 content::RenderView::FromWebView(top_frame->view()); 255 content::RenderView::FromWebView(top_frame->view());
200 256
201 for (size_t i = 0; i < scripts_.size(); ++i) { 257 UserScript* script = NULL;
not at google - send to devlin 2014/05/15 00:31:54 why declared outside scope?
Devlin 2014/05/20 16:48:09 Carryover from Uni days, when allocating 8 bytes o
202 std::vector<WebScriptSource> sources; 258 ScriptsRunInfo scripts_run_info;
203 UserScript* script = scripts_[i]; 259 for (std::vector<UserScript*>::const_iterator iter = scripts_.begin();
260 iter != scripts_.end();
261 ++iter) {
262 script = *iter;
204 263
205 if (frame->parent() && !script->match_all_frames()) 264 if (frame->parent() && !script->match_all_frames())
206 continue; // Only match subframes if the script declared it wanted to. 265 continue; // Only match subframes if the script declared it wanted to.
207 266
208 const Extension* extension = extensions_->GetByID(script->extension_id()); 267 const Extension* extension = extensions_->GetByID(script->extension_id());
209 268
210 // Since extension info is sent separately from user script info, they can 269 // Since extension info is sent separately from user script info, they can
211 // be out of sync. We just ignore this situation. 270 // be out of sync. We just ignore this situation.
212 if (!extension) 271 if (!extension)
213 continue; 272 continue;
214 273
215 // Content scripts are not tab-specific. 274 // Content scripts are not tab-specific.
216 const int kNoTabId = -1; 275 const int kNoTabId = -1;
217 // We don't have a process id in this context. 276 // We don't have a process id in this context.
218 const int kNoProcessId = -1; 277 const int kNoProcessId = -1;
219 if (!PermissionsData::CanExecuteScriptOnPage(extension, 278 if (!PermissionsData::CanExecuteScriptOnPage(extension,
220 data_source_url, 279 data_source_url,
221 top_frame->document().url(), 280 top_frame->document().url(),
222 kNoTabId, 281 kNoTabId,
223 script, 282 script,
224 kNoProcessId, 283 kNoProcessId,
225 NULL)) { 284 NULL)) {
226 continue; 285 continue;
227 } 286 }
228 287
229 if (location == UserScript::DOCUMENT_START) { 288 bool run_css = !script->css_scripts().empty() &&
230 num_css += script->css_scripts().size(); 289 location == UserScript::DOCUMENT_START;
231 for (UserScript::FileList::const_iterator iter = 290 bool run_js = !script->js_scripts().empty() &&
232 script->css_scripts().begin(); 291 script->run_location() == location;
233 iter != script->css_scripts().end(); 292 if (!run_css && !run_js)
234 ++iter) { 293 continue;
235 frame->document().insertStyleSheet( 294
236 WebString::fromUTF8(iter->GetContent().as_string())); 295 if (PermissionsData::RequiresActionForScriptExecution(extension)) {
237 } 296 int id = AddPendingInjection(script, location, frame->uniqueName());
297 top_render_view->Send(
298 new ExtensionHostMsg_RequestContentScriptPermission(
299 top_render_view->GetRoutingID(),
300 extension->id(),
301 top_render_view->GetPageId(),
302 id));
303 continue;
238 } 304 }
239 305
240 if (script->run_location() == location) { 306 if (run_css)
241 // TODO(rdevlin.cronin): Right now, this is just a notification, but soon 307 InjectCSSScripts(frame, script, &scripts_run_info);
242 // we should block without user consent. 308 if (run_js)
243 if (PermissionsData::RequiresActionForScriptExecution(extension)) { 309 InjectJSScripts(frame, script, extension, &scripts_run_info);
244 top_render_view->Send( 310 }
245 new ExtensionHostMsg_NotifyExtensionScriptExecution( 311
246 top_render_view->GetRoutingID(), 312 LogScriptsRun(frame, location, scripts_run_info);
247 extension->id(), 313 }
248 top_render_view->GetPageId())); 314
249 } 315 void UserScriptSlave::OnContentScriptGrantedPermission(
250 num_scripts += script->js_scripts().size(); 316 content::RenderView* render_view, int request_id) {
251 for (size_t j = 0; j < script->js_scripts().size(); ++j) { 317 if (!render_view)
252 UserScript::File& file = script->js_scripts()[j]; 318 return;
253 std::string content = file.GetContent().as_string(); 319
254 320 PendingInjectionList::iterator iter = pending_injections_.begin();
255 // We add this dumb function wrapper for standalone user script to 321 for (; iter != pending_injections_.end() && (*iter)->id != request_id; ++iter)
256 // emulate what Greasemonkey does. 322 ; // Intentionally empty.
257 // TODO(aa): I think that maybe "is_standalone" scripts don't exist 323 if (iter == pending_injections_.end())
258 // anymore. Investigate. 324 return;
259 if (script->is_standalone() || script->emulate_greasemonkey()) { 325
260 content.insert(0, kUserScriptHead); 326 linked_ptr<PendingInjection> pending_injection = *iter;
261 content += kUserScriptTail; 327
262 } 328 blink::WebView* web_view = render_view->GetWebView();
263 sources.push_back( 329 if (!web_view)
264 WebScriptSource(WebString::fromUTF8(content), file.url())); 330 return;
265 } 331
332 blink::WebFrame* web_frame =
333 web_view->findFrameByName(pending_injection->web_frame_name);
334 if (!web_frame)
335 return;
336
337 const Extension* extension =
338 extensions_->GetByID(pending_injection->extension_id);
339 if (!extension)
340 return;
341
342 ScriptsRunInfo scripts_run_info;
343 for (std::set<UserScript*>::const_iterator iter =
344 pending_injection->user_scripts.begin();
345 iter != pending_injection->user_scripts.end();
346 ++iter) {
347 UserScript* script = *iter;
348 if (!script->css_scripts().empty())
349 InjectCSSScripts(web_frame, script, &scripts_run_info);
350 if (!script->js_scripts().empty())
351 InjectJSScripts(web_frame, script, extension, &scripts_run_info);
352 }
353 LogScriptsRun(web_frame,
354 pending_injection->latest_run_location, // TODO: Add new
355 scripts_run_info);
356 }
357
358 int UserScriptSlave::AddPendingInjection(UserScript* script,
359 UserScript::RunLocation location,
360 const blink::WebString& frame_name) {
361 // Look for an existing entry with the same extension id and same web frame.
362 const std::string& extension_id = script->extension_id();
363 PendingInjectionList::iterator existing = pending_injections_.begin();
364 while (existing != pending_injections_.end() &&
365 ((*existing)->extension_id != extension_id ||
366 (*existing)->web_frame_name != frame_name)) {
367 ++existing;
368 }
369
370 // If there is an existing entry, append the script and latest run location.
371 // Otherwise, add a new entry.
372 if (existing != pending_injections_.end()) {
373 (*existing)->user_scripts.insert(script);
374 (*existing)->latest_run_location =
375 std::max(location, (*existing)->latest_run_location);
376 } else {
377 existing = pending_injections_.insert(
378 existing,
379 make_linked_ptr(new PendingInjection(script, location, frame_name)));
380 }
381 return (*existing)->id;
382 }
383
384 void UserScriptSlave::InjectCSSScripts(WebFrame* frame,
385 UserScript* script,
386 ScriptsRunInfo* scripts_run_info) {
387 DCHECK(frame);
388 DCHECK(frame->parent() || script->match_all_frames());
389 DCHECK(scripts_run_info);
390
391 const UserScript::FileList& css_scripts = script->css_scripts();
392 scripts_run_info->num_css += css_scripts.size();
393 for (UserScript::FileList::const_iterator iter = css_scripts.begin();
394 iter != css_scripts.end();
395 ++iter) {
396 frame->document().insertStyleSheet(
397 WebString::fromUTF8(iter->GetContent().as_string()));
398 }
399 }
400
401 void UserScriptSlave::InjectJSScripts(WebFrame* frame,
402 UserScript* script,
403 const Extension* extension,
404 ScriptsRunInfo* scripts_run_info) {
405 DCHECK(frame);
406 DCHECK(!frame->parent() || script->match_all_frames());
407 DCHECK(extension);
408 DCHECK(scripts_run_info);
409
410 const UserScript::FileList& js_scripts = script->js_scripts();
411 std::vector<WebScriptSource> sources;
412 bool is_standalone_or_emulate_greasemonkey =
413 script->is_standalone() || script->emulate_greasemonkey();
414 scripts_run_info->num_js += js_scripts.size();
415 for (UserScript::FileList::const_iterator iter = js_scripts.begin();
416 iter != js_scripts.end();
417 ++iter) {
418 std::string content = iter->GetContent().as_string();
419
420 // We add this dumb function wrapper for standalone user script to
421 // emulate what Greasemonkey does.
422 // TODO(aa): I think that maybe "is_standalone" scripts don't exist
423 // anymore. Investigate.
424 if (is_standalone_or_emulate_greasemonkey) {
425 content.insert(0, kUserScriptHead);
426 content += kUserScriptTail;
266 } 427 }
267 428 sources.push_back(
268 if (!sources.empty()) { 429 WebScriptSource(WebString::fromUTF8(content), iter->url()));
269 // Emulate Greasemonkey API for scripts that were converted to extensions 430 }
270 // and "standalone" user scripts. 431
271 if (script->is_standalone() || script->emulate_greasemonkey()) { 432 // Emulate Greasemonkey API for scripts that were converted to extensions
272 sources.insert( 433 // and "standalone" user scripts.
273 sources.begin(), 434 if (is_standalone_or_emulate_greasemonkey) {
274 WebScriptSource(WebString::fromUTF8(api_js_.as_string()))); 435 sources.insert(
275 } 436 sources.begin(),
276 437 WebScriptSource(WebString::fromUTF8(api_js_.as_string())));
277 int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame); 438 }
278 439
279 base::ElapsedTimer exec_timer; 440 int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame);
280 DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id()); 441 base::ElapsedTimer exec_timer;
281 frame->executeScriptInIsolatedWorld(isolated_world_id, 442 DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id());
282 &sources.front(), 443 frame->executeScriptInIsolatedWorld(isolated_world_id,
283 sources.size(), 444 &sources.front(),
284 EXTENSION_GROUP_CONTENT_SCRIPTS); 445 sources.size(),
285 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed()); 446 EXTENSION_GROUP_CONTENT_SCRIPTS);
286 447 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
287 for (std::vector<WebScriptSource>::const_iterator iter = sources.begin(); 448
288 iter != sources.end(); 449 for (std::vector<WebScriptSource>::const_iterator iter = sources.begin();
289 ++iter) { 450 iter != sources.end();
290 extensions_executing_scripts[extension->id()].insert( 451 ++iter) {
291 GURL(iter->url).path()); 452 scripts_run_info->executing_scripts[extension->id()].insert(
292 } 453 GURL(iter->url).path());
293 } 454 }
294 } 455 }
295 456
457 void UserScriptSlave::LogScriptsRun(blink::WebFrame* frame,
458 UserScript::RunLocation location,
459 const ScriptsRunInfo& info) {
296 // Notify the browser if any extensions are now executing scripts. 460 // Notify the browser if any extensions are now executing scripts.
297 if (!extensions_executing_scripts.empty()) { 461 if (!info.executing_scripts.empty()) {
298 top_render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( 462 content::RenderView* render_view =
299 top_render_view->GetRoutingID(), 463 content::RenderView::FromWebView(frame->view());
300 extensions_executing_scripts, 464 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
301 top_render_view->GetPageId(), 465 render_view->GetRoutingID(),
302 ScriptContext::GetDataSourceURLForFrame(top_frame))); 466 info.executing_scripts,
303 } 467 render_view->GetPageId(),
304 468 ScriptContext::GetDataSourceURLForFrame(frame)));
305 // Log debug info. 469 }
470
306 if (location == UserScript::DOCUMENT_START) { 471 if (location == UserScript::DOCUMENT_START) {
307 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css); 472 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount",
308 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_scripts); 473 info.num_css);
309 if (num_css || num_scripts) 474 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", info.num_js);
310 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed()); 475 if (info.num_css || info.num_js)
476 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", info.timer.Elapsed());
311 } else if (location == UserScript::DOCUMENT_END) { 477 } else if (location == UserScript::DOCUMENT_END) {
312 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_scripts); 478 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js);
313 if (num_scripts) 479 if (info.num_js)
314 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed()); 480 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed());
315 } else if (location == UserScript::DOCUMENT_IDLE) { 481 } else if (location == UserScript::DOCUMENT_IDLE) {
316 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_scripts); 482 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", info.num_js);
317 if (num_scripts) 483 if (info.num_js)
318 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed()); 484 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed());
319 } else { 485 } else {
320 NOTREACHED(); 486 NOTREACHED();
321 } 487 }
322 } 488 }
323 489
324 } // namespace extensions 490 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698