[email protected] | ac2f8937 | 2014-06-23 21:44:25 | [diff] [blame^] | 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/renderer/extension_helper.h" |
| 17 | #include "extensions/renderer/programmatic_script_injection.h" |
| 18 | #include "extensions/renderer/script_context.h" |
| 19 | #include "ipc/ipc_message_macros.h" |
| 20 | #include "third_party/WebKit/public/web/WebFrame.h" |
| 21 | #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| 22 | #include "third_party/WebKit/public/web/WebView.h" |
| 23 | #include "url/gurl.h" |
| 24 | |
| 25 | namespace extensions { |
| 26 | |
| 27 | namespace { |
| 28 | |
| 29 | // The length of time to wait after the DOM is complete to try and run user |
| 30 | // scripts. |
| 31 | const int kScriptIdleTimeoutInMs = 200; |
| 32 | |
| 33 | // Log information about a given script run. |
| 34 | void LogScriptsRun(blink::WebFrame* frame, |
| 35 | UserScript::RunLocation location, |
| 36 | const ScriptInjection::ScriptsRunInfo& info) { |
| 37 | // Notify the browser if any extensions are now executing scripts. |
| 38 | if (!info.executing_scripts.empty()) { |
| 39 | content::RenderView* render_view = |
| 40 | content::RenderView::FromWebView(frame->view()); |
| 41 | render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( |
| 42 | render_view->GetRoutingID(), |
| 43 | info.executing_scripts, |
| 44 | render_view->GetPageId(), |
| 45 | ScriptContext::GetDataSourceURLForFrame(frame))); |
| 46 | } |
| 47 | |
| 48 | switch (location) { |
| 49 | case UserScript::DOCUMENT_START: |
| 50 | UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", info.num_css); |
| 51 | UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", |
| 52 | info.num_js); |
| 53 | if (info.num_css || info.num_js) |
| 54 | UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", |
| 55 | info.timer.Elapsed()); |
| 56 | break; |
| 57 | case UserScript::DOCUMENT_END: |
| 58 | UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js); |
| 59 | if (info.num_js) |
| 60 | UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed()); |
| 61 | break; |
| 62 | case UserScript::DOCUMENT_IDLE: |
| 63 | UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", |
| 64 | info.num_js); |
| 65 | if (info.num_js) |
| 66 | UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed()); |
| 67 | break; |
| 68 | case UserScript::RUN_DEFERRED: |
| 69 | // TODO(rdevlin.cronin): Add histograms. |
| 70 | break; |
| 71 | case UserScript::UNDEFINED: |
| 72 | case UserScript::RUN_LOCATION_LAST: |
| 73 | NOTREACHED(); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | } // namespace |
| 78 | |
| 79 | class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver { |
| 80 | public: |
| 81 | RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager); |
| 82 | virtual ~RVOHelper(); |
| 83 | |
| 84 | private: |
| 85 | // RenderViewObserver implementation. |
| 86 | virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; |
| 87 | virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE; |
| 88 | virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE; |
| 89 | virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE; |
| 90 | virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE; |
| 91 | virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE; |
| 92 | virtual void OnDestruct() OVERRIDE; |
| 93 | |
| 94 | virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params); |
| 95 | virtual void OnPermitScriptInjection(int64 request_id); |
| 96 | |
| 97 | // Tells the ScriptInjectionManager to run tasks associated with |
| 98 | // document_idle. |
| 99 | void RunIdle(blink::WebFrame* frame); |
| 100 | |
| 101 | ScriptInjectionManager* manager_; |
| 102 | base::WeakPtrFactory<RVOHelper> weak_factory_; |
| 103 | }; |
| 104 | |
| 105 | ScriptInjectionManager::RVOHelper::RVOHelper( |
| 106 | content::RenderView* render_view, |
| 107 | ScriptInjectionManager* manager) |
| 108 | : content::RenderViewObserver(render_view), |
| 109 | manager_(manager), |
| 110 | weak_factory_(this) { |
| 111 | } |
| 112 | |
| 113 | ScriptInjectionManager::RVOHelper::~RVOHelper() { |
| 114 | } |
| 115 | |
| 116 | bool ScriptInjectionManager::RVOHelper::OnMessageReceived( |
| 117 | const IPC::Message& message) { |
| 118 | bool handled = true; |
| 119 | IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message) |
| 120 | IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode) |
| 121 | IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection, |
| 122 | OnPermitScriptInjection) |
| 123 | IPC_MESSAGE_UNHANDLED(handled = false) |
| 124 | IPC_END_MESSAGE_MAP() |
| 125 | return handled; |
| 126 | } |
| 127 | |
| 128 | void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement( |
| 129 | blink::WebLocalFrame* frame) { |
| 130 | manager_->InjectScripts(frame, UserScript::DOCUMENT_START); |
| 131 | } |
| 132 | |
| 133 | void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad( |
| 134 | blink::WebLocalFrame* frame) { |
| 135 | manager_->InjectScripts(frame, UserScript::DOCUMENT_END); |
| 136 | base::MessageLoop::current()->PostDelayedTask( |
| 137 | FROM_HERE, |
| 138 | base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, |
| 139 | weak_factory_.GetWeakPtr(), |
| 140 | frame), |
| 141 | base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs)); |
| 142 | } |
| 143 | |
| 144 | void ScriptInjectionManager::RVOHelper::DidFinishLoad( |
| 145 | blink::WebLocalFrame* frame) { |
| 146 | // Ensure that running scripts does not keep any progress UI running. |
| 147 | base::MessageLoop::current()->PostTask( |
| 148 | FROM_HERE, |
| 149 | base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle, |
| 150 | weak_factory_.GetWeakPtr(), |
| 151 | frame)); |
| 152 | } |
| 153 | |
| 154 | void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad( |
| 155 | blink::WebLocalFrame* frame) { |
| 156 | manager_->InvalidateForFrame(frame); |
| 157 | } |
| 158 | |
| 159 | void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) { |
| 160 | manager_->InvalidateForFrame(frame); |
| 161 | } |
| 162 | |
| 163 | void ScriptInjectionManager::RVOHelper::OnDestruct() { |
| 164 | manager_->RemoveObserver(this); |
| 165 | } |
| 166 | |
| 167 | void ScriptInjectionManager::RVOHelper::OnExecuteCode( |
| 168 | const ExtensionMsg_ExecuteCode_Params& params) { |
| 169 | manager_->HandleExecuteCode(params, render_view()); |
| 170 | } |
| 171 | |
| 172 | void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection( |
| 173 | int64 request_id) { |
| 174 | manager_->HandlePermitScriptInjection(request_id); |
| 175 | } |
| 176 | |
| 177 | void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) { |
| 178 | manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE); |
| 179 | } |
| 180 | |
| 181 | ScriptInjectionManager::ScriptInjectionManager( |
| 182 | const ExtensionSet* extensions, |
| 183 | UserScriptSet* user_script_set) |
| 184 | : extensions_(extensions), |
| 185 | user_script_set_(user_script_set), |
| 186 | user_script_set_observer_(this) { |
| 187 | user_script_set_observer_.Add(user_script_set_); |
| 188 | } |
| 189 | |
| 190 | ScriptInjectionManager::~ScriptInjectionManager() { |
| 191 | } |
| 192 | |
| 193 | void ScriptInjectionManager::OnRenderViewCreated( |
| 194 | content::RenderView* render_view) { |
| 195 | rvo_helpers_.push_back(new RVOHelper(render_view, this)); |
| 196 | } |
| 197 | |
| 198 | void ScriptInjectionManager::OnUserScriptsUpdated( |
| 199 | const std::set<std::string>& changed_extensions, |
| 200 | const std::vector<UserScript*>& scripts) { |
| 201 | for (ScopedVector<ScriptInjection>::iterator iter = |
| 202 | pending_injections_.begin(); |
| 203 | iter != pending_injections_.end();) { |
| 204 | if (changed_extensions.count((*iter)->extension_id()) > 0) |
| 205 | iter = pending_injections_.erase(iter); |
| 206 | else |
| 207 | ++iter; |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) { |
| 212 | for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin(); |
| 213 | iter != rvo_helpers_.end(); |
| 214 | ++iter) { |
| 215 | if (*iter == helper) { |
| 216 | rvo_helpers_.erase(iter); |
| 217 | break; |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) { |
| 223 | for (ScopedVector<ScriptInjection>::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 | frame_statuses_.erase(frame); |
| 233 | } |
| 234 | |
| 235 | void ScriptInjectionManager::InjectScripts( |
| 236 | blink::WebFrame* frame, UserScript::RunLocation run_location) { |
| 237 | FrameStatusMap::iterator iter = frame_statuses_.find(frame); |
| 238 | // Check if the frame is already in our map. |
| 239 | if (iter == frame_statuses_.end()) { |
| 240 | // If the frame isn't in our map, and the run location isn't document_start, |
| 241 | // then abort. This can happen in two ways: |
| 242 | // 1. We just received a delayed idle run for a frame which is invalidated. |
| 243 | // Obviously, we don't want to run. |
| 244 | // 2. We somehow received a document_end or document_idle notification |
| 245 | // without ever receiving a document_start. We don't want to run because |
| 246 | // extensions may have requirements that scripts running at start have |
| 247 | // run by the time scripts run at idle. Better to just not run. |
| 248 | if (run_location != UserScript::DOCUMENT_START) |
| 249 | return; |
| 250 | |
| 251 | // Otherwise, add a new entry to the map. |
| 252 | frame_statuses_[frame] = UserScript::DOCUMENT_START; |
| 253 | } else { // Already in the map. |
| 254 | // If we've already run the given location (happens in the case of idle |
| 255 | // since we notify in two places), return. |
| 256 | if (iter->second == run_location) |
| 257 | return; |
| 258 | |
| 259 | // Otherwise, update the frame status. |
| 260 | iter->second = run_location; |
| 261 | } |
| 262 | |
| 263 | // Inject any scripts that were waiting for the right run location. |
| 264 | ScriptInjection::ScriptsRunInfo scripts_run_info; |
| 265 | for (ScopedVector<ScriptInjection>::iterator iter = |
| 266 | pending_injections_.begin(); |
| 267 | iter != pending_injections_.end();) { |
| 268 | if ((*iter)->web_frame() == frame && |
| 269 | (*iter)->TryToInject(run_location, |
| 270 | extensions_->GetByID((*iter)->extension_id()), |
| 271 | &scripts_run_info)) { |
| 272 | iter = pending_injections_.erase(iter); |
| 273 | } else { |
| 274 | ++iter; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | // Try to inject any user scripts that should run for this location. If they |
| 279 | // don't complete their injection (for example, waiting for a permission |
| 280 | // response) then they will be added to |pending_injections_|. |
| 281 | ScopedVector<ScriptInjection> user_script_injections; |
| 282 | int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView( |
| 283 | frame->top()->view()))->tab_id(); |
| 284 | user_script_set_->GetInjections( |
| 285 | &user_script_injections, frame, tab_id, run_location); |
| 286 | for (ScopedVector<ScriptInjection>::iterator iter = |
| 287 | user_script_injections.begin(); |
| 288 | iter != user_script_injections.end();) { |
| 289 | scoped_ptr<ScriptInjection> injection(*iter); |
| 290 | iter = user_script_injections.weak_erase(iter); |
| 291 | if (!injection->TryToInject(run_location, |
| 292 | extensions_->GetByID(injection->extension_id()), |
| 293 | &scripts_run_info)) { |
| 294 | pending_injections_.push_back(injection.release()); |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | LogScriptsRun(frame, run_location, scripts_run_info); |
| 299 | } |
| 300 | |
| 301 | void ScriptInjectionManager::HandleExecuteCode( |
| 302 | const ExtensionMsg_ExecuteCode_Params& params, |
| 303 | content::RenderView* render_view) { |
| 304 | blink::WebFrame* main_frame = render_view->GetWebView()->mainFrame(); |
| 305 | if (!main_frame) { |
| 306 | render_view->Send( |
| 307 | new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(), |
| 308 | params.request_id, |
| 309 | "No main frame", |
| 310 | -1, |
| 311 | GURL(std::string()), |
| 312 | base::ListValue())); |
| 313 | return; |
| 314 | } |
| 315 | |
| 316 | scoped_ptr<ScriptInjection> injection(new ProgrammaticScriptInjection( |
| 317 | main_frame, params, ExtensionHelper::Get(render_view)->tab_id())); |
| 318 | |
| 319 | ScriptInjection::ScriptsRunInfo scripts_run_info; |
| 320 | FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame); |
| 321 | if (!injection->TryToInject( |
| 322 | iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second, |
| 323 | extensions_->GetByID(injection->extension_id()), |
| 324 | &scripts_run_info)) { |
| 325 | pending_injections_.push_back(injection.release()); |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | void ScriptInjectionManager::HandlePermitScriptInjection(int request_id) { |
| 330 | ScopedVector<ScriptInjection>::iterator iter = |
| 331 | pending_injections_.begin(); |
| 332 | for (; iter != pending_injections_.end(); ++iter) { |
| 333 | if ((*iter)->request_id() == request_id) |
| 334 | break; |
| 335 | } |
| 336 | if (iter == pending_injections_.end()) |
| 337 | return; |
| 338 | |
| 339 | scoped_ptr<ScriptInjection> injection(*iter); |
| 340 | pending_injections_.weak_erase(iter); |
| 341 | |
| 342 | ScriptInjection::ScriptsRunInfo scripts_run_info; |
| 343 | if (injection->OnPermissionGranted(extensions_->GetByID( |
| 344 | injection->extension_id()), |
| 345 | &scripts_run_info)) { |
| 346 | LogScriptsRun( |
| 347 | injection->web_frame(), UserScript::RUN_DEFERRED, scripts_run_info); |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | } // namespace extensions |