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