OLD | NEW |
(Empty) | |
| 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/metrics/histogram.h" |
| 10 #include "base/values.h" |
| 11 #include "content/public/renderer/render_view.h" |
| 12 #include "content/public/renderer/render_view_observer.h" |
| 13 #include "extensions/common/extension.h" |
| 14 #include "extensions/common/extension_messages.h" |
| 15 #include "extensions/common/extension_set.h" |
| 16 #include "extensions/common/feature_switch.h" |
| 17 #include "extensions/renderer/extension_helper.h" |
| 18 #include "extensions/renderer/programmatic_script_injection.h" |
| 19 #include "extensions/renderer/script_context.h" |
| 20 #include "ipc/ipc_message_macros.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 const int kInvalidRequestId = -1; |
| 35 |
| 36 // The id of the next pending injection. |
| 37 int64 g_next_pending_id = 0; |
| 38 |
| 39 // Removes any injections with the given |web_frame| from |injections|. |
| 40 void RemoveInjectionsWithFrame(ScopedVector<ScriptInjection>* injections, |
| 41 blink::WebFrame* web_frame) { |
| 42 for (ScopedVector<ScriptInjection>::iterator iter = injections->begin(); |
| 43 iter != injections->end();) { |
| 44 if ((*iter)->web_frame() == web_frame) |
| 45 iter = injections->erase(iter); |
| 46 else |
| 47 ++iter; |
| 48 } |
| 49 } |
| 50 |
| 51 // Removes any injections associated with an extension in |extension_ids|. |
| 52 void RemoveInjectionsWithExtensionId( |
| 53 ScopedVector<ScriptInjection>* injections, |
| 54 const std::set<std::string>& extension_ids) { |
| 55 for (ScopedVector<ScriptInjection>::iterator iter = injections->begin(); |
| 56 iter != injections->end();) { |
| 57 if (extension_ids.count((*iter)->extension_id()) > 0) |
| 58 iter = injections->erase(iter); |
| 59 else |
| 60 ++iter; |
| 61 } |
| 62 } |
| 63 |
| 64 // Log information about a given script run. |
| 65 void LogScriptsRun(blink::WebFrame* frame, |
| 66 UserScript::RunLocation location, |
| 67 const ScriptInjection::ScriptsRunInfo& info) { |
| 68 // Notify the browser if any extensions are now executing scripts. |
| 69 if (!info.executing_scripts.empty()) { |
| 70 content::RenderView* render_view = |
| 71 content::RenderView::FromWebView(frame->view()); |
| 72 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( |
| 73 render_view->GetRoutingID(), |
| 74 info.executing_scripts, |
| 75 render_view->GetPageId(), |
| 76 ScriptContext::GetDataSourceURLForFrame(frame))); |
| 77 } |
| 78 |
| 79 switch (location) { |
| 80 case UserScript::DOCUMENT_START: |
| 81 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", info.num_css); |
| 82 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", |
| 83 info.num_js); |
| 84 if (info.num_css || info.num_js) |
| 85 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", |
| 86 info.timer.Elapsed()); |
| 87 break; |
| 88 case UserScript::DOCUMENT_END: |
| 89 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js); |
| 90 if (info.num_js) |
| 91 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed()); |
| 92 break; |
| 93 case UserScript::DOCUMENT_IDLE: |
| 94 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", |
| 95 info.num_js); |
| 96 if (info.num_js) |
| 97 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed()); |
| 98 break; |
| 99 case UserScript::RUN_DEFERRED: |
| 100 // TODO(rdevlin.cronin): Add histograms. |
| 101 break; |
| 102 case UserScript::UNDEFINED: |
| 103 case UserScript::RUN_LOCATION_LAST: |
| 104 NOTREACHED(); |
| 105 } |
| 106 } |
| 107 |
| 108 } // namespace |
| 109 |
| 110 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver { |
| 111 public: |
| 112 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager); |
| 113 virtual ~RVOHelper(); |
| 114 |
| 115 private: |
| 116 // RenderViewObserver implementation. |
| 117 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; |
| 118 virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE; |
| 119 virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE; |
| 120 virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE; |
| 121 virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE; |
| 122 virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE; |
| 123 virtual void OnDestruct() OVERRIDE; |
| 124 |
| 125 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params); |
| 126 virtual void OnPermitScriptInjection(int64 request_id); |
| 127 |
| 128 // Tells the ScriptInjectionManager to run tasks associated with |
| 129 // document_idle. |
| 130 void RunIdle(blink::WebFrame* frame); |
| 131 |
| 132 ScriptInjectionManager* manager_; |
| 133 base::WeakPtrFactory<RVOHelper> weak_factory_; |
| 134 }; |
| 135 |
| 136 ScriptInjectionManager::RVOHelper::RVOHelper( |
| 137 content::RenderView* render_view, |
| 138 ScriptInjectionManager* manager) |
| 139 : content::RenderViewObserver(render_view), |
| 140 manager_(manager), |
| 141 weak_factory_(this) { |
| 142 } |
| 143 |
| 144 ScriptInjectionManager::RVOHelper::~RVOHelper() { |
| 145 } |
| 146 |
| 147 bool ScriptInjectionManager::RVOHelper::OnMessageReceived( |
| 148 const IPC::Message& message) { |
| 149 bool handled = true; |
| 150 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message) |
| 151 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode) |
| 152 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection, |
| 153 OnPermitScriptInjection) |
| 154 IPC_MESSAGE_UNHANDLED(handled = false) |
| 155 IPC_END_MESSAGE_MAP() |
| 156 return handled; |
| 157 } |
| 158 |
| 159 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement( |
| 160 blink::WebLocalFrame* frame) { |
| 161 manager_->InjectScripts(frame, UserScript::DOCUMENT_START); |
| 162 } |
| 163 |
| 164 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad( |
| 165 blink::WebLocalFrame* frame) { |
| 166 manager_->InjectScripts(frame, UserScript::DOCUMENT_END); |
| 167 base::MessageLoop::current()->PostDelayedTask( |
| 168 FROM_HERE, |
| 169 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, |
| 170 weak_factory_.GetWeakPtr(), |
| 171 frame), |
| 172 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs)); |
| 173 } |
| 174 |
| 175 void ScriptInjectionManager::RVOHelper::DidFinishLoad( |
| 176 blink::WebLocalFrame* frame) { |
| 177 // Ensure that running scripts does not keep any progress UI running. |
| 178 base::MessageLoop::current()->PostTask( |
| 179 FROM_HERE, |
| 180 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, |
| 181 weak_factory_.GetWeakPtr(), |
| 182 frame)); |
| 183 } |
| 184 |
| 185 void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad( |
| 186 blink::WebLocalFrame* frame) { |
| 187 weak_factory_.InvalidateWeakPtrs(); |
| 188 manager_->InvalidateForFrame(frame); |
| 189 } |
| 190 |
| 191 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) { |
| 192 weak_factory_.InvalidateWeakPtrs(); |
| 193 manager_->InvalidateForFrame(frame); |
| 194 } |
| 195 |
| 196 void ScriptInjectionManager::RVOHelper::OnDestruct() { |
| 197 manager_->RemoveObserver(this); |
| 198 } |
| 199 |
| 200 void ScriptInjectionManager::RVOHelper::OnExecuteCode( |
| 201 const ExtensionMsg_ExecuteCode_Params& params) { |
| 202 manager_->HandleExecuteCode(params, render_view()); |
| 203 } |
| 204 |
| 205 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection( |
| 206 int64 request_id) { |
| 207 manager_->HandlePermitScriptInjection(request_id); |
| 208 } |
| 209 |
| 210 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) { |
| 211 manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE); |
| 212 } |
| 213 |
| 214 ScriptInjectionManager::FrameStatus::FrameStatus() |
| 215 : current_location(UserScript::UNDEFINED), has_run_idle(false) { |
| 216 } |
| 217 |
| 218 ScriptInjectionManager::ScriptInjectionManager(const ExtensionSet* extensions) |
| 219 : extensions_(extensions), |
| 220 user_script_injection_list_(new UserScriptInjectionList()), |
| 221 user_script_injection_list_observer_(this) { |
| 222 user_script_injection_list_observer_.Add(user_script_injection_list_.get()); |
| 223 } |
| 224 |
| 225 ScriptInjectionManager::~ScriptInjectionManager() { |
| 226 } |
| 227 |
| 228 void ScriptInjectionManager::OnRenderViewCreated( |
| 229 content::RenderView* render_view) { |
| 230 rvo_helpers_.push_back(new RVOHelper(render_view, this)); |
| 231 } |
| 232 |
| 233 void ScriptInjectionManager::OnUserScriptsUpdated( |
| 234 const std::set<std::string>& changed_extensions, |
| 235 const std::vector<UserScript*>& scripts) { |
| 236 RemoveInjectionsWithExtensionId(&injections_awaiting_permission_, |
| 237 changed_extensions); |
| 238 RemoveInjectionsWithExtensionId(&injections_awaiting_location_, |
| 239 changed_extensions); |
| 240 } |
| 241 |
| 242 void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) { |
| 243 ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin(); |
| 244 for (; iter != rvo_helpers_.end() && *iter != helper; ++iter) |
| 245 ; // intentionally empty. |
| 246 if (iter != rvo_helpers_.end()) |
| 247 rvo_helpers_.erase(iter); |
| 248 } |
| 249 |
| 250 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) { |
| 251 RemoveInjectionsWithFrame(&injections_awaiting_location_, frame); |
| 252 RemoveInjectionsWithFrame(&injections_awaiting_permission_, frame); |
| 253 frame_statuses_.erase(frame); |
| 254 } |
| 255 |
| 256 void ScriptInjectionManager::InjectScripts( |
| 257 blink::WebFrame* frame, UserScript::RunLocation run_location) { |
| 258 FrameStatus& frame_status = frame_statuses_[frame]; |
| 259 frame_status.current_location = run_location; |
| 260 if (run_location == UserScript::DOCUMENT_IDLE) { |
| 261 if (frame_status.has_run_idle) |
| 262 return; |
| 263 frame_status.has_run_idle = true; |
| 264 } |
| 265 |
| 266 ScriptInjection::ScriptsRunInfo scripts_run_info; |
| 267 for (ScopedVector<ScriptInjection>::iterator iter = |
| 268 injections_awaiting_location_.begin(); |
| 269 iter != injections_awaiting_location_.end();) { |
| 270 if ((*iter)->run_location() == run_location) { |
| 271 scoped_ptr<ScriptInjection> injection(*iter); |
| 272 iter = injections_awaiting_location_.weak_erase(iter); |
| 273 |
| 274 // Since extension info is sent separately from user script info, they can |
| 275 // be out of sync. We just ignore this situation. |
| 276 const Extension* extension = |
| 277 extensions_->GetByID(injection->extension_id()); |
| 278 if (!extension) |
| 279 injection->ExtensionNotFound(); |
| 280 else if (injection->Allowed(extension)) |
| 281 injection->Inject(&scripts_run_info, extension); |
| 282 } else { |
| 283 ++iter; |
| 284 } |
| 285 } |
| 286 |
| 287 GURL document_url = UserScriptInjectionList::GetDocumentUrlForFrame(frame); |
| 288 ScopedVector<ScriptInjection> user_script_injections; |
| 289 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView( |
| 290 frame->top()->view()))->tab_id(); |
| 291 user_script_injection_list_->GetInjections(&user_script_injections, |
| 292 frame, |
| 293 tab_id, |
| 294 run_location, |
| 295 document_url, |
| 296 extensions_); |
| 297 for (ScopedVector<ScriptInjection>::iterator iter = |
| 298 user_script_injections.begin(); |
| 299 iter != user_script_injections.end();) { |
| 300 scoped_ptr<ScriptInjection> injection(*iter); |
| 301 iter = user_script_injections.weak_erase(iter); |
| 302 |
| 303 const Extension* extension = |
| 304 extensions_->GetByID(injection->extension_id()); |
| 305 if (!extension) { |
| 306 injection->ExtensionNotFound(); |
| 307 continue; |
| 308 } |
| 309 |
| 310 if (injection->Allowed(extension)) |
| 311 injection->Inject(&scripts_run_info, extension); |
| 312 else |
| 313 RequestPermissionOrInject(injection.Pass(), &scripts_run_info, extension); |
| 314 } |
| 315 |
| 316 LogScriptsRun(frame, run_location, scripts_run_info); |
| 317 } |
| 318 |
| 319 void ScriptInjectionManager::RequestPermissionOrInject( |
| 320 scoped_ptr<ScriptInjection> injection, |
| 321 ScriptInjection::ScriptsRunInfo* scripts_run_info, |
| 322 const Extension* extension) { |
| 323 content::RenderView* render_view = |
| 324 content::RenderView::FromWebView(injection->web_frame()->top()->view()); |
| 325 |
| 326 // By default, we allow injection. |
| 327 bool should_inject = true; |
| 328 int64 request_id = kInvalidRequestId; |
| 329 int page_id = render_view->GetPageId(); |
| 330 |
| 331 if (FeatureSwitch::scripts_require_action()->IsEnabled()) { |
| 332 should_inject = false; |
| 333 request_id = g_next_pending_id++; |
| 334 injection->set_request_id(request_id); |
| 335 injections_awaiting_permission_.push_back(injection.release()); |
| 336 } |
| 337 |
| 338 render_view->Send(new ExtensionHostMsg_RequestScriptInjectionPermission( |
| 339 render_view->GetRoutingID(), extension->id(), page_id, request_id)); |
| 340 |
| 341 // Since delaying script injection is hidden behind a flag, inject the script |
| 342 // immediately if the feature is disabled. |
| 343 if (should_inject) |
| 344 injection->Inject(scripts_run_info, extension); |
| 345 } |
| 346 |
| 347 void ScriptInjectionManager::HandleExecuteCode( |
| 348 const ExtensionMsg_ExecuteCode_Params& params, |
| 349 content::RenderView* render_view) { |
| 350 blink::WebFrame* main_frame = render_view->GetWebView()->mainFrame(); |
| 351 if (!main_frame) { |
| 352 render_view->Send( |
| 353 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(), |
| 354 params.request_id, |
| 355 "No main frame", |
| 356 -1, |
| 357 GURL(std::string()), |
| 358 base::ListValue())); |
| 359 return; |
| 360 } |
| 361 |
| 362 scoped_ptr<ScriptInjection> injection(new ProgrammaticScriptInjection( |
| 363 main_frame, params, ExtensionHelper::Get(render_view)->tab_id())); |
| 364 |
| 365 if (injection->run_location() > |
| 366 frame_statuses_[main_frame].current_location) { |
| 367 injections_awaiting_location_.push_back(injection.release()); |
| 368 return; |
| 369 } |
| 370 |
| 371 const Extension* extension = extensions_->GetByID(injection->extension_id()); |
| 372 ScriptInjection::ScriptsRunInfo scripts_run_info; |
| 373 if (!extension) |
| 374 injection->ExtensionNotFound(); |
| 375 else if (injection->Allowed(extension)) |
| 376 injection->Inject(&scripts_run_info, extension); |
| 377 } |
| 378 |
| 379 void ScriptInjectionManager::HandlePermitScriptInjection(int request_id) { |
| 380 ScopedVector<ScriptInjection>::iterator iter = |
| 381 injections_awaiting_permission_.begin(); |
| 382 for (; iter != injections_awaiting_permission_.end(); ++iter) { |
| 383 if ((*iter)->request_id() == request_id) |
| 384 break; |
| 385 } |
| 386 if (iter == injections_awaiting_permission_.end()) |
| 387 return; |
| 388 |
| 389 scoped_ptr<ScriptInjection> injection(*iter); |
| 390 injections_awaiting_permission_.weak_erase(iter); |
| 391 |
| 392 const Extension* extension = extensions_->GetByID(injection->extension_id()); |
| 393 if (!extension) { |
| 394 injection->ExtensionNotFound(); |
| 395 return; |
| 396 } |
| 397 |
| 398 ScriptInjection::ScriptsRunInfo scripts_run_info; |
| 399 injection->Inject(&scripts_run_info, extension); |
| 400 LogScriptsRun( |
| 401 injection->web_frame(), UserScript::RUN_DEFERRED, scripts_run_info); |
| 402 } |
| 403 |
| 404 } // namespace extensions |
OLD | NEW |