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