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