Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 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 "chrome/renderer/extensions/user_script_scheduler.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/logging.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "chrome/common/extensions/extension_manifest_constants.h"
     11 #include "chrome/common/extensions/extension_messages.h"
     12 #include "chrome/common/extensions/permissions/permissions_data.h"
     13 #include "chrome/renderer/chrome_render_process_observer.h"
     14 #include "chrome/renderer/extensions/dispatcher.h"
     15 #include "chrome/renderer/extensions/dom_activity_logger.h"
     16 #include "chrome/renderer/extensions/extension_groups.h"
     17 #include "chrome/renderer/extensions/extension_helper.h"
     18 #include "chrome/renderer/extensions/user_script_slave.h"
     19 #include "content/public/renderer/render_view.h"
     20 #include "content/public/renderer/v8_value_converter.h"
     21 #include "extensions/common/error_utils.h"
     22 #include "third_party/WebKit/public/platform/WebString.h"
     23 #include "third_party/WebKit/public/platform/WebVector.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/WebView.h"
     27 #include "v8/include/v8.h"
     28 
     29 namespace {
     30 // The length of time to wait after the DOM is complete to try and run user
     31 // scripts.
     32 const int kUserScriptIdleTimeoutMs = 200;
     33 }
     34 
     35 using WebKit::WebDocument;
     36 using WebKit::WebFrame;
     37 using WebKit::WebString;
     38 using WebKit::WebVector;
     39 using WebKit::WebView;
     40 
     41 namespace extensions {
     42 
     43 UserScriptScheduler::UserScriptScheduler(WebFrame* frame,
     44                                          Dispatcher* dispatcher)
     45     : weak_factory_(this),
     46       frame_(frame),
     47       current_location_(UserScript::UNDEFINED),
     48       has_run_idle_(false),
     49       dispatcher_(dispatcher) {
     50   for (int i = UserScript::UNDEFINED; i < UserScript::RUN_LOCATION_LAST; ++i) {
     51     pending_execution_map_[static_cast<UserScript::RunLocation>(i)] =
     52       std::queue<linked_ptr<ExtensionMsg_ExecuteCode_Params> >();
     53   }
     54 }
     55 
     56 UserScriptScheduler::~UserScriptScheduler() {
     57 }
     58 
     59 void UserScriptScheduler::ExecuteCode(
     60     const ExtensionMsg_ExecuteCode_Params& params) {
     61   UserScript::RunLocation run_at =
     62     static_cast<UserScript::RunLocation>(params.run_at);
     63   if (current_location_ < run_at) {
     64     pending_execution_map_[run_at].push(
     65         linked_ptr<ExtensionMsg_ExecuteCode_Params>(
     66             new ExtensionMsg_ExecuteCode_Params(params)));
     67     return;
     68   }
     69 
     70   ExecuteCodeImpl(params);
     71 }
     72 
     73 void UserScriptScheduler::DidCreateDocumentElement() {
     74   current_location_ = UserScript::DOCUMENT_START;
     75   MaybeRun();
     76 }
     77 
     78 void UserScriptScheduler::DidFinishDocumentLoad() {
     79   current_location_ = UserScript::DOCUMENT_END;
     80   MaybeRun();
     81   // Schedule a run for DOCUMENT_IDLE
     82   base::MessageLoop::current()->PostDelayedTask(
     83       FROM_HERE,
     84       base::Bind(&UserScriptScheduler::IdleTimeout, weak_factory_.GetWeakPtr()),
     85       base::TimeDelta::FromMilliseconds(kUserScriptIdleTimeoutMs));
     86 }
     87 
     88 void UserScriptScheduler::DidFinishLoad() {
     89   current_location_ = UserScript::DOCUMENT_IDLE;
     90   // Ensure that running scripts does not keep any progress UI running.
     91   base::MessageLoop::current()->PostTask(
     92       FROM_HERE,
     93       base::Bind(&UserScriptScheduler::MaybeRun, weak_factory_.GetWeakPtr()));
     94 }
     95 
     96 void UserScriptScheduler::DidStartProvisionalLoad() {
     97   // The frame is navigating, so reset the state since we'll want to inject
     98   // scripts once the load finishes.
     99   current_location_ = UserScript::UNDEFINED;
    100   has_run_idle_ = false;
    101   weak_factory_.InvalidateWeakPtrs();
    102   std::map<UserScript::RunLocation, ExecutionQueue>::iterator itr =
    103     pending_execution_map_.begin();
    104   for (itr = pending_execution_map_.begin();
    105        itr != pending_execution_map_.end(); ++itr) {
    106     while (!itr->second.empty())
    107       itr->second.pop();
    108   }
    109 }
    110 
    111 void UserScriptScheduler::IdleTimeout() {
    112   current_location_ = UserScript::DOCUMENT_IDLE;
    113   MaybeRun();
    114 }
    115 
    116 void UserScriptScheduler::MaybeRun() {
    117   if (current_location_ == UserScript::UNDEFINED)
    118     return;
    119 
    120   if (!has_run_idle_ && current_location_ == UserScript::DOCUMENT_IDLE) {
    121     has_run_idle_ = true;
    122     dispatcher_->user_script_slave()->InjectScripts(
    123         frame_, UserScript::DOCUMENT_IDLE);
    124   }
    125 
    126   // Run all tasks from the current time and earlier.
    127   for (int i = UserScript::DOCUMENT_START;
    128        i <= current_location_; ++i) {
    129     UserScript::RunLocation run_time = static_cast<UserScript::RunLocation>(i);
    130     while (!pending_execution_map_[run_time].empty()) {
    131       linked_ptr<ExtensionMsg_ExecuteCode_Params>& params =
    132         pending_execution_map_[run_time].front();
    133       ExecuteCodeImpl(*params);
    134       pending_execution_map_[run_time].pop();
    135     }
    136   }
    137 }
    138 
    139 void UserScriptScheduler::ExecuteCodeImpl(
    140     const ExtensionMsg_ExecuteCode_Params& params) {
    141   const Extension* extension = dispatcher_->extensions()->GetByID(
    142       params.extension_id);
    143   content::RenderView* render_view =
    144       content::RenderView::FromWebView(frame_->view());
    145   ExtensionHelper* extension_helper = ExtensionHelper::Get(render_view);
    146   base::ListValue execution_results;
    147 
    148   // Since extension info is sent separately from user script info, they can
    149   // be out of sync. We just ignore this situation.
    150   if (!extension) {
    151     render_view->Send(
    152         new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
    153                                                  params.request_id,
    154                                                  std::string(),  // no error
    155                                                  -1,
    156                                                  GURL(std::string()),
    157                                                  execution_results));
    158     return;
    159   }
    160 
    161   std::vector<WebFrame*> frame_vector;
    162   frame_vector.push_back(frame_);
    163   if (params.all_frames)
    164     GetAllChildFrames(frame_, &frame_vector);
    165 
    166   std::string error;
    167 
    168   for (std::vector<WebFrame*>::iterator frame_it = frame_vector.begin();
    169        frame_it != frame_vector.end(); ++frame_it) {
    170     WebFrame* child_frame = *frame_it;
    171     if (params.is_javascript) {
    172       // We recheck access here in the renderer for extra safety against races
    173       // with navigation.
    174       //
    175       // But different frames can have different URLs, and the extension might
    176       // only have access to a subset of them. For the top frame, we can
    177       // immediately send an error and stop because the browser process
    178       // considers that an error too.
    179       //
    180       // For child frames, we just skip ones the extension doesn't have access
    181       // to and carry on.
    182       if (!params.is_web_view &&
    183           !PermissionsData::CanExecuteScriptOnPage(
    184               extension,
    185               child_frame->document().url(),
    186               frame_->document().url(),
    187               extension_helper->tab_id(),
    188               NULL,
    189               -1,
    190               NULL)) {
    191         if (child_frame->parent()) {
    192           continue;
    193         } else {
    194           error = ErrorUtils::FormatErrorMessage(
    195               extension_manifest_errors::kCannotAccessPage,
    196               child_frame->document().url().spec());
    197           break;
    198         }
    199       }
    200 
    201       WebScriptSource source(WebString::fromUTF8(params.code));
    202       v8::Isolate* isolate = v8::Isolate::GetCurrent();
    203       v8::HandleScope scope(isolate);
    204 
    205       scoped_ptr<content::V8ValueConverter> v8_converter(
    206           content::V8ValueConverter::create());
    207       v8::Handle<v8::Value> script_value;
    208 
    209       if (params.in_main_world) {
    210         DOMActivityLogger::AttachToWorld(
    211             DOMActivityLogger::kMainWorldId,
    212             extension->id(),
    213             UserScriptSlave::GetDataSourceURLForFrame(child_frame),
    214             child_frame->document().title());
    215         script_value = child_frame->executeScriptAndReturnValue(source);
    216       } else {
    217         WebKit::WebVector<v8::Local<v8::Value> > results;
    218         std::vector<WebScriptSource> sources;
    219         sources.push_back(source);
    220         int isolated_world_id =
    221             dispatcher_->user_script_slave()->GetIsolatedWorldIdForExtension(
    222                 extension, child_frame);
    223         DOMActivityLogger::AttachToWorld(
    224             isolated_world_id,
    225             extension->id(),
    226             UserScriptSlave::GetDataSourceURLForFrame(child_frame),
    227             child_frame->document().title());
    228         child_frame->executeScriptInIsolatedWorld(
    229             isolated_world_id, &sources.front(),
    230             sources.size(), EXTENSION_GROUP_CONTENT_SCRIPTS, &results);
    231         // We only expect one value back since we only pushed one source
    232         if (results.size() == 1 && !results[0].IsEmpty())
    233           script_value = results[0];
    234       }
    235       if (!script_value.IsEmpty()) {
    236         v8::Local<v8::Context> context = v8::Context::New(isolate);
    237         base::Value* base_val =
    238             v8_converter->FromV8Value(script_value, context);
    239         // Always append an execution result (i.e. no result == null result) so
    240         // that |execution_results| lines up with the frames.
    241         execution_results.Append(base_val ? base_val :
    242                                             base::Value::CreateNullValue());
    243         script_value.Clear();
    244       }
    245     } else {
    246       child_frame->document().insertUserStyleSheet(
    247           WebString::fromUTF8(params.code),
    248           // Author level is consistent with WebView::addUserStyleSheet.
    249           WebDocument::UserStyleAuthorLevel);
    250     }
    251   }
    252 
    253   render_view->Send(new ExtensionHostMsg_ExecuteCodeFinished(
    254       render_view->GetRoutingID(),
    255       params.request_id,
    256       error,
    257       render_view->GetPageId(),
    258       UserScriptSlave::GetDataSourceURLForFrame(frame_),
    259       execution_results));
    260 }
    261 
    262 bool UserScriptScheduler::GetAllChildFrames(
    263     WebFrame* parent_frame,
    264     std::vector<WebFrame*>* frames_vector) const {
    265   if (!parent_frame)
    266     return false;
    267 
    268   for (WebFrame* child_frame = parent_frame->firstChild(); child_frame;
    269        child_frame = child_frame->nextSibling()) {
    270     frames_vector->push_back(child_frame);
    271     GetAllChildFrames(child_frame, frames_vector);
    272   }
    273   return true;
    274 }
    275 
    276 }  // namespace extensions
    277