1 // Copyright (c) 2012 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 "chrome/renderer/extensions/user_script_scheduler.h" 6 7 #include "base/bind.h" 8 #include "base/logging.h" 9 #include "base/message_loop/message_loop.h" 10 #include "chrome/common/extensions/extension_messages.h" 11 #include "chrome/renderer/chrome_render_process_observer.h" 12 #include "chrome/renderer/extensions/chrome_v8_context.h" 13 #include "chrome/renderer/extensions/dispatcher.h" 14 #include "chrome/renderer/extensions/dom_activity_logger.h" 15 #include "chrome/renderer/extensions/extension_groups.h" 16 #include "chrome/renderer/extensions/extension_helper.h" 17 #include "chrome/renderer/extensions/user_script_slave.h" 18 #include "content/public/renderer/render_view.h" 19 #include "content/public/renderer/v8_value_converter.h" 20 #include "extensions/common/error_utils.h" 21 #include "extensions/common/manifest_constants.h" 22 #include "extensions/common/permissions/permissions_data.h" 23 #include "third_party/WebKit/public/platform/WebString.h" 24 #include "third_party/WebKit/public/platform/WebVector.h" 25 #include "third_party/WebKit/public/web/WebDocument.h" 26 #include "third_party/WebKit/public/web/WebFrame.h" 27 #include "third_party/WebKit/public/web/WebView.h" 28 #include "v8/include/v8.h" 29 30 namespace { 31 // The length of time to wait after the DOM is complete to try and run user 32 // scripts. 33 const int kUserScriptIdleTimeoutMs = 200; 34 } 35 36 using blink::WebDocument; 37 using blink::WebFrame; 38 using blink::WebString; 39 using blink::WebVector; 40 using blink::WebView; 41 42 namespace extensions { 43 44 UserScriptScheduler::UserScriptScheduler(WebFrame* frame, 45 Dispatcher* dispatcher) 46 : weak_factory_(this), 47 frame_(frame), 48 current_location_(UserScript::UNDEFINED), 49 has_run_idle_(false), 50 dispatcher_(dispatcher) { 51 for (int i = UserScript::UNDEFINED; i < UserScript::RUN_LOCATION_LAST; ++i) { 52 pending_execution_map_[static_cast<UserScript::RunLocation>(i)] = 53 std::queue<linked_ptr<ExtensionMsg_ExecuteCode_Params> >(); 54 } 55 } 56 57 UserScriptScheduler::~UserScriptScheduler() { 58 } 59 60 void UserScriptScheduler::ExecuteCode( 61 const ExtensionMsg_ExecuteCode_Params& params) { 62 UserScript::RunLocation run_at = 63 static_cast<UserScript::RunLocation>(params.run_at); 64 if (current_location_ < run_at) { 65 pending_execution_map_[run_at].push( 66 linked_ptr<ExtensionMsg_ExecuteCode_Params>( 67 new ExtensionMsg_ExecuteCode_Params(params))); 68 return; 69 } 70 71 ExecuteCodeImpl(params); 72 } 73 74 void UserScriptScheduler::DidCreateDocumentElement() { 75 current_location_ = UserScript::DOCUMENT_START; 76 MaybeRun(); 77 } 78 79 void UserScriptScheduler::DidFinishDocumentLoad() { 80 current_location_ = UserScript::DOCUMENT_END; 81 MaybeRun(); 82 // Schedule a run for DOCUMENT_IDLE 83 base::MessageLoop::current()->PostDelayedTask( 84 FROM_HERE, 85 base::Bind(&UserScriptScheduler::IdleTimeout, weak_factory_.GetWeakPtr()), 86 base::TimeDelta::FromMilliseconds(kUserScriptIdleTimeoutMs)); 87 } 88 89 void UserScriptScheduler::DidFinishLoad() { 90 current_location_ = UserScript::DOCUMENT_IDLE; 91 // Ensure that running scripts does not keep any progress UI running. 92 base::MessageLoop::current()->PostTask( 93 FROM_HERE, 94 base::Bind(&UserScriptScheduler::MaybeRun, weak_factory_.GetWeakPtr())); 95 } 96 97 void UserScriptScheduler::DidStartProvisionalLoad() { 98 // The frame is navigating, so reset the state since we'll want to inject 99 // scripts once the load finishes. 100 current_location_ = UserScript::UNDEFINED; 101 has_run_idle_ = false; 102 weak_factory_.InvalidateWeakPtrs(); 103 std::map<UserScript::RunLocation, ExecutionQueue>::iterator itr = 104 pending_execution_map_.begin(); 105 for (itr = pending_execution_map_.begin(); 106 itr != pending_execution_map_.end(); ++itr) { 107 while (!itr->second.empty()) 108 itr->second.pop(); 109 } 110 } 111 112 void UserScriptScheduler::IdleTimeout() { 113 current_location_ = UserScript::DOCUMENT_IDLE; 114 MaybeRun(); 115 } 116 117 void UserScriptScheduler::MaybeRun() { 118 if (current_location_ == UserScript::UNDEFINED) 119 return; 120 121 if (!has_run_idle_ && current_location_ == UserScript::DOCUMENT_IDLE) { 122 has_run_idle_ = true; 123 dispatcher_->user_script_slave()->InjectScripts( 124 frame_, UserScript::DOCUMENT_IDLE); 125 } 126 127 // Run all tasks from the current time and earlier. 128 for (int i = UserScript::DOCUMENT_START; 129 i <= current_location_; ++i) { 130 UserScript::RunLocation run_time = static_cast<UserScript::RunLocation>(i); 131 while (!pending_execution_map_[run_time].empty()) { 132 linked_ptr<ExtensionMsg_ExecuteCode_Params>& params = 133 pending_execution_map_[run_time].front(); 134 ExecuteCodeImpl(*params); 135 pending_execution_map_[run_time].pop(); 136 } 137 } 138 } 139 140 void UserScriptScheduler::ExecuteCodeImpl( 141 const ExtensionMsg_ExecuteCode_Params& params) { 142 const Extension* extension = dispatcher_->extensions()->GetByID( 143 params.extension_id); 144 content::RenderView* render_view = 145 content::RenderView::FromWebView(frame_->view()); 146 ExtensionHelper* extension_helper = ExtensionHelper::Get(render_view); 147 base::ListValue execution_results; 148 149 // Since extension info is sent separately from user script info, they can 150 // be out of sync. We just ignore this situation. 151 if (!extension) { 152 render_view->Send( 153 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(), 154 params.request_id, 155 std::string(), // no error 156 -1, 157 GURL(std::string()), 158 execution_results)); 159 return; 160 } 161 162 std::vector<WebFrame*> frame_vector; 163 frame_vector.push_back(frame_); 164 if (params.all_frames) 165 GetAllChildFrames(frame_, &frame_vector); 166 167 std::string error; 168 169 for (std::vector<WebFrame*>::iterator frame_it = frame_vector.begin(); 170 frame_it != frame_vector.end(); ++frame_it) { 171 WebFrame* child_frame = *frame_it; 172 if (params.is_javascript) { 173 // We recheck access here in the renderer for extra safety against races 174 // with navigation. 175 // 176 // But different frames can have different URLs, and the extension might 177 // only have access to a subset of them. For the top frame, we can 178 // immediately send an error and stop because the browser process 179 // considers that an error too. 180 // 181 // For child frames, we just skip ones the extension doesn't have access 182 // to and carry on. 183 if (!params.is_web_view && 184 !PermissionsData::CanExecuteScriptOnPage( 185 extension, 186 child_frame->document().url(), 187 frame_->document().url(), 188 extension_helper->tab_id(), 189 NULL, 190 -1, 191 NULL)) { 192 if (child_frame->parent()) { 193 continue; 194 } else { 195 error = ErrorUtils::FormatErrorMessage( 196 manifest_errors::kCannotAccessPage, 197 child_frame->document().url().spec()); 198 break; 199 } 200 } 201 202 WebScriptSource source(WebString::fromUTF8(params.code), params.file_url); 203 v8::HandleScope scope(v8::Isolate::GetCurrent()); 204 205 scoped_ptr<content::V8ValueConverter> v8_converter( 206 content::V8ValueConverter::create()); 207 v8::Local<v8::Value> script_value; 208 209 if (params.in_main_world) { 210 DOMActivityLogger::AttachToWorld(DOMActivityLogger::kMainWorldId, 211 extension->id()); 212 script_value = child_frame->executeScriptAndReturnValue(source); 213 } else { 214 blink::WebVector<v8::Local<v8::Value> > results; 215 std::vector<WebScriptSource> sources; 216 sources.push_back(source); 217 int isolated_world_id = 218 dispatcher_->user_script_slave()->GetIsolatedWorldIdForExtension( 219 extension, child_frame); 220 DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id()); 221 child_frame->executeScriptInIsolatedWorld( 222 isolated_world_id, &sources.front(), 223 sources.size(), EXTENSION_GROUP_CONTENT_SCRIPTS, &results); 224 // We only expect one value back since we only pushed one source 225 if (results.size() == 1 && !results[0].IsEmpty()) 226 script_value = results[0]; 227 } 228 229 if (params.wants_result && !script_value.IsEmpty()) { 230 // It's safe to always use the main world context when converting here. 231 // V8ValueConverterImpl shouldn't actually care about the context scope, 232 // and it switches to v8::Object's creation context when encountered. 233 v8::Local<v8::Context> context = child_frame->mainWorldScriptContext(); 234 base::Value* result = v8_converter->FromV8Value(script_value, context); 235 // Always append an execution result (i.e. no result == null result) so 236 // that |execution_results| lines up with the frames. 237 execution_results.Append( 238 result ? result : base::Value::CreateNullValue()); 239 } 240 } else { 241 child_frame->document().insertUserStyleSheet( 242 WebString::fromUTF8(params.code), 243 // Author level is consistent with WebView::injectStyleSheet. 244 WebDocument::UserStyleAuthorLevel); 245 } 246 } 247 248 render_view->Send(new ExtensionHostMsg_ExecuteCodeFinished( 249 render_view->GetRoutingID(), 250 params.request_id, 251 error, 252 render_view->GetPageId(), 253 UserScriptSlave::GetDataSourceURLForFrame(frame_), 254 execution_results)); 255 } 256 257 bool UserScriptScheduler::GetAllChildFrames( 258 WebFrame* parent_frame, 259 std::vector<WebFrame*>* frames_vector) const { 260 if (!parent_frame) 261 return false; 262 263 for (WebFrame* child_frame = parent_frame->firstChild(); child_frame; 264 child_frame = child_frame->nextSibling()) { 265 frames_vector->push_back(child_frame); 266 GetAllChildFrames(child_frame, frames_vector); 267 } 268 return true; 269 } 270 271 } // namespace extensions 272