Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / extensions / renderer / script_injection_manager.cc
blob8c5206319ecafa7ad2756c7b0847a3b5d33cb440
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/auto_reset.h"
8 #include "base/bind.h"
9 #include "base/memory/weak_ptr.h"
10 #include "base/values.h"
11 #include "content/public/renderer/render_frame.h"
12 #include "content/public/renderer/render_frame_observer.h"
13 #include "content/public/renderer/render_thread.h"
14 #include "extensions/common/extension.h"
15 #include "extensions/common/extension_messages.h"
16 #include "extensions/common/extension_set.h"
17 #include "extensions/renderer/extension_frame_helper.h"
18 #include "extensions/renderer/extension_injection_host.h"
19 #include "extensions/renderer/programmatic_script_injector.h"
20 #include "extensions/renderer/script_injection.h"
21 #include "extensions/renderer/scripts_run_info.h"
22 #include "extensions/renderer/web_ui_injection_host.h"
23 #include "ipc/ipc_message_macros.h"
24 #include "third_party/WebKit/public/web/WebDocument.h"
25 #include "third_party/WebKit/public/web/WebFrame.h"
26 #include "third_party/WebKit/public/web/WebLocalFrame.h"
27 #include "third_party/WebKit/public/web/WebView.h"
28 #include "url/gurl.h"
30 namespace extensions {
32 namespace {
34 // The length of time to wait after the DOM is complete to try and run user
35 // scripts.
36 const int kScriptIdleTimeoutInMs = 200;
38 // Returns the RunLocation that follows |run_location|.
39 UserScript::RunLocation NextRunLocation(UserScript::RunLocation run_location) {
40 switch (run_location) {
41 case UserScript::DOCUMENT_START:
42 return UserScript::DOCUMENT_END;
43 case UserScript::DOCUMENT_END:
44 return UserScript::DOCUMENT_IDLE;
45 case UserScript::DOCUMENT_IDLE:
46 return UserScript::RUN_LOCATION_LAST;
47 case UserScript::UNDEFINED:
48 case UserScript::RUN_DEFERRED:
49 case UserScript::BROWSER_DRIVEN:
50 case UserScript::RUN_LOCATION_LAST:
51 break;
53 NOTREACHED();
54 return UserScript::RUN_LOCATION_LAST;
57 } // namespace
59 class ScriptInjectionManager::RFOHelper : public content::RenderFrameObserver {
60 public:
61 RFOHelper(content::RenderFrame* render_frame,
62 ScriptInjectionManager* manager);
63 ~RFOHelper() override;
65 private:
66 // RenderFrameObserver implementation.
67 bool OnMessageReceived(const IPC::Message& message) override;
68 void DidCreateNewDocument() override;
69 void DidCreateDocumentElement() override;
70 void DidFinishDocumentLoad() override;
71 void DidFinishLoad() override;
72 void FrameDetached() override;
73 void OnDestruct() override;
75 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
76 virtual void OnExecuteDeclarativeScript(int tab_id,
77 const ExtensionId& extension_id,
78 int script_id,
79 const GURL& url);
80 virtual void OnPermitScriptInjection(int64 request_id);
82 // Tells the ScriptInjectionManager to run tasks associated with
83 // document_idle.
84 void RunIdle();
86 // Indicate that the frame is no longer valid because it is starting
87 // a new load or closing.
88 void InvalidateAndResetFrame();
90 // The owning ScriptInjectionManager.
91 ScriptInjectionManager* manager_;
93 bool should_run_idle_;
95 base::WeakPtrFactory<RFOHelper> weak_factory_;
98 ScriptInjectionManager::RFOHelper::RFOHelper(content::RenderFrame* render_frame,
99 ScriptInjectionManager* manager)
100 : content::RenderFrameObserver(render_frame),
101 manager_(manager),
102 should_run_idle_(true),
103 weak_factory_(this) {
106 ScriptInjectionManager::RFOHelper::~RFOHelper() {
109 bool ScriptInjectionManager::RFOHelper::OnMessageReceived(
110 const IPC::Message& message) {
111 bool handled = true;
112 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RFOHelper, message)
113 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
114 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
115 OnPermitScriptInjection)
116 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
117 OnExecuteDeclarativeScript)
118 IPC_MESSAGE_UNHANDLED(handled = false)
119 IPC_END_MESSAGE_MAP()
120 return handled;
123 void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
124 // A new document is going to be shown, so invalidate the old document state.
125 // Check that the frame's state is known before invalidating the frame,
126 // because it is possible that a script injection was scheduled before the
127 // page was loaded, e.g. by navigating to a javascript: URL before the page
128 // has loaded.
129 if (manager_->frame_statuses_.count(render_frame()) != 0)
130 InvalidateAndResetFrame();
133 void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
134 manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_START);
137 void ScriptInjectionManager::RFOHelper::DidFinishDocumentLoad() {
138 DCHECK(content::RenderThread::Get());
139 manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_END);
140 // We try to run idle in two places: here and DidFinishLoad.
141 // DidFinishDocumentLoad() corresponds to completing the document's load,
142 // whereas DidFinishLoad corresponds to completing the document and all
143 // subresources' load. We don't want to hold up script injection for a
144 // particularly slow subresource, so we set a delayed task from here - but if
145 // we finish everything before that point (i.e., DidFinishLoad() is
146 // triggered), then there's no reason to keep waiting.
147 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
148 FROM_HERE,
149 base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
150 weak_factory_.GetWeakPtr()),
151 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
154 void ScriptInjectionManager::RFOHelper::DidFinishLoad() {
155 DCHECK(content::RenderThread::Get());
156 // Ensure that we don't block any UI progress by running scripts.
157 base::ThreadTaskRunnerHandle::Get()->PostTask(
158 FROM_HERE,
159 base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
160 weak_factory_.GetWeakPtr()));
163 void ScriptInjectionManager::RFOHelper::FrameDetached() {
164 // The frame is closing - invalidate.
165 InvalidateAndResetFrame();
168 void ScriptInjectionManager::RFOHelper::OnDestruct() {
169 manager_->RemoveObserver(this);
172 void ScriptInjectionManager::RFOHelper::OnExecuteCode(
173 const ExtensionMsg_ExecuteCode_Params& params) {
174 manager_->HandleExecuteCode(params, render_frame());
177 void ScriptInjectionManager::RFOHelper::OnExecuteDeclarativeScript(
178 int tab_id,
179 const ExtensionId& extension_id,
180 int script_id,
181 const GURL& url) {
182 // TODO(markdittmer): URL-checking isn't the best security measure.
183 // Begin script injection workflow only if the current URL is identical to
184 // the one that matched declarative conditions in the browser.
185 if (render_frame()->GetWebFrame()->document().url() == url) {
186 manager_->HandleExecuteDeclarativeScript(render_frame(),
187 tab_id,
188 extension_id,
189 script_id,
190 url);
194 void ScriptInjectionManager::RFOHelper::OnPermitScriptInjection(
195 int64 request_id) {
196 manager_->HandlePermitScriptInjection(request_id);
199 void ScriptInjectionManager::RFOHelper::RunIdle() {
200 // Only notify the manager if the frame hasn't either been removed or already
201 // had idle run since the task to RunIdle() was posted.
202 if (should_run_idle_) {
203 should_run_idle_ = false;
204 manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_IDLE);
208 void ScriptInjectionManager::RFOHelper::InvalidateAndResetFrame() {
209 // Invalidate any pending idle injections, and reset the frame inject on idle.
210 weak_factory_.InvalidateWeakPtrs();
211 // We reset to inject on idle, because the frame can be reused (in the case of
212 // navigation).
213 should_run_idle_ = true;
214 manager_->InvalidateForFrame(render_frame());
217 ScriptInjectionManager::ScriptInjectionManager(
218 const ExtensionSet* extensions,
219 UserScriptSetManager* user_script_set_manager)
220 : extensions_(extensions),
221 user_script_set_manager_(user_script_set_manager),
222 user_script_set_manager_observer_(this) {
223 user_script_set_manager_observer_.Add(user_script_set_manager_);
226 ScriptInjectionManager::~ScriptInjectionManager() {
229 void ScriptInjectionManager::OnRenderFrameCreated(
230 content::RenderFrame* render_frame) {
231 rfo_helpers_.push_back(new RFOHelper(render_frame, this));
234 void ScriptInjectionManager::OnExtensionUnloaded(
235 const std::string& extension_id) {
236 for (auto iter = pending_injections_.begin();
237 iter != pending_injections_.end();) {
238 if ((*iter)->host_id().id() == extension_id) {
239 (*iter)->OnHostRemoved();
240 iter = pending_injections_.erase(iter);
241 } else {
242 ++iter;
247 void ScriptInjectionManager::OnInjectionFinished(
248 ScriptInjection* injection) {
249 ScopedVector<ScriptInjection>::iterator iter =
250 std::find(running_injections_.begin(),
251 running_injections_.end(),
252 injection);
253 if (iter != running_injections_.end())
254 running_injections_.erase(iter);
257 void ScriptInjectionManager::OnUserScriptsUpdated(
258 const std::set<HostID>& changed_hosts,
259 const std::vector<UserScript*>& scripts) {
260 for (ScopedVector<ScriptInjection>::iterator iter =
261 pending_injections_.begin();
262 iter != pending_injections_.end();) {
263 if (changed_hosts.count((*iter)->host_id()) > 0)
264 iter = pending_injections_.erase(iter);
265 else
266 ++iter;
270 void ScriptInjectionManager::RemoveObserver(RFOHelper* helper) {
271 for (ScopedVector<RFOHelper>::iterator iter = rfo_helpers_.begin();
272 iter != rfo_helpers_.end();
273 ++iter) {
274 if (*iter == helper) {
275 rfo_helpers_.erase(iter);
276 break;
281 void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame* frame) {
282 // If the frame invalidated is the frame being injected into, we need to
283 // note it.
284 active_injection_frames_.erase(frame);
286 for (ScopedVector<ScriptInjection>::iterator iter =
287 pending_injections_.begin();
288 iter != pending_injections_.end();) {
289 if ((*iter)->render_frame() == frame)
290 iter = pending_injections_.erase(iter);
291 else
292 ++iter;
295 frame_statuses_.erase(frame);
298 void ScriptInjectionManager::StartInjectScripts(
299 content::RenderFrame* frame,
300 UserScript::RunLocation run_location) {
301 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
302 // We also don't execute if we detect that the run location is somehow out of
303 // order. This can happen if:
304 // - The first run location reported for the frame isn't DOCUMENT_START, or
305 // - The run location reported doesn't immediately follow the previous
306 // reported run location.
307 // We don't want to run because extensions may have requirements that scripts
308 // running in an earlier run location have run by the time a later script
309 // runs. Better to just not run.
310 // Note that we check run_location > NextRunLocation() in the second clause
311 // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
312 // can happen multiple times, so we can receive earlier/equal run locations.
313 if ((iter == frame_statuses_.end() &&
314 run_location != UserScript::DOCUMENT_START) ||
315 (iter != frame_statuses_.end() &&
316 run_location > NextRunLocation(iter->second))) {
317 // We also invalidate the frame, because the run order of pending injections
318 // may also be bad.
319 InvalidateForFrame(frame);
320 return;
321 } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
322 // Certain run location signals (like DidCreateDocumentElement) can happen
323 // multiple times. Ignore the subsequent signals.
324 return;
327 // Otherwise, all is right in the world, and we can get on with the
328 // injections!
329 frame_statuses_[frame] = run_location;
330 InjectScripts(frame, run_location);
333 void ScriptInjectionManager::InjectScripts(
334 content::RenderFrame* frame,
335 UserScript::RunLocation run_location) {
336 // Find any injections that want to run on the given frame.
337 ScopedVector<ScriptInjection> frame_injections;
338 for (ScopedVector<ScriptInjection>::iterator iter =
339 pending_injections_.begin();
340 iter != pending_injections_.end();) {
341 if ((*iter)->render_frame() == frame) {
342 frame_injections.push_back(*iter);
343 iter = pending_injections_.weak_erase(iter);
344 } else {
345 ++iter;
349 // Add any injections for user scripts.
350 int tab_id = ExtensionFrameHelper::Get(frame)->tab_id();
351 user_script_set_manager_->GetAllInjections(
352 &frame_injections, frame, tab_id, run_location);
354 // Note that we are running in |frame|.
355 active_injection_frames_.insert(frame);
357 ScriptsRunInfo scripts_run_info(frame, run_location);
358 std::vector<ScriptInjection*> released_injections;
359 frame_injections.release(&released_injections);
360 for (ScriptInjection* injection : released_injections) {
361 // It's possible for the frame to be invalidated in the course of injection
362 // (if a script removes its own frame, for example). If this happens, abort.
363 if (!active_injection_frames_.count(frame))
364 break;
365 TryToInject(make_scoped_ptr(injection), run_location, &scripts_run_info);
368 // We are done running in the frame.
369 active_injection_frames_.erase(frame);
371 scripts_run_info.LogRun();
374 void ScriptInjectionManager::TryToInject(
375 scoped_ptr<ScriptInjection> injection,
376 UserScript::RunLocation run_location,
377 ScriptsRunInfo* scripts_run_info) {
378 // Try to inject the script. If the injection is waiting (i.e., for
379 // permission), add it to the list of pending injections. If the injection
380 // has blocked, add it to the list of running injections.
381 // The Unretained below is safe because this object owns all the
382 // ScriptInjections, so is guaranteed to outlive them.
383 switch (injection->TryToInject(
384 run_location,
385 scripts_run_info,
386 base::Bind(&ScriptInjectionManager::OnInjectionFinished,
387 base::Unretained(this)))) {
388 case ScriptInjection::INJECTION_WAITING:
389 pending_injections_.push_back(injection.Pass());
390 break;
391 case ScriptInjection::INJECTION_BLOCKED:
392 running_injections_.push_back(injection.Pass());
393 break;
394 case ScriptInjection::INJECTION_FINISHED:
395 break;
399 void ScriptInjectionManager::HandleExecuteCode(
400 const ExtensionMsg_ExecuteCode_Params& params,
401 content::RenderFrame* render_frame) {
402 scoped_ptr<const InjectionHost> injection_host;
403 if (params.host_id.type() == HostID::EXTENSIONS) {
404 injection_host = ExtensionInjectionHost::Create(params.host_id.id(),
405 extensions_);
406 if (!injection_host)
407 return;
408 } else if (params.host_id.type() == HostID::WEBUI) {
409 injection_host.reset(
410 new WebUIInjectionHost(params.host_id));
413 scoped_ptr<ScriptInjection> injection(new ScriptInjection(
414 scoped_ptr<ScriptInjector>(
415 new ProgrammaticScriptInjector(params, render_frame)),
416 render_frame,
417 injection_host.Pass(),
418 static_cast<UserScript::RunLocation>(params.run_at),
419 ExtensionFrameHelper::Get(render_frame)->tab_id()));
421 FrameStatusMap::const_iterator iter = frame_statuses_.find(render_frame);
422 UserScript::RunLocation run_location =
423 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second;
425 ScriptsRunInfo scripts_run_info(render_frame, run_location);
426 TryToInject(injection.Pass(), run_location, &scripts_run_info);
429 void ScriptInjectionManager::HandleExecuteDeclarativeScript(
430 content::RenderFrame* render_frame,
431 int tab_id,
432 const ExtensionId& extension_id,
433 int script_id,
434 const GURL& url) {
435 scoped_ptr<ScriptInjection> injection =
436 user_script_set_manager_->GetInjectionForDeclarativeScript(
437 script_id,
438 render_frame,
439 tab_id,
440 url,
441 extension_id);
442 if (injection.get()) {
443 ScriptsRunInfo scripts_run_info(render_frame, UserScript::BROWSER_DRIVEN);
444 // TODO(markdittmer): Use return value of TryToInject for error handling.
445 TryToInject(injection.Pass(),
446 UserScript::BROWSER_DRIVEN,
447 &scripts_run_info);
449 scripts_run_info.LogRun();
453 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) {
454 ScopedVector<ScriptInjection>::iterator iter =
455 pending_injections_.begin();
456 for (; iter != pending_injections_.end(); ++iter) {
457 if ((*iter)->request_id() == request_id) {
458 DCHECK((*iter)->host_id().type() == HostID::EXTENSIONS);
459 break;
462 if (iter == pending_injections_.end())
463 return;
465 // At this point, because the request is present in pending_injections_, we
466 // know that this is the same page that issued the request (otherwise,
467 // RFOHelper's DidStartProvisionalLoad callback would have caused it to be
468 // cleared out).
470 scoped_ptr<ScriptInjection> injection(*iter);
471 pending_injections_.weak_erase(iter);
473 ScriptsRunInfo scripts_run_info(injection->render_frame(),
474 UserScript::RUN_DEFERRED);
475 ScriptInjection::InjectionResult res = injection->OnPermissionGranted(
476 &scripts_run_info);
477 if (res == ScriptInjection::INJECTION_BLOCKED)
478 running_injections_.push_back(injection.Pass());
479 scripts_run_info.LogRun();
482 } // namespace extensions