Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(308)

Side by Side Diff: extensions/renderer/user_script_slave.cc

Issue 284153006: Introduce ScriptInjection in extensions/renderer to inject UserScripts (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « extensions/renderer/user_script_slave.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "extensions/renderer/user_script_slave.h" 5 #include "extensions/renderer/user_script_slave.h"
6 6
7 #include <map> 7 #include <map>
8 8
9 #include "base/command_line.h"
10 #include "base/logging.h" 9 #include "base/logging.h"
11 #include "base/memory/shared_memory.h" 10 #include "base/memory/shared_memory.h"
12 #include "base/metrics/histogram.h" 11 #include "base/metrics/histogram.h"
13 #include "base/pickle.h" 12 #include "base/pickle.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/timer/elapsed_timer.h" 13 #include "base/timer/elapsed_timer.h"
16 #include "content/public/common/url_constants.h"
17 #include "content/public/renderer/render_thread.h" 14 #include "content/public/renderer/render_thread.h"
18 #include "content/public/renderer/render_view.h" 15 #include "content/public/renderer/render_view.h"
19 #include "extensions/common/extension.h" 16 #include "extensions/common/extension.h"
20 #include "extensions/common/extension_messages.h" 17 #include "extensions/common/extension_messages.h"
21 #include "extensions/common/extension_set.h" 18 #include "extensions/common/extension_set.h"
22 #include "extensions/common/manifest_handlers/csp_info.h" 19 #include "extensions/common/manifest_handlers/csp_info.h"
23 #include "extensions/common/permissions/permissions_data.h" 20 #include "extensions/common/permissions/permissions_data.h"
24 #include "extensions/renderer/dom_activity_logger.h"
25 #include "extensions/renderer/extension_groups.h"
26 #include "extensions/renderer/extensions_renderer_client.h" 21 #include "extensions/renderer/extensions_renderer_client.h"
27 #include "extensions/renderer/script_context.h" 22 #include "extensions/renderer/script_context.h"
28 #include "grit/renderer_resources.h"
29 #include "third_party/WebKit/public/platform/WebURLRequest.h"
30 #include "third_party/WebKit/public/platform/WebVector.h"
31 #include "third_party/WebKit/public/web/WebDocument.h"
32 #include "third_party/WebKit/public/web/WebFrame.h" 23 #include "third_party/WebKit/public/web/WebFrame.h"
33 #include "third_party/WebKit/public/web/WebSecurityOrigin.h" 24 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
34 #include "third_party/WebKit/public/web/WebSecurityPolicy.h" 25 #include "third_party/WebKit/public/web/WebSecurityPolicy.h"
35 #include "third_party/WebKit/public/web/WebView.h" 26 #include "third_party/WebKit/public/web/WebView.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "url/gurl.h" 27 #include "url/gurl.h"
38 28
39 using blink::WebFrame; 29 using blink::WebFrame;
40 using blink::WebSecurityOrigin; 30 using blink::WebSecurityOrigin;
41 using blink::WebSecurityPolicy; 31 using blink::WebSecurityPolicy;
42 using blink::WebString; 32 using blink::WebString;
43 using blink::WebVector;
44 using blink::WebView; 33 using blink::WebView;
45 using content::RenderThread; 34 using content::RenderThread;
46 35
47 namespace extensions { 36 namespace extensions {
48 37
49 // These two strings are injected before and after the Greasemonkey API and
50 // user script to wrap it in an anonymous scope.
51 static const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
52 static const char kUserScriptTail[] = "\n})(window);";
53
54 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension, 38 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension,
55 WebFrame* frame) { 39 WebFrame* frame) {
56 static int g_next_isolated_world_id = 40 static int g_next_isolated_world_id =
57 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId(); 41 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
58 42
43 int id = 0;
59 IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id()); 44 IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id());
60 if (iter != isolated_world_ids_.end()) { 45 if (iter != isolated_world_ids_.end()) {
61 // We need to set the isolated world origin and CSP even if it's not a new 46 id = iter->second;
62 // world since these are stored per frame, and we might not have used this 47 } else {
63 // isolated world in this frame before. 48 id = g_next_isolated_world_id++;
64 frame->setIsolatedWorldSecurityOrigin( 49 // This map will tend to pile up over time, but realistically, you're never
65 iter->second, WebSecurityOrigin::create(extension->url())); 50 // going to have enough extensions for it to matter.
66 frame->setIsolatedWorldContentSecurityPolicy( 51 isolated_world_ids_[extension->id()] = id;
67 iter->second,
68 WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
69 return iter->second;
70 } 52 }
71 53
72 int new_id = g_next_isolated_world_id; 54 // We need to set the isolated world origin and CSP even if it's not a new
73 ++g_next_isolated_world_id; 55 // world since these are stored per frame, and we might not have used this
56 // isolated world in this frame before.
57 frame->setIsolatedWorldSecurityOrigin(
58 id, WebSecurityOrigin::create(extension->url()));
59 frame->setIsolatedWorldContentSecurityPolicy(
60 id, WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
74 61
75 // This map will tend to pile up over time, but realistically, you're never 62 return id;
76 // going to have enough extensions for it to matter.
77 isolated_world_ids_[extension->id()] = new_id;
78 frame->setIsolatedWorldSecurityOrigin(
79 new_id, WebSecurityOrigin::create(extension->url()));
80 frame->setIsolatedWorldContentSecurityPolicy(
81 new_id,
82 WebString::fromUTF8(CSPInfo::GetContentSecurityPolicy(extension)));
83 return new_id;
84 } 63 }
85 64
86 std::string UserScriptSlave::GetExtensionIdForIsolatedWorld( 65 std::string UserScriptSlave::GetExtensionIdForIsolatedWorld(
87 int isolated_world_id) { 66 int isolated_world_id) {
88 for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin(); 67 for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin();
89 iter != isolated_world_ids_.end(); 68 iter != isolated_world_ids_.end();
90 ++iter) { 69 ++iter) {
91 if (iter->second == isolated_world_id) 70 if (iter->second == isolated_world_id)
92 return iter->first; 71 return iter->first;
93 } 72 }
94 return std::string(); 73 return std::string();
95 } 74 }
96 75
97 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) { 76 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
98 isolated_world_ids_.erase(extension_id); 77 isolated_world_ids_.erase(extension_id);
99 } 78 }
100 79
101 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions) 80 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
102 : script_deleter_(&scripts_), extensions_(extensions) { 81 : extensions_(extensions) {
103 api_js_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
104 IDR_GREASEMONKEY_API_JS);
105 } 82 }
106 83
107 UserScriptSlave::~UserScriptSlave() { 84 UserScriptSlave::~UserScriptSlave() {
108 } 85 }
109 86
110 void UserScriptSlave::GetActiveExtensions( 87 void UserScriptSlave::GetActiveExtensions(
111 std::set<std::string>* extension_ids) { 88 std::set<std::string>* extension_ids) {
112 for (size_t i = 0; i < scripts_.size(); ++i) { 89 DCHECK(extension_ids);
113 DCHECK(!scripts_[i]->extension_id().empty()); 90 for (ScopedVector<ScriptInjection>::const_iterator iter =
114 extension_ids->insert(scripts_[i]->extension_id()); 91 script_injections_.begin();
92 iter != script_injections_.end();
93 ++iter) {
94 DCHECK(!(*iter)->extension_id().empty());
95 extension_ids->insert((*iter)->extension_id());
115 } 96 }
116 } 97 }
117 98
99 const Extension* UserScriptSlave::GetExtension(
100 const std::string& extension_id) {
101 return extensions_->GetByID(extension_id);
102 }
103
118 bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { 104 bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) {
119 scripts_.clear(); 105 script_injections_.clear();
120 106
121 bool only_inject_incognito = 107 bool only_inject_incognito =
122 ExtensionsRendererClient::Get()->IsIncognitoProcess(); 108 ExtensionsRendererClient::Get()->IsIncognitoProcess();
123 109
124 // Create the shared memory object (read only). 110 // Create the shared memory object (read only).
125 shared_memory_.reset(new base::SharedMemory(shared_memory, true)); 111 shared_memory_.reset(new base::SharedMemory(shared_memory, true));
126 if (!shared_memory_.get()) 112 if (!shared_memory_.get())
127 return false; 113 return false;
128 114
129 // First get the size of the memory block. 115 // First get the size of the memory block.
130 if (!shared_memory_->Map(sizeof(Pickle::Header))) 116 if (!shared_memory_->Map(sizeof(Pickle::Header)))
131 return false; 117 return false;
132 Pickle::Header* pickle_header = 118 Pickle::Header* pickle_header =
133 reinterpret_cast<Pickle::Header*>(shared_memory_->memory()); 119 reinterpret_cast<Pickle::Header*>(shared_memory_->memory());
134 120
135 // Now map in the rest of the block. 121 // Now map in the rest of the block.
136 int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size; 122 int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size;
137 shared_memory_->Unmap(); 123 shared_memory_->Unmap();
138 if (!shared_memory_->Map(pickle_size)) 124 if (!shared_memory_->Map(pickle_size))
139 return false; 125 return false;
140 126
141 // Unpickle scripts. 127 // Unpickle scripts.
142 uint64 num_scripts = 0; 128 uint64 num_scripts = 0;
143 Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size); 129 Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size);
144 PickleIterator iter(pickle); 130 PickleIterator iter(pickle);
145 CHECK(pickle.ReadUInt64(&iter, &num_scripts)); 131 CHECK(pickle.ReadUInt64(&iter, &num_scripts));
146 132
147 scripts_.reserve(num_scripts); 133 script_injections_.reserve(num_scripts);
148 for (uint64 i = 0; i < num_scripts; ++i) { 134 for (uint64 i = 0; i < num_scripts; ++i) {
149 scripts_.push_back(new UserScript()); 135 scoped_ptr<UserScript> script(new UserScript());
150 UserScript* script = scripts_.back();
151 script->Unpickle(pickle, &iter); 136 script->Unpickle(pickle, &iter);
152 137
153 // Note that this is a pointer into shared memory. We don't own it. It gets 138 // Note that this is a pointer into shared memory. We don't own it. It gets
154 // cleared up when the last renderer or browser process drops their 139 // cleared up when the last renderer or browser process drops their
155 // reference to the shared memory. 140 // reference to the shared memory.
156 for (size_t j = 0; j < script->js_scripts().size(); ++j) { 141 for (size_t j = 0; j < script->js_scripts().size(); ++j) {
157 const char* body = NULL; 142 const char* body = NULL;
158 int body_length = 0; 143 int body_length = 0;
159 CHECK(pickle.ReadData(&iter, &body, &body_length)); 144 CHECK(pickle.ReadData(&iter, &body, &body_length));
160 script->js_scripts()[j].set_external_content( 145 script->js_scripts()[j].set_external_content(
161 base::StringPiece(body, body_length)); 146 base::StringPiece(body, body_length));
162 } 147 }
163 for (size_t j = 0; j < script->css_scripts().size(); ++j) { 148 for (size_t j = 0; j < script->css_scripts().size(); ++j) {
164 const char* body = NULL; 149 const char* body = NULL;
165 int body_length = 0; 150 int body_length = 0;
166 CHECK(pickle.ReadData(&iter, &body, &body_length)); 151 CHECK(pickle.ReadData(&iter, &body, &body_length));
167 script->css_scripts()[j].set_external_content( 152 script->css_scripts()[j].set_external_content(
168 base::StringPiece(body, body_length)); 153 base::StringPiece(body, body_length));
169 } 154 }
170 155
171 if (only_inject_incognito && !script->is_incognito_enabled()) { 156 if (only_inject_incognito && !script->is_incognito_enabled())
172 // This script shouldn't run in an incognito tab. 157 continue; // This script shouldn't run in an incognito tab.
173 delete script; 158
174 scripts_.pop_back(); 159 script_injections_.push_back(new ScriptInjection(script.Pass(), this));
175 }
176 } 160 }
177 161
178 return true; 162 return true;
179 } 163 }
180 164
181 void UserScriptSlave::InjectScripts(WebFrame* frame, 165 void UserScriptSlave::InjectScripts(WebFrame* frame,
182 UserScript::RunLocation location) { 166 UserScript::RunLocation location) {
183 GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame); 167 GURL document_url = ScriptInjection::GetDocumentUrlForFrame(frame);
184 if (data_source_url.is_empty()) 168 if (document_url.is_empty())
185 return; 169 return;
186 170
187 if (frame->isViewSourceModeEnabled()) 171 content::RenderView* top_render_view =
188 data_source_url = GURL(content::kViewSourceScheme + std::string(":") + 172 content::RenderView::FromWebView(frame->top()->view());
189 data_source_url.spec());
190 173
191 base::ElapsedTimer timer; 174 ScriptInjection::ScriptsRunInfo scripts_run_info;
192 int num_css = 0; 175 for (ScopedVector<ScriptInjection>::const_iterator iter =
193 int num_scripts = 0; 176 script_injections_.begin();
194 177 iter != script_injections_.end();
195 ExecutingScriptsMap extensions_executing_scripts; 178 ++iter) {
196 179 ScriptInjection* injection = *iter;
197 blink::WebFrame* top_frame = frame->top(); 180 if (!injection->WantsToRun(frame, location, document_url))
198 content::RenderView* top_render_view =
199 content::RenderView::FromWebView(top_frame->view());
200
201 for (size_t i = 0; i < scripts_.size(); ++i) {
202 std::vector<WebScriptSource> sources;
203 UserScript* script = scripts_[i];
204
205 if (frame->parent() && !script->match_all_frames())
206 continue; // Only match subframes if the script declared it wanted to.
207
208 const Extension* extension = extensions_->GetByID(script->extension_id());
209
210 // Since extension info is sent separately from user script info, they can
211 // be out of sync. We just ignore this situation.
212 if (!extension)
213 continue; 181 continue;
214 182
215 const GURL& document_url = ScriptContext::GetEffectiveDocumentURL( 183 const Extension* extension = GetExtension(injection->extension_id());
216 frame, data_source_url, script->match_about_blank()); 184 DCHECK(extension);
217 185
218 // Content scripts are not tab-specific. 186 if (PermissionsData::RequiresActionForScriptExecution(extension)) {
219 const int kNoTabId = -1; 187 // TODO(rdevlin.cronin): Right now, this is just a notification, but soon
220 // We don't have a process id in this context. 188 // we should block without user consent.
221 const int kNoProcessId = -1; 189 top_render_view->Send(
222 if (!PermissionsData::CanExecuteScriptOnPage(extension, 190 new ExtensionHostMsg_NotifyExtensionScriptExecution(
223 document_url, 191 top_render_view->GetRoutingID(),
224 top_frame->document().url(), 192 extension->id(),
225 kNoTabId, 193 top_render_view->GetPageId()));
226 script,
227 kNoProcessId,
228 NULL)) {
229 continue;
230 } 194 }
231 195
232 if (location == UserScript::DOCUMENT_START) { 196 injection->Inject(frame, location, &scripts_run_info);
233 num_css += script->css_scripts().size();
234 for (UserScript::FileList::const_iterator iter =
235 script->css_scripts().begin();
236 iter != script->css_scripts().end();
237 ++iter) {
238 frame->document().insertStyleSheet(
239 WebString::fromUTF8(iter->GetContent().as_string()));
240 }
241 }
242
243 if (script->run_location() == location) {
244 // TODO(rdevlin.cronin): Right now, this is just a notification, but soon
245 // we should block without user consent.
246 if (PermissionsData::RequiresActionForScriptExecution(extension)) {
247 top_render_view->Send(
248 new ExtensionHostMsg_NotifyExtensionScriptExecution(
249 top_render_view->GetRoutingID(),
250 extension->id(),
251 top_render_view->GetPageId()));
252 }
253 num_scripts += script->js_scripts().size();
254 for (size_t j = 0; j < script->js_scripts().size(); ++j) {
255 UserScript::File& file = script->js_scripts()[j];
256 std::string content = file.GetContent().as_string();
257
258 // We add this dumb function wrapper for standalone user script to
259 // emulate what Greasemonkey does.
260 // TODO(aa): I think that maybe "is_standalone" scripts don't exist
261 // anymore. Investigate.
262 if (script->is_standalone() || script->emulate_greasemonkey()) {
263 content.insert(0, kUserScriptHead);
264 content += kUserScriptTail;
265 }
266 sources.push_back(
267 WebScriptSource(WebString::fromUTF8(content), file.url()));
268 }
269 }
270
271 if (!sources.empty()) {
272 // Emulate Greasemonkey API for scripts that were converted to extensions
273 // and "standalone" user scripts.
274 if (script->is_standalone() || script->emulate_greasemonkey()) {
275 sources.insert(
276 sources.begin(),
277 WebScriptSource(WebString::fromUTF8(api_js_.as_string())));
278 }
279
280 int isolated_world_id = GetIsolatedWorldIdForExtension(extension, frame);
281
282 base::ElapsedTimer exec_timer;
283 DOMActivityLogger::AttachToWorld(isolated_world_id, extension->id());
284 frame->executeScriptInIsolatedWorld(isolated_world_id,
285 &sources.front(),
286 sources.size(),
287 EXTENSION_GROUP_CONTENT_SCRIPTS);
288 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
289
290 for (std::vector<WebScriptSource>::const_iterator iter = sources.begin();
291 iter != sources.end();
292 ++iter) {
293 extensions_executing_scripts[extension->id()].insert(
294 GURL(iter->url).path());
295 }
296 }
297 } 197 }
298 198
199 LogScriptsRun(frame, location, scripts_run_info);
200 }
201
202 void UserScriptSlave::LogScriptsRun(
203 blink::WebFrame* frame,
204 UserScript::RunLocation location,
205 const ScriptInjection::ScriptsRunInfo& info) {
299 // Notify the browser if any extensions are now executing scripts. 206 // Notify the browser if any extensions are now executing scripts.
300 if (!extensions_executing_scripts.empty()) { 207 if (!info.executing_scripts.empty()) {
301 top_render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( 208 content::RenderView* render_view =
302 top_render_view->GetRoutingID(), 209 content::RenderView::FromWebView(frame->view());
303 extensions_executing_scripts, 210 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
304 top_render_view->GetPageId(), 211 render_view->GetRoutingID(),
305 ScriptContext::GetDataSourceURLForFrame(top_frame))); 212 info.executing_scripts,
213 render_view->GetPageId(),
214 ScriptContext::GetDataSourceURLForFrame(frame)));
306 } 215 }
307 216
308 // Log debug info.
309 if (location == UserScript::DOCUMENT_START) { 217 if (location == UserScript::DOCUMENT_START) {
310 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css); 218 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount",
311 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_scripts); 219 info.num_css);
312 if (num_css || num_scripts) 220 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", info.num_js);
313 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed()); 221 if (info.num_css || info.num_js)
222 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", info.timer.Elapsed());
314 } else if (location == UserScript::DOCUMENT_END) { 223 } else if (location == UserScript::DOCUMENT_END) {
315 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_scripts); 224 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js);
316 if (num_scripts) 225 if (info.num_js)
317 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed()); 226 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed());
318 } else if (location == UserScript::DOCUMENT_IDLE) { 227 } else if (location == UserScript::DOCUMENT_IDLE) {
319 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_scripts); 228 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", info.num_js);
320 if (num_scripts) 229 if (info.num_js)
321 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed()); 230 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed());
322 } else { 231 } else {
323 NOTREACHED(); 232 NOTREACHED();
324 } 233 }
325 } 234 }
326 235
327 } // namespace extensions 236 } // namespace extensions
OLDNEW
« no previous file with comments | « extensions/renderer/user_script_slave.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698