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_manager.h" 6 7 #include "base/bind.h" 8 #include "base/memory/weak_ptr.h" 9 #include "base/values.h" 10 #include "content/public/renderer/render_view.h" 11 #include "content/public/renderer/render_view_observer.h" 12 #include "extensions/common/extension.h" 13 #include "extensions/common/extension_messages.h" 14 #include "extensions/common/extension_set.h" 15 #include "extensions/renderer/extension_helper.h" 16 #include "extensions/renderer/programmatic_script_injector.h" 17 #include "extensions/renderer/script_injection.h" 18 #include "extensions/renderer/scripts_run_info.h" 19 #include "ipc/ipc_message_macros.h" 20 #include "third_party/WebKit/public/web/WebDocument.h" 21 #include "third_party/WebKit/public/web/WebFrame.h" 22 #include "third_party/WebKit/public/web/WebLocalFrame.h" 23 #include "third_party/WebKit/public/web/WebView.h" 24 #include "url/gurl.h" 25 26 namespace extensions { 27 28 namespace { 29 30 // The length of time to wait after the DOM is complete to try and run user 31 // scripts. 32 const int kScriptIdleTimeoutInMs = 200; 33 34 } // namespace 35 36 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver { 37 public: 38 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager); 39 virtual ~RVOHelper(); 40 41 private: 42 // RenderViewObserver implementation. 43 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; 44 virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE; 45 virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE; 46 virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE; 47 virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE; 48 virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE; 49 virtual void OnDestruct() OVERRIDE; 50 51 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params); 52 virtual void OnExecuteDeclarativeScript(int tab_id, 53 const ExtensionId& extension_id, 54 int script_id, 55 const GURL& url); 56 virtual void OnPermitScriptInjection(int64 request_id); 57 58 // Tells the ScriptInjectionManager to run tasks associated with 59 // document_idle. 60 void RunIdle(blink::WebFrame* frame); 61 62 // Indicate that the given |frame| is no longer valid because it is starting 63 // a new load or closing. 64 void InvalidateFrame(blink::WebFrame* frame); 65 66 // The owning ScriptInjectionManager. 67 ScriptInjectionManager* manager_; 68 69 // The set of frames that we are about to notify for DOCUMENT_IDLE. We keep 70 // a set of those that are valid, so we don't notify that an invalid frame 71 // became idle. 72 std::set<blink::WebFrame*> pending_idle_frames_; 73 74 base::WeakPtrFactory<RVOHelper> weak_factory_; 75 }; 76 77 ScriptInjectionManager::RVOHelper::RVOHelper( 78 content::RenderView* render_view, 79 ScriptInjectionManager* manager) 80 : content::RenderViewObserver(render_view), 81 manager_(manager), 82 weak_factory_(this) { 83 } 84 85 ScriptInjectionManager::RVOHelper::~RVOHelper() { 86 } 87 88 bool ScriptInjectionManager::RVOHelper::OnMessageReceived( 89 const IPC::Message& message) { 90 bool handled = true; 91 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message) 92 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode) 93 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection, 94 OnPermitScriptInjection) 95 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript, 96 OnExecuteDeclarativeScript) 97 IPC_MESSAGE_UNHANDLED(handled = false) 98 IPC_END_MESSAGE_MAP() 99 return handled; 100 } 101 102 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement( 103 blink::WebLocalFrame* frame) { 104 manager_->InjectScripts(frame, UserScript::DOCUMENT_START); 105 } 106 107 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad( 108 blink::WebLocalFrame* frame) { 109 manager_->InjectScripts(frame, UserScript::DOCUMENT_END); 110 pending_idle_frames_.insert(frame); 111 // We try to run idle in two places: here and DidFinishLoad. 112 // DidFinishDocumentLoad() corresponds to completing the document's load, 113 // whereas DidFinishLoad corresponds to completing the document and all 114 // subresources' load. We don't want to hold up script injection for a 115 // particularly slow subresource, so we set a delayed task from here - but if 116 // we finish everything before that point (i.e., DidFinishLoad() is 117 // triggered), then there's no reason to keep waiting. 118 base::MessageLoop::current()->PostDelayedTask( 119 FROM_HERE, 120 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, 121 weak_factory_.GetWeakPtr(), 122 frame), 123 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs)); 124 } 125 126 void ScriptInjectionManager::RVOHelper::DidFinishLoad( 127 blink::WebLocalFrame* frame) { 128 // Ensure that we don't block any UI progress by running scripts. 129 // We *don't* add the frame to |pending_idle_frames_| here because 130 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the 131 // first posted task to RunIdle() pops it out of the set. This ensures we 132 // don't try to run idle twice. 133 base::MessageLoop::current()->PostTask( 134 FROM_HERE, 135 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, 136 weak_factory_.GetWeakPtr(), 137 frame)); 138 } 139 140 void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad( 141 blink::WebLocalFrame* frame) { 142 // We're starting a new load - invalidate. 143 InvalidateFrame(frame); 144 } 145 146 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) { 147 // The frame is closing - invalidate. 148 InvalidateFrame(frame); 149 } 150 151 void ScriptInjectionManager::RVOHelper::OnDestruct() { 152 manager_->RemoveObserver(this); 153 } 154 155 void ScriptInjectionManager::RVOHelper::OnExecuteCode( 156 const ExtensionMsg_ExecuteCode_Params& params) { 157 manager_->HandleExecuteCode(params, render_view()); 158 } 159 160 void ScriptInjectionManager::RVOHelper::OnExecuteDeclarativeScript( 161 int tab_id, 162 const ExtensionId& extension_id, 163 int script_id, 164 const GURL& url) { 165 blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); 166 CHECK(main_frame); 167 168 // TODO(markdittmer): This would be cleaner if we compared page_ids instead. 169 // Begin script injeciton workflow only if the current URL is identical to 170 // the one that matched declarative conditions in the browser. 171 if (main_frame->top()->document().url() == url) { 172 manager_->HandleExecuteDeclarativeScript(main_frame, 173 tab_id, 174 extension_id, 175 script_id, 176 url); 177 } 178 } 179 180 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection( 181 int64 request_id) { 182 manager_->HandlePermitScriptInjection(request_id); 183 } 184 185 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) { 186 // Only notify the manager if the frame hasn't either been removed or already 187 // had idle run since the task to RunIdle() was posted. 188 if (pending_idle_frames_.count(frame) > 0) { 189 manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE); 190 pending_idle_frames_.erase(frame); 191 } 192 } 193 194 void ScriptInjectionManager::RVOHelper::InvalidateFrame( 195 blink::WebFrame* frame) { 196 pending_idle_frames_.erase(frame); 197 manager_->InvalidateForFrame(frame); 198 } 199 200 ScriptInjectionManager::ScriptInjectionManager( 201 const ExtensionSet* extensions, 202 UserScriptSetManager* user_script_set_manager) 203 : extensions_(extensions), 204 user_script_set_manager_(user_script_set_manager), 205 user_script_set_manager_observer_(this) { 206 user_script_set_manager_observer_.Add(user_script_set_manager_); 207 } 208 209 ScriptInjectionManager::~ScriptInjectionManager() { 210 } 211 212 void ScriptInjectionManager::OnRenderViewCreated( 213 content::RenderView* render_view) { 214 rvo_helpers_.push_back(new RVOHelper(render_view, this)); 215 } 216 217 void ScriptInjectionManager::OnUserScriptsUpdated( 218 const std::set<std::string>& changed_extensions, 219 const std::vector<UserScript*>& scripts) { 220 for (ScopedVector<ScriptInjection>::iterator iter = 221 pending_injections_.begin(); 222 iter != pending_injections_.end();) { 223 if (changed_extensions.count((*iter)->extension_id()) > 0) 224 iter = pending_injections_.erase(iter); 225 else 226 ++iter; 227 } 228 } 229 230 void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) { 231 for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin(); 232 iter != rvo_helpers_.end(); 233 ++iter) { 234 if (*iter == helper) { 235 rvo_helpers_.erase(iter); 236 break; 237 } 238 } 239 } 240 241 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) { 242 for (ScopedVector<ScriptInjection>::iterator iter = 243 pending_injections_.begin(); 244 iter != pending_injections_.end();) { 245 if ((*iter)->web_frame() == frame) 246 iter = pending_injections_.erase(iter); 247 else 248 ++iter; 249 } 250 251 frame_statuses_.erase(frame); 252 } 253 254 void ScriptInjectionManager::InjectScripts( 255 blink::WebFrame* frame, UserScript::RunLocation run_location) { 256 FrameStatusMap::iterator iter = frame_statuses_.find(frame); 257 // We also don't execute if we detect that the run location is somehow out of 258 // order. This can happen if: 259 // - The first run location reported for the frame isn't DOCUMENT_START, or 260 // - The run location reported doesn't immediately follow the previous 261 // reported run location. 262 // We don't want to run because extensions may have requirements that scripts 263 // running in an earlier run location have run by the time a later script 264 // runs. Better to just not run. 265 if ((iter == frame_statuses_.end() && 266 run_location != UserScript::DOCUMENT_START) || 267 (iter != frame_statuses_.end() && run_location - iter->second > 1)) { 268 // We also invalidate the frame, because the run order of pending injections 269 // may also be bad. 270 InvalidateForFrame(frame); 271 return; 272 } else if (iter != frame_statuses_.end() && iter->second > run_location) { 273 // Certain run location signals (like DidCreateDocumentElement) can happen 274 // multiple times. Ignore the subsequent signals. 275 return; 276 } 277 278 // Otherwise, all is right in the world, and we can get on with the 279 // injections! 280 281 frame_statuses_[frame] = run_location; 282 283 // Inject any scripts that were waiting for the right run location. 284 ScriptsRunInfo scripts_run_info; 285 for (ScopedVector<ScriptInjection>::iterator iter = 286 pending_injections_.begin(); 287 iter != pending_injections_.end();) { 288 if ((*iter)->web_frame() == frame && 289 (*iter)->TryToInject(run_location, 290 extensions_->GetByID((*iter)->extension_id()), 291 &scripts_run_info)) { 292 iter = pending_injections_.erase(iter); 293 } else { 294 ++iter; 295 } 296 } 297 298 // Try to inject any user scripts that should run for this location. If they 299 // don't complete their injection (for example, waiting for a permission 300 // response) then they will be added to |pending_injections_|. 301 ScopedVector<ScriptInjection> user_script_injections; 302 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView( 303 frame->top()->view()))->tab_id(); 304 user_script_set_manager_->GetAllInjections( 305 &user_script_injections, frame, tab_id, run_location); 306 for (ScopedVector<ScriptInjection>::iterator iter = 307 user_script_injections.begin(); 308 iter != user_script_injections.end();) { 309 scoped_ptr<ScriptInjection> injection(*iter); 310 iter = user_script_injections.weak_erase(iter); 311 if (!injection->TryToInject(run_location, 312 extensions_->GetByID(injection->extension_id()), 313 &scripts_run_info)) { 314 pending_injections_.push_back(injection.release()); 315 } 316 } 317 318 scripts_run_info.LogRun(frame, run_location); 319 } 320 321 void ScriptInjectionManager::HandleExecuteCode( 322 const ExtensionMsg_ExecuteCode_Params& params, 323 content::RenderView* render_view) { 324 // TODO(dcheng): Not sure how this can happen today. In an OOPI world, it 325 // would indicate a logic error--the browser must direct this request to the 326 // right renderer process to begin with. 327 blink::WebLocalFrame* main_frame = 328 render_view->GetWebView()->mainFrame()->toWebLocalFrame(); 329 if (!main_frame) { 330 render_view->Send( 331 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(), 332 params.request_id, 333 "No main frame", 334 GURL(std::string()), 335 base::ListValue())); 336 return; 337 } 338 339 scoped_ptr<ScriptInjection> injection(new ScriptInjection( 340 scoped_ptr<ScriptInjector>( 341 new ProgrammaticScriptInjector(params, main_frame)), 342 main_frame, 343 params.extension_id, 344 static_cast<UserScript::RunLocation>(params.run_at), 345 ExtensionHelper::Get(render_view)->tab_id())); 346 347 ScriptsRunInfo scripts_run_info; 348 FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame); 349 if (!injection->TryToInject( 350 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second, 351 extensions_->GetByID(injection->extension_id()), 352 &scripts_run_info)) { 353 pending_injections_.push_back(injection.release()); 354 } 355 } 356 357 void ScriptInjectionManager::HandleExecuteDeclarativeScript( 358 blink::WebFrame* web_frame, 359 int tab_id, 360 const ExtensionId& extension_id, 361 int script_id, 362 const GURL& url) { 363 const Extension* extension = extensions_->GetByID(extension_id); 364 // TODO(dcheng): This function signature should really be a WebLocalFrame, 365 // rather than trying to coerce it here. 366 scoped_ptr<ScriptInjection> injection = 367 user_script_set_manager_->GetInjectionForDeclarativeScript( 368 script_id, 369 web_frame->toWebLocalFrame(), 370 tab_id, 371 url, 372 extension); 373 if (injection.get()) { 374 ScriptsRunInfo scripts_run_info; 375 // TODO(markdittmer): Use return value of TryToInject for error handling. 376 injection->TryToInject(UserScript::BROWSER_DRIVEN, 377 extension, 378 &scripts_run_info); 379 scripts_run_info.LogRun(web_frame, UserScript::BROWSER_DRIVEN); 380 } 381 } 382 383 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) { 384 ScopedVector<ScriptInjection>::iterator iter = 385 pending_injections_.begin(); 386 for (; iter != pending_injections_.end(); ++iter) { 387 if ((*iter)->request_id() == request_id) 388 break; 389 } 390 if (iter == pending_injections_.end()) 391 return; 392 393 // At this point, because the request is present in pending_injections_, we 394 // know that this is the same page that issued the request (otherwise, 395 // RVOHelper's DidStartProvisionalLoad callback would have caused it to be 396 // cleared out). 397 398 scoped_ptr<ScriptInjection> injection(*iter); 399 pending_injections_.weak_erase(iter); 400 401 ScriptsRunInfo scripts_run_info; 402 if (injection->OnPermissionGranted(extensions_->GetByID( 403 injection->extension_id()), 404 &scripts_run_info)) { 405 scripts_run_info.LogRun(injection->web_frame(), UserScript::RUN_DEFERRED); 406 } 407 } 408 409 } // namespace extensions 410