1 // Copyright 2014 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 "extensions/renderer/user_script_slave.h" 6 7 #include <map> 8 9 #include "base/logging.h" 10 #include "base/memory/shared_memory.h" 11 #include "base/metrics/histogram.h" 12 #include "base/pickle.h" 13 #include "base/timer/elapsed_timer.h" 14 #include "content/public/renderer/render_thread.h" 15 #include "content/public/renderer/render_view.h" 16 #include "extensions/common/extension.h" 17 #include "extensions/common/extension_messages.h" 18 #include "extensions/common/extension_set.h" 19 #include "extensions/common/manifest_handlers/csp_info.h" 20 #include "extensions/common/permissions/permissions_data.h" 21 #include "extensions/renderer/extension_helper.h" 22 #include "extensions/renderer/extensions_renderer_client.h" 23 #include "extensions/renderer/script_context.h" 24 #include "third_party/WebKit/public/web/WebFrame.h" 25 #include "third_party/WebKit/public/web/WebSecurityOrigin.h" 26 #include "third_party/WebKit/public/web/WebSecurityPolicy.h" 27 #include "third_party/WebKit/public/web/WebView.h" 28 #include "url/gurl.h" 29 30 using blink::WebFrame; 31 using blink::WebSecurityOrigin; 32 using blink::WebSecurityPolicy; 33 using blink::WebString; 34 using content::RenderThread; 35 36 namespace extensions { 37 38 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension, 39 WebFrame* frame) { 40 static int g_next_isolated_world_id = 41 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId(); 42 43 int id = 0; 44 IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id()); 45 if (iter != isolated_world_ids_.end()) { 46 id = iter->second; 47 } else { 48 id = g_next_isolated_world_id++; 49 // This map will tend to pile up over time, but realistically, you're never 50 // going to have enough extensions for it to matter. 51 isolated_world_ids_[extension->id()] = id; 52 } 53 54 // We need to set the isolated world origin and CSP even if it's not a new 55 // world since these are stored per frame, and we might not have used this 56 // isolated world in this frame before. 57 frame->setIsolatedWorldSecurityOrigin( 58 id, WebSecurityOrigin::create(extension->url())); 59 frame->setIsolatedWorldContentSecurityPolicy( 60 id, WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension))); 61 62 return id; 63 } 64 65 std::string UserScriptSlave::GetExtensionIdForIsolatedWorld( 66 int isolated_world_id) { 67 for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin(); 68 iter != isolated_world_ids_.end(); 69 ++iter) { 70 if (iter->second == isolated_world_id) 71 return iter->first; 72 } 73 return std::string(); 74 } 75 76 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) { 77 isolated_world_ids_.erase(extension_id); 78 } 79 80 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions) 81 : extensions_(extensions) { 82 } 83 84 UserScriptSlave::~UserScriptSlave() { 85 } 86 87 void UserScriptSlave::GetActiveExtensions( 88 std::set<std::string>* extension_ids) { 89 DCHECK(extension_ids); 90 for (ScopedVector<ScriptInjection>::const_iterator iter = 91 script_injections_.begin(); 92 iter != script_injections_.end(); 93 ++iter) { 94 DCHECK(!(*iter)->extension_id().empty()); 95 extension_ids->insert((*iter)->extension_id()); 96 } 97 } 98 99 const Extension* UserScriptSlave::GetExtension( 100 const std::string& extension_id) { 101 return extensions_->GetByID(extension_id); 102 } 103 104 bool UserScriptSlave::UpdateScripts( 105 base::SharedMemoryHandle shared_memory, 106 const std::set<std::string>& changed_extensions) { 107 bool only_inject_incognito = 108 ExtensionsRendererClient::Get()->IsIncognitoProcess(); 109 110 // Create the shared memory object (read only). 111 shared_memory_.reset(new base::SharedMemory(shared_memory, true)); 112 if (!shared_memory_.get()) 113 return false; 114 115 // First get the size of the memory block. 116 if (!shared_memory_->Map(sizeof(Pickle::Header))) 117 return false; 118 Pickle::Header* pickle_header = 119 reinterpret_cast<Pickle::Header*>(shared_memory_->memory()); 120 121 // Now map in the rest of the block. 122 int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size; 123 shared_memory_->Unmap(); 124 if (!shared_memory_->Map(pickle_size)) 125 return false; 126 127 // Unpickle scripts. 128 uint64 num_scripts = 0; 129 Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size); 130 PickleIterator iter(pickle); 131 CHECK(pickle.ReadUInt64(&iter, &num_scripts)); 132 133 // If we pass no explicit extension ids, we should refresh all extensions. 134 bool include_all_extensions = changed_extensions.empty(); 135 136 // If we include all extensions, then we clear the script injections and 137 // start from scratch. If not, then clear only the scripts for extension ids 138 // that we are updating. This is important to maintain pending script 139 // injection state for each ScriptInjection. 140 if (include_all_extensions) { 141 script_injections_.clear(); 142 } else { 143 for (ScopedVector<ScriptInjection>::iterator iter = 144 script_injections_.begin(); 145 iter != script_injections_.end();) { 146 if (changed_extensions.count((*iter)->extension_id()) > 0) 147 iter = script_injections_.erase(iter); 148 else 149 ++iter; 150 } 151 } 152 153 script_injections_.reserve(num_scripts); 154 for (uint64 i = 0; i < num_scripts; ++i) { 155 scoped_ptr<UserScript> script(new UserScript()); 156 script->Unpickle(pickle, &iter); 157 158 // Note that this is a pointer into shared memory. We don't own it. It gets 159 // cleared up when the last renderer or browser process drops their 160 // reference to the shared memory. 161 for (size_t j = 0; j < script->js_scripts().size(); ++j) { 162 const char* body = NULL; 163 int body_length = 0; 164 CHECK(pickle.ReadData(&iter, &body, &body_length)); 165 script->js_scripts()[j].set_external_content( 166 base::StringPiece(body, body_length)); 167 } 168 for (size_t j = 0; j < script->css_scripts().size(); ++j) { 169 const char* body = NULL; 170 int body_length = 0; 171 CHECK(pickle.ReadData(&iter, &body, &body_length)); 172 script->css_scripts()[j].set_external_content( 173 base::StringPiece(body, body_length)); 174 } 175 176 if (only_inject_incognito && !script->is_incognito_enabled()) 177 continue; // This script shouldn't run in an incognito tab. 178 179 // If we include all extensions or the given extension changed, we add a 180 // new script injection. 181 if (include_all_extensions || 182 changed_extensions.count(script->extension_id()) > 0) { 183 script_injections_.push_back(new ScriptInjection(script.Pass(), this)); 184 } else { 185 // Otherwise, we need to update the existing script injection with the 186 // new user script (since the old content was invalidated). 187 // 188 // Note: Yes, this is O(n^2). But vectors are faster than maps for 189 // relatively few elements, and less than 1% of our users actually have 190 // enough content scripts for it to matter. If this changes, or if 191 // std::maps get a much faster implementation, we should look into 192 // making a map for script injections. 193 for (ScopedVector<ScriptInjection>::iterator iter = 194 script_injections_.begin(); 195 iter != script_injections_.end(); 196 ++iter) { 197 if ((*iter)->script()->id() == script->id()) { 198 (*iter)->SetScript(script.Pass()); 199 break; 200 } 201 } 202 } 203 } 204 return true; 205 } 206 207 void UserScriptSlave::InjectScripts(WebFrame* frame, 208 UserScript::RunLocation location) { 209 GURL document_url = ScriptInjection::GetDocumentUrlForFrame(frame); 210 if (document_url.is_empty()) 211 return; 212 213 ScriptInjection::ScriptsRunInfo scripts_run_info; 214 for (ScopedVector<ScriptInjection>::const_iterator iter = 215 script_injections_.begin(); 216 iter != script_injections_.end(); 217 ++iter) { 218 (*iter)->InjectIfAllowed(frame, location, document_url, &scripts_run_info); 219 } 220 221 LogScriptsRun(frame, location, scripts_run_info); 222 } 223 224 void UserScriptSlave::OnContentScriptGrantedPermission( 225 content::RenderView* render_view, int request_id) { 226 ScriptInjection::ScriptsRunInfo run_info; 227 blink::WebFrame* frame = NULL; 228 // Notify the injections that a request to inject has been granted. 229 for (ScopedVector<ScriptInjection>::iterator iter = 230 script_injections_.begin(); 231 iter != script_injections_.end(); 232 ++iter) { 233 if ((*iter)->NotifyScriptPermitted(request_id, 234 render_view, 235 &run_info, 236 &frame)) { 237 DCHECK(frame); 238 LogScriptsRun(frame, UserScript::RUN_DEFERRED, run_info); 239 break; 240 } 241 } 242 } 243 244 void UserScriptSlave::FrameDetached(blink::WebFrame* frame) { 245 for (ScopedVector<ScriptInjection>::iterator iter = 246 script_injections_.begin(); 247 iter != script_injections_.end(); 248 ++iter) { 249 (*iter)->FrameDetached(frame); 250 } 251 } 252 253 void UserScriptSlave::LogScriptsRun( 254 blink::WebFrame* frame, 255 UserScript::RunLocation location, 256 const ScriptInjection::ScriptsRunInfo& info) { 257 // Notify the browser if any extensions are now executing scripts. 258 if (!info.executing_scripts.empty()) { 259 content::RenderView* render_view = 260 content::RenderView::FromWebView(frame->view()); 261 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( 262 render_view->GetRoutingID(), 263 info.executing_scripts, 264 render_view->GetPageId(), 265 ScriptContext::GetDataSourceURLForFrame(frame))); 266 } 267 268 switch (location) { 269 case UserScript::DOCUMENT_START: 270 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", 271 info.num_css); 272 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", 273 info.num_js); 274 if (info.num_css || info.num_js) 275 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", 276 info.timer.Elapsed()); 277 break; 278 case UserScript::DOCUMENT_END: 279 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js); 280 if (info.num_js) 281 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed()); 282 break; 283 case UserScript::DOCUMENT_IDLE: 284 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", 285 info.num_js); 286 if (info.num_js) 287 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed()); 288 break; 289 case UserScript::RUN_DEFERRED: 290 // TODO(rdevlin.cronin): Add histograms. 291 break; 292 case UserScript::UNDEFINED: 293 case UserScript::RUN_LOCATION_LAST: 294 NOTREACHED(); 295 } 296 } 297 298 } // namespace extensions 299