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/user_script_slave.h" | |
6 | |
7 #include <map> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/memory/shared_memory.h" | |
11 #include "base/metrics/histogram.h" | |
12 #include "base/pickle.h" | |
13 #include "base/timer/elapsed_timer.h" | |
14 #include "content/public/renderer/render_thread.h" | |
15 #include "content/public/renderer/render_view.h" | |
16 #include "extensions/common/extension.h" | |
17 #include "extensions/common/extension_messages.h" | |
18 #include "extensions/common/extension_set.h" | |
19 #include "extensions/common/manifest_handlers/csp_info.h" | |
20 #include "extensions/common/permissions/permissions_data.h" | |
21 #include "extensions/renderer/extension_helper.h" | |
22 #include "extensions/renderer/extensions_renderer_client.h" | |
23 #include "extensions/renderer/script_context.h" | |
24 #include "third_party/WebKit/public/web/WebFrame.h" | |
25 #include "third_party/WebKit/public/web/WebSecurityOrigin.h" | |
26 #include "third_party/WebKit/public/web/WebSecurityPolicy.h" | |
27 #include "third_party/WebKit/public/web/WebView.h" | |
28 #include "url/gurl.h" | |
29 | |
30 using blink::WebFrame; | |
31 using blink::WebSecurityOrigin; | |
32 using blink::WebSecurityPolicy; | |
33 using blink::WebString; | |
34 using content::RenderThread; | |
35 | |
36 namespace extensions { | |
37 | |
38 int UserScriptSlave::GetIsolatedWorldIdForExtension(const Extension* extension, | |
39 WebFrame* frame) { | |
40 static int g_next_isolated_world_id = | |
41 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId(); | |
42 | |
43 int id = 0; | |
44 IsolatedWorldMap::iterator iter = isolated_world_ids_.find(extension->id()); | |
45 if (iter != isolated_world_ids_.end()) { | |
46 id = iter->second; | |
47 } else { | |
48 id = g_next_isolated_world_id++; | |
49 // This map will tend to pile up over time, but realistically, you're never | |
50 // going to have enough extensions for it to matter. | |
51 isolated_world_ids_[extension->id()] = id; | |
52 } | |
53 | |
54 // We need to set the isolated world origin and CSP even if it's not a new | |
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))); | |
61 | |
62 return id; | |
63 } | |
64 | |
65 std::string UserScriptSlave::GetExtensionIdForIsolatedWorld( | |
66 int isolated_world_id) { | |
67 for (IsolatedWorldMap::iterator iter = isolated_world_ids_.begin(); | |
68 iter != isolated_world_ids_.end(); | |
69 ++iter) { | |
70 if (iter->second == isolated_world_id) | |
71 return iter->first; | |
72 } | |
73 return std::string(); | |
74 } | |
75 | |
76 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) { | |
77 isolated_world_ids_.erase(extension_id); | |
78 } | |
79 | |
80 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions) | |
81 : extensions_(extensions) { | |
82 } | |
83 | |
84 UserScriptSlave::~UserScriptSlave() { | |
85 } | |
86 | |
87 void UserScriptSlave::GetActiveExtensions( | |
88 std::set<std::string>* extension_ids) { | |
89 DCHECK(extension_ids); | |
90 for (ScopedVector<ScriptInjection>::const_iterator iter = | |
91 script_injections_.begin(); | |
92 iter != script_injections_.end(); | |
93 ++iter) { | |
94 DCHECK(!(*iter)->extension_id().empty()); | |
95 extension_ids->insert((*iter)->extension_id()); | |
96 } | |
97 } | |
98 | |
99 const Extension* UserScriptSlave::GetExtension( | |
100 const std::string& extension_id) { | |
101 return extensions_->GetByID(extension_id); | |
102 } | |
103 | |
104 bool UserScriptSlave::UpdateScripts( | |
105 base::SharedMemoryHandle shared_memory, | |
106 const std::set<std::string>& changed_extensions) { | |
107 bool only_inject_incognito = | |
108 ExtensionsRendererClient::Get()->IsIncognitoProcess(); | |
109 | |
110 // Create the shared memory object (read only). | |
111 shared_memory_.reset(new base::SharedMemory(shared_memory, true)); | |
112 if (!shared_memory_.get()) | |
113 return false; | |
114 | |
115 // First get the size of the memory block. | |
116 if (!shared_memory_->Map(sizeof(Pickle::Header))) | |
117 return false; | |
118 Pickle::Header* pickle_header = | |
119 reinterpret_cast<Pickle::Header*>(shared_memory_->memory()); | |
120 | |
121 // Now map in the rest of the block. | |
122 int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size; | |
123 shared_memory_->Unmap(); | |
124 if (!shared_memory_->Map(pickle_size)) | |
125 return false; | |
126 | |
127 // Unpickle scripts. | |
128 uint64 num_scripts = 0; | |
129 Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size); | |
130 PickleIterator iter(pickle); | |
131 CHECK(pickle.ReadUInt64(&iter, &num_scripts)); | |
132 | |
133 // If we pass no explicit extension ids, we should refresh all extensions. | |
134 bool include_all_extensions = changed_extensions.empty(); | |
135 | |
136 // If we include all extensions, then we clear the script injections and | |
137 // start from scratch. If not, then clear only the scripts for extension ids | |
138 // that we are updating. This is important to maintain pending script | |
139 // injection state for each ScriptInjection. | |
140 if (include_all_extensions) { | |
141 script_injections_.clear(); | |
142 } else { | |
143 for (ScopedVector<ScriptInjection>::iterator iter = | |
144 script_injections_.begin(); | |
145 iter != script_injections_.end();) { | |
146 if (changed_extensions.count((*iter)->extension_id()) > 0) | |
147 iter = script_injections_.erase(iter); | |
148 else | |
149 ++iter; | |
150 } | |
151 } | |
152 | |
153 script_injections_.reserve(num_scripts); | |
154 for (uint64 i = 0; i < num_scripts; ++i) { | |
155 scoped_ptr<UserScript> script(new UserScript()); | |
156 script->Unpickle(pickle, &iter); | |
157 | |
158 // Note that this is a pointer into shared memory. We don't own it. It gets | |
159 // cleared up when the last renderer or browser process drops their | |
160 // reference to the shared memory. | |
161 for (size_t j = 0; j < script->js_scripts().size(); ++j) { | |
162 const char* body = NULL; | |
163 int body_length = 0; | |
164 CHECK(pickle.ReadData(&iter, &body, &body_length)); | |
165 script->js_scripts()[j].set_external_content( | |
166 base::StringPiece(body, body_length)); | |
167 } | |
168 for (size_t j = 0; j < script->css_scripts().size(); ++j) { | |
169 const char* body = NULL; | |
170 int body_length = 0; | |
171 CHECK(pickle.ReadData(&iter, &body, &body_length)); | |
172 script->css_scripts()[j].set_external_content( | |
173 base::StringPiece(body, body_length)); | |
174 } | |
175 | |
176 if (only_inject_incognito && !script->is_incognito_enabled()) | |
177 continue; // This script shouldn't run in an incognito tab. | |
178 | |
179 // If we include all extensions or the given extension changed, we add a | |
180 // new script injection. | |
181 if (include_all_extensions || | |
182 changed_extensions.count(script->extension_id()) > 0) { | |
183 script_injections_.push_back(new ScriptInjection(script.Pass(), this)); | |
184 } else { | |
185 // Otherwise, we need to update the existing script injection with the | |
186 // new user script (since the old content was invalidated). | |
187 // | |
188 // Note: Yes, this is O(n^2). But vectors are faster than maps for | |
189 // relatively few elements, and less than 1% of our users actually have | |
190 // enough content scripts for it to matter. If this changes, or if | |
191 // std::maps get a much faster implementation, we should look into | |
192 // making a map for script injections. | |
193 for (ScopedVector<ScriptInjection>::iterator iter = | |
194 script_injections_.begin(); | |
195 iter != script_injections_.end(); | |
196 ++iter) { | |
197 if ((*iter)->script()->id() == script->id()) { | |
198 (*iter)->SetScript(script.Pass()); | |
199 break; | |
200 } | |
201 } | |
202 } | |
203 } | |
204 return true; | |
205 } | |
206 | |
207 void UserScriptSlave::InjectScripts(WebFrame* frame, | |
208 UserScript::RunLocation location) { | |
209 GURL document_url = ScriptInjection::GetDocumentUrlForFrame(frame); | |
210 if (document_url.is_empty()) | |
211 return; | |
212 | |
213 ScriptInjection::ScriptsRunInfo scripts_run_info; | |
214 for (ScopedVector<ScriptInjection>::const_iterator iter = | |
215 script_injections_.begin(); | |
216 iter != script_injections_.end(); | |
217 ++iter) { | |
218 (*iter)->InjectIfAllowed(frame, location, document_url, &scripts_run_info); | |
219 } | |
220 | |
221 LogScriptsRun(frame, location, scripts_run_info); | |
222 } | |
223 | |
224 void UserScriptSlave::OnContentScriptGrantedPermission( | |
225 content::RenderView* render_view, int request_id) { | |
226 ScriptInjection::ScriptsRunInfo run_info; | |
227 blink::WebFrame* frame = NULL; | |
228 // Notify the injections that a request to inject has been granted. | |
229 for (ScopedVector<ScriptInjection>::iterator iter = | |
230 script_injections_.begin(); | |
231 iter != script_injections_.end(); | |
232 ++iter) { | |
233 if ((*iter)->NotifyScriptPermitted(request_id, | |
234 render_view, | |
235 &run_info, | |
236 &frame)) { | |
237 DCHECK(frame); | |
238 LogScriptsRun(frame, UserScript::RUN_DEFERRED, run_info); | |
239 break; | |
240 } | |
241 } | |
242 } | |
243 | |
244 void UserScriptSlave::FrameDetached(blink::WebFrame* frame) { | |
245 for (ScopedVector<ScriptInjection>::iterator iter = | |
246 script_injections_.begin(); | |
247 iter != script_injections_.end(); | |
248 ++iter) { | |
249 (*iter)->FrameDetached(frame); | |
250 } | |
251 } | |
252 | |
253 void UserScriptSlave::LogScriptsRun( | |
254 blink::WebFrame* frame, | |
255 UserScript::RunLocation location, | |
256 const ScriptInjection::ScriptsRunInfo& info) { | |
257 // Notify the browser if any extensions are now executing scripts. | |
258 if (!info.executing_scripts.empty()) { | |
259 content::RenderView* render_view = | |
260 content::RenderView::FromWebView(frame->view()); | |
261 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting( | |
262 render_view->GetRoutingID(), | |
263 info.executing_scripts, | |
264 render_view->GetPageId(), | |
265 ScriptContext::GetDataSourceURLForFrame(frame))); | |
266 } | |
267 | |
268 switch (location) { | |
269 case UserScript::DOCUMENT_START: | |
270 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", | |
271 info.num_css); | |
272 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", | |
273 info.num_js); | |
274 if (info.num_css || info.num_js) | |
275 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", | |
276 info.timer.Elapsed()); | |
277 break; | |
278 case UserScript::DOCUMENT_END: | |
279 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js); | |
280 if (info.num_js) | |
281 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed()); | |
282 break; | |
283 case UserScript::DOCUMENT_IDLE: | |
284 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", | |
285 info.num_js); | |
286 if (info.num_js) | |
287 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed()); | |
288 break; | |
289 case UserScript::RUN_DEFERRED: | |
290 // TODO(rdevlin.cronin): Add histograms. | |
291 break; | |
292 case UserScript::UNDEFINED: | |
293 case UserScript::RUN_LOCATION_LAST: | |
294 NOTREACHED(); | |
295 } | |
296 } | |
297 | |
298 } // namespace extensions | |
OLD | NEW |