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

Unified Diff: extensions/renderer/script_injection_manager.cc

Issue 321993003: Refactor renderer-side script injection (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Missing files Created 6 years, 6 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 side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698