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 40 using WebKit::WebFrame; 41 using WebKit::WebSecurityOrigin; 42 using WebKit::WebSecurityPolicy; 43 using WebKit::WebString; 44 using WebKit::WebVector; 45 using WebKit::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 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 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::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 243 GURL 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 258 void 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