Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 |
| OLD | NEW |