abd7d80cd82ad9077009c16b4eb3d7fa75034b98
[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/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"
25
26 namespace extensions {
27
28 namespace {
29
30 // The length of time to wait after the DOM is complete to try and run user
31 // scripts.
32 const int kScriptIdleTimeoutInMs = 200;
33
34 }  // namespace
35
36 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
37  public:
38   RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
39   virtual ~RVOHelper();
40
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;
50
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);
57
58   // Tells the ScriptInjectionManager to run tasks associated with
59   // document_idle.
60   void RunIdle(blink::WebFrame* frame);
61
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);
65
66   // The owning ScriptInjectionManager.
67   ScriptInjectionManager* manager_;
68
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_;
73
74   base::WeakPtrFactory<RVOHelper> weak_factory_;
75 };
76
77 ScriptInjectionManager::RVOHelper::RVOHelper(
78     content::RenderView* render_view,
79     ScriptInjectionManager* manager)
80     : content::RenderViewObserver(render_view),
81       manager_(manager),
82       weak_factory_(this) {
83 }
84
85 ScriptInjectionManager::RVOHelper::~RVOHelper() {
86 }
87
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;
100 }
101
102 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
103     blink::WebLocalFrame* frame) {
104   manager_->InjectScripts(frame, UserScript::DOCUMENT_START);
105 }
106
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));
124 }
125
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));
138 }
139
140 void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad(
141     blink::WebLocalFrame* frame) {
142   // We're starting a new load - invalidate.
143   InvalidateFrame(frame);
144 }
145
146 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
147   // The frame is closing - invalidate.
148   InvalidateFrame(frame);
149 }
150
151 void ScriptInjectionManager::RVOHelper::OnDestruct() {
152   manager_->RemoveObserver(this);
153 }
154
155 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
156     const ExtensionMsg_ExecuteCode_Params& params) {
157   manager_->HandleExecuteCode(params, render_view());
158 }
159
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);
167
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);
177   }
178 }
179
180 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
181     int64 request_id) {
182   manager_->HandlePermitScriptInjection(request_id);
183 }
184
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);
191   }
192 }
193
194 void ScriptInjectionManager::RVOHelper::InvalidateFrame(
195     blink::WebFrame* frame) {
196   pending_idle_frames_.erase(frame);
197   manager_->InvalidateForFrame(frame);
198 }
199
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_);
207 }
208
209 ScriptInjectionManager::~ScriptInjectionManager() {
210 }
211
212 void ScriptInjectionManager::OnRenderViewCreated(
213     content::RenderView* render_view) {
214   rvo_helpers_.push_back(new RVOHelper(render_view, this));
215 }
216
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;
227   }
228 }
229
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;
237     }
238   }
239 }
240
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;
249   }
250
251   frame_statuses_.erase(frame);
252 }
253
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;
276   }
277
278   // Otherwise, all is right in the world, and we can get on with the
279   // injections!
280
281   frame_statuses_[frame] = run_location;
282
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;
295     }
296   }
297
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());
315     }
316   }
317
318   scripts_run_info.LogRun(frame, run_location);
319 }
320
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;
337   }
338
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()));
346
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());
354   }
355 }
356
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);
380   }
381 }
382
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;
389   }
390   if (iter == pending_injections_.end())
391     return;
392
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).
397
398   scoped_ptr<ScriptInjection> injection(*iter);
399   pending_injections_.weak_erase(iter);
400
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);
406   }
407 }
408
409 }  // namespace extensions