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 <map> 8 9 #include "base/lazy_instance.h" 10 #include "base/metrics/histogram.h" 11 #include "base/timer/elapsed_timer.h" 12 #include "base/values.h" 13 #include "content/public/renderer/render_view.h" 14 #include "content/public/renderer/v8_value_converter.h" 15 #include "extensions/common/extension.h" 16 #include "extensions/common/extension_messages.h" 17 #include "extensions/common/feature_switch.h" 18 #include "extensions/common/manifest_handlers/csp_info.h" 19 #include "extensions/renderer/dom_activity_logger.h" 20 #include "extensions/renderer/extension_groups.h" 21 #include "extensions/renderer/extensions_renderer_client.h" 22 #include "third_party/WebKit/public/platform/WebString.h" 23 #include "third_party/WebKit/public/web/WebDocument.h" 24 #include "third_party/WebKit/public/web/WebLocalFrame.h" 25 #include "third_party/WebKit/public/web/WebScopedUserGesture.h" 26 #include "third_party/WebKit/public/web/WebScriptSource.h" 27 #include "third_party/WebKit/public/web/WebSecurityOrigin.h" 28 #include "url/gurl.h" 29 30 namespace extensions { 31 32 namespace { 33 34 typedef std::map<std::string, int> IsolatedWorldMap; 35 base::LazyInstance<IsolatedWorldMap> g_isolated_worlds = 36 LAZY_INSTANCE_INITIALIZER; 37 38 const int64 kInvalidRequestId = -1; 39 40 // The id of the next pending injection. 41 int64 g_next_pending_id = 0; 42 43 bool ShouldNotifyBrowserOfInjections() { 44 return !FeatureSwitch::scripts_require_action()->IsEnabled(); 45 } 46 47 // Append all the child frames of |parent_frame| to |frames_vector|. 48 void AppendAllChildFrames(blink::WebFrame* parent_frame, 49 std::vector<blink::WebFrame*>* frames_vector) { 50 DCHECK(parent_frame); 51 for (blink::WebFrame* child_frame = parent_frame->firstChild(); child_frame; 52 child_frame = child_frame->nextSibling()) { 53 frames_vector->push_back(child_frame); 54 AppendAllChildFrames(child_frame, frames_vector); 55 } 56 } 57 58 // Gets the isolated world ID to use for the given |extension| in the given 59 // |frame|. If no isolated world has been created for that extension, 60 // one will be created and initialized. 61 int GetIsolatedWorldIdForExtension(const Extension* extension, 62 blink::WebLocalFrame* frame) { 63 static int g_next_isolated_world_id = 64 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId(); 65 66 IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get(); 67 68 int id = 0; 69 IsolatedWorldMap::iterator iter = isolated_worlds.find(extension->id()); 70 if (iter != isolated_worlds.end()) { 71 id = iter->second; 72 } else { 73 id = g_next_isolated_world_id++; 74 // This map will tend to pile up over time, but realistically, you're never 75 // going to have enough extensions for it to matter. 76 isolated_worlds[extension->id()] = id; 77 } 78 79 // We need to set the isolated world origin and CSP even if it's not a new 80 // world since these are stored per frame, and we might not have used this 81 // isolated world in this frame before. 82 frame->setIsolatedWorldSecurityOrigin( 83 id, blink::WebSecurityOrigin::create(extension->url())); 84 frame->setIsolatedWorldContentSecurityPolicy( 85 id, 86 blink::WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension))); 87 frame->setIsolatedWorldHumanReadableName( 88 id, 89 blink::WebString::fromUTF8(extension->name())); 90 91 return id; 92 } 93 94 } // namespace 95 96 // static 97 std::string ScriptInjection::GetExtensionIdForIsolatedWorld( 98 int isolated_world_id) { 99 IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get(); 100 101 for (IsolatedWorldMap::iterator iter = isolated_worlds.begin(); 102 iter != isolated_worlds.end(); 103 ++iter) { 104 if (iter->second == isolated_world_id) 105 return iter->first; 106 } 107 return std::string(); 108 } 109 110 // static 111 void ScriptInjection::RemoveIsolatedWorld(const std::string& extension_id) { 112 g_isolated_worlds.Get().erase(extension_id); 113 } 114 115 ScriptInjection::ScriptInjection( 116 scoped_ptr<ScriptInjector> injector, 117 blink::WebLocalFrame* web_frame, 118 const std::string& extension_id, 119 UserScript::RunLocation run_location, 120 int tab_id) 121 : injector_(injector.Pass()), 122 web_frame_(web_frame), 123 extension_id_(extension_id), 124 run_location_(run_location), 125 tab_id_(tab_id), 126 request_id_(kInvalidRequestId), 127 complete_(false) { 128 } 129 130 ScriptInjection::~ScriptInjection() { 131 if (!complete_) 132 injector_->OnWillNotInject(ScriptInjector::WONT_INJECT); 133 } 134 135 bool ScriptInjection::TryToInject(UserScript::RunLocation current_location, 136 const Extension* extension, 137 ScriptsRunInfo* scripts_run_info) { 138 if (current_location < run_location_) 139 return false; // Wait for the right location. 140 141 if (request_id_ != kInvalidRequestId) 142 return false; // We're waiting for permission right now, try again later. 143 144 if (!extension) { 145 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED); 146 return true; // We're done. 147 } 148 149 switch (injector_->CanExecuteOnFrame( 150 extension, web_frame_, tab_id_, web_frame_->top()->document().url())) { 151 case PermissionsData::ACCESS_DENIED: 152 NotifyWillNotInject(ScriptInjector::NOT_ALLOWED); 153 return true; // We're done. 154 case PermissionsData::ACCESS_WITHHELD: 155 RequestPermission(); 156 return false; // Wait around for permission. 157 case PermissionsData::ACCESS_ALLOWED: 158 Inject(extension, scripts_run_info); 159 return true; // We're done! 160 } 161 162 // Some compilers don't realize that we always return from the switch() above. 163 // Make them happy. 164 return false; 165 } 166 167 bool ScriptInjection::OnPermissionGranted(const Extension* extension, 168 ScriptsRunInfo* scripts_run_info) { 169 if (!extension) { 170 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED); 171 return false; 172 } 173 174 Inject(extension, scripts_run_info); 175 return true; 176 } 177 178 void ScriptInjection::RequestPermission() { 179 content::RenderView* render_view = 180 content::RenderView::FromWebView(web_frame()->top()->view()); 181 182 // If we are just notifying the browser of the injection, then send an 183 // invalid request (which is treated like a notification). 184 request_id_ = ShouldNotifyBrowserOfInjections() ? kInvalidRequestId 185 : g_next_pending_id++; 186 render_view->Send(new ExtensionHostMsg_RequestScriptInjectionPermission( 187 render_view->GetRoutingID(), 188 extension_id_, 189 injector_->script_type(), 190 request_id_)); 191 } 192 193 void ScriptInjection::NotifyWillNotInject( 194 ScriptInjector::InjectFailureReason reason) { 195 complete_ = true; 196 injector_->OnWillNotInject(reason); 197 } 198 199 void ScriptInjection::Inject(const Extension* extension, 200 ScriptsRunInfo* scripts_run_info) { 201 DCHECK(extension); 202 DCHECK(scripts_run_info); 203 DCHECK(!complete_); 204 205 if (ShouldNotifyBrowserOfInjections()) 206 RequestPermission(); 207 208 std::vector<blink::WebFrame*> frame_vector; 209 frame_vector.push_back(web_frame_); 210 if (injector_->ShouldExecuteInChildFrames()) 211 AppendAllChildFrames(web_frame_, &frame_vector); 212 213 scoped_ptr<blink::WebScopedUserGesture> gesture; 214 if (injector_->IsUserGesture()) 215 gesture.reset(new blink::WebScopedUserGesture()); 216 217 bool inject_js = injector_->ShouldInjectJs(run_location_); 218 bool inject_css = injector_->ShouldInjectCss(run_location_); 219 DCHECK(inject_js || inject_css); 220 221 scoped_ptr<base::ListValue> execution_results(new base::ListValue()); 222 GURL top_url = web_frame_->top()->document().url(); 223 for (std::vector<blink::WebFrame*>::iterator iter = frame_vector.begin(); 224 iter != frame_vector.end(); 225 ++iter) { 226 // TODO(dcheng): Unfortunately, the code as written won't work in an OOPI 227 // world. This is just a temporary hack to make things compile. 228 blink::WebLocalFrame* frame = (*iter)->toWebLocalFrame(); 229 230 // We recheck access here in the renderer for extra safety against races 231 // with navigation, but different frames can have different URLs, and the 232 // extension might only have access to a subset of them. 233 // For child frames, we just skip ones the extension doesn't have access 234 // to and carry on. 235 // Note: we don't consider ACCESS_WITHHELD because there is nowhere to 236 // surface a request for a child frame. 237 // TODO(rdevlin.cronin): We should ask for permission somehow. 238 if (injector_->CanExecuteOnFrame(extension, frame, tab_id_, top_url) == 239 PermissionsData::ACCESS_DENIED) { 240 DCHECK(frame->parent()); 241 continue; 242 } 243 if (inject_js) 244 InjectJs(extension, frame, execution_results.get()); 245 if (inject_css) 246 InjectCss(frame); 247 } 248 249 complete_ = true; 250 injector_->OnInjectionComplete(execution_results.Pass(), 251 scripts_run_info, 252 run_location_); 253 } 254 255 void ScriptInjection::InjectJs(const Extension* extension, 256 blink::WebLocalFrame* frame, 257 base::ListValue* execution_results) { 258 std::vector<blink::WebScriptSource> sources = 259 injector_->GetJsSources(run_location_); 260 bool in_main_world = injector_->ShouldExecuteInMainWorld(); 261 int world_id = in_main_world 262 ? DOMActivityLogger::kMainWorldId 263 : GetIsolatedWorldIdForExtension(extension, frame); 264 bool expects_results = injector_->ExpectsResults(); 265 266 base::ElapsedTimer exec_timer; 267 DOMActivityLogger::AttachToWorld(world_id, extension->id()); 268 v8::HandleScope scope(v8::Isolate::GetCurrent()); 269 v8::Local<v8::Value> script_value; 270 if (in_main_world) { 271 // We only inject in the main world for javascript: urls. 272 DCHECK_EQ(1u, sources.size()); 273 274 const blink::WebScriptSource& source = sources.front(); 275 if (expects_results) 276 script_value = frame->executeScriptAndReturnValue(source); 277 else 278 frame->executeScript(source); 279 } else { // in isolated world 280 scoped_ptr<blink::WebVector<v8::Local<v8::Value> > > results; 281 if (expects_results) 282 results.reset(new blink::WebVector<v8::Local<v8::Value> >()); 283 frame->executeScriptInIsolatedWorld(world_id, 284 &sources.front(), 285 sources.size(), 286 EXTENSION_GROUP_CONTENT_SCRIPTS, 287 results.get()); 288 if (expects_results && !results->isEmpty()) 289 script_value = (*results)[0]; 290 } 291 292 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed()); 293 294 if (expects_results) { 295 // Right now, we only support returning single results (per frame). 296 scoped_ptr<content::V8ValueConverter> v8_converter( 297 content::V8ValueConverter::create()); 298 // It's safe to always use the main world context when converting 299 // here. V8ValueConverterImpl shouldn't actually care about the 300 // context scope, and it switches to v8::Object's creation context 301 // when encountered. 302 v8::Local<v8::Context> context = frame->mainWorldScriptContext(); 303 scoped_ptr<base::Value> result( 304 v8_converter->FromV8Value(script_value, context)); 305 // Always append an execution result (i.e. no result == null result) 306 // so that |execution_results| lines up with the frames. 307 execution_results->Append(result.get() ? result.release() 308 : base::Value::CreateNullValue()); 309 } 310 } 311 312 void ScriptInjection::InjectCss(blink::WebLocalFrame* frame) { 313 std::vector<std::string> css_sources = 314 injector_->GetCssSources(run_location_); 315 for (std::vector<std::string>::const_iterator iter = css_sources.begin(); 316 iter != css_sources.end(); 317 ++iter) { 318 frame->document().insertStyleSheet(blink::WebString::fromUTF8(*iter)); 319 } 320 } 321 322 } // namespace extensions 323