Chromium Code Reviews| Index: extensions/renderer/user_script_slave.cc |
| diff --git a/extensions/renderer/user_script_slave.cc b/extensions/renderer/user_script_slave.cc |
| index f05d214799a264c3cefc665c90571b9351abf7ee..efa6691d2646621ad16c80d3d296eff00aedbf21 100644 |
| --- a/extensions/renderer/user_script_slave.cc |
| +++ b/extensions/renderer/user_script_slave.cc |
| @@ -4,6 +4,7 @@ |
| #include "extensions/renderer/user_script_slave.h" |
| +#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
|
| #include <map> |
| #include "base/command_line.h" |
| @@ -46,10 +47,71 @@ using content::RenderThread; |
| namespace extensions { |
| +namespace { |
| + |
| // These two strings are injected before and after the Greasemonkey API and |
| // user script to wrap it in an anonymous scope. |
| -static const char kUserScriptHead[] = "(function (unsafeWindow) {\n"; |
| -static const char kUserScriptTail[] = "\n})(window);"; |
| +const char kUserScriptHead[] = "(function (unsafeWindow) {\n"; |
| +const char kUserScriptTail[] = "\n})(window);"; |
| + |
| +int64 g_next_pending_id = 0; |
| + |
| +} // namespace |
| + |
| +struct UserScriptSlave::ScriptsRunInfo { |
| + ScriptsRunInfo(); |
| + |
| + size_t num_css; |
| + size_t num_js; |
| + ExecutingScriptsMap executing_scripts; |
| + base::ElapsedTimer timer; |
| + |
| + private: |
| + // Technically, this is safe to copy, but it's slow because of |
| + // |executing_scripts|. Disallow it until we need it. |
| + DISALLOW_COPY_AND_ASSIGN(ScriptsRunInfo); |
| +}; |
| + |
| +UserScriptSlave::ScriptsRunInfo::ScriptsRunInfo() : num_css(0u), num_js(0u) { |
| +} |
| + |
| +struct UserScriptSlave::PendingInjection { |
| + PendingInjection(UserScript* user_script, |
| + UserScript::RunLocation run_location, |
| + const blink::WebString& web_frame_name); |
| + ~PendingInjection(); |
| + |
| + // The user scripts to run. Weak, but guaranteed to be alive because they |
| + // have the same parent (UserScriptSlave). |
| + std::set<UserScript*> user_scripts; |
| + |
| + // The extension id relating to these user scripts. |
| + const std::string extension_id; |
| + |
| + // The pending injection's latest run location. Scripts with any RunLocation |
| + // before or equal to this are run when the injection is executed. |
| + UserScript::RunLocation latest_run_location; |
| + |
| + // The unique name of the web frame to inject into. |
| + blink::WebString web_frame_name; |
| + |
| + // The id of this PendingInjection, guaranteed to be unique. |
| + int64 id; |
| +}; |
|
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!
|
| + |
| +UserScriptSlave::PendingInjection::PendingInjection( |
| + UserScript* user_script, |
| + UserScript::RunLocation run_location, |
| + const blink::WebString& web_frame_name) |
| + : extension_id(user_script->extension_id()), |
| + latest_run_location(run_location), |
| + web_frame_name(web_frame_name), |
| + id(g_next_pending_id++) { |
| + user_scripts.insert(user_script); |
| +} |
| + |
| +UserScriptSlave::PendingInjection::~PendingInjection() { |
| +} |
| int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension, |
| WebFrame* frame) { |
| @@ -188,19 +250,16 @@ void UserScriptSlave::InjectScripts(WebFrame* frame, |
| data_source_url = GURL(content::kViewSourceScheme + std::string(":") + |
| data_source_url.spec()); |
| - base::ElapsedTimer timer; |
| - int num_css = 0; |
| - int num_scripts = 0; |
| - |
| - ExecutingScriptsMap extensions_executing_scripts; |
| - |
| blink::WebFrame* top_frame = frame->top(); |
| content::RenderView* top_render_view = |
| content::RenderView::FromWebView(top_frame->view()); |
| - for (size_t i = 0; i < scripts_.size(); ++i) { |
| - std::vector<WebScriptSource> sources; |
| - UserScript* script = scripts_[i]; |
| + 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
|
| + ScriptsRunInfo scripts_run_info; |
| + for (std::vector<UserScript*>::const_iterator iter = scripts_.begin(); |
| + iter != scripts_.end(); |
| + ++iter) { |
| + script = *iter; |
| if (frame->parent() && !script->match_all_frames()) |
| continue; // Only match subframes if the script declared it wanted to. |
| @@ -226,96 +285,203 @@ void UserScriptSlave::InjectScripts(WebFrame* frame, |
| continue; |
| } |
| - if (location == UserScript::DOCUMENT_START) { |
| - num_css += script->css_scripts().size(); |
| - for (UserScript::FileList::const_iterator iter = |
| - script->css_scripts().begin(); |
| - iter != script->css_scripts().end(); |
| - ++iter) { |
| - frame->document().insertStyleSheet( |
| - WebString::fromUTF8(iter->GetContent().as_string())); |
| - } |
| - } |
| + bool run_css = !script->css_scripts().empty() && |
| + location == UserScript::DOCUMENT_START; |
| + bool run_js = !script->js_scripts().empty() && |
| + script->run_location() == location; |
| + if (!run_css && !run_js) |
| + continue; |
| - if (script->run_location() == location) { |
| - // TODO(rdevlin.cronin): Right now, this is just a notification, but soon |
| - // we should block without user consent. |
| - if (PermissionsData::RequiresActionForScriptExecution(extension)) { |
| - top_render_view->Send( |
| - new ExtensionHostMsg_NotifyExtensionScriptExecution( |
| - top_render_view->GetRoutingID(), |
| - extension->id(), |
| - top_render_view->GetPageId())); |
| - } |
| - num_scripts += script->js_scripts().size(); |
| - for (size_t j = 0; j < script->js_scripts().size(); ++j) { |
| - UserScript::File& file = script->js_scripts()[j]; |
| - std::string content = file.GetContent().as_string(); |
| - |
| - // We add this dumb function wrapper for standalone user script to |
| - // emulate what Greasemonkey does. |
| - // TODO(aa): I think that maybe "is_standalone" scripts don't exist |
| - // anymore. Investigate. |
| - if (script->is_standalone() || script->emulate_greasemonkey()) { |
| - content.insert(0, kUserScriptHead); |
| - content += kUserScriptTail; |
| - } |
| - sources.push_back( |
| - WebScriptSource(WebString::fromUTF8(content), file.url())); |
| - } |
| + if (PermissionsData::RequiresActionForScriptExecution(extension)) { |
| + int id = AddPendingInjection(script, location, frame->uniqueName()); |
| + top_render_view->Send( |
| + new ExtensionHostMsg_RequestContentScriptPermission( |
| + top_render_view->GetRoutingID(), |
| + extension->id(), |
| + top_render_view->GetPageId(), |
| + id)); |
| + continue; |
| } |
| - if (!sources.empty()) { |
| - // Emulate Greasemonkey API for scripts that were converted to extensions |
| - // and "standalone" user scripts. |
| - if (script->is_standalone() || script->emulate_greasemonkey()) { |
| - sources.insert( |
| - sources.begin(), |
| - WebScriptSource(WebString::fromUTF8(api_js_.as_string()))); |
| - } |
| - |
| - int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame); |
| - |
| - base::ElapsedTimer exec_timer; |
| - DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id()); |
| - frame->executeScriptInIsolatedWorld(isolated_world_id, |
| - &sources.front(), |
| - sources.size(), |
| - EXTENSION_GROUP_CONTENT_SCRIPTS); |
| - UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed()); |
| - |
| - for (std::vector<WebScriptSource>::const_iterator iter = sources.begin(); |
| - iter != sources.end(); |
| - ++iter) { |
| - extensions_executing_scripts[extension->id()].insert( |
| - GURL(iter->url).path()); |
| - } |
| + if (run_css) |
| + InjectCSSScripts(frame, script, &scripts_run_info); |
| + if (run_js) |
| + InjectJSScripts(frame, script, extension, &scripts_run_info); |
| + } |
| + |
| + LogScriptsRun(frame, location, scripts_run_info); |
| +} |
| + |
| +void UserScriptSlave::OnContentScriptGrantedPermission( |
| + content::RenderView* render_view, int request_id) { |
| + if (!render_view) |
| + return; |
| + |
| + PendingInjectionList::iterator iter = pending_injections_.begin(); |
| + for (; iter != pending_injections_.end() && (*iter)->id != request_id; ++iter) |
| + ; // Intentionally empty. |
| + if (iter == pending_injections_.end()) |
| + return; |
| + |
| + linked_ptr<PendingInjection> pending_injection = *iter; |
| + |
| + blink::WebView* web_view = render_view->GetWebView(); |
| + if (!web_view) |
| + return; |
| + |
| + blink::WebFrame* web_frame = |
| + web_view->findFrameByName(pending_injection->web_frame_name); |
| + if (!web_frame) |
| + return; |
| + |
| + const Extension* extension = |
| + extensions_->GetByID(pending_injection->extension_id); |
| + if (!extension) |
| + return; |
| + |
| + ScriptsRunInfo scripts_run_info; |
| + for (std::set<UserScript*>::const_iterator iter = |
| + pending_injection->user_scripts.begin(); |
| + iter != pending_injection->user_scripts.end(); |
| + ++iter) { |
| + UserScript* script = *iter; |
| + if (!script->css_scripts().empty()) |
| + InjectCSSScripts(web_frame, script, &scripts_run_info); |
| + if (!script->js_scripts().empty()) |
| + InjectJSScripts(web_frame, script, extension, &scripts_run_info); |
| + } |
| + LogScriptsRun(web_frame, |
| + pending_injection->latest_run_location, // TODO: Add new |
| + scripts_run_info); |
| +} |
| + |
| +int UserScriptSlave::AddPendingInjection(UserScript* script, |
| + UserScript::RunLocation location, |
| + const blink::WebString& frame_name) { |
| + // Look for an existing entry with the same extension id and same web frame. |
| + const std::string& extension_id = script->extension_id(); |
| + PendingInjectionList::iterator existing = pending_injections_.begin(); |
| + while (existing != pending_injections_.end() && |
| + ((*existing)->extension_id != extension_id || |
| + (*existing)->web_frame_name != frame_name)) { |
| + ++existing; |
| + } |
| + |
| + // If there is an existing entry, append the script and latest run location. |
| + // Otherwise, add a new entry. |
| + if (existing != pending_injections_.end()) { |
| + (*existing)->user_scripts.insert(script); |
| + (*existing)->latest_run_location = |
| + std::max(location, (*existing)->latest_run_location); |
| + } else { |
| + existing = pending_injections_.insert( |
| + existing, |
| + make_linked_ptr(new PendingInjection(script, location, frame_name))); |
| + } |
| + return (*existing)->id; |
| +} |
| + |
| +void UserScriptSlave::InjectCSSScripts(WebFrame* frame, |
| + UserScript* script, |
| + ScriptsRunInfo* scripts_run_info) { |
| + DCHECK(frame); |
| + DCHECK(frame->parent() || script->match_all_frames()); |
| + DCHECK(scripts_run_info); |
| + |
| + const UserScript::FileList& css_scripts = script->css_scripts(); |
| + scripts_run_info->num_css += css_scripts.size(); |
| + for (UserScript::FileList::const_iterator iter = css_scripts.begin(); |
| + iter != css_scripts.end(); |
| + ++iter) { |
| + frame->document().insertStyleSheet( |
| + WebString::fromUTF8(iter->GetContent().as_string())); |
| + } |
| +} |
| + |
| +void UserScriptSlave::InjectJSScripts(WebFrame* frame, |
| + UserScript* script, |
| + const Extension* extension, |
| + ScriptsRunInfo* scripts_run_info) { |
| + DCHECK(frame); |
| + DCHECK(!frame->parent() || script->match_all_frames()); |
| + DCHECK(extension); |
| + DCHECK(scripts_run_info); |
| + |
| + const UserScript::FileList& js_scripts = script->js_scripts(); |
| + std::vector<WebScriptSource> sources; |
| + bool is_standalone_or_emulate_greasemonkey = |
| + script->is_standalone() || script->emulate_greasemonkey(); |
| + scripts_run_info->num_js += js_scripts.size(); |
| + for (UserScript::FileList::const_iterator iter = js_scripts.begin(); |
| + iter != js_scripts.end(); |
| + ++iter) { |
| + std::string content = iter->GetContent().as_string(); |
| + |
| + // We add this dumb function wrapper for standalone user script to |
| + // emulate what Greasemonkey does. |
| + // TODO(aa): I think that maybe "is_standalone" scripts don't exist |
| + // anymore. Investigate. |
| + if (is_standalone_or_emulate_greasemonkey) { |
| + content.insert(0, kUserScriptHead); |
| + content += kUserScriptTail; |
| } |
| + sources.push_back( |
| + WebScriptSource(WebString::fromUTF8(content), iter->url())); |
| + } |
| + |
| + // Emulate Greasemonkey API for scripts that were converted to extensions |
| + // and "standalone" user scripts. |
| + if (is_standalone_or_emulate_greasemonkey) { |
| + sources.insert( |
| + sources.begin(), |
| + WebScriptSource(WebString::fromUTF8(api_js_.as_string()))); |
| } |
| + int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame); |
| + base::ElapsedTimer exec_timer; |
| + DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id()); |
| + frame->executeScriptInIsolatedWorld(isolated_world_id, |
| + &sources.front(), |
| + sources.size(), |
| + EXTENSION_GROUP_CONTENT_SCRIPTS); |
| + UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed()); |
| + |
| + for (std::vector<WebScriptSource>::const_iterator iter = sources.begin(); |
| + iter != sources.end(); |
| + ++iter) { |
| + scripts_run_info->executing_scripts[extension->id()].insert( |
| + GURL(iter->url).path()); |
| + } |
| +} |
| + |
| +void UserScriptSlave::LogScriptsRun(blink::WebFrame* frame, |
| + UserScript::RunLocation location, |
| + const ScriptsRunInfo& info) { |
| // Notify the browser if any extensions are now executing scripts. |
| - if (!extensions_executing_scripts.empty()) { |
| - top_render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( |
| - top_render_view->GetRoutingID(), |
| - extensions_executing_scripts, |
| - top_render_view->GetPageId(), |
| - ScriptContext::GetDataSourceURLForFrame(top_frame))); |
| + if (!info.executing_scripts.empty()) { |
| + content::RenderView* render_view = |
| + content::RenderView::FromWebView(frame->view()); |
| + render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( |
| + render_view->GetRoutingID(), |
| + info.executing_scripts, |
| + render_view->GetPageId(), |
| + ScriptContext::GetDataSourceURLForFrame(frame))); |
| } |
| - // Log debug info. |
| if (location == UserScript::DOCUMENT_START) { |
| - UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css); |
| - UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_scripts); |
| - if (num_css || num_scripts) |
| - UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed()); |
| + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", |
| + info.num_css); |
| + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", info.num_js); |
| + if (info.num_css || info.num_js) |
| + UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", info.timer.Elapsed()); |
| } else if (location == UserScript::DOCUMENT_END) { |
| - UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_scripts); |
| - if (num_scripts) |
| - UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed()); |
| + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js); |
| + if (info.num_js) |
| + UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed()); |
| } else if (location == UserScript::DOCUMENT_IDLE) { |
| - UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_scripts); |
| - if (num_scripts) |
| - UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed()); |
| + UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", info.num_js); |
| + if (info.num_js) |
| + UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed()); |
| } else { |
| NOTREACHED(); |
| } |