blob: bfb0156128398269bf0a26c4448e2cdc65770295 [file] [log] [blame]
[email protected]ac2f89372014-06-23 21:44:251// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "extensions/renderer/script_injection_manager.h"
6
7#include "base/bind.h"
8#include "base/memory/weak_ptr.h"
9#include "base/metrics/histogram.h"
10#include "base/values.h"
11#include "content/public/renderer/render_view.h"
12#include "content/public/renderer/render_view_observer.h"
13#include "extensions/common/extension.h"
14#include "extensions/common/extension_messages.h"
15#include "extensions/common/extension_set.h"
16#include "extensions/renderer/extension_helper.h"
17#include "extensions/renderer/programmatic_script_injection.h"
18#include "extensions/renderer/script_context.h"
19#include "ipc/ipc_message_macros.h"
20#include "third_party/WebKit/public/web/WebFrame.h"
21#include "third_party/WebKit/public/web/WebLocalFrame.h"
22#include "third_party/WebKit/public/web/WebView.h"
23#include "url/gurl.h"
24
25namespace extensions {
26
27namespace {
28
29// The length of time to wait after the DOM is complete to try and run user
30// scripts.
31const int kScriptIdleTimeoutInMs = 200;
32
33// Log information about a given script run.
34void LogScriptsRun(blink::WebFrame* frame,
35 UserScript::RunLocation location,
36 const ScriptInjection::ScriptsRunInfo& info) {
37 // Notify the browser if any extensions are now executing scripts.
38 if (!info.executing_scripts.empty()) {
39 content::RenderView* render_view =
40 content::RenderView::FromWebView(frame->view());
41 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
42 render_view->GetRoutingID(),
43 info.executing_scripts,
44 render_view->GetPageId(),
45 ScriptContext::GetDataSourceURLForFrame(frame)));
46 }
47
48 switch (location) {
49 case UserScript::DOCUMENT_START:
50 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", info.num_css);
51 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount",
52 info.num_js);
53 if (info.num_css || info.num_js)
54 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time",
55 info.timer.Elapsed());
56 break;
57 case UserScript::DOCUMENT_END:
58 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js);
59 if (info.num_js)
60 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed());
61 break;
62 case UserScript::DOCUMENT_IDLE:
63 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount",
64 info.num_js);
65 if (info.num_js)
66 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed());
67 break;
68 case UserScript::RUN_DEFERRED:
69 // TODO(rdevlin.cronin): Add histograms.
70 break;
71 case UserScript::UNDEFINED:
72 case UserScript::RUN_LOCATION_LAST:
73 NOTREACHED();
74 }
75}
76
77} // namespace
78
79class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
80 public:
81 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
82 virtual ~RVOHelper();
83
84 private:
85 // RenderViewObserver implementation.
86 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
87 virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE;
88 virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE;
89 virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE;
90 virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE;
91 virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE;
92 virtual void OnDestruct() OVERRIDE;
93
94 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
95 virtual void OnPermitScriptInjection(int64 request_id);
96
97 // Tells the ScriptInjectionManager to run tasks associated with
98 // document_idle.
99 void RunIdle(blink::WebFrame* frame);
100
101 ScriptInjectionManager* manager_;
102 base::WeakPtrFactory<RVOHelper> weak_factory_;
103};
104
105ScriptInjectionManager::RVOHelper::RVOHelper(
106 content::RenderView* render_view,
107 ScriptInjectionManager* manager)
108 : content::RenderViewObserver(render_view),
109 manager_(manager),
110 weak_factory_(this) {
111}
112
113ScriptInjectionManager::RVOHelper::~RVOHelper() {
114}
115
116bool ScriptInjectionManager::RVOHelper::OnMessageReceived(
117 const IPC::Message& message) {
118 bool handled = true;
119 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message)
120 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
121 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
122 OnPermitScriptInjection)
123 IPC_MESSAGE_UNHANDLED(handled = false)
124 IPC_END_MESSAGE_MAP()
125 return handled;
126}
127
128void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
129 blink::WebLocalFrame* frame) {
130 manager_->InjectScripts(frame, UserScript::DOCUMENT_START);
131}
132
133void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
134 blink::WebLocalFrame* frame) {
135 manager_->InjectScripts(frame, UserScript::DOCUMENT_END);
136 base::MessageLoop::current()->PostDelayedTask(
137 FROM_HERE,
138 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
139 weak_factory_.GetWeakPtr(),
140 frame),
141 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
142}
143
144void ScriptInjectionManager::RVOHelper::DidFinishLoad(
145 blink::WebLocalFrame* frame) {
146 // Ensure that running scripts does not keep any progress UI running.
147 base::MessageLoop::current()->PostTask(
148 FROM_HERE,
149 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
150 weak_factory_.GetWeakPtr(),
151 frame));
152}
153
154void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad(
155 blink::WebLocalFrame* frame) {
156 manager_->InvalidateForFrame(frame);
157}
158
159void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
160 manager_->InvalidateForFrame(frame);
161}
162
163void ScriptInjectionManager::RVOHelper::OnDestruct() {
164 manager_->RemoveObserver(this);
165}
166
167void ScriptInjectionManager::RVOHelper::OnExecuteCode(
168 const ExtensionMsg_ExecuteCode_Params& params) {
169 manager_->HandleExecuteCode(params, render_view());
170}
171
172void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
173 int64 request_id) {
174 manager_->HandlePermitScriptInjection(request_id);
175}
176
177void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) {
178 manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE);
179}
180
181ScriptInjectionManager::ScriptInjectionManager(
182 const ExtensionSet* extensions,
183 UserScriptSet* user_script_set)
184 : extensions_(extensions),
185 user_script_set_(user_script_set),
186 user_script_set_observer_(this) {
187 user_script_set_observer_.Add(user_script_set_);
188}
189
190ScriptInjectionManager::~ScriptInjectionManager() {
191}
192
193void ScriptInjectionManager::OnRenderViewCreated(
194 content::RenderView* render_view) {
195 rvo_helpers_.push_back(new RVOHelper(render_view, this));
196}
197
198void ScriptInjectionManager::OnUserScriptsUpdated(
199 const std::set<std::string>& changed_extensions,
200 const std::vector<UserScript*>& scripts) {
201 for (ScopedVector<ScriptInjection>::iterator iter =
202 pending_injections_.begin();
203 iter != pending_injections_.end();) {
204 if (changed_extensions.count((*iter)->extension_id()) > 0)
205 iter = pending_injections_.erase(iter);
206 else
207 ++iter;
208 }
209}
210
211void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) {
212 for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin();
213 iter != rvo_helpers_.end();
214 ++iter) {
215 if (*iter == helper) {
216 rvo_helpers_.erase(iter);
217 break;
218 }
219 }
220}
221
222void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) {
223 for (ScopedVector<ScriptInjection>::iterator iter =
224 pending_injections_.begin();
225 iter != pending_injections_.end();) {
226 if ((*iter)->web_frame() == frame)
227 iter = pending_injections_.erase(iter);
228 else
229 ++iter;
230 }
231
232 frame_statuses_.erase(frame);
233}
234
235void ScriptInjectionManager::InjectScripts(
236 blink::WebFrame* frame, UserScript::RunLocation run_location) {
237 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
238 // Check if the frame is already in our map.
239 if (iter == frame_statuses_.end()) {
240 // If the frame isn't in our map, and the run location isn't document_start,
241 // then abort. This can happen in two ways:
242 // 1. We just received a delayed idle run for a frame which is invalidated.
243 // Obviously, we don't want to run.
244 // 2. We somehow received a document_end or document_idle notification
245 // without ever receiving a document_start. We don't want to run because
246 // extensions may have requirements that scripts running at start have
247 // run by the time scripts run at idle. Better to just not run.
248 if (run_location != UserScript::DOCUMENT_START)
249 return;
250
251 // Otherwise, add a new entry to the map.
252 frame_statuses_[frame] = UserScript::DOCUMENT_START;
253 } else { // Already in the map.
254 // If we've already run the given location (happens in the case of idle
255 // since we notify in two places), return.
256 if (iter->second == run_location)
257 return;
258
259 // Otherwise, update the frame status.
260 iter->second = run_location;
261 }
262
263 // Inject any scripts that were waiting for the right run location.
264 ScriptInjection::ScriptsRunInfo scripts_run_info;
265 for (ScopedVector<ScriptInjection>::iterator iter =
266 pending_injections_.begin();
267 iter != pending_injections_.end();) {
268 if ((*iter)->web_frame() == frame &&
269 (*iter)->TryToInject(run_location,
270 extensions_->GetByID((*iter)->extension_id()),
271 &scripts_run_info)) {
272 iter = pending_injections_.erase(iter);
273 } else {
274 ++iter;
275 }
276 }
277
278 // Try to inject any user scripts that should run for this location. If they
279 // don't complete their injection (for example, waiting for a permission
280 // response) then they will be added to |pending_injections_|.
281 ScopedVector<ScriptInjection> user_script_injections;
282 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView(
283 frame->top()->view()))->tab_id();
284 user_script_set_->GetInjections(
285 &user_script_injections, frame, tab_id, run_location);
286 for (ScopedVector<ScriptInjection>::iterator iter =
287 user_script_injections.begin();
288 iter != user_script_injections.end();) {
289 scoped_ptr<ScriptInjection> injection(*iter);
290 iter = user_script_injections.weak_erase(iter);
291 if (!injection->TryToInject(run_location,
292 extensions_->GetByID(injection->extension_id()),
293 &scripts_run_info)) {
294 pending_injections_.push_back(injection.release());
295 }
296 }
297
298 LogScriptsRun(frame, run_location, scripts_run_info);
299}
300
301void ScriptInjectionManager::HandleExecuteCode(
302 const ExtensionMsg_ExecuteCode_Params& params,
303 content::RenderView* render_view) {
304 blink::WebFrame* main_frame = render_view->GetWebView()->mainFrame();
305 if (!main_frame) {
306 render_view->Send(
307 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
308 params.request_id,
309 "No main frame",
310 -1,
311 GURL(std::string()),
312 base::ListValue()));
313 return;
314 }
315
316 scoped_ptr<ScriptInjection> injection(new ProgrammaticScriptInjection(
317 main_frame, params, ExtensionHelper::Get(render_view)->tab_id()));
318
319 ScriptInjection::ScriptsRunInfo scripts_run_info;
320 FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame);
321 if (!injection->TryToInject(
322 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second,
323 extensions_->GetByID(injection->extension_id()),
324 &scripts_run_info)) {
325 pending_injections_.push_back(injection.release());
326 }
327}
328
329void ScriptInjectionManager::HandlePermitScriptInjection(int request_id) {
330 ScopedVector<ScriptInjection>::iterator iter =
331 pending_injections_.begin();
332 for (; iter != pending_injections_.end(); ++iter) {
333 if ((*iter)->request_id() == request_id)
334 break;
335 }
336 if (iter == pending_injections_.end())
337 return;
338
339 scoped_ptr<ScriptInjection> injection(*iter);
340 pending_injections_.weak_erase(iter);
341
342 ScriptInjection::ScriptsRunInfo scripts_run_info;
343 if (injection->OnPermissionGranted(extensions_->GetByID(
344 injection->extension_id()),
345 &scripts_run_info)) {
346 LogScriptsRun(
347 injection->web_frame(), UserScript::RUN_DEFERRED, scripts_run_info);
348 }
349}
350
351} // namespace extensions