blob: 47c9fca243b32e11ee96ae2e82737bbcfac09ba9 [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"
[email protected]ac2f89372014-06-23 21:44:259#include "base/values.h"
10#include "content/public/renderer/render_view.h"
11#include "content/public/renderer/render_view_observer.h"
12#include "extensions/common/extension.h"
13#include "extensions/common/extension_messages.h"
14#include "extensions/common/extension_set.h"
15#include "extensions/renderer/extension_helper.h"
[email protected]c11e6592014-06-27 17:07:3416#include "extensions/renderer/programmatic_script_injector.h"
17#include "extensions/renderer/script_injection.h"
18#include "extensions/renderer/scripts_run_info.h"
[email protected]ac2f89372014-06-23 21:44:2519#include "ipc/ipc_message_macros.h"
markdittmer9ea140f2014-08-29 02:46:1520#include "third_party/WebKit/public/web/WebDocument.h"
[email protected]ac2f89372014-06-23 21:44:2521#include "third_party/WebKit/public/web/WebFrame.h"
22#include "third_party/WebKit/public/web/WebLocalFrame.h"
23#include "third_party/WebKit/public/web/WebView.h"
24#include "url/gurl.h"
25
26namespace extensions {
27
28namespace {
29
30// The length of time to wait after the DOM is complete to try and run user
31// scripts.
32const int kScriptIdleTimeoutInMs = 200;
33
[email protected]ac2f89372014-06-23 21:44:2534} // namespace
35
36class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
37 public:
38 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
dcheng9168b2f2014-10-21 12:38:2439 ~RVOHelper() override;
[email protected]ac2f89372014-06-23 21:44:2540
41 private:
42 // RenderViewObserver implementation.
dcheng9168b2f2014-10-21 12:38:2443 bool OnMessageReceived(const IPC::Message& message) override;
rob5ef11ff2014-11-17 23:56:2044 void DidCreateNewDocument(blink::WebLocalFrame* frame) override;
dcheng9168b2f2014-10-21 12:38:2445 void DidCreateDocumentElement(blink::WebLocalFrame* frame) override;
46 void DidFinishDocumentLoad(blink::WebLocalFrame* frame) override;
47 void DidFinishLoad(blink::WebLocalFrame* frame) override;
dcheng9168b2f2014-10-21 12:38:2448 void FrameDetached(blink::WebFrame* frame) override;
49 void OnDestruct() override;
[email protected]ac2f89372014-06-23 21:44:2550
51 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
markdittmer9ea140f2014-08-29 02:46:1552 virtual void OnExecuteDeclarativeScript(int tab_id,
53 const ExtensionId& extension_id,
54 int script_id,
55 const GURL& url);
[email protected]ac2f89372014-06-23 21:44:2556 virtual void OnPermitScriptInjection(int64 request_id);
57
58 // Tells the ScriptInjectionManager to run tasks associated with
59 // document_idle.
60 void RunIdle(blink::WebFrame* frame);
61
[email protected]5672620d2014-07-30 10:07:1762 // Indicate that the given |frame| is no longer valid because it is starting
63 // a new load or closing.
64 void InvalidateFrame(blink::WebFrame* frame);
65
66 // The owning ScriptInjectionManager.
[email protected]ac2f89372014-06-23 21:44:2567 ScriptInjectionManager* manager_;
[email protected]5672620d2014-07-30 10:07:1768
69 // The set of frames that we are about to notify for DOCUMENT_IDLE. We keep
70 // a set of those that are valid, so we don't notify that an invalid frame
71 // became idle.
72 std::set<blink::WebFrame*> pending_idle_frames_;
73
[email protected]ac2f89372014-06-23 21:44:2574 base::WeakPtrFactory<RVOHelper> weak_factory_;
75};
76
77ScriptInjectionManager::RVOHelper::RVOHelper(
78 content::RenderView* render_view,
79 ScriptInjectionManager* manager)
80 : content::RenderViewObserver(render_view),
81 manager_(manager),
82 weak_factory_(this) {
83}
84
85ScriptInjectionManager::RVOHelper::~RVOHelper() {
86}
87
88bool ScriptInjectionManager::RVOHelper::OnMessageReceived(
89 const IPC::Message& message) {
90 bool handled = true;
91 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message)
92 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
93 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
94 OnPermitScriptInjection)
markdittmer9ea140f2014-08-29 02:46:1595 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
96 OnExecuteDeclarativeScript)
[email protected]ac2f89372014-06-23 21:44:2597 IPC_MESSAGE_UNHANDLED(handled = false)
98 IPC_END_MESSAGE_MAP()
99 return handled;
100}
101
rob5ef11ff2014-11-17 23:56:20102void ScriptInjectionManager::RVOHelper::DidCreateNewDocument(
103 blink::WebLocalFrame* frame) {
104 // A new document is going to be shown, so invalidate the old document state.
105 // Check that the frame's state is known before invalidating the frame,
106 // because it is possible that a script injection was scheduled before the
107 // page was loaded, e.g. by navigating to a javascript: URL before the page
108 // has loaded.
109 if (manager_->frame_statuses_.find(frame) != manager_->frame_statuses_.end())
110 InvalidateFrame(frame);
111}
112
[email protected]ac2f89372014-06-23 21:44:25113void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
114 blink::WebLocalFrame* frame) {
115 manager_->InjectScripts(frame, UserScript::DOCUMENT_START);
116}
117
118void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
119 blink::WebLocalFrame* frame) {
120 manager_->InjectScripts(frame, UserScript::DOCUMENT_END);
[email protected]5672620d2014-07-30 10:07:17121 pending_idle_frames_.insert(frame);
122 // We try to run idle in two places: here and DidFinishLoad.
123 // DidFinishDocumentLoad() corresponds to completing the document's load,
124 // whereas DidFinishLoad corresponds to completing the document and all
125 // subresources' load. We don't want to hold up script injection for a
126 // particularly slow subresource, so we set a delayed task from here - but if
127 // we finish everything before that point (i.e., DidFinishLoad() is
128 // triggered), then there's no reason to keep waiting.
[email protected]ac2f89372014-06-23 21:44:25129 base::MessageLoop::current()->PostDelayedTask(
130 FROM_HERE,
131 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
132 weak_factory_.GetWeakPtr(),
133 frame),
134 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
135}
136
137void ScriptInjectionManager::RVOHelper::DidFinishLoad(
138 blink::WebLocalFrame* frame) {
[email protected]5672620d2014-07-30 10:07:17139 // Ensure that we don't block any UI progress by running scripts.
140 // We *don't* add the frame to |pending_idle_frames_| here because
141 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the
142 // first posted task to RunIdle() pops it out of the set. This ensures we
143 // don't try to run idle twice.
[email protected]ac2f89372014-06-23 21:44:25144 base::MessageLoop::current()->PostTask(
145 FROM_HERE,
146 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
147 weak_factory_.GetWeakPtr(),
148 frame));
149}
150
[email protected]ac2f89372014-06-23 21:44:25151void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
[email protected]5672620d2014-07-30 10:07:17152 // The frame is closing - invalidate.
153 InvalidateFrame(frame);
[email protected]ac2f89372014-06-23 21:44:25154}
155
156void ScriptInjectionManager::RVOHelper::OnDestruct() {
157 manager_->RemoveObserver(this);
158}
159
160void ScriptInjectionManager::RVOHelper::OnExecuteCode(
161 const ExtensionMsg_ExecuteCode_Params& params) {
162 manager_->HandleExecuteCode(params, render_view());
163}
164
markdittmer9ea140f2014-08-29 02:46:15165void ScriptInjectionManager::RVOHelper::OnExecuteDeclarativeScript(
166 int tab_id,
167 const ExtensionId& extension_id,
168 int script_id,
169 const GURL& url) {
170 blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
171 CHECK(main_frame);
172
173 // TODO(markdittmer): This would be cleaner if we compared page_ids instead.
174 // Begin script injeciton workflow only if the current URL is identical to
175 // the one that matched declarative conditions in the browser.
176 if (main_frame->top()->document().url() == url) {
177 manager_->HandleExecuteDeclarativeScript(main_frame,
178 tab_id,
179 extension_id,
180 script_id,
181 url);
182 }
183}
184
[email protected]ac2f89372014-06-23 21:44:25185void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
186 int64 request_id) {
187 manager_->HandlePermitScriptInjection(request_id);
188}
189
190void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) {
[email protected]5672620d2014-07-30 10:07:17191 // Only notify the manager if the frame hasn't either been removed or already
192 // had idle run since the task to RunIdle() was posted.
193 if (pending_idle_frames_.count(frame) > 0) {
194 manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE);
195 pending_idle_frames_.erase(frame);
196 }
197}
198
199void ScriptInjectionManager::RVOHelper::InvalidateFrame(
200 blink::WebFrame* frame) {
201 pending_idle_frames_.erase(frame);
202 manager_->InvalidateForFrame(frame);
[email protected]ac2f89372014-06-23 21:44:25203}
204
205ScriptInjectionManager::ScriptInjectionManager(
206 const ExtensionSet* extensions,
[email protected]4c356902014-07-30 09:52:02207 UserScriptSetManager* user_script_set_manager)
[email protected]ac2f89372014-06-23 21:44:25208 : extensions_(extensions),
[email protected]4c356902014-07-30 09:52:02209 user_script_set_manager_(user_script_set_manager),
210 user_script_set_manager_observer_(this) {
211 user_script_set_manager_observer_.Add(user_script_set_manager_);
[email protected]ac2f89372014-06-23 21:44:25212}
213
214ScriptInjectionManager::~ScriptInjectionManager() {
215}
216
217void ScriptInjectionManager::OnRenderViewCreated(
218 content::RenderView* render_view) {
219 rvo_helpers_.push_back(new RVOHelper(render_view, this));
220}
221
222void ScriptInjectionManager::OnUserScriptsUpdated(
223 const std::set<std::string>& changed_extensions,
224 const std::vector<UserScript*>& scripts) {
225 for (ScopedVector<ScriptInjection>::iterator iter =
226 pending_injections_.begin();
227 iter != pending_injections_.end();) {
228 if (changed_extensions.count((*iter)->extension_id()) > 0)
229 iter = pending_injections_.erase(iter);
230 else
231 ++iter;
232 }
233}
234
235void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) {
236 for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin();
237 iter != rvo_helpers_.end();
238 ++iter) {
239 if (*iter == helper) {
240 rvo_helpers_.erase(iter);
241 break;
242 }
243 }
244}
245
246void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) {
247 for (ScopedVector<ScriptInjection>::iterator iter =
248 pending_injections_.begin();
249 iter != pending_injections_.end();) {
250 if ((*iter)->web_frame() == frame)
251 iter = pending_injections_.erase(iter);
252 else
253 ++iter;
254 }
255
256 frame_statuses_.erase(frame);
257}
258
259void ScriptInjectionManager::InjectScripts(
260 blink::WebFrame* frame, UserScript::RunLocation run_location) {
261 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
[email protected]5672620d2014-07-30 10:07:17262 // We also don't execute if we detect that the run location is somehow out of
263 // order. This can happen if:
264 // - The first run location reported for the frame isn't DOCUMENT_START, or
265 // - The run location reported doesn't immediately follow the previous
266 // reported run location.
267 // We don't want to run because extensions may have requirements that scripts
268 // running in an earlier run location have run by the time a later script
269 // runs. Better to just not run.
270 if ((iter == frame_statuses_.end() &&
271 run_location != UserScript::DOCUMENT_START) ||
272 (iter != frame_statuses_.end() && run_location - iter->second > 1)) {
273 // We also invalidate the frame, because the run order of pending injections
274 // may also be bad.
275 InvalidateForFrame(frame);
276 return;
277 } else if (iter != frame_statuses_.end() && iter->second > run_location) {
278 // Certain run location signals (like DidCreateDocumentElement) can happen
279 // multiple times. Ignore the subsequent signals.
280 return;
[email protected]ac2f89372014-06-23 21:44:25281 }
282
[email protected]5672620d2014-07-30 10:07:17283 // Otherwise, all is right in the world, and we can get on with the
284 // injections!
285
286 frame_statuses_[frame] = run_location;
287
[email protected]ac2f89372014-06-23 21:44:25288 // Inject any scripts that were waiting for the right run location.
[email protected]c11e6592014-06-27 17:07:34289 ScriptsRunInfo scripts_run_info;
[email protected]ac2f89372014-06-23 21:44:25290 for (ScopedVector<ScriptInjection>::iterator iter =
291 pending_injections_.begin();
292 iter != pending_injections_.end();) {
293 if ((*iter)->web_frame() == frame &&
294 (*iter)->TryToInject(run_location,
295 extensions_->GetByID((*iter)->extension_id()),
296 &scripts_run_info)) {
297 iter = pending_injections_.erase(iter);
298 } else {
299 ++iter;
300 }
301 }
302
303 // Try to inject any user scripts that should run for this location. If they
304 // don't complete their injection (for example, waiting for a permission
305 // response) then they will be added to |pending_injections_|.
306 ScopedVector<ScriptInjection> user_script_injections;
307 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView(
308 frame->top()->view()))->tab_id();
[email protected]4c356902014-07-30 09:52:02309 user_script_set_manager_->GetAllInjections(
[email protected]ac2f89372014-06-23 21:44:25310 &user_script_injections, frame, tab_id, run_location);
311 for (ScopedVector<ScriptInjection>::iterator iter =
312 user_script_injections.begin();
313 iter != user_script_injections.end();) {
314 scoped_ptr<ScriptInjection> injection(*iter);
315 iter = user_script_injections.weak_erase(iter);
316 if (!injection->TryToInject(run_location,
317 extensions_->GetByID(injection->extension_id()),
318 &scripts_run_info)) {
319 pending_injections_.push_back(injection.release());
320 }
321 }
322
[email protected]c11e6592014-06-27 17:07:34323 scripts_run_info.LogRun(frame, run_location);
[email protected]ac2f89372014-06-23 21:44:25324}
325
326void ScriptInjectionManager::HandleExecuteCode(
327 const ExtensionMsg_ExecuteCode_Params& params,
328 content::RenderView* render_view) {
dcheng2e449172014-09-24 04:30:56329 // TODO(dcheng): Not sure how this can happen today. In an OOPI world, it
330 // would indicate a logic error--the browser must direct this request to the
331 // right renderer process to begin with.
332 blink::WebLocalFrame* main_frame =
333 render_view->GetWebView()->mainFrame()->toWebLocalFrame();
[email protected]ac2f89372014-06-23 21:44:25334 if (!main_frame) {
335 render_view->Send(
336 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
337 params.request_id,
338 "No main frame",
[email protected]ac2f89372014-06-23 21:44:25339 GURL(std::string()),
340 base::ListValue()));
341 return;
342 }
343
[email protected]c11e6592014-06-27 17:07:34344 scoped_ptr<ScriptInjection> injection(new ScriptInjection(
345 scoped_ptr<ScriptInjector>(
346 new ProgrammaticScriptInjector(params, main_frame)),
347 main_frame,
348 params.extension_id,
349 static_cast<UserScript::RunLocation>(params.run_at),
350 ExtensionHelper::Get(render_view)->tab_id()));
[email protected]ac2f89372014-06-23 21:44:25351
[email protected]c11e6592014-06-27 17:07:34352 ScriptsRunInfo scripts_run_info;
[email protected]ac2f89372014-06-23 21:44:25353 FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame);
354 if (!injection->TryToInject(
355 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second,
356 extensions_->GetByID(injection->extension_id()),
357 &scripts_run_info)) {
358 pending_injections_.push_back(injection.release());
359 }
360}
361
markdittmer9ea140f2014-08-29 02:46:15362void ScriptInjectionManager::HandleExecuteDeclarativeScript(
363 blink::WebFrame* web_frame,
364 int tab_id,
365 const ExtensionId& extension_id,
366 int script_id,
367 const GURL& url) {
368 const Extension* extension = extensions_->GetByID(extension_id);
dcheng2e449172014-09-24 04:30:56369 // TODO(dcheng): This function signature should really be a WebLocalFrame,
370 // rather than trying to coerce it here.
markdittmer9ea140f2014-08-29 02:46:15371 scoped_ptr<ScriptInjection> injection =
372 user_script_set_manager_->GetInjectionForDeclarativeScript(
373 script_id,
dcheng2e449172014-09-24 04:30:56374 web_frame->toWebLocalFrame(),
markdittmer9ea140f2014-08-29 02:46:15375 tab_id,
376 url,
377 extension);
378 if (injection.get()) {
379 ScriptsRunInfo scripts_run_info;
380 // TODO(markdittmer): Use return value of TryToInject for error handling.
381 injection->TryToInject(UserScript::BROWSER_DRIVEN,
382 extension,
383 &scripts_run_info);
384 scripts_run_info.LogRun(web_frame, UserScript::BROWSER_DRIVEN);
385 }
386}
387
[email protected]d2056002014-07-03 06:18:06388void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) {
[email protected]ac2f89372014-06-23 21:44:25389 ScopedVector<ScriptInjection>::iterator iter =
390 pending_injections_.begin();
391 for (; iter != pending_injections_.end(); ++iter) {
392 if ((*iter)->request_id() == request_id)
393 break;
394 }
395 if (iter == pending_injections_.end())
396 return;
397
[email protected]d2056002014-07-03 06:18:06398 // At this point, because the request is present in pending_injections_, we
399 // know that this is the same page that issued the request (otherwise,
400 // RVOHelper's DidStartProvisionalLoad callback would have caused it to be
401 // cleared out).
402
[email protected]ac2f89372014-06-23 21:44:25403 scoped_ptr<ScriptInjection> injection(*iter);
404 pending_injections_.weak_erase(iter);
405
[email protected]c11e6592014-06-27 17:07:34406 ScriptsRunInfo scripts_run_info;
[email protected]ac2f89372014-06-23 21:44:25407 if (injection->OnPermissionGranted(extensions_->GetByID(
408 injection->extension_id()),
409 &scripts_run_info)) {
[email protected]c11e6592014-06-27 17:07:34410 scripts_run_info.LogRun(injection->web_frame(), UserScript::RUN_DEFERRED);
[email protected]ac2f89372014-06-23 21:44:25411 }
412}
413
414} // namespace extensions