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.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/lazy_instance.h"
     10 #include "base/metrics/histogram.h"
     11 #include "content/public/common/url_constants.h"
     12 #include "content/public/renderer/render_view.h"
     13 #include "extensions/common/extension.h"
     14 #include "extensions/common/extension_messages.h"
     15 #include "extensions/common/feature_switch.h"
     16 #include "extensions/common/permissions/permissions_data.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 "grit/extensions_renderer_resources.h"
     23 #include "third_party/WebKit/public/web/WebDocument.h"
     24 #include "third_party/WebKit/public/web/WebFrame.h"
     25 #include "third_party/WebKit/public/web/WebScriptSource.h"
     26 #include "third_party/WebKit/public/web/WebView.h"
     27 #include "ui/base/resource/resource_bundle.h"
     28 #include "url/gurl.h"
     29 
     30 namespace extensions {
     31 
     32 namespace {
     33 
     34 // The id of the next pending injection.
     35 int64 g_next_pending_id = 0;
     36 
     37 // The number of an invalid request, which is used if the feature to delay
     38 // script injection is not enabled.
     39 const int64 kInvalidRequestId = -1;
     40 
     41 // These two strings are injected before and after the Greasemonkey API and
     42 // user script to wrap it in an anonymous scope.
     43 const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
     44 const char kUserScriptTail[] = "\n})(window);";
     45 
     46 // Greasemonkey API source that is injected with the scripts.
     47 struct GreasemonkeyApiJsString {
     48   GreasemonkeyApiJsString();
     49   blink::WebScriptSource source;
     50 };
     51 
     52 // The below constructor, monstrous as it is, just makes a WebScriptSource from
     53 // the GreasemonkeyApiJs resource.
     54 GreasemonkeyApiJsString::GreasemonkeyApiJsString()
     55     : source(blink::WebScriptSource(blink::WebString::fromUTF8(
     56           ResourceBundle::GetSharedInstance().GetRawDataResource(
     57               IDR_GREASEMONKEY_API_JS).as_string()))) {
     58 }
     59 
     60 base::LazyInstance<GreasemonkeyApiJsString> g_greasemonkey_api =
     61     LAZY_INSTANCE_INITIALIZER;
     62 
     63 }  // namespace
     64 
     65 ScriptInjection::ScriptsRunInfo::ScriptsRunInfo() : num_css(0u), num_js(0u) {
     66 }
     67 
     68 ScriptInjection::ScriptsRunInfo::~ScriptsRunInfo() {
     69 }
     70 
     71 struct ScriptInjection::PendingInjection {
     72   PendingInjection(blink::WebFrame* web_frame,
     73                    UserScript::RunLocation run_location,
     74                    int page_id);
     75   ~PendingInjection();
     76 
     77   // The globally-unique id of this request.
     78   int64 id;
     79 
     80   // The pointer to the web frame into which the script should be injected.
     81   // This is weak, but safe because we remove pending requests when a frame is
     82   // terminated.
     83   blink::WebFrame* web_frame;
     84 
     85   // The run location to inject at.
     86   // Note: This could be a lie - we might inject well after this run location
     87   // has come and gone. But we need to know it to know which scripts to inject.
     88   UserScript::RunLocation run_location;
     89 
     90   // The corresponding page id, to protect against races.
     91   int page_id;
     92 };
     93 
     94 ScriptInjection::PendingInjection::PendingInjection(
     95     blink::WebFrame* web_frame,
     96     UserScript::RunLocation run_location,
     97     int page_id)
     98     : id(g_next_pending_id++),
     99       web_frame(web_frame),
    100       run_location(run_location),
    101       page_id(page_id) {
    102 }
    103 
    104 ScriptInjection::PendingInjection::~PendingInjection() {
    105 }
    106 
    107 // static
    108 GURL ScriptInjection::GetDocumentUrlForFrame(blink::WebFrame* frame) {
    109   GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame);
    110   if (!data_source_url.is_empty() && frame->isViewSourceModeEnabled()) {
    111     data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
    112                            data_source_url.spec());
    113   }
    114 
    115   return data_source_url;
    116 }
    117 
    118 ScriptInjection::ScriptInjection(
    119     scoped_ptr<UserScript> script,
    120     UserScriptSlave* user_script_slave)
    121     : script_(script.Pass()),
    122       extension_id_(script_->extension_id()),
    123       user_script_slave_(user_script_slave),
    124       is_standalone_or_emulate_greasemonkey_(
    125           script_->is_standalone() || script_->emulate_greasemonkey()) {
    126 }
    127 
    128 ScriptInjection::~ScriptInjection() {
    129 }
    130 
    131 void ScriptInjection::InjectIfAllowed(blink::WebFrame* frame,
    132                                       UserScript::RunLocation run_location,
    133                                       const GURL& document_url,
    134                                       ScriptsRunInfo* scripts_run_info) {
    135   if (!WantsToRun(frame, run_location, document_url))
    136     return;
    137 
    138   const Extension* extension = user_script_slave_->GetExtension(extension_id_);
    139   DCHECK(extension);  // WantsToRun() should be false if there's no extension.
    140 
    141   // We use the top render view here (instead of the render view for the
    142   // frame), because script injection on any frame requires permission for
    143   // the top frame. Additionally, if we have to show any UI for permissions,
    144   // it should only be done on the top frame.
    145   content::RenderView* top_render_view =
    146       content::RenderView::FromWebView(frame->top()->view());
    147 
    148   int tab_id = ExtensionHelper::Get(top_render_view)->tab_id();
    149 
    150   // By default, we allow injection.
    151   bool should_inject = true;
    152 
    153   // Check if the extension requires user consent for injection *and* we have a
    154   // valid tab id (if we don't have a tab id, we have no UI surface to ask for
    155   // user consent).
    156   if (tab_id != -1 &&
    157       extension->permissions_data()->RequiresActionForScriptExecution(
    158           extension, tab_id, frame->top()->document().url())) {
    159     int64 request_id = kInvalidRequestId;
    160     int page_id = top_render_view->GetPageId();
    161 
    162     // We only delay the injection if the feature is enabled.
    163     // Otherwise, we simply treat this as a notification by passing an invalid
    164     // id.
    165     if (FeatureSwitch::scripts_require_action()->IsEnabled()) {
    166       should_inject = false;
    167       ScopedVector<PendingInjection>::iterator pending_injection =
    168           pending_injections_.insert(
    169               pending_injections_.end(),
    170               new PendingInjection(frame, run_location, page_id));
    171       request_id = (*pending_injection)->id;
    172     }
    173 
    174     top_render_view->Send(
    175         new ExtensionHostMsg_RequestContentScriptPermission(
    176             top_render_view->GetRoutingID(),
    177             extension->id(),
    178             page_id,
    179             request_id));
    180   }
    181 
    182   if (should_inject)
    183     Inject(frame, run_location, scripts_run_info);
    184 }
    185 
    186 bool ScriptInjection::NotifyScriptPermitted(
    187     int64 request_id,
    188     content::RenderView* render_view,
    189     ScriptsRunInfo* scripts_run_info,
    190     blink::WebFrame** frame_out) {
    191   ScopedVector<PendingInjection>::iterator iter = pending_injections_.begin();
    192   while (iter != pending_injections_.end() && (*iter)->id != request_id)
    193     ++iter;
    194 
    195   // No matching request.
    196   if (iter == pending_injections_.end())
    197     return false;
    198 
    199   // We found the request, so pull it out of the pending list.
    200   scoped_ptr<PendingInjection> pending_injection(*iter);
    201   pending_injections_.weak_erase(iter);
    202 
    203   // Ensure the Page ID and Extension are still valid. Otherwise, don't inject.
    204   if (render_view->GetPageId() != pending_injection->page_id)
    205     return false;
    206 
    207   const Extension* extension = user_script_slave_->GetExtension(extension_id_);
    208   if (!extension)
    209     return false;
    210 
    211   // Everything matches! Inject the script.
    212   if (frame_out)
    213     *frame_out = pending_injection->web_frame;
    214   Inject(pending_injection->web_frame,
    215          pending_injection->run_location,
    216          scripts_run_info);
    217   return true;
    218 }
    219 
    220 void ScriptInjection::FrameDetached(blink::WebFrame* frame) {
    221   // Any pending injections associated with the given frame will never run.
    222   // Remove them.
    223   for (ScopedVector<PendingInjection>::iterator iter =
    224            pending_injections_.begin();
    225        iter != pending_injections_.end();) {
    226     if ((*iter)->web_frame == frame)
    227       iter = pending_injections_.erase(iter);
    228     else
    229       ++iter;
    230   }
    231 }
    232 
    233 void ScriptInjection::SetScript(scoped_ptr<UserScript> script) {
    234   script_.reset(script.release());
    235 }
    236 
    237 bool ScriptInjection::WantsToRun(blink::WebFrame* frame,
    238                                  UserScript::RunLocation run_location,
    239                                  const GURL& document_url) const {
    240   if (frame->parent() && !script_->match_all_frames())
    241     return false;  // Only match subframes if the script declared it wanted to.
    242 
    243   const Extension* extension = user_script_slave_->GetExtension(extension_id_);
    244   // Since extension info is sent separately from user script info, they can
    245   // be out of sync. We just ignore this situation.
    246   if (!extension)
    247     return false;
    248 
    249   // Content scripts are not tab-specific.
    250   static const int kNoTabId = -1;
    251   // We don't have a process id in this context.
    252   static const int kNoProcessId = -1;
    253 
    254   GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
    255       frame, document_url, script_->match_about_blank());
    256 
    257   if (!script_->MatchesURL(effective_document_url))
    258     return false;
    259 
    260   if (!extension->permissions_data()->CanRunContentScriptOnPage(
    261           extension,
    262           effective_document_url,
    263           frame->top()->document().url(),
    264           kNoTabId,
    265           kNoProcessId,
    266           NULL /* ignore error */)) {
    267     return false;
    268   }
    269 
    270   return ShouldInjectCSS(run_location) || ShouldInjectJS(run_location);
    271 }
    272 
    273 void ScriptInjection::Inject(blink::WebFrame* frame,
    274                              UserScript::RunLocation run_location,
    275                              ScriptsRunInfo* scripts_run_info) const {
    276   DCHECK(frame);
    277   DCHECK(scripts_run_info);
    278   DCHECK(WantsToRun(frame, run_location, GetDocumentUrlForFrame(frame)));
    279   DCHECK(user_script_slave_->GetExtension(extension_id_));
    280 
    281   if (ShouldInjectCSS(run_location))
    282     InjectCSS(frame, scripts_run_info);
    283   if (ShouldInjectJS(run_location))
    284     InjectJS(frame, scripts_run_info);
    285 }
    286 
    287 bool ScriptInjection::ShouldInjectJS(UserScript::RunLocation run_location)
    288     const {
    289   return !script_->js_scripts().empty() &&
    290          script_->run_location() == run_location;
    291 }
    292 
    293 bool ScriptInjection::ShouldInjectCSS(UserScript::RunLocation run_location)
    294     const {
    295   return !script_->css_scripts().empty() &&
    296          run_location == UserScript::DOCUMENT_START;
    297 }
    298 
    299 void ScriptInjection::InjectJS(blink::WebFrame* frame,
    300                                ScriptsRunInfo* scripts_run_info) const {
    301   const UserScript::FileList& js_scripts = script_->js_scripts();
    302   std::vector<blink::WebScriptSource> sources;
    303   scripts_run_info->num_js += js_scripts.size();
    304   for (UserScript::FileList::const_iterator iter = js_scripts.begin();
    305        iter != js_scripts.end();
    306        ++iter) {
    307     std::string content = iter->GetContent().as_string();
    308 
    309     // We add this dumb function wrapper for standalone user script to
    310     // emulate what Greasemonkey does.
    311     // TODO(aa): I think that maybe "is_standalone" scripts don't exist
    312     // anymore. Investigate.
    313     if (is_standalone_or_emulate_greasemonkey_) {
    314       content.insert(0, kUserScriptHead);
    315       content += kUserScriptTail;
    316     }
    317     sources.push_back(blink::WebScriptSource(
    318         blink::WebString::fromUTF8(content), iter->url()));
    319   }
    320 
    321   // Emulate Greasemonkey API for scripts that were converted to extensions
    322   // and "standalone" user scripts.
    323   if (is_standalone_or_emulate_greasemonkey_)
    324     sources.insert(sources.begin(), g_greasemonkey_api.Get().source);
    325 
    326   int isolated_world_id =
    327       user_script_slave_->GetIsolatedWorldIdForExtension(
    328           user_script_slave_->GetExtension(extension_id_), frame);
    329   base::ElapsedTimer exec_timer;
    330   DOMActivityLogger::AttachToWorld(isolated_world_id, extension_id_);
    331   frame->executeScriptInIsolatedWorld(isolated_world_id,
    332                                       &sources.front(),
    333                                       sources.size(),
    334                                       EXTENSION_GROUP_CONTENT_SCRIPTS);
    335   UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
    336 
    337   for (std::vector<blink::WebScriptSource>::const_iterator iter =
    338            sources.begin();
    339        iter != sources.end();
    340        ++iter) {
    341     scripts_run_info->executing_scripts[extension_id_].insert(
    342         GURL(iter->url).path());
    343   }
    344 }
    345 
    346 void ScriptInjection::InjectCSS(blink::WebFrame* frame,
    347                                 ScriptsRunInfo* scripts_run_info) const {
    348   const UserScript::FileList& css_scripts = script_->css_scripts();
    349   scripts_run_info->num_css += css_scripts.size();
    350   for (UserScript::FileList::const_iterator iter = css_scripts.begin();
    351        iter != css_scripts.end();
    352        ++iter) {
    353     frame->document().insertStyleSheet(
    354         blink::WebString::fromUTF8(iter->GetContent().as_string()));
    355   }
    356 }
    357 
    358 }  // namespace extensions
    359