Home | History | Annotate | Download | only in renderer
      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
    410