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/perftimer.h"
14#include "base/pickle.h"
15#include "base/strings/stringprintf.h"
16#include "chrome/common/extensions/csp_handler.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/common/extensions/extension_messages.h"
19#include "chrome/common/extensions/extension_set.h"
20#include "chrome/common/extensions/permissions/permissions_data.h"
21#include "chrome/common/url_constants.h"
22#include "chrome/renderer/chrome_render_process_observer.h"
23#include "chrome/renderer/extensions/dom_activity_logger.h"
24#include "chrome/renderer/extensions/extension_groups.h"
25#include "chrome/renderer/isolated_world_ids.h"
26#include "content/public/renderer/render_thread.h"
27#include "content/public/renderer/render_view.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
40using WebKit::WebFrame;
41using WebKit::WebSecurityOrigin;
42using WebKit::WebSecurityPolicy;
43using WebKit::WebString;
44using WebKit::WebVector;
45using WebKit::WebView;
46using content::RenderThread;
47
48namespace 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.
52static const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
53static const char kUserScriptTail[] = "\n})(window);";
54
55int 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
89std::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
100void 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      chrome::kHttpScheme,
108      chrome::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
124void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
125  isolated_world_ids_.erase(extension_id);
126}
127
128UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
129    : script_deleter_(&scripts_), extensions_(extensions) {
130  api_js_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
131      IDR_GREASEMONKEY_API_JS);
132}
133
134UserScriptSlave::~UserScriptSlave() {}
135
136void 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
144bool 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::removeAllUserContent();
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::addUserStyleSheet(
231          WebString::fromUTF8(content),
232          patterns,
233           script->match_all_frames() ?
234              WebView::UserContentInjectInAllFrames :
235              WebView::UserContentInjectInTopFrameOnly,
236          WebView::UserStyleInjectInExistingDocuments);
237    }
238  }
239
240  return true;
241}
242
243GURL UserScriptSlave::GetDataSourceURLForFrame(const WebFrame* frame) {
244  // Normally we would use frame->document().url() to determine the document's
245  // URL, but to decide whether to inject a content script, we use the URL from
246  // the data source. This "quirk" helps prevents content scripts from
247  // inadvertently adding DOM elements to the compose iframe in Gmail because
248  // the compose iframe's dataSource URL is about:blank, but the document URL
249  // changes to match the parent document after Gmail document.writes into
250  // it to create the editor.
251  // http://code.google.com/p/chromium/issues/detail?id=86742
252  WebKit::WebDataSource* data_source = frame->provisionalDataSource() ?
253      frame->provisionalDataSource() : frame->dataSource();
254  CHECK(data_source);
255  return GURL(data_source->request().url());
256}
257
258void UserScriptSlave::InjectScripts(WebFrame* frame,
259                                    UserScript::RunLocation location) {
260  GURL data_source_url = GetDataSourceURLForFrame(frame);
261  if (data_source_url.is_empty())
262    return;
263
264  if (frame->isViewSourceModeEnabled())
265    data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
266                           data_source_url.spec());
267
268  PerfTimer timer;
269  int num_css = 0;
270  int num_scripts = 0;
271
272  ExecutingScriptsMap extensions_executing_scripts;
273
274  for (size_t i = 0; i < scripts_.size(); ++i) {
275    std::vector<WebScriptSource> sources;
276    UserScript* script = scripts_[i];
277
278    if (frame->parent() && !script->match_all_frames())
279      continue;  // Only match subframes if the script declared it wanted to.
280
281    const Extension* extension = extensions_->GetByID(script->extension_id());
282
283    // Since extension info is sent separately from user script info, they can
284    // be out of sync. We just ignore this situation.
285    if (!extension)
286      continue;
287
288    // Content scripts are not tab-specific.
289    const int kNoTabId = -1;
290    // We don't have a process id in this context.
291    const int kNoProcessId = -1;
292    if (!PermissionsData::CanExecuteScriptOnPage(extension,
293                                                 data_source_url,
294                                                 frame->top()->document().url(),
295                                                 kNoTabId,
296                                                 script,
297                                                 kNoProcessId,
298                                                 NULL)) {
299      continue;
300    }
301
302    // We rely on WebCore for CSS injection, but it's still useful to know how
303    // many css files there are.
304    if (location == UserScript::DOCUMENT_START)
305      num_css += script->css_scripts().size();
306
307    if (script->run_location() == location) {
308      num_scripts += script->js_scripts().size();
309      for (size_t j = 0; j < script->js_scripts().size(); ++j) {
310        UserScript::File &file = script->js_scripts()[j];
311        std::string content = file.GetContent().as_string();
312
313        // We add this dumb function wrapper for standalone user script to
314        // emulate what Greasemonkey does.
315        // TODO(aa): I think that maybe "is_standalone" scripts don't exist
316        // anymore. Investigate.
317        if (script->is_standalone() || script->emulate_greasemonkey()) {
318          content.insert(0, kUserScriptHead);
319          content += kUserScriptTail;
320        }
321        sources.push_back(
322            WebScriptSource(WebString::fromUTF8(content), file.url()));
323      }
324    }
325
326    if (!sources.empty()) {
327      // Emulate Greasemonkey API for scripts that were converted to extensions
328      // and "standalone" user scripts.
329      if (script->is_standalone() || script->emulate_greasemonkey()) {
330        sources.insert(sources.begin(),
331            WebScriptSource(WebString::fromUTF8(api_js_.as_string())));
332      }
333
334      int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame);
335
336      PerfTimer exec_timer;
337      DOMActivityLogger::AttachToWorld(
338          isolated_world_id,
339          extension->id(),
340          UserScriptSlave::GetDataSourceURLForFrame(frame),
341          frame->document().title());
342      frame->executeScriptInIsolatedWorld(
343          isolated_world_id, &sources.front(), sources.size(),
344          EXTENSION_GROUP_CONTENT_SCRIPTS);
345      UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
346
347      for (std::vector<WebScriptSource>::const_iterator iter = sources.begin();
348           iter != sources.end(); ++iter) {
349        extensions_executing_scripts[extension->id()].insert(
350            GURL(iter->url).path());
351      }
352    }
353  }
354
355  // Notify the browser if any extensions are now executing scripts.
356  if (!extensions_executing_scripts.empty()) {
357    WebKit::WebFrame* top_frame = frame->top();
358    content::RenderView* render_view =
359        content::RenderView::FromWebView(top_frame->view());
360    render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
361        render_view->GetRoutingID(),
362        extensions_executing_scripts,
363        render_view->GetPageId(),
364        GetDataSourceURLForFrame(top_frame)));
365  }
366
367  // Log debug info.
368  if (location == UserScript::DOCUMENT_START) {
369    UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css);
370    UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_scripts);
371    if (num_css || num_scripts)
372      UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed());
373  } else if (location == UserScript::DOCUMENT_END) {
374    UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_scripts);
375    if (num_scripts)
376      UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed());
377  } else if (location == UserScript::DOCUMENT_IDLE) {
378    UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_scripts);
379    if (num_scripts)
380      UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed());
381  } else {
382    NOTREACHED();
383  }
384}
385
386}  // namespace extensions
387