[Extensions OOPI] Clean up script injection for OOPI more
[chromium-blink-merge.git] / extensions / renderer / script_injection_manager.cc
blob326452b8fad2de0a6aa183c2d2434fa0cd083b5d
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 InvalidateFrame();
90 // The owning ScriptInjectionManager.
91 ScriptInjectionManager* manager_;
93 // The set of frames that we are about to notify for DOCUMENT_IDLE. We keep
94 // a set of those that are valid, so we don't notify that an invalid frame
95 // became idle.
96 std::set<content::RenderFrame*> pending_idle_frames_;
98 base::WeakPtrFactory<RFOHelper> weak_factory_;
101 ScriptInjectionManager::RFOHelper::RFOHelper(
102 content::RenderFrame* render_frame,
103 ScriptInjectionManager* manager)
104 : content::RenderFrameObserver(render_frame),
105 manager_(manager),
106 weak_factory_(this) {
109 ScriptInjectionManager::RFOHelper::~RFOHelper() {
112 bool ScriptInjectionManager::RFOHelper::OnMessageReceived(
113 const IPC::Message& message) {
114 bool handled = true;
115 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RFOHelper, message)
116 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
117 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
118 OnPermitScriptInjection)
119 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
120 OnExecuteDeclarativeScript)
121 IPC_MESSAGE_UNHANDLED(handled = false)
122 IPC_END_MESSAGE_MAP()
123 return handled;
126 void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
127 // A new document is going to be shown, so invalidate the old document state.
128 // Check that the frame's state is known before invalidating the frame,
129 // because it is possible that a script injection was scheduled before the
130 // page was loaded, e.g. by navigating to a javascript: URL before the page
131 // has loaded.
132 if (manager_->frame_statuses_.count(render_frame()) != 0)
133 InvalidateFrame();
136 void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
137 manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_START);
140 void ScriptInjectionManager::RFOHelper::DidFinishDocumentLoad() {
141 manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_END);
142 pending_idle_frames_.insert(render_frame());
143 // We try to run idle in two places: here and DidFinishLoad.
144 // DidFinishDocumentLoad() corresponds to completing the document's load,
145 // whereas DidFinishLoad corresponds to completing the document and all
146 // subresources' load. We don't want to hold up script injection for a
147 // particularly slow subresource, so we set a delayed task from here - but if
148 // we finish everything before that point (i.e., DidFinishLoad() is
149 // triggered), then there's no reason to keep waiting.
150 content::RenderThread::Get()->GetTaskRunner()->PostDelayedTask(
151 FROM_HERE,
152 base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
153 weak_factory_.GetWeakPtr()),
154 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
157 void ScriptInjectionManager::RFOHelper::DidFinishLoad() {
158 // Ensure that we don't block any UI progress by running scripts.
159 // We *don't* add the frame to |pending_idle_frames_| here because
160 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the
161 // first posted task to RunIdle() pops it out of the set. This ensures we
162 // don't try to run idle twice.
163 content::RenderThread::Get()->GetTaskRunner()->PostTask(
164 FROM_HERE,
165 base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
166 weak_factory_.GetWeakPtr()));
169 void ScriptInjectionManager::RFOHelper::FrameDetached() {
170 // The frame is closing - invalidate.
171 InvalidateFrame();
174 void ScriptInjectionManager::RFOHelper::OnDestruct() {
175 manager_->RemoveObserver(this);
178 void ScriptInjectionManager::RFOHelper::OnExecuteCode(
179 const ExtensionMsg_ExecuteCode_Params& params) {
180 manager_->HandleExecuteCode(params, render_frame());
183 void ScriptInjectionManager::RFOHelper::OnExecuteDeclarativeScript(
184 int tab_id,
185 const ExtensionId& extension_id,
186 int script_id,
187 const GURL& url) {
188 // TODO(markdittmer): URL-checking isn't the best security measure.
189 // Begin script injection workflow only if the current URL is identical to
190 // the one that matched declarative conditions in the browser.
191 if (render_frame()->GetWebFrame()->document().url() == url) {
192 manager_->HandleExecuteDeclarativeScript(render_frame(),
193 tab_id,
194 extension_id,
195 script_id,
196 url);
200 void ScriptInjectionManager::RFOHelper::OnPermitScriptInjection(
201 int64 request_id) {
202 manager_->HandlePermitScriptInjection(request_id);
205 void ScriptInjectionManager::RFOHelper::RunIdle() {
206 // Only notify the manager if the frame hasn't either been removed or already
207 // had idle run since the task to RunIdle() was posted.
208 if (pending_idle_frames_.count(render_frame()) > 0) {
209 manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_IDLE);
210 pending_idle_frames_.erase(render_frame());
214 void ScriptInjectionManager::RFOHelper::InvalidateFrame() {
215 pending_idle_frames_.erase(render_frame());
216 manager_->InvalidateForFrame(render_frame());
219 ScriptInjectionManager::ScriptInjectionManager(
220 const ExtensionSet* extensions,
221 UserScriptSetManager* user_script_set_manager)
222 : extensions_(extensions),
223 user_script_set_manager_(user_script_set_manager),
224 user_script_set_manager_observer_(this) {
225 user_script_set_manager_observer_.Add(user_script_set_manager_);
228 ScriptInjectionManager::~ScriptInjectionManager() {
231 void ScriptInjectionManager::OnRenderFrameCreated(
232 content::RenderFrame* render_frame) {
233 rfo_helpers_.push_back(new RFOHelper(render_frame, this));
236 void ScriptInjectionManager::OnExtensionUnloaded(
237 const std::string& extension_id) {
238 for (auto iter = pending_injections_.begin();
239 iter != pending_injections_.end();) {
240 if ((*iter)->host_id().id() == extension_id) {
241 (*iter)->OnHostRemoved();
242 iter = pending_injections_.erase(iter);
243 } else {
244 ++iter;
249 void ScriptInjectionManager::OnInjectionFinished(
250 ScriptInjection* injection) {
251 ScopedVector<ScriptInjection>::iterator iter =
252 std::find(running_injections_.begin(),
253 running_injections_.end(),
254 injection);
255 if (iter != running_injections_.end())
256 running_injections_.erase(iter);
259 void ScriptInjectionManager::OnUserScriptsUpdated(
260 const std::set<HostID>& changed_hosts,
261 const std::vector<UserScript*>& scripts) {
262 for (ScopedVector<ScriptInjection>::iterator iter =
263 pending_injections_.begin();
264 iter != pending_injections_.end();) {
265 if (changed_hosts.count((*iter)->host_id()) > 0)
266 iter = pending_injections_.erase(iter);
267 else
268 ++iter;
272 void ScriptInjectionManager::RemoveObserver(RFOHelper* helper) {
273 for (ScopedVector<RFOHelper>::iterator iter = rfo_helpers_.begin();
274 iter != rfo_helpers_.end();
275 ++iter) {
276 if (*iter == helper) {
277 rfo_helpers_.erase(iter);
278 break;
283 void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame* frame) {
284 for (ScopedVector<ScriptInjection>::iterator iter =
285 pending_injections_.begin();
286 iter != pending_injections_.end();) {
287 if ((*iter)->render_frame() == frame)
288 iter = pending_injections_.erase(iter);
289 else
290 ++iter;
293 frame_statuses_.erase(frame);
296 void ScriptInjectionManager::StartInjectScripts(
297 content::RenderFrame* frame,
298 UserScript::RunLocation run_location) {
299 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
300 // We also don't execute if we detect that the run location is somehow out of
301 // order. This can happen if:
302 // - The first run location reported for the frame isn't DOCUMENT_START, or
303 // - The run location reported doesn't immediately follow the previous
304 // reported run location.
305 // We don't want to run because extensions may have requirements that scripts
306 // running in an earlier run location have run by the time a later script
307 // runs. Better to just not run.
308 // Note that we check run_location > NextRunLocation() in the second clause
309 // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
310 // can happen multiple times, so we can receive earlier/equal run locations.
311 if ((iter == frame_statuses_.end() &&
312 run_location != UserScript::DOCUMENT_START) ||
313 (iter != frame_statuses_.end() &&
314 run_location > NextRunLocation(iter->second))) {
315 // We also invalidate the frame, because the run order of pending injections
316 // may also be bad.
317 InvalidateForFrame(frame);
318 return;
319 } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
320 // Certain run location signals (like DidCreateDocumentElement) can happen
321 // multiple times. Ignore the subsequent signals.
322 return;
325 // Otherwise, all is right in the world, and we can get on with the
326 // injections!
327 frame_statuses_[frame] = run_location;
328 InjectScripts(frame, run_location);
331 void ScriptInjectionManager::InjectScripts(
332 content::RenderFrame* frame,
333 UserScript::RunLocation run_location) {
334 // Find any injections that want to run on the given frame.
335 ScopedVector<ScriptInjection> frame_injections;
336 for (ScopedVector<ScriptInjection>::iterator iter =
337 pending_injections_.begin();
338 iter != pending_injections_.end();) {
339 if ((*iter)->render_frame() == frame) {
340 frame_injections.push_back(*iter);
341 iter = pending_injections_.weak_erase(iter);
342 } else {
343 ++iter;
347 // Add any injections for user scripts.
348 int tab_id = ExtensionFrameHelper::Get(frame)->tab_id();
349 user_script_set_manager_->GetAllInjections(
350 &frame_injections, frame, tab_id, run_location);
352 ScriptsRunInfo scripts_run_info;
353 std::vector<ScriptInjection*> released_injections;
354 frame_injections.release(&released_injections);
355 for (ScriptInjection* injection : released_injections)
356 TryToInject(make_scoped_ptr(injection), run_location, &scripts_run_info);
358 scripts_run_info.LogRun(frame->GetWebFrame(), run_location);
361 void ScriptInjectionManager::TryToInject(
362 scoped_ptr<ScriptInjection> injection,
363 UserScript::RunLocation run_location,
364 ScriptsRunInfo* scripts_run_info) {
365 // Try to inject the script. If the injection is waiting (i.e., for
366 // permission), add it to the list of pending injections. If the injection
367 // has blocked, add it to the list of running injections.
368 // The Unretained below is safe because this object owns all the
369 // ScriptInjections, so is guaranteed to outlive them.
370 switch (injection->TryToInject(
371 run_location,
372 scripts_run_info,
373 base::Bind(&ScriptInjectionManager::OnInjectionFinished,
374 base::Unretained(this)))) {
375 case ScriptInjection::INJECTION_WAITING:
376 pending_injections_.push_back(injection.Pass());
377 break;
378 case ScriptInjection::INJECTION_BLOCKED:
379 running_injections_.push_back(injection.Pass());
380 break;
381 case ScriptInjection::INJECTION_FINISHED:
382 break;
386 void ScriptInjectionManager::HandleExecuteCode(
387 const ExtensionMsg_ExecuteCode_Params& params,
388 content::RenderFrame* render_frame) {
389 scoped_ptr<const InjectionHost> injection_host;
390 if (params.host_id.type() == HostID::EXTENSIONS) {
391 injection_host = ExtensionInjectionHost::Create(params.host_id.id(),
392 extensions_);
393 if (!injection_host)
394 return;
395 } else if (params.host_id.type() == HostID::WEBUI) {
396 injection_host.reset(
397 new WebUIInjectionHost(params.host_id));
400 scoped_ptr<ScriptInjection> injection(new ScriptInjection(
401 scoped_ptr<ScriptInjector>(
402 new ProgrammaticScriptInjector(params, render_frame)),
403 render_frame,
404 injection_host.Pass(),
405 static_cast<UserScript::RunLocation>(params.run_at),
406 ExtensionFrameHelper::Get(render_frame)->tab_id()));
408 ScriptsRunInfo scripts_run_info;
409 FrameStatusMap::const_iterator iter = frame_statuses_.find(render_frame);
411 TryToInject(
412 injection.Pass(),
413 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second,
414 &scripts_run_info);
417 void ScriptInjectionManager::HandleExecuteDeclarativeScript(
418 content::RenderFrame* render_frame,
419 int tab_id,
420 const ExtensionId& extension_id,
421 int script_id,
422 const GURL& url) {
423 scoped_ptr<ScriptInjection> injection =
424 user_script_set_manager_->GetInjectionForDeclarativeScript(
425 script_id,
426 render_frame,
427 tab_id,
428 url,
429 extension_id);
430 if (injection.get()) {
431 ScriptsRunInfo scripts_run_info;
432 // TODO(markdittmer): Use return value of TryToInject for error handling.
433 TryToInject(injection.Pass(),
434 UserScript::BROWSER_DRIVEN,
435 &scripts_run_info);
437 scripts_run_info.LogRun(render_frame->GetWebFrame(),
438 UserScript::BROWSER_DRIVEN);
442 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) {
443 ScopedVector<ScriptInjection>::iterator iter =
444 pending_injections_.begin();
445 for (; iter != pending_injections_.end(); ++iter) {
446 if ((*iter)->request_id() == request_id) {
447 DCHECK((*iter)->host_id().type() == HostID::EXTENSIONS);
448 break;
451 if (iter == pending_injections_.end())
452 return;
454 // At this point, because the request is present in pending_injections_, we
455 // know that this is the same page that issued the request (otherwise,
456 // RFOHelper's DidStartProvisionalLoad callback would have caused it to be
457 // cleared out).
459 scoped_ptr<ScriptInjection> injection(*iter);
460 pending_injections_.weak_erase(iter);
462 ScriptsRunInfo scripts_run_info;
463 ScriptInjection::InjectionResult res = injection->OnPermissionGranted(
464 &scripts_run_info);
465 if (res == ScriptInjection::INJECTION_BLOCKED)
466 running_injections_.push_back(injection.Pass());
467 scripts_run_info.LogRun(injection->render_frame()->GetWebFrame(),
468 UserScript::RUN_DEFERRED);
471 } // namespace extensions