• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetIsolatedWorldIdForExtension(const Extension * extension,WebFrame * frame)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 
GetExtensionIdForIsolatedWorld(int isolated_world_id)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 
RemoveIsolatedWorld(const std::string & extension_id)76 void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
77   isolated_world_ids_.erase(extension_id);
78 }
79 
UserScriptSlave(const ExtensionSet * extensions)80 UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
81     : extensions_(extensions) {
82 }
83 
~UserScriptSlave()84 UserScriptSlave::~UserScriptSlave() {
85 }
86 
GetActiveExtensions(std::set<std::string> * extension_ids)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 
GetExtension(const std::string & extension_id)99 const Extension* UserScriptSlave::GetExtension(
100     const std::string& extension_id) {
101   return extensions_->GetByID(extension_id);
102 }
103 
UpdateScripts(base::SharedMemoryHandle shared_memory,const std::set<std::string> & changed_extensions)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 
InjectScripts(WebFrame * frame,UserScript::RunLocation location)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 
OnContentScriptGrantedPermission(content::RenderView * render_view,int request_id)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 
FrameDetached(blink::WebFrame * frame)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 
LogScriptsRun(blink::WebFrame * frame,UserScript::RunLocation location,const ScriptInjection::ScriptsRunInfo & info)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
299