• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetIsolatedWorldIdForExtension(const Extension * extension,WebFrame * frame)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 
GetExtensionIdForIsolatedWorld(int isolated_world_id)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
InitializeIsolatedWorld(int isolated_world_id,const Extension * extension)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 
RemoveIsolatedWorld(const std::string & extension_id)124 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
125   isolated_world_ids_.erase(extension_id);
126 }
127 
UserScriptSlave(const ExtensionSet * extensions)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 
~UserScriptSlave()134 UserScriptSlave::~UserScriptSlave() {}
135 
GetActiveExtensions(std::set<std::string> * extension_ids)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 
UpdateScripts(base::SharedMemoryHandle shared_memory)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 
GetDataSourceURLForFrame(const WebFrame * frame)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 
InjectScripts(WebFrame * frame,UserScript::RunLocation location)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