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/script_injection.h" 6 7 #include <vector> 8 9 #include "base/lazy_instance.h" 10 #include "base/metrics/histogram.h" 11 #include "content/public/common/url_constants.h" 12 #include "content/public/renderer/render_view.h" 13 #include "extensions/common/extension.h" 14 #include "extensions/common/extension_messages.h" 15 #include "extensions/common/feature_switch.h" 16 #include "extensions/common/permissions/permissions_data.h" 17 #include "extensions/renderer/dom_activity_logger.h" 18 #include "extensions/renderer/extension_groups.h" 19 #include "extensions/renderer/extension_helper.h" 20 #include "extensions/renderer/script_context.h" 21 #include "extensions/renderer/user_script_slave.h" 22 #include "grit/extensions_renderer_resources.h" 23 #include "third_party/WebKit/public/web/WebDocument.h" 24 #include "third_party/WebKit/public/web/WebFrame.h" 25 #include "third_party/WebKit/public/web/WebScriptSource.h" 26 #include "third_party/WebKit/public/web/WebView.h" 27 #include "ui/base/resource/resource_bundle.h" 28 #include "url/gurl.h" 29 30 namespace extensions { 31 32 namespace { 33 34 // The id of the next pending injection. 35 int64 g_next_pending_id = 0; 36 37 // The number of an invalid request, which is used if the feature to delay 38 // script injection is not enabled. 39 const int64 kInvalidRequestId = -1; 40 41 // These two strings are injected before and after the Greasemonkey API and 42 // user script to wrap it in an anonymous scope. 43 const char kUserScriptHead[] = "(function (unsafeWindow) {\n"; 44 const char kUserScriptTail[] = "\n})(window);"; 45 46 // Greasemonkey API source that is injected with the scripts. 47 struct GreasemonkeyApiJsString { 48 GreasemonkeyApiJsString(); 49 blink::WebScriptSource source; 50 }; 51 52 // The below constructor, monstrous as it is, just makes a WebScriptSource from 53 // the GreasemonkeyApiJs resource. 54 GreasemonkeyApiJsString::GreasemonkeyApiJsString() 55 : source(blink::WebScriptSource(blink::WebString::fromUTF8( 56 ResourceBundle::GetSharedInstance().GetRawDataResource( 57 IDR_GREASEMONKEY_API_JS).as_string()))) { 58 } 59 60 base::LazyInstance<GreasemonkeyApiJsString> g_greasemonkey_api = 61 LAZY_INSTANCE_INITIALIZER; 62 63 } // namespace 64 65 ScriptInjection::ScriptsRunInfo::ScriptsRunInfo() : num_css(0u), num_js(0u) { 66 } 67 68 ScriptInjection::ScriptsRunInfo::~ScriptsRunInfo() { 69 } 70 71 struct ScriptInjection::PendingInjection { 72 PendingInjection(blink::WebFrame* web_frame, 73 UserScript::RunLocation run_location, 74 int page_id); 75 ~PendingInjection(); 76 77 // The globally-unique id of this request. 78 int64 id; 79 80 // The pointer to the web frame into which the script should be injected. 81 // This is weak, but safe because we remove pending requests when a frame is 82 // terminated. 83 blink::WebFrame* web_frame; 84 85 // The run location to inject at. 86 // Note: This could be a lie - we might inject well after this run location 87 // has come and gone. But we need to know it to know which scripts to inject. 88 UserScript::RunLocation run_location; 89 90 // The corresponding page id, to protect against races. 91 int page_id; 92 }; 93 94 ScriptInjection::PendingInjection::PendingInjection( 95 blink::WebFrame* web_frame, 96 UserScript::RunLocation run_location, 97 int page_id) 98 : id(g_next_pending_id++), 99 web_frame(web_frame), 100 run_location(run_location), 101 page_id(page_id) { 102 } 103 104 ScriptInjection::PendingInjection::~PendingInjection() { 105 } 106 107 // static 108 GURL ScriptInjection::GetDocumentUrlForFrame(blink::WebFrame* frame) { 109 GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame); 110 if (!data_source_url.is_empty() && frame->isViewSourceModeEnabled()) { 111 data_source_url = GURL(content::kViewSourceScheme + std::string(":") + 112 data_source_url.spec()); 113 } 114 115 return data_source_url; 116 } 117 118 ScriptInjection::ScriptInjection( 119 scoped_ptr<UserScript> script, 120 UserScriptSlave* user_script_slave) 121 : script_(script.Pass()), 122 extension_id_(script_->extension_id()), 123 user_script_slave_(user_script_slave), 124 is_standalone_or_emulate_greasemonkey_( 125 script_->is_standalone() || script_->emulate_greasemonkey()) { 126 } 127 128 ScriptInjection::~ScriptInjection() { 129 } 130 131 void ScriptInjection::InjectIfAllowed(blink::WebFrame* frame, 132 UserScript::RunLocation run_location, 133 const GURL& document_url, 134 ScriptsRunInfo* scripts_run_info) { 135 if (!WantsToRun(frame, run_location, document_url)) 136 return; 137 138 const Extension* extension = user_script_slave_->GetExtension(extension_id_); 139 DCHECK(extension); // WantsToRun() should be false if there's no extension. 140 141 // We use the top render view here (instead of the render view for the 142 // frame), because script injection on any frame requires permission for 143 // the top frame. Additionally, if we have to show any UI for permissions, 144 // it should only be done on the top frame. 145 content::RenderView* top_render_view = 146 content::RenderView::FromWebView(frame->top()->view()); 147 148 int tab_id = ExtensionHelper::Get(top_render_view)->tab_id(); 149 150 // By default, we allow injection. 151 bool should_inject = true; 152 153 // Check if the extension requires user consent for injection *and* we have a 154 // valid tab id (if we don't have a tab id, we have no UI surface to ask for 155 // user consent). 156 if (tab_id != -1 && 157 extension->permissions_data()->RequiresActionForScriptExecution( 158 extension, tab_id, frame->top()->document().url())) { 159 int64 request_id = kInvalidRequestId; 160 int page_id = top_render_view->GetPageId(); 161 162 // We only delay the injection if the feature is enabled. 163 // Otherwise, we simply treat this as a notification by passing an invalid 164 // id. 165 if (FeatureSwitch::scripts_require_action()->IsEnabled()) { 166 should_inject = false; 167 ScopedVector<PendingInjection>::iterator pending_injection = 168 pending_injections_.insert( 169 pending_injections_.end(), 170 new PendingInjection(frame, run_location, page_id)); 171 request_id = (*pending_injection)->id; 172 } 173 174 top_render_view->Send( 175 new ExtensionHostMsg_RequestContentScriptPermission( 176 top_render_view->GetRoutingID(), 177 extension->id(), 178 page_id, 179 request_id)); 180 } 181 182 if (should_inject) 183 Inject(frame, run_location, scripts_run_info); 184 } 185 186 bool ScriptInjection::NotifyScriptPermitted( 187 int64 request_id, 188 content::RenderView* render_view, 189 ScriptsRunInfo* scripts_run_info, 190 blink::WebFrame** frame_out) { 191 ScopedVector<PendingInjection>::iterator iter = pending_injections_.begin(); 192 while (iter != pending_injections_.end() && (*iter)->id != request_id) 193 ++iter; 194 195 // No matching request. 196 if (iter == pending_injections_.end()) 197 return false; 198 199 // We found the request, so pull it out of the pending list. 200 scoped_ptr<PendingInjection> pending_injection(*iter); 201 pending_injections_.weak_erase(iter); 202 203 // Ensure the Page ID and Extension are still valid. Otherwise, don't inject. 204 if (render_view->GetPageId() != pending_injection->page_id) 205 return false; 206 207 const Extension* extension = user_script_slave_->GetExtension(extension_id_); 208 if (!extension) 209 return false; 210 211 // Everything matches! Inject the script. 212 if (frame_out) 213 *frame_out = pending_injection->web_frame; 214 Inject(pending_injection->web_frame, 215 pending_injection->run_location, 216 scripts_run_info); 217 return true; 218 } 219 220 void ScriptInjection::FrameDetached(blink::WebFrame* frame) { 221 // Any pending injections associated with the given frame will never run. 222 // Remove them. 223 for (ScopedVector<PendingInjection>::iterator iter = 224 pending_injections_.begin(); 225 iter != pending_injections_.end();) { 226 if ((*iter)->web_frame == frame) 227 iter = pending_injections_.erase(iter); 228 else 229 ++iter; 230 } 231 } 232 233 void ScriptInjection::SetScript(scoped_ptr<UserScript> script) { 234 script_.reset(script.release()); 235 } 236 237 bool ScriptInjection::WantsToRun(blink::WebFrame* frame, 238 UserScript::RunLocation run_location, 239 const GURL& document_url) const { 240 if (frame->parent() && !script_->match_all_frames()) 241 return false; // Only match subframes if the script declared it wanted to. 242 243 const Extension* extension = user_script_slave_->GetExtension(extension_id_); 244 // Since extension info is sent separately from user script info, they can 245 // be out of sync. We just ignore this situation. 246 if (!extension) 247 return false; 248 249 // Content scripts are not tab-specific. 250 static const int kNoTabId = -1; 251 // We don't have a process id in this context. 252 static const int kNoProcessId = -1; 253 254 GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL( 255 frame, document_url, script_->match_about_blank()); 256 257 if (!script_->MatchesURL(effective_document_url)) 258 return false; 259 260 if (!extension->permissions_data()->CanRunContentScriptOnPage( 261 extension, 262 effective_document_url, 263 frame->top()->document().url(), 264 kNoTabId, 265 kNoProcessId, 266 NULL /* ignore error */)) { 267 return false; 268 } 269 270 return ShouldInjectCSS(run_location) || ShouldInjectJS(run_location); 271 } 272 273 void ScriptInjection::Inject(blink::WebFrame* frame, 274 UserScript::RunLocation run_location, 275 ScriptsRunInfo* scripts_run_info) const { 276 DCHECK(frame); 277 DCHECK(scripts_run_info); 278 DCHECK(WantsToRun(frame, run_location, GetDocumentUrlForFrame(frame))); 279 DCHECK(user_script_slave_->GetExtension(extension_id_)); 280 281 if (ShouldInjectCSS(run_location)) 282 InjectCSS(frame, scripts_run_info); 283 if (ShouldInjectJS(run_location)) 284 InjectJS(frame, scripts_run_info); 285 } 286 287 bool ScriptInjection::ShouldInjectJS(UserScript::RunLocation run_location) 288 const { 289 return !script_->js_scripts().empty() && 290 script_->run_location() == run_location; 291 } 292 293 bool ScriptInjection::ShouldInjectCSS(UserScript::RunLocation run_location) 294 const { 295 return !script_->css_scripts().empty() && 296 run_location == UserScript::DOCUMENT_START; 297 } 298 299 void ScriptInjection::InjectJS(blink::WebFrame* frame, 300 ScriptsRunInfo* scripts_run_info) const { 301 const UserScript::FileList& js_scripts = script_->js_scripts(); 302 std::vector<blink::WebScriptSource> sources; 303 scripts_run_info->num_js += js_scripts.size(); 304 for (UserScript::FileList::const_iterator iter = js_scripts.begin(); 305 iter != js_scripts.end(); 306 ++iter) { 307 std::string content = iter->GetContent().as_string(); 308 309 // We add this dumb function wrapper for standalone user script to 310 // emulate what Greasemonkey does. 311 // TODO(aa): I think that maybe "is_standalone" scripts don't exist 312 // anymore. Investigate. 313 if (is_standalone_or_emulate_greasemonkey_) { 314 content.insert(0, kUserScriptHead); 315 content += kUserScriptTail; 316 } 317 sources.push_back(blink::WebScriptSource( 318 blink::WebString::fromUTF8(content), iter->url())); 319 } 320 321 // Emulate Greasemonkey API for scripts that were converted to extensions 322 // and "standalone" user scripts. 323 if (is_standalone_or_emulate_greasemonkey_) 324 sources.insert(sources.begin(), g_greasemonkey_api.Get().source); 325 326 int isolated_world_id = 327 user_script_slave_->GetIsolatedWorldIdForExtension( 328 user_script_slave_->GetExtension(extension_id_), frame); 329 base::ElapsedTimer exec_timer; 330 DOMActivityLogger::AttachToWorld(isolated_world_id, extension_id_); 331 frame->executeScriptInIsolatedWorld(isolated_world_id, 332 &sources.front(), 333 sources.size(), 334 EXTENSION_GROUP_CONTENT_SCRIPTS); 335 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed()); 336 337 for (std::vector<blink::WebScriptSource>::const_iterator iter = 338 sources.begin(); 339 iter != sources.end(); 340 ++iter) { 341 scripts_run_info->executing_scripts[extension_id_].insert( 342 GURL(iter->url).path()); 343 } 344 } 345 346 void ScriptInjection::InjectCSS(blink::WebFrame* frame, 347 ScriptsRunInfo* scripts_run_info) const { 348 const UserScript::FileList& css_scripts = script_->css_scripts(); 349 scripts_run_info->num_css += css_scripts.size(); 350 for (UserScript::FileList::const_iterator iter = css_scripts.begin(); 351 iter != css_scripts.end(); 352 ++iter) { 353 frame->document().insertStyleSheet( 354 blink::WebString::fromUTF8(iter->GetContent().as_string())); 355 } 356 } 357 358 } // namespace extensions 359