Chromecast: adds Chromecast build # to User-Agent string.
[chromium-blink-merge.git] / extensions / renderer / script_injection_manager.cc
blobabd7d80cd82ad9077009c16b4eb3d7fa75034b98
1 // 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.
5 #include "extensions/renderer/script_injection_manager.h"
7 #include "base/bind.h"
8 #include "base/memory/weak_ptr.h"
9 #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"
16 #include "extensions/renderer/programmatic_script_injector.h"
17 #include "extensions/renderer/script_injection.h"
18 #include "extensions/renderer/scripts_run_info.h"
19 #include "ipc/ipc_message_macros.h"
20 #include "third_party/WebKit/public/web/WebDocument.h"
21 #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"
26 namespace extensions {
28 namespace {
30 // The length of time to wait after the DOM is complete to try and run user
31 // scripts.
32 const int kScriptIdleTimeoutInMs = 200;
34 } // namespace
36 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
37 public:
38 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
39 virtual ~RVOHelper();
41 private:
42 // RenderViewObserver implementation.
43 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
44 virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE;
45 virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE;
46 virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE;
47 virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE;
48 virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE;
49 virtual void OnDestruct() OVERRIDE;
51 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
52 virtual void OnExecuteDeclarativeScript(int tab_id,
53 const ExtensionId& extension_id,
54 int script_id,
55 const GURL& url);
56 virtual void OnPermitScriptInjection(int64 request_id);
58 // Tells the ScriptInjectionManager to run tasks associated with
59 // document_idle.
60 void RunIdle(blink::WebFrame* frame);
62 // 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);
66 // The owning ScriptInjectionManager.
67 ScriptInjectionManager* manager_;
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_;
74 base::WeakPtrFactory<RVOHelper> weak_factory_;
77 ScriptInjectionManager::RVOHelper::RVOHelper(
78 content::RenderView* render_view,
79 ScriptInjectionManager* manager)
80 : content::RenderViewObserver(render_view),
81 manager_(manager),
82 weak_factory_(this) {
85 ScriptInjectionManager::RVOHelper::~RVOHelper() {
88 bool 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)
95 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
96 OnExecuteDeclarativeScript)
97 IPC_MESSAGE_UNHANDLED(handled = false)
98 IPC_END_MESSAGE_MAP()
99 return handled;
102 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
103 blink::WebLocalFrame* frame) {
104 manager_->InjectScripts(frame, UserScript::DOCUMENT_START);
107 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
108 blink::WebLocalFrame* frame) {
109 manager_->InjectScripts(frame, UserScript::DOCUMENT_END);
110 pending_idle_frames_.insert(frame);
111 // We try to run idle in two places: here and DidFinishLoad.
112 // DidFinishDocumentLoad() corresponds to completing the document's load,
113 // whereas DidFinishLoad corresponds to completing the document and all
114 // subresources' load. We don't want to hold up script injection for a
115 // particularly slow subresource, so we set a delayed task from here - but if
116 // we finish everything before that point (i.e., DidFinishLoad() is
117 // triggered), then there's no reason to keep waiting.
118 base::MessageLoop::current()->PostDelayedTask(
119 FROM_HERE,
120 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
121 weak_factory_.GetWeakPtr(),
122 frame),
123 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
126 void ScriptInjectionManager::RVOHelper::DidFinishLoad(
127 blink::WebLocalFrame* frame) {
128 // Ensure that we don't block any UI progress by running scripts.
129 // We *don't* add the frame to |pending_idle_frames_| here because
130 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the
131 // first posted task to RunIdle() pops it out of the set. This ensures we
132 // don't try to run idle twice.
133 base::MessageLoop::current()->PostTask(
134 FROM_HERE,
135 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
136 weak_factory_.GetWeakPtr(),
137 frame));
140 void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad(
141 blink::WebLocalFrame* frame) {
142 // We're starting a new load - invalidate.
143 InvalidateFrame(frame);
146 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
147 // The frame is closing - invalidate.
148 InvalidateFrame(frame);
151 void ScriptInjectionManager::RVOHelper::OnDestruct() {
152 manager_->RemoveObserver(this);
155 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
156 const ExtensionMsg_ExecuteCode_Params& params) {
157 manager_->HandleExecuteCode(params, render_view());
160 void ScriptInjectionManager::RVOHelper::OnExecuteDeclarativeScript(
161 int tab_id,
162 const ExtensionId& extension_id,
163 int script_id,
164 const GURL& url) {
165 blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
166 CHECK(main_frame);
168 // TODO(markdittmer): This would be cleaner if we compared page_ids instead.
169 // Begin script injeciton workflow only if the current URL is identical to
170 // the one that matched declarative conditions in the browser.
171 if (main_frame->top()->document().url() == url) {
172 manager_->HandleExecuteDeclarativeScript(main_frame,
173 tab_id,
174 extension_id,
175 script_id,
176 url);
180 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
181 int64 request_id) {
182 manager_->HandlePermitScriptInjection(request_id);
185 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) {
186 // Only notify the manager if the frame hasn't either been removed or already
187 // had idle run since the task to RunIdle() was posted.
188 if (pending_idle_frames_.count(frame) > 0) {
189 manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE);
190 pending_idle_frames_.erase(frame);
194 void ScriptInjectionManager::RVOHelper::InvalidateFrame(
195 blink::WebFrame* frame) {
196 pending_idle_frames_.erase(frame);
197 manager_->InvalidateForFrame(frame);
200 ScriptInjectionManager::ScriptInjectionManager(
201 const ExtensionSet* extensions,
202 UserScriptSetManager* user_script_set_manager)
203 : extensions_(extensions),
204 user_script_set_manager_(user_script_set_manager),
205 user_script_set_manager_observer_(this) {
206 user_script_set_manager_observer_.Add(user_script_set_manager_);
209 ScriptInjectionManager::~ScriptInjectionManager() {
212 void ScriptInjectionManager::OnRenderViewCreated(
213 content::RenderView* render_view) {
214 rvo_helpers_.push_back(new RVOHelper(render_view, this));
217 void ScriptInjectionManager::OnUserScriptsUpdated(
218 const std::set<std::string>& changed_extensions,
219 const std::vector<UserScript*>& scripts) {
220 for (ScopedVector<ScriptInjection>::iterator iter =
221 pending_injections_.begin();
222 iter != pending_injections_.end();) {
223 if (changed_extensions.count((*iter)->extension_id()) > 0)
224 iter = pending_injections_.erase(iter);
225 else
226 ++iter;
230 void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) {
231 for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin();
232 iter != rvo_helpers_.end();
233 ++iter) {
234 if (*iter == helper) {
235 rvo_helpers_.erase(iter);
236 break;
241 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) {
242 for (ScopedVector<ScriptInjection>::iterator iter =
243 pending_injections_.begin();
244 iter != pending_injections_.end();) {
245 if ((*iter)->web_frame() == frame)
246 iter = pending_injections_.erase(iter);
247 else
248 ++iter;
251 frame_statuses_.erase(frame);
254 void ScriptInjectionManager::InjectScripts(
255 blink::WebFrame* frame, UserScript::RunLocation run_location) {
256 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
257 // We also don't execute if we detect that the run location is somehow out of
258 // order. This can happen if:
259 // - The first run location reported for the frame isn't DOCUMENT_START, or
260 // - The run location reported doesn't immediately follow the previous
261 // reported run location.
262 // We don't want to run because extensions may have requirements that scripts
263 // running in an earlier run location have run by the time a later script
264 // runs. Better to just not run.
265 if ((iter == frame_statuses_.end() &&
266 run_location != UserScript::DOCUMENT_START) ||
267 (iter != frame_statuses_.end() && run_location - iter->second > 1)) {
268 // We also invalidate the frame, because the run order of pending injections
269 // may also be bad.
270 InvalidateForFrame(frame);
271 return;
272 } else if (iter != frame_statuses_.end() && iter->second > run_location) {
273 // Certain run location signals (like DidCreateDocumentElement) can happen
274 // multiple times. Ignore the subsequent signals.
275 return;
278 // Otherwise, all is right in the world, and we can get on with the
279 // injections!
281 frame_statuses_[frame] = run_location;
283 // Inject any scripts that were waiting for the right run location.
284 ScriptsRunInfo scripts_run_info;
285 for (ScopedVector<ScriptInjection>::iterator iter =
286 pending_injections_.begin();
287 iter != pending_injections_.end();) {
288 if ((*iter)->web_frame() == frame &&
289 (*iter)->TryToInject(run_location,
290 extensions_->GetByID((*iter)->extension_id()),
291 &scripts_run_info)) {
292 iter = pending_injections_.erase(iter);
293 } else {
294 ++iter;
298 // Try to inject any user scripts that should run for this location. If they
299 // don't complete their injection (for example, waiting for a permission
300 // response) then they will be added to |pending_injections_|.
301 ScopedVector<ScriptInjection> user_script_injections;
302 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView(
303 frame->top()->view()))->tab_id();
304 user_script_set_manager_->GetAllInjections(
305 &user_script_injections, frame, tab_id, run_location);
306 for (ScopedVector<ScriptInjection>::iterator iter =
307 user_script_injections.begin();
308 iter != user_script_injections.end();) {
309 scoped_ptr<ScriptInjection> injection(*iter);
310 iter = user_script_injections.weak_erase(iter);
311 if (!injection->TryToInject(run_location,
312 extensions_->GetByID(injection->extension_id()),
313 &scripts_run_info)) {
314 pending_injections_.push_back(injection.release());
318 scripts_run_info.LogRun(frame, run_location);
321 void ScriptInjectionManager::HandleExecuteCode(
322 const ExtensionMsg_ExecuteCode_Params& params,
323 content::RenderView* render_view) {
324 // TODO(dcheng): Not sure how this can happen today. In an OOPI world, it
325 // would indicate a logic error--the browser must direct this request to the
326 // right renderer process to begin with.
327 blink::WebLocalFrame* main_frame =
328 render_view->GetWebView()->mainFrame()->toWebLocalFrame();
329 if (!main_frame) {
330 render_view->Send(
331 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
332 params.request_id,
333 "No main frame",
334 GURL(std::string()),
335 base::ListValue()));
336 return;
339 scoped_ptr<ScriptInjection> injection(new ScriptInjection(
340 scoped_ptr<ScriptInjector>(
341 new ProgrammaticScriptInjector(params, main_frame)),
342 main_frame,
343 params.extension_id,
344 static_cast<UserScript::RunLocation>(params.run_at),
345 ExtensionHelper::Get(render_view)->tab_id()));
347 ScriptsRunInfo scripts_run_info;
348 FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame);
349 if (!injection->TryToInject(
350 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second,
351 extensions_->GetByID(injection->extension_id()),
352 &scripts_run_info)) {
353 pending_injections_.push_back(injection.release());
357 void ScriptInjectionManager::HandleExecuteDeclarativeScript(
358 blink::WebFrame* web_frame,
359 int tab_id,
360 const ExtensionId& extension_id,
361 int script_id,
362 const GURL& url) {
363 const Extension* extension = extensions_->GetByID(extension_id);
364 // TODO(dcheng): This function signature should really be a WebLocalFrame,
365 // rather than trying to coerce it here.
366 scoped_ptr<ScriptInjection> injection =
367 user_script_set_manager_->GetInjectionForDeclarativeScript(
368 script_id,
369 web_frame->toWebLocalFrame(),
370 tab_id,
371 url,
372 extension);
373 if (injection.get()) {
374 ScriptsRunInfo scripts_run_info;
375 // TODO(markdittmer): Use return value of TryToInject for error handling.
376 injection->TryToInject(UserScript::BROWSER_DRIVEN,
377 extension,
378 &scripts_run_info);
379 scripts_run_info.LogRun(web_frame, UserScript::BROWSER_DRIVEN);
383 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) {
384 ScopedVector<ScriptInjection>::iterator iter =
385 pending_injections_.begin();
386 for (; iter != pending_injections_.end(); ++iter) {
387 if ((*iter)->request_id() == request_id)
388 break;
390 if (iter == pending_injections_.end())
391 return;
393 // At this point, because the request is present in pending_injections_, we
394 // know that this is the same page that issued the request (otherwise,
395 // RVOHelper's DidStartProvisionalLoad callback would have caused it to be
396 // cleared out).
398 scoped_ptr<ScriptInjection> injection(*iter);
399 pending_injections_.weak_erase(iter);
401 ScriptsRunInfo scripts_run_info;
402 if (injection->OnPermissionGranted(extensions_->GetByID(
403 injection->extension_id()),
404 &scripts_run_info)) {
405 scripts_run_info.LogRun(injection->web_frame(), UserScript::RUN_DEFERRED);
409 } // namespace extensions