Index: extensions/renderer/script_injection_manager.cc |
diff --git a/extensions/renderer/script_injection_manager.cc b/extensions/renderer/script_injection_manager.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..663bc891bddf7f5959d8e820bae86fe1ef320757 |
--- /dev/null |
+++ b/extensions/renderer/script_injection_manager.cc |
@@ -0,0 +1,404 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "extensions/renderer/script_injection_manager.h" |
+ |
+#include "base/bind.h" |
+#include "base/memory/weak_ptr.h" |
+#include "base/metrics/histogram.h" |
+#include "base/values.h" |
+#include "content/public/renderer/render_view.h" |
+#include "content/public/renderer/render_view_observer.h" |
+#include "extensions/common/extension.h" |
+#include "extensions/common/extension_messages.h" |
+#include "extensions/common/extension_set.h" |
+#include "extensions/common/feature_switch.h" |
+#include "extensions/renderer/extension_helper.h" |
+#include "extensions/renderer/programmatic_script_injection.h" |
+#include "extensions/renderer/script_context.h" |
+#include "ipc/ipc_message_macros.h" |
+#include "third_party/WebKit/public/web/WebFrame.h" |
+#include "third_party/WebKit/public/web/WebLocalFrame.h" |
+#include "third_party/WebKit/public/web/WebView.h" |
+#include "url/gurl.h" |
+ |
+namespace extensions { |
+ |
+namespace { |
+ |
+// The length of time to wait after the DOM is complete to try and run user |
+// scripts. |
+const int kScriptIdleTimeoutInMs = 200; |
+ |
+const int kInvalidRequestId = -1; |
+ |
+// The id of the next pending injection. |
+int64 g_next_pending_id = 0; |
+ |
+// Removes any injections with the given |web_frame| from |injections|. |
+void RemoveInjectionsWithFrame(ScopedVector<ScriptInjection>* injections, |
+ blink::WebFrame* web_frame) { |
+ for (ScopedVector<ScriptInjection>::iterator iter = injections->begin(); |
+ iter != injections->end();) { |
+ if ((*iter)->web_frame() == web_frame) |
+ iter = injections->erase(iter); |
+ else |
+ ++iter; |
+ } |
+} |
+ |
+// Removes any injections associated with an extension in |extension_ids|. |
+void RemoveInjectionsWithExtensionId( |
+ ScopedVector<ScriptInjection>* injections, |
+ const std::set<std::string>& extension_ids) { |
+ for (ScopedVector<ScriptInjection>::iterator iter = injections->begin(); |
+ iter != injections->end();) { |
+ if (extension_ids.count((*iter)->extension_id()) > 0) |
+ iter = injections->erase(iter); |
+ else |
+ ++iter; |
+ } |
+} |
+ |
+// Log information about a given script run. |
+void LogScriptsRun(blink::WebFrame* frame, |
+ UserScript::RunLocation location, |
+ const ScriptInjection::ScriptsRunInfo& info) { |
+ // Notify the browser if any extensions are now executing scripts. |
+ 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))); |
+ } |
+ |
+ switch (location) { |
+ case UserScript::DOCUMENT_START: |
+ 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()); |
+ break; |
+ case UserScript::DOCUMENT_END: |
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js); |
+ if (info.num_js) |
+ UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed()); |
+ break; |
+ case UserScript::DOCUMENT_IDLE: |
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", |
+ info.num_js); |
+ if (info.num_js) |
+ UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed()); |
+ break; |
+ case UserScript::RUN_DEFERRED: |
+ // TODO(rdevlin.cronin): Add histograms. |
+ break; |
+ case UserScript::UNDEFINED: |
+ case UserScript::RUN_LOCATION_LAST: |
+ NOTREACHED(); |
+ } |
+} |
+ |
+} // namespace |
+ |
+class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver { |
+ public: |
+ RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager); |
+ virtual ~RVOHelper(); |
+ |
+ private: |
+ // RenderViewObserver implementation. |
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; |
+ virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE; |
+ virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE; |
+ virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE; |
+ virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE; |
+ virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE; |
+ virtual void OnDestruct() OVERRIDE; |
+ |
+ virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params); |
+ virtual void OnPermitScriptInjection(int64 request_id); |
+ |
+ // Tells the ScriptInjectionManager to run tasks associated with |
+ // document_idle. |
+ void RunIdle(blink::WebFrame* frame); |
+ |
+ ScriptInjectionManager* manager_; |
+ base::WeakPtrFactory<RVOHelper> weak_factory_; |
+}; |
+ |
+ScriptInjectionManager::RVOHelper::RVOHelper( |
+ content::RenderView* render_view, |
+ ScriptInjectionManager* manager) |
+ : content::RenderViewObserver(render_view), |
+ manager_(manager), |
+ weak_factory_(this) { |
+} |
+ |
+ScriptInjectionManager::RVOHelper::~RVOHelper() { |
+} |
+ |
+bool ScriptInjectionManager::RVOHelper::OnMessageReceived( |
+ const IPC::Message& message) { |
+ bool handled = true; |
+ IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message) |
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode) |
+ IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection, |
+ OnPermitScriptInjection) |
+ IPC_MESSAGE_UNHANDLED(handled = false) |
+ IPC_END_MESSAGE_MAP() |
+ return handled; |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement( |
+ blink::WebLocalFrame* frame) { |
+ manager_->InjectScripts(frame, UserScript::DOCUMENT_START); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad( |
+ blink::WebLocalFrame* frame) { |
+ manager_->InjectScripts(frame, UserScript::DOCUMENT_END); |
+ base::MessageLoop::current()->PostDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, |
+ weak_factory_.GetWeakPtr(), |
+ frame), |
+ base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs)); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::DidFinishLoad( |
+ blink::WebLocalFrame* frame) { |
+ // Ensure that running scripts does not keep any progress UI running. |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, |
+ weak_factory_.GetWeakPtr(), |
+ frame)); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad( |
+ blink::WebLocalFrame* frame) { |
+ weak_factory_.InvalidateWeakPtrs(); |
+ manager_->InvalidateForFrame(frame); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) { |
+ weak_factory_.InvalidateWeakPtrs(); |
+ manager_->InvalidateForFrame(frame); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::OnDestruct() { |
+ manager_->RemoveObserver(this); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::OnExecuteCode( |
+ const ExtensionMsg_ExecuteCode_Params& params) { |
+ manager_->HandleExecuteCode(params, render_view()); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection( |
+ int64 request_id) { |
+ manager_->HandlePermitScriptInjection(request_id); |
+} |
+ |
+void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) { |
+ manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE); |
+} |
+ |
+ScriptInjectionManager::FrameStatus::FrameStatus() |
+ : current_location(UserScript::UNDEFINED), has_run_idle(false) { |
+} |
+ |
+ScriptInjectionManager::ScriptInjectionManager(const ExtensionSet* extensions) |
+ : extensions_(extensions), |
+ user_script_injection_list_(new UserScriptInjectionList()), |
+ user_script_injection_list_observer_(this) { |
+ user_script_injection_list_observer_.Add(user_script_injection_list_.get()); |
+} |
+ |
+ScriptInjectionManager::~ScriptInjectionManager() { |
+} |
+ |
+void ScriptInjectionManager::OnRenderViewCreated( |
+ content::RenderView* render_view) { |
+ rvo_helpers_.push_back(new RVOHelper(render_view, this)); |
+} |
+ |
+void ScriptInjectionManager::OnUserScriptsUpdated( |
+ const std::set<std::string>& changed_extensions, |
+ const std::vector<UserScript*>& scripts) { |
+ RemoveInjectionsWithExtensionId(&injections_awaiting_permission_, |
+ changed_extensions); |
+ RemoveInjectionsWithExtensionId(&injections_awaiting_location_, |
+ changed_extensions); |
+} |
+ |
+void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) { |
+ ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin(); |
+ for (; iter != rvo_helpers_.end() && *iter != helper; ++iter) |
+ ; // intentionally empty. |
+ if (iter != rvo_helpers_.end()) |
+ rvo_helpers_.erase(iter); |
+} |
+ |
+void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) { |
+ RemoveInjectionsWithFrame(&injections_awaiting_location_, frame); |
+ RemoveInjectionsWithFrame(&injections_awaiting_permission_, frame); |
+ frame_statuses_.erase(frame); |
+} |
+ |
+void ScriptInjectionManager::InjectScripts( |
+ blink::WebFrame* frame, UserScript::RunLocation run_location) { |
+ FrameStatus& frame_status = frame_statuses_[frame]; |
+ frame_status.current_location = run_location; |
+ if (run_location == UserScript::DOCUMENT_IDLE) { |
+ if (frame_status.has_run_idle) |
+ return; |
+ frame_status.has_run_idle = true; |
+ } |
+ |
+ ScriptInjection::ScriptsRunInfo scripts_run_info; |
+ for (ScopedVector<ScriptInjection>::iterator iter = |
+ injections_awaiting_location_.begin(); |
+ iter != injections_awaiting_location_.end();) { |
+ if ((*iter)->run_location() == run_location) { |
+ scoped_ptr<ScriptInjection> injection(*iter); |
+ iter = injections_awaiting_location_.weak_erase(iter); |
+ |
+ // Since extension info is sent separately from user script info, they can |
+ // be out of sync. We just ignore this situation. |
+ const Extension* extension = |
+ extensions_->GetByID(injection->extension_id()); |
+ if (!extension) |
+ injection->ExtensionNotFound(); |
+ else if (injection->Allowed(extension)) |
+ injection->Inject(&scripts_run_info, extension); |
+ } else { |
+ ++iter; |
+ } |
+ } |
+ |
+ GURL document_url = UserScriptInjectionList::GetDocumentUrlForFrame(frame); |
+ ScopedVector<ScriptInjection> user_script_injections; |
+ int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView( |
+ frame->top()->view()))->tab_id(); |
+ user_script_injection_list_->GetInjections(&user_script_injections, |
+ frame, |
+ tab_id, |
+ run_location, |
+ document_url, |
+ extensions_); |
+ for (ScopedVector<ScriptInjection>::iterator iter = |
+ user_script_injections.begin(); |
+ iter != user_script_injections.end();) { |
+ scoped_ptr<ScriptInjection> injection(*iter); |
+ iter = user_script_injections.weak_erase(iter); |
+ |
+ const Extension* extension = |
+ extensions_->GetByID(injection->extension_id()); |
+ if (!extension) { |
+ injection->ExtensionNotFound(); |
+ continue; |
+ } |
+ |
+ if (injection->Allowed(extension)) |
+ injection->Inject(&scripts_run_info, extension); |
+ else |
+ RequestPermissionOrInject(injection.Pass(), &scripts_run_info, extension); |
+ } |
+ |
+ LogScriptsRun(frame, run_location, scripts_run_info); |
+} |
+ |
+void ScriptInjectionManager::RequestPermissionOrInject( |
+ scoped_ptr<ScriptInjection> injection, |
+ ScriptInjection::ScriptsRunInfo* scripts_run_info, |
+ const Extension* extension) { |
+ content::RenderView* render_view = |
+ content::RenderView::FromWebView(injection->web_frame()->top()->view()); |
+ |
+ // By default, we allow injection. |
+ bool should_inject = true; |
+ int64 request_id = kInvalidRequestId; |
+ int page_id = render_view->GetPageId(); |
+ |
+ if (FeatureSwitch::scripts_require_action()->IsEnabled()) { |
+ should_inject = false; |
+ request_id = g_next_pending_id++; |
+ injection->set_request_id(request_id); |
+ injections_awaiting_permission_.push_back(injection.release()); |
+ } |
+ |
+ render_view->Send(new ExtensionHostMsg_RequestScriptInjectionPermission( |
+ render_view->GetRoutingID(), extension->id(), page_id, request_id)); |
+ |
+ // Since delaying script injection is hidden behind a flag, inject the script |
+ // immediately if the feature is disabled. |
+ if (should_inject) |
+ injection->Inject(scripts_run_info, extension); |
+} |
+ |
+void ScriptInjectionManager::HandleExecuteCode( |
+ const ExtensionMsg_ExecuteCode_Params& params, |
+ content::RenderView* render_view) { |
+ blink::WebFrame* main_frame = render_view->GetWebView()->mainFrame(); |
+ if (!main_frame) { |
+ render_view->Send( |
+ new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(), |
+ params.request_id, |
+ "No main frame", |
+ -1, |
+ GURL(std::string()), |
+ base::ListValue())); |
+ return; |
+ } |
+ |
+ scoped_ptr<ScriptInjection> injection(new ProgrammaticScriptInjection( |
+ main_frame, params, ExtensionHelper::Get(render_view)->tab_id())); |
+ |
+ if (injection->run_location() > |
+ frame_statuses_[main_frame].current_location) { |
+ injections_awaiting_location_.push_back(injection.release()); |
+ return; |
+ } |
+ |
+ const Extension* extension = extensions_->GetByID(injection->extension_id()); |
+ ScriptInjection::ScriptsRunInfo scripts_run_info; |
+ if (!extension) |
+ injection->ExtensionNotFound(); |
+ else if (injection->Allowed(extension)) |
+ injection->Inject(&scripts_run_info, extension); |
+} |
+ |
+void ScriptInjectionManager::HandlePermitScriptInjection(int request_id) { |
+ ScopedVector<ScriptInjection>::iterator iter = |
+ injections_awaiting_permission_.begin(); |
+ for (; iter != injections_awaiting_permission_.end(); ++iter) { |
+ if ((*iter)->request_id() == request_id) |
+ break; |
+ } |
+ if (iter == injections_awaiting_permission_.end()) |
+ return; |
+ |
+ scoped_ptr<ScriptInjection> injection(*iter); |
+ injections_awaiting_permission_.weak_erase(iter); |
+ |
+ const Extension* extension = extensions_->GetByID(injection->extension_id()); |
+ if (!extension) { |
+ injection->ExtensionNotFound(); |
+ return; |
+ } |
+ |
+ ScriptInjection::ScriptsRunInfo scripts_run_info; |
+ injection->Inject(&scripts_run_info, extension); |
+ LogScriptsRun( |
+ injection->web_frame(), UserScript::RUN_DEFERRED, scripts_run_info); |
+} |
+ |
+} // namespace extensions |