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_slave.h"
      6 
      7 #include <map>
      8 
      9 #include "base/command_line.h"
     10 #include "base/logging.h"
     11 #include "base/memory/shared_memory.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/pickle.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "base/timer/elapsed_timer.h"
     16 #include "chrome/common/extensions/extension_messages.h"
     17 #include "chrome/common/extensions/extension_set.h"
     18 #include "chrome/common/url_constants.h"
     19 #include "chrome/renderer/chrome_render_process_observer.h"
     20 #include "chrome/renderer/extensions/dom_activity_logger.h"
     21 #include "chrome/renderer/extensions/extension_groups.h"
     22 #include "chrome/renderer/isolated_world_ids.h"
     23 #include "content/public/renderer/render_thread.h"
     24 #include "content/public/renderer/render_view.h"
     25 #include "extensions/common/extension.h"
     26 #include "extensions/common/manifest_handlers/csp_info.h"
     27 #include "extensions/common/permissions/permissions_data.h"
     28 #include "grit/renderer_resources.h"
     29 #include "third_party/WebKit/public/platform/WebURLRequest.h"
     30 #include "third_party/WebKit/public/platform/WebVector.h"
     31 #include "third_party/WebKit/public/web/WebDataSource.h"
     32 #include "third_party/WebKit/public/web/WebDocument.h"
     33 #include "third_party/WebKit/public/web/WebFrame.h"
     34 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
     35 #include "third_party/WebKit/public/web/WebSecurityPolicy.h"
     36 #include "third_party/WebKit/public/web/WebView.h"
     37 #include "ui/base/resource/resource_bundle.h"
     38 #include "url/gurl.h"
     39 
     40 using blink::WebFrame;
     41 using blink::WebSecurityOrigin;
     42 using blink::WebSecurityPolicy;
     43 using blink::WebString;
     44 using blink::WebVector;
     45 using blink::WebView;
     46 using content::RenderThread;
     47 
     48 namespace extensions {
     49 
     50 // These two strings are injected before and after the Greasemonkey API and
     51 // user script to wrap it in an anonymous scope.
     52 static const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
     53 static const char kUserScriptTail[] = "\n})(window);";
     54 
     55 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension,
     56                                                     WebFrame* frame) {
     57   static int g_next_isolated_world_id = chrome::ISOLATED_WORLD_ID_EXTENSIONS;
     58 
     59   IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id());
     60   if (iter != isolated_world_ids_.end()) {
     61     // We need to set the isolated world origin and CSP even if it's not a new
     62     // world since these are stored per frame, and we might not have used this
     63     // isolated world in this frame before.
     64     frame->setIsolatedWorldSecurityOrigin(
     65         iter->second,
     66         WebSecurityOrigin::create(extension->url()));
     67     frame->setIsolatedWorldContentSecurityPolicy(
     68         iter->second,
     69         WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
     70     return iter->second;
     71   }
     72 
     73   int new_id = g_next_isolated_world_id;
     74   ++g_next_isolated_world_id;
     75 
     76   // This map will tend to pile up over time, but realistically, you're never
     77   // going to have enough extensions for it to matter.
     78   isolated_world_ids_[extension->id()] = new_id;
     79   InitializeIsolatedWorld(new_id, extension);
     80   frame->setIsolatedWorldSecurityOrigin(
     81       new_id,
     82       WebSecurityOrigin::create(extension->url()));
     83   frame->setIsolatedWorldContentSecurityPolicy(
     84       new_id,
     85       WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
     86   return new_id;
     87 }
     88 
     89 std::string UserScriptSlave::GetExtensionIdForIsolatedWorld(
     90     int isolated_world_id) {
     91   for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin();
     92        iter != isolated_world_ids_.end(); ++iter) {
     93     if (iter->second == isolated_world_id)
     94       return iter->first;
     95   }
     96   return std::string();
     97 }
     98 
     99 // static
    100 void UserScriptSlave::InitializeIsolatedWorld(int isolated_world_id,
    101                                               const Extension* extension) {
    102   const URLPatternSet& permissions =
    103       PermissionsData::GetEffectiveHostPermissions(extension);
    104   for (URLPatternSet::const_iterator i = permissions.begin();
    105        i != permissions.end(); ++i) {
    106     const char* schemes[] = {
    107       content::kHttpScheme,
    108       content::kHttpsScheme,
    109       chrome::kFileScheme,
    110       chrome::kChromeUIScheme,
    111     };
    112     for (size_t j = 0; j < arraysize(schemes); ++j) {
    113       if (i->MatchesScheme(schemes[j])) {
    114         WebSecurityPolicy::addOriginAccessWhitelistEntry(
    115             extension->url(),
    116             WebString::fromUTF8(schemes[j]),
    117             WebString::fromUTF8(i->host()),
    118             i->match_subdomains());
    119       }
    120     }
    121   }
    122 }
    123 
    124 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
    125   isolated_world_ids_.erase(extension_id);
    126 }
    127 
    128 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
    129     : script_deleter_(&scripts_), extensions_(extensions) {
    130   api_js_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
    131       IDR_GREASEMONKEY_API_JS);
    132 }
    133 
    134 UserScriptSlave::~UserScriptSlave() {}
    135 
    136 void UserScriptSlave::GetActiveExtensions(
    137     std::set<std::string>* extension_ids) {
    138   for (size_t i = 0; i < scripts_.size(); ++i) {
    139     DCHECK(!scripts_[i]->extension_id().empty());
    140     extension_ids->insert(scripts_[i]->extension_id());
    141   }
    142 }
    143 
    144 bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) {
    145   scripts_.clear();
    146 
    147   bool only_inject_incognito =
    148       ChromeRenderProcessObserver::is_incognito_process();
    149 
    150   // Create the shared memory object (read only).
    151   shared_memory_.reset(new base::SharedMemory(shared_memory, true));
    152   if (!shared_memory_.get())
    153     return false;
    154 
    155   // First get the size of the memory block.
    156   if (!shared_memory_->Map(sizeof(Pickle::Header)))
    157     return false;
    158   Pickle::Header* pickle_header =
    159       reinterpret_cast<Pickle::Header*>(shared_memory_->memory());
    160 
    161   // Now map in the rest of the block.
    162   int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size;
    163   shared_memory_->Unmap();
    164   if (!shared_memory_->Map(pickle_size))
    165     return false;
    166 
    167   // Unpickle scripts.
    168   uint64 num_scripts = 0;
    169   Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()),
    170                 pickle_size);
    171   PickleIterator iter(pickle);
    172   CHECK(pickle.ReadUInt64(&iter, &num_scripts));
    173 
    174   scripts_.reserve(num_scripts);
    175   for (uint64 i = 0; i < num_scripts; ++i) {
    176     scripts_.push_back(new UserScript());
    177     UserScript* script = scripts_.back();
    178     script->Unpickle(pickle, &iter);
    179 
    180     // Note that this is a pointer into shared memory. We don't own it. It gets
    181     // cleared up when the last renderer or browser process drops their
    182     // reference to the shared memory.
    183     for (size_t j = 0; j < script->js_scripts().size(); ++j) {
    184       const char* body = NULL;
    185       int body_length = 0;
    186       CHECK(pickle.ReadData(&iter, &body, &body_length));
    187       script->js_scripts()[j].set_external_content(
    188           base::StringPiece(body, body_length));
    189     }
    190     for (size_t j = 0; j < script->css_scripts().size(); ++j) {
    191       const char* body = NULL;
    192       int body_length = 0;
    193       CHECK(pickle.ReadData(&iter, &body, &body_length));
    194       script->css_scripts()[j].set_external_content(
    195           base::StringPiece(body, body_length));
    196     }
    197 
    198     if (only_inject_incognito && !script->is_incognito_enabled()) {
    199       // This script shouldn't run in an incognito tab.
    200       delete script;
    201       scripts_.pop_back();
    202     }
    203   }
    204 
    205   // Push user styles down into WebCore
    206   RenderThread::Get()->EnsureWebKitInitialized();
    207   WebView::removeInjectedStyleSheets();
    208   for (size_t i = 0; i < scripts_.size(); ++i) {
    209     UserScript* script = scripts_[i];
    210     if (script->css_scripts().empty())
    211       continue;
    212 
    213     WebVector<WebString> patterns;
    214     std::vector<WebString> temp_patterns;
    215     const URLPatternSet& url_patterns = script->url_patterns();
    216     for (URLPatternSet::const_iterator k = url_patterns.begin();
    217          k != url_patterns.end(); ++k) {
    218       URLPatternList explicit_patterns = k->ConvertToExplicitSchemes();
    219       for (size_t m = 0; m < explicit_patterns.size(); ++m) {
    220         temp_patterns.push_back(WebString::fromUTF8(
    221             explicit_patterns[m].GetAsString()));
    222       }
    223     }
    224     patterns.assign(temp_patterns);
    225 
    226     for (size_t j = 0; j < script->css_scripts().size(); ++j) {
    227       const UserScript::File& file = scripts_[i]->css_scripts()[j];
    228       std::string content = file.GetContent().as_string();
    229 
    230       WebView::injectStyleSheet(
    231           WebString::fromUTF8(content),
    232           patterns,
    233            script->match_all_frames() ?
    234               WebView::InjectStyleInAllFrames :
    235               WebView::InjectStyleInTopFrameOnly);
    236     }
    237   }
    238 
    239   return true;
    240 }
    241 
    242 GURL UserScriptSlave::GetDataSourceURLForFrame(const WebFrame* frame) {
    243   // Normally we would use frame->document().url() to determine the document's
    244   // URL, but to decide whether to inject a content script, we use the URL from
    245   // the data source. This "quirk" helps prevents content scripts from
    246   // inadvertently adding DOM elements to the compose iframe in Gmail because
    247   // the compose iframe's dataSource URL is about:blank, but the document URL
    248   // changes to match the parent document after Gmail document.writes into
    249   // it to create the editor.
    250   // http://code.google.com/p/chromium/issues/detail?id=86742
    251   blink::WebDataSource* data_source = frame->provisionalDataSource() ?
    252       frame->provisionalDataSource() : frame->dataSource();
    253   CHECK(data_source);
    254   return GURL(data_source->request().url());
    255 }
    256 
    257 void UserScriptSlave::InjectScripts(WebFrame* frame,
    258                                     UserScript::RunLocation location) {
    259   GURL data_source_url = GetDataSourceURLForFrame(frame);
    260   if (data_source_url.is_empty())
    261     return;
    262 
    263   if (frame->isViewSourceModeEnabled())
    264     data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
    265                            data_source_url.spec());
    266 
    267   base::ElapsedTimer timer;
    268   int num_css = 0;
    269   int num_scripts = 0;
    270 
    271   ExecutingScriptsMap extensions_executing_scripts;
    272 
    273   for (size_t i = 0; i < scripts_.size(); ++i) {
    274     std::vector<WebScriptSource> sources;
    275     UserScript* script = scripts_[i];
    276 
    277     if (frame->parent() && !script->match_all_frames())
    278       continue;  // Only match subframes if the script declared it wanted to.
    279 
    280     const Extension* extension = extensions_->GetByID(script->extension_id());
    281 
    282     // Since extension info is sent separately from user script info, they can
    283     // be out of sync. We just ignore this situation.
    284     if (!extension)
    285       continue;
    286 
    287     // Content scripts are not tab-specific.
    288     const int kNoTabId = -1;
    289     // We don't have a process id in this context.
    290     const int kNoProcessId = -1;
    291     if (!PermissionsData::CanExecuteScriptOnPage(extension,
    292                                                  data_source_url,
    293                                                  frame->top()->document().url(),
    294                                                  kNoTabId,
    295                                                  script,
    296                                                  kNoProcessId,
    297                                                  NULL)) {
    298       continue;
    299     }
    300 
    301     // We rely on WebCore for CSS injection, but it's still useful to know how
    302     // many css files there are.
    303     if (location == UserScript::DOCUMENT_START)
    304       num_css += script->css_scripts().size();
    305 
    306     if (script->run_location() == location) {
    307       num_scripts += script->js_scripts().size();
    308       for (size_t j = 0; j < script->js_scripts().size(); ++j) {
    309         UserScript::File &file = script->js_scripts()[j];
    310         std::string content = file.GetContent().as_string();
    311 
    312         // We add this dumb function wrapper for standalone user script to
    313         // emulate what Greasemonkey does.
    314         // TODO(aa): I think that maybe "is_standalone" scripts don't exist
    315         // anymore. Investigate.
    316         if (script->is_standalone() || script->emulate_greasemonkey()) {
    317           content.insert(0, kUserScriptHead);
    318           content += kUserScriptTail;
    319         }
    320         sources.push_back(
    321             WebScriptSource(WebString::fromUTF8(content), file.url()));
    322       }
    323     }
    324 
    325     if (!sources.empty()) {
    326       // Emulate Greasemonkey API for scripts that were converted to extensions
    327       // and "standalone" user scripts.
    328       if (script->is_standalone() || script->emulate_greasemonkey()) {
    329         sources.insert(sources.begin(),
    330             WebScriptSource(WebString::fromUTF8(api_js_.as_string())));
    331       }
    332 
    333       int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame);
    334 
    335       base::ElapsedTimer exec_timer;
    336       DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id());
    337       frame->executeScriptInIsolatedWorld(
    338           isolated_world_id, &sources.front(), sources.size(),
    339           EXTENSION_GROUP_CONTENT_SCRIPTS);
    340       UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
    341 
    342       for (std::vector<WebScriptSource>::const_iterator iter = sources.begin();
    343            iter != sources.end(); ++iter) {
    344         extensions_executing_scripts[extension->id()].insert(
    345             GURL(iter->url).path());
    346       }
    347     }
    348   }
    349 
    350   // Notify the browser if any extensions are now executing scripts.
    351   if (!extensions_executing_scripts.empty()) {
    352     blink::WebFrame* top_frame = frame->top();
    353     content::RenderView* render_view =
    354         content::RenderView::FromWebView(top_frame->view());
    355     render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
    356         render_view->GetRoutingID(),
    357         extensions_executing_scripts,
    358         render_view->GetPageId(),
    359         GetDataSourceURLForFrame(top_frame)));
    360   }
    361 
    362   // Log debug info.
    363   if (location == UserScript::DOCUMENT_START) {
    364     UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css);
    365     UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_scripts);
    366     if (num_css || num_scripts)
    367       UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed());
    368   } else if (location == UserScript::DOCUMENT_END) {
    369     UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_scripts);
    370     if (num_scripts)
    371       UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed());
    372   } else if (location == UserScript::DOCUMENT_IDLE) {
    373     UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_scripts);
    374     if (num_scripts)
    375       UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed());
    376   } else {
    377     NOTREACHED();
    378   }
    379 }
    380 
    381 }  // namespace extensions
    382