Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / extensions / renderer / script_injection_manager.cc
blob2d18757f2caceb601b39c7a771d4bef5713c61f7
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/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"
25 namespace extensions {
27 namespace {
29 // The length of time to wait after the DOM is complete to try and run user
30 // scripts.
31 const int kScriptIdleTimeoutInMs = 200;
33 } // namespace
35 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
36 public:
37 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
38 virtual ~RVOHelper();
40 private:
41 // RenderViewObserver implementation.
42 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
43 virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE;
44 virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE;
45 virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE;
46 virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE;
47 virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE;
48 virtual void OnDestruct() OVERRIDE;
50 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
51 virtual void OnPermitScriptInjection(int64 request_id);
53 // Tells the ScriptInjectionManager to run tasks associated with
54 // document_idle.
55 void RunIdle(blink::WebFrame* frame);
57 // Indicate that the given |frame| is no longer valid because it is starting
58 // a new load or closing.
59 void InvalidateFrame(blink::WebFrame* frame);
61 // The owning ScriptInjectionManager.
62 ScriptInjectionManager* manager_;
64 // The set of frames that we are about to notify for DOCUMENT_IDLE. We keep
65 // a set of those that are valid, so we don't notify that an invalid frame
66 // became idle.
67 std::set<blink::WebFrame*> pending_idle_frames_;
69 base::WeakPtrFactory<RVOHelper> weak_factory_;
72 ScriptInjectionManager::RVOHelper::RVOHelper(
73 content::RenderView* render_view,
74 ScriptInjectionManager* manager)
75 : content::RenderViewObserver(render_view),
76 manager_(manager),
77 weak_factory_(this) {
80 ScriptInjectionManager::RVOHelper::~RVOHelper() {
83 bool ScriptInjectionManager::RVOHelper::OnMessageReceived(
84 const IPC::Message& message) {
85 bool handled = true;
86 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message)
87 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
88 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
89 OnPermitScriptInjection)
90 IPC_MESSAGE_UNHANDLED(handled = false)
91 IPC_END_MESSAGE_MAP()
92 return handled;
95 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
96 blink::WebLocalFrame* frame) {
97 manager_->InjectScripts(frame, UserScript::DOCUMENT_START);
100 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
101 blink::WebLocalFrame* frame) {
102 manager_->InjectScripts(frame, UserScript::DOCUMENT_END);
103 pending_idle_frames_.insert(frame);
104 // We try to run idle in two places: here and DidFinishLoad.
105 // DidFinishDocumentLoad() corresponds to completing the document's load,
106 // whereas DidFinishLoad corresponds to completing the document and all
107 // subresources' load. We don't want to hold up script injection for a
108 // particularly slow subresource, so we set a delayed task from here - but if
109 // we finish everything before that point (i.e., DidFinishLoad() is
110 // triggered), then there's no reason to keep waiting.
111 base::MessageLoop::current()->PostDelayedTask(
112 FROM_HERE,
113 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
114 weak_factory_.GetWeakPtr(),
115 frame),
116 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
119 void ScriptInjectionManager::RVOHelper::DidFinishLoad(
120 blink::WebLocalFrame* frame) {
121 // Ensure that we don't block any UI progress by running scripts.
122 // We *don't* add the frame to |pending_idle_frames_| here because
123 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the
124 // first posted task to RunIdle() pops it out of the set. This ensures we
125 // don't try to run idle twice.
126 base::MessageLoop::current()->PostTask(
127 FROM_HERE,
128 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
129 weak_factory_.GetWeakPtr(),
130 frame));
133 void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad(
134 blink::WebLocalFrame* frame) {
135 // We're starting a new load - invalidate.
136 InvalidateFrame(frame);
139 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
140 // The frame is closing - invalidate.
141 InvalidateFrame(frame);
144 void ScriptInjectionManager::RVOHelper::OnDestruct() {
145 manager_->RemoveObserver(this);
148 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
149 const ExtensionMsg_ExecuteCode_Params& params) {
150 manager_->HandleExecuteCode(params, render_view());
153 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
154 int64 request_id) {
155 manager_->HandlePermitScriptInjection(request_id);
158 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) {
159 // Only notify the manager if the frame hasn't either been removed or already
160 // had idle run since the task to RunIdle() was posted.
161 if (pending_idle_frames_.count(frame) > 0) {
162 manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE);
163 pending_idle_frames_.erase(frame);
167 void ScriptInjectionManager::RVOHelper::InvalidateFrame(
168 blink::WebFrame* frame) {
169 pending_idle_frames_.erase(frame);
170 manager_->InvalidateForFrame(frame);
173 ScriptInjectionManager::ScriptInjectionManager(
174 const ExtensionSet* extensions,
175 UserScriptSetManager* user_script_set_manager)
176 : extensions_(extensions),
177 user_script_set_manager_(user_script_set_manager),
178 user_script_set_manager_observer_(this) {
179 user_script_set_manager_observer_.Add(user_script_set_manager_);
182 ScriptInjectionManager::~ScriptInjectionManager() {
185 void ScriptInjectionManager::OnRenderViewCreated(
186 content::RenderView* render_view) {
187 rvo_helpers_.push_back(new RVOHelper(render_view, this));
190 void ScriptInjectionManager::OnUserScriptsUpdated(
191 const std::set<std::string>& changed_extensions,
192 const std::vector<UserScript*>& scripts) {
193 for (ScopedVector<ScriptInjection>::iterator iter =
194 pending_injections_.begin();
195 iter != pending_injections_.end();) {
196 if (changed_extensions.count((*iter)->extension_id()) > 0)
197 iter = pending_injections_.erase(iter);
198 else
199 ++iter;
203 void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) {
204 for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin();
205 iter != rvo_helpers_.end();
206 ++iter) {
207 if (*iter == helper) {
208 rvo_helpers_.erase(iter);
209 break;
214 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) {
215 for (ScopedVector<ScriptInjection>::iterator iter =
216 pending_injections_.begin();
217 iter != pending_injections_.end();) {
218 if ((*iter)->web_frame() == frame)
219 iter = pending_injections_.erase(iter);
220 else
221 ++iter;
224 frame_statuses_.erase(frame);
227 void ScriptInjectionManager::InjectScripts(
228 blink::WebFrame* frame, UserScript::RunLocation run_location) {
229 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
230 // We also don't execute if we detect that the run location is somehow out of
231 // order. This can happen if:
232 // - The first run location reported for the frame isn't DOCUMENT_START, or
233 // - The run location reported doesn't immediately follow the previous
234 // reported run location.
235 // We don't want to run because extensions may have requirements that scripts
236 // running in an earlier run location have run by the time a later script
237 // runs. Better to just not run.
238 if ((iter == frame_statuses_.end() &&
239 run_location != UserScript::DOCUMENT_START) ||
240 (iter != frame_statuses_.end() && run_location - iter->second > 1)) {
241 // We also invalidate the frame, because the run order of pending injections
242 // may also be bad.
243 InvalidateForFrame(frame);
244 return;
245 } else if (iter != frame_statuses_.end() && iter->second > run_location) {
246 // Certain run location signals (like DidCreateDocumentElement) can happen
247 // multiple times. Ignore the subsequent signals.
248 return;
251 // Otherwise, all is right in the world, and we can get on with the
252 // injections!
254 frame_statuses_[frame] = run_location;
256 // Inject any scripts that were waiting for the right run location.
257 ScriptsRunInfo scripts_run_info;
258 for (ScopedVector<ScriptInjection>::iterator iter =
259 pending_injections_.begin();
260 iter != pending_injections_.end();) {
261 if ((*iter)->web_frame() == frame &&
262 (*iter)->TryToInject(run_location,
263 extensions_->GetByID((*iter)->extension_id()),
264 &scripts_run_info)) {
265 iter = pending_injections_.erase(iter);
266 } else {
267 ++iter;
271 // Try to inject any user scripts that should run for this location. If they
272 // don't complete their injection (for example, waiting for a permission
273 // response) then they will be added to |pending_injections_|.
274 ScopedVector<ScriptInjection> user_script_injections;
275 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView(
276 frame->top()->view()))->tab_id();
277 user_script_set_manager_->GetAllInjections(
278 &user_script_injections, frame, tab_id, run_location);
279 for (ScopedVector<ScriptInjection>::iterator iter =
280 user_script_injections.begin();
281 iter != user_script_injections.end();) {
282 scoped_ptr<ScriptInjection> injection(*iter);
283 iter = user_script_injections.weak_erase(iter);
284 if (!injection->TryToInject(run_location,
285 extensions_->GetByID(injection->extension_id()),
286 &scripts_run_info)) {
287 pending_injections_.push_back(injection.release());
291 scripts_run_info.LogRun(frame, run_location);
294 void ScriptInjectionManager::HandleExecuteCode(
295 const ExtensionMsg_ExecuteCode_Params& params,
296 content::RenderView* render_view) {
297 blink::WebFrame* main_frame = render_view->GetWebView()->mainFrame();
298 if (!main_frame) {
299 render_view->Send(
300 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
301 params.request_id,
302 "No main frame",
303 GURL(std::string()),
304 base::ListValue()));
305 return;
308 scoped_ptr<ScriptInjection> injection(new ScriptInjection(
309 scoped_ptr<ScriptInjector>(
310 new ProgrammaticScriptInjector(params, main_frame)),
311 main_frame,
312 params.extension_id,
313 static_cast<UserScript::RunLocation>(params.run_at),
314 ExtensionHelper::Get(render_view)->tab_id()));
316 ScriptsRunInfo scripts_run_info;
317 FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame);
318 if (!injection->TryToInject(
319 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second,
320 extensions_->GetByID(injection->extension_id()),
321 &scripts_run_info)) {
322 pending_injections_.push_back(injection.release());
326 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) {
327 ScopedVector<ScriptInjection>::iterator iter =
328 pending_injections_.begin();
329 for (; iter != pending_injections_.end(); ++iter) {
330 if ((*iter)->request_id() == request_id)
331 break;
333 if (iter == pending_injections_.end())
334 return;
336 // At this point, because the request is present in pending_injections_, we
337 // know that this is the same page that issued the request (otherwise,
338 // RVOHelper's DidStartProvisionalLoad callback would have caused it to be
339 // cleared out).
341 scoped_ptr<ScriptInjection> injection(*iter);
342 pending_injections_.weak_erase(iter);
344 ScriptsRunInfo scripts_run_info;
345 if (injection->OnPermissionGranted(extensions_->GetByID(
346 injection->extension_id()),
347 &scripts_run_info)) {
348 scripts_run_info.LogRun(injection->web_frame(), UserScript::RUN_DEFERRED);
352 } // namespace extensions