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_slave.h"
      6 
      7 #include <map>
      8 
      9 #include "base/logging.h"
     10 #include "base/memory/shared_memory.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/pickle.h"
     13 #include "base/timer/elapsed_timer.h"
     14 #include "content/public/renderer/render_thread.h"
     15 #include "content/public/renderer/render_view.h"
     16 #include "extensions/common/extension.h"
     17 #include "extensions/common/extension_messages.h"
     18 #include "extensions/common/extension_set.h"
     19 #include "extensions/common/manifest_handlers/csp_info.h"
     20 #include "extensions/common/permissions/permissions_data.h"
     21 #include "extensions/renderer/extension_helper.h"
     22 #include "extensions/renderer/extensions_renderer_client.h"
     23 #include "extensions/renderer/script_context.h"
     24 #include "third_party/WebKit/public/web/WebFrame.h"
     25 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
     26 #include "third_party/WebKit/public/web/WebSecurityPolicy.h"
     27 #include "third_party/WebKit/public/web/WebView.h"
     28 #include "url/gurl.h"
     29 
     30 using blink::WebFrame;
     31 using blink::WebSecurityOrigin;
     32 using blink::WebSecurityPolicy;
     33 using blink::WebString;
     34 using content::RenderThread;
     35 
     36 namespace extensions {
     37 
     38 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension,
     39                                                     WebFrame* frame) {
     40   static int g_next_isolated_world_id =
     41       ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
     42 
     43   int id = 0;
     44   IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id());
     45   if (iter != isolated_world_ids_.end()) {
     46     id = iter->second;
     47   } else {
     48     id = g_next_isolated_world_id++;
     49     // This map will tend to pile up over time, but realistically, you're never
     50     // going to have enough extensions for it to matter.
     51     isolated_world_ids_[extension->id()] = id;
     52   }
     53 
     54   // We need to set the isolated world origin and CSP even if it's not a new
     55   // world since these are stored per frame, and we might not have used this
     56   // isolated world in this frame before.
     57   frame->setIsolatedWorldSecurityOrigin(
     58       id, WebSecurityOrigin::create(extension->url()));
     59   frame->setIsolatedWorldContentSecurityPolicy(
     60       id, WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
     61 
     62   return id;
     63 }
     64 
     65 std::string UserScriptSlave::GetExtensionIdForIsolatedWorld(
     66     int isolated_world_id) {
     67   for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin();
     68        iter != isolated_world_ids_.end();
     69        ++iter) {
     70     if (iter->second == isolated_world_id)
     71       return iter->first;
     72   }
     73   return std::string();
     74 }
     75 
     76 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
     77   isolated_world_ids_.erase(extension_id);
     78 }
     79 
     80 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
     81     : extensions_(extensions) {
     82 }
     83 
     84 UserScriptSlave::~UserScriptSlave() {
     85 }
     86 
     87 void UserScriptSlave::GetActiveExtensions(
     88     std::set<std::string>* extension_ids) {
     89   DCHECK(extension_ids);
     90   for (ScopedVector<ScriptInjection>::const_iterator iter =
     91            script_injections_.begin();
     92        iter != script_injections_.end();
     93        ++iter) {
     94     DCHECK(!(*iter)->extension_id().empty());
     95     extension_ids->insert((*iter)->extension_id());
     96   }
     97 }
     98 
     99 const Extension* UserScriptSlave::GetExtension(
    100     const std::string& extension_id) {
    101   return extensions_->GetByID(extension_id);
    102 }
    103 
    104 bool UserScriptSlave::UpdateScripts(
    105     base::SharedMemoryHandle shared_memory,
    106     const std::set<std::string>& changed_extensions) {
    107   bool only_inject_incognito =
    108       ExtensionsRendererClient::Get()->IsIncognitoProcess();
    109 
    110   // Create the shared memory object (read only).
    111   shared_memory_.reset(new base::SharedMemory(shared_memory, true));
    112   if (!shared_memory_.get())
    113     return false;
    114 
    115   // First get the size of the memory block.
    116   if (!shared_memory_->Map(sizeof(Pickle::Header)))
    117     return false;
    118   Pickle::Header* pickle_header =
    119       reinterpret_cast<Pickle::Header*>(shared_memory_->memory());
    120 
    121   // Now map in the rest of the block.
    122   int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size;
    123   shared_memory_->Unmap();
    124   if (!shared_memory_->Map(pickle_size))
    125     return false;
    126 
    127   // Unpickle scripts.
    128   uint64 num_scripts = 0;
    129   Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size);
    130   PickleIterator iter(pickle);
    131   CHECK(pickle.ReadUInt64(&iter, &num_scripts));
    132 
    133   // If we pass no explicit extension ids, we should refresh all extensions.
    134   bool include_all_extensions = changed_extensions.empty();
    135 
    136   // If we include all extensions, then we clear the script injections and
    137   // start from scratch. If not, then clear only the scripts for extension ids
    138   // that we are updating. This is important to maintain pending script
    139   // injection state for each ScriptInjection.
    140   if (include_all_extensions) {
    141     script_injections_.clear();
    142   } else {
    143     for (ScopedVector<ScriptInjection>::iterator iter =
    144              script_injections_.begin();
    145          iter != script_injections_.end();) {
    146       if (changed_extensions.count((*iter)->extension_id()) > 0)
    147         iter = script_injections_.erase(iter);
    148       else
    149         ++iter;
    150     }
    151   }
    152 
    153   script_injections_.reserve(num_scripts);
    154   for (uint64 i = 0; i < num_scripts; ++i) {
    155     scoped_ptr<UserScript> script(new UserScript());
    156     script->Unpickle(pickle, &iter);
    157 
    158     // Note that this is a pointer into shared memory. We don't own it. It gets
    159     // cleared up when the last renderer or browser process drops their
    160     // reference to the shared memory.
    161     for (size_t j = 0; j < script->js_scripts().size(); ++j) {
    162       const char* body = NULL;
    163       int body_length = 0;
    164       CHECK(pickle.ReadData(&iter, &body, &body_length));
    165       script->js_scripts()[j].set_external_content(
    166           base::StringPiece(body, body_length));
    167     }
    168     for (size_t j = 0; j < script->css_scripts().size(); ++j) {
    169       const char* body = NULL;
    170       int body_length = 0;
    171       CHECK(pickle.ReadData(&iter, &body, &body_length));
    172       script->css_scripts()[j].set_external_content(
    173           base::StringPiece(body, body_length));
    174     }
    175 
    176     if (only_inject_incognito && !script->is_incognito_enabled())
    177       continue; // This script shouldn't run in an incognito tab.
    178 
    179     // If we include all extensions or the given extension changed, we add a
    180     // new script injection.
    181     if (include_all_extensions ||
    182         changed_extensions.count(script->extension_id()) > 0) {
    183       script_injections_.push_back(new ScriptInjection(script.Pass(), this));
    184     } else {
    185       // Otherwise, we need to update the existing script injection with the
    186       // new user script (since the old content was invalidated).
    187       //
    188       // Note: Yes, this is O(n^2). But vectors are faster than maps for
    189       // relatively few elements, and less than 1% of our users actually have
    190       // enough content scripts for it to matter. If this changes, or if
    191       // std::maps get a much faster implementation, we should look into
    192       // making a map for script injections.
    193       for (ScopedVector<ScriptInjection>::iterator iter =
    194                script_injections_.begin();
    195            iter != script_injections_.end();
    196            ++iter) {
    197         if ((*iter)->script()->id() == script->id()) {
    198           (*iter)->SetScript(script.Pass());
    199           break;
    200         }
    201       }
    202     }
    203   }
    204   return true;
    205 }
    206 
    207 void UserScriptSlave::InjectScripts(WebFrame* frame,
    208                                     UserScript::RunLocation location) {
    209   GURL document_url = ScriptInjection::GetDocumentUrlForFrame(frame);
    210   if (document_url.is_empty())
    211     return;
    212 
    213   ScriptInjection::ScriptsRunInfo scripts_run_info;
    214   for (ScopedVector<ScriptInjection>::const_iterator iter =
    215            script_injections_.begin();
    216        iter != script_injections_.end();
    217        ++iter) {
    218     (*iter)->InjectIfAllowed(frame, location, document_url, &scripts_run_info);
    219   }
    220 
    221   LogScriptsRun(frame, location, scripts_run_info);
    222 }
    223 
    224 void UserScriptSlave::OnContentScriptGrantedPermission(
    225     content::RenderView* render_view, int request_id) {
    226   ScriptInjection::ScriptsRunInfo run_info;
    227   blink::WebFrame* frame = NULL;
    228   // Notify the injections that a request to inject has been granted.
    229   for (ScopedVector<ScriptInjection>::iterator iter =
    230            script_injections_.begin();
    231        iter != script_injections_.end();
    232        ++iter) {
    233     if ((*iter)->NotifyScriptPermitted(request_id,
    234                                        render_view,
    235                                        &run_info,
    236                                        &frame)) {
    237       DCHECK(frame);
    238       LogScriptsRun(frame, UserScript::RUN_DEFERRED, run_info);
    239       break;
    240     }
    241   }
    242 }
    243 
    244 void UserScriptSlave::FrameDetached(blink::WebFrame* frame) {
    245   for (ScopedVector<ScriptInjection>::iterator iter =
    246            script_injections_.begin();
    247        iter != script_injections_.end();
    248        ++iter) {
    249     (*iter)->FrameDetached(frame);
    250   }
    251 }
    252 
    253 void UserScriptSlave::LogScriptsRun(
    254     blink::WebFrame* frame,
    255     UserScript::RunLocation location,
    256     const ScriptInjection::ScriptsRunInfo& info) {
    257   // Notify the browser if any extensions are now executing scripts.
    258   if (!info.executing_scripts.empty()) {
    259     content::RenderView* render_view =
    260         content::RenderView::FromWebView(frame->view());
    261     render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
    262         render_view->GetRoutingID(),
    263         info.executing_scripts,
    264         render_view->GetPageId(),
    265         ScriptContext::GetDataSourceURLForFrame(frame)));
    266   }
    267 
    268   switch (location) {
    269     case UserScript::DOCUMENT_START:
    270       UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount",
    271                                info.num_css);
    272       UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount",
    273                                info.num_js);
    274       if (info.num_css || info.num_js)
    275         UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time",
    276                             info.timer.Elapsed());
    277       break;
    278     case UserScript::DOCUMENT_END:
    279       UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js);
    280       if (info.num_js)
    281         UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed());
    282       break;
    283     case UserScript::DOCUMENT_IDLE:
    284       UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount",
    285                                info.num_js);
    286       if (info.num_js)
    287         UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed());
    288       break;
    289     case UserScript::RUN_DEFERRED:
    290       // TODO(rdevlin.cronin): Add histograms.
    291       break;
    292     case UserScript::UNDEFINED:
    293     case UserScript::RUN_LOCATION_LAST:
    294       NOTREACHED();
    295   }
    296 }
    297 
    298 }  // namespace extensions
    299