| 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
|
|
|