Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / extensions / renderer / script_injection_manager.cc
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.
4
5 #include "extensions/renderer/script_injection_manager.h"
6
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"
24
25 namespace extensions {
26
27 namespace {
28
29 // The length of time to wait after the DOM is complete to try and run user
30 // scripts.
31 const int kScriptIdleTimeoutInMs = 200;
32
33 }  // namespace
34
35 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
36  public:
37   RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
38   virtual ~RVOHelper();
39
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;
49
50   virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
51   virtual void OnPermitScriptInjection(int64 request_id);
52
53   // Tells the ScriptInjectionManager to run tasks associated with
54   // document_idle.
55   void RunIdle(blink::WebFrame* frame);
56
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);
60
61   // The owning ScriptInjectionManager.
62   ScriptInjectionManager* manager_;
63
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_;
68
69   base::WeakPtrFactory<RVOHelper> weak_factory_;
70 };
71
72 ScriptInjectionManager::RVOHelper::RVOHelper(
73     content::RenderView* render_view,
74     ScriptInjectionManager* manager)
75     : content::RenderViewObserver(render_view),
76       manager_(manager),
77       weak_factory_(this) {
78 }
79
80 ScriptInjectionManager::RVOHelper::~RVOHelper() {
81 }
82
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;
93 }
94
95 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
96     blink::WebLocalFrame* frame) {
97   manager_->InjectScripts(frame, UserScript::DOCUMENT_START);
98 }
99
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));
117 }
118
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));
131 }
132
133 void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad(
134     blink::WebLocalFrame* frame) {
135   // We're starting a new load - invalidate.
136   InvalidateFrame(frame);
137 }
138
139 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
140   // The frame is closing - invalidate.
141   InvalidateFrame(frame);
142 }
143
144 void ScriptInjectionManager::RVOHelper::OnDestruct() {
145   manager_->RemoveObserver(this);
146 }
147
148 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
149     const ExtensionMsg_ExecuteCode_Params& params) {
150   manager_->HandleExecuteCode(params, render_view());
151 }
152
153 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
154     int64 request_id) {
155   manager_->HandlePermitScriptInjection(request_id);
156 }
157
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);
164   }
165 }
166
167 void ScriptInjectionManager::RVOHelper::InvalidateFrame(
168     blink::WebFrame* frame) {
169   pending_idle_frames_.erase(frame);
170   manager_->InvalidateForFrame(frame);
171 }
172
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_);
180 }
181
182 ScriptInjectionManager::~ScriptInjectionManager() {
183 }
184
185 void ScriptInjectionManager::OnRenderViewCreated(
186     content::RenderView* render_view) {
187   rvo_helpers_.push_back(new RVOHelper(render_view, this));
188 }
189
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;
200   }
201 }
202
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;
210     }
211   }
212 }
213
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;
222   }
223
224   frame_statuses_.erase(frame);
225 }
226
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;
249   }
250
251   // Otherwise, all is right in the world, and we can get on with the
252   // injections!
253
254   frame_statuses_[frame] = run_location;
255
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;
268     }
269   }
270
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());
288     }
289   }
290
291   scripts_run_info.LogRun(frame, run_location);
292 }
293
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;
306   }
307
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()));
315
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());
323   }
324 }
325
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;
332   }
333   if (iter == pending_injections_.end())
334     return;
335
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).
340
341   scoped_ptr<ScriptInjection> injection(*iter);
342   pending_injections_.weak_erase(iter);
343
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);
349   }
350 }
351
352 }  // namespace extensions