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
30using blink::WebFrame;
31using blink::WebSecurityOrigin;
32using blink::WebSecurityPolicy;
33using blink::WebString;
34using content::RenderThread;
35
36namespace extensions {
37
38int 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
65std::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
76void UserScriptSlave::RemoveIsolatedWorld(const std::string& extension_id) {
77  isolated_world_ids_.erase(extension_id);
78}
79
80UserScriptSlave::UserScriptSlave(const ExtensionSet* extensions)
81    : extensions_(extensions) {
82}
83
84UserScriptSlave::~UserScriptSlave() {
85}
86
87void 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
99const Extension* UserScriptSlave::GetExtension(
100    const std::string& extension_id) {
101  return extensions_->GetByID(extension_id);
102}
103
104bool 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
207void 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
224void 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
244void 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
253void 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