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_set.h"
6
7#include "content/public/common/url_constants.h"
8#include "content/public/renderer/render_thread.h"
9#include "extensions/common/extension.h"
10#include "extensions/common/extension_set.h"
11#include "extensions/common/permissions/permissions_data.h"
12#include "extensions/renderer/extensions_renderer_client.h"
13#include "extensions/renderer/script_context.h"
14#include "extensions/renderer/script_injection.h"
15#include "extensions/renderer/user_script_injector.h"
16#include "third_party/WebKit/public/web/WebDocument.h"
17#include "third_party/WebKit/public/web/WebFrame.h"
18#include "url/gurl.h"
19
20namespace extensions {
21
22namespace {
23
24GURL GetDocumentUrlForFrame(blink::WebFrame* frame) {
25  GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame);
26  if (!data_source_url.is_empty() && frame->isViewSourceModeEnabled()) {
27    data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
28                           data_source_url.spec());
29  }
30
31  return data_source_url;
32}
33
34}  // namespace
35
36UserScriptSet::UserScriptSet(const ExtensionSet* extensions)
37    : extensions_(extensions) {
38}
39
40UserScriptSet::~UserScriptSet() {
41}
42
43void UserScriptSet::AddObserver(Observer* observer) {
44  observers_.AddObserver(observer);
45}
46
47void UserScriptSet::RemoveObserver(Observer* observer) {
48  observers_.RemoveObserver(observer);
49}
50
51void UserScriptSet::GetActiveExtensionIds(
52    std::set<std::string>* ids) const {
53  for (ScopedVector<UserScript>::const_iterator iter = scripts_.begin();
54       iter != scripts_.end();
55       ++iter) {
56    DCHECK(!(*iter)->extension_id().empty());
57    ids->insert((*iter)->extension_id());
58  }
59}
60
61void UserScriptSet::GetInjections(
62    ScopedVector<ScriptInjection>* injections,
63    blink::WebFrame* web_frame,
64    int tab_id,
65    UserScript::RunLocation run_location) {
66  GURL document_url = GetDocumentUrlForFrame(web_frame);
67  for (ScopedVector<UserScript>::const_iterator iter = scripts_.begin();
68       iter != scripts_.end();
69       ++iter) {
70    const Extension* extension = extensions_->GetByID((*iter)->extension_id());
71    if (!extension)
72      continue;
73    scoped_ptr<ScriptInjection> injection = GetInjectionForScript(
74        *iter,
75        web_frame,
76        tab_id,
77        run_location,
78        document_url,
79        extension,
80        false /* is_declarative */);
81    if (injection.get())
82      injections->push_back(injection.release());
83  }
84}
85
86bool UserScriptSet::UpdateUserScripts(
87    base::SharedMemoryHandle shared_memory,
88    const std::set<std::string>& changed_extensions) {
89  bool only_inject_incognito =
90      ExtensionsRendererClient::Get()->IsIncognitoProcess();
91
92  // Create the shared memory object (read only).
93  shared_memory_.reset(new base::SharedMemory(shared_memory, true));
94  if (!shared_memory_.get())
95    return false;
96
97  // First get the size of the memory block.
98  if (!shared_memory_->Map(sizeof(Pickle::Header)))
99    return false;
100  Pickle::Header* pickle_header =
101      reinterpret_cast<Pickle::Header*>(shared_memory_->memory());
102
103  // Now map in the rest of the block.
104  int pickle_size = sizeof(Pickle::Header) + pickle_header->payload_size;
105  shared_memory_->Unmap();
106  if (!shared_memory_->Map(pickle_size))
107    return false;
108
109  // Unpickle scripts.
110  uint64 num_scripts = 0;
111  Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()), pickle_size);
112  PickleIterator iter(pickle);
113  CHECK(pickle.ReadUInt64(&iter, &num_scripts));
114
115  scripts_.clear();
116  scripts_.reserve(num_scripts);
117  for (uint64 i = 0; i < num_scripts; ++i) {
118    scoped_ptr<UserScript> script(new UserScript());
119    script->Unpickle(pickle, &iter);
120
121    // Note that this is a pointer into shared memory. We don't own it. It gets
122    // cleared up when the last renderer or browser process drops their
123    // reference to the shared memory.
124    for (size_t j = 0; j < script->js_scripts().size(); ++j) {
125      const char* body = NULL;
126      int body_length = 0;
127      CHECK(pickle.ReadData(&iter, &body, &body_length));
128      script->js_scripts()[j].set_external_content(
129          base::StringPiece(body, body_length));
130    }
131    for (size_t j = 0; j < script->css_scripts().size(); ++j) {
132      const char* body = NULL;
133      int body_length = 0;
134      CHECK(pickle.ReadData(&iter, &body, &body_length));
135      script->css_scripts()[j].set_external_content(
136          base::StringPiece(body, body_length));
137    }
138
139    if (only_inject_incognito && !script->is_incognito_enabled())
140      continue;  // This script shouldn't run in an incognito tab.
141
142    scripts_.push_back(script.release());
143  }
144
145  FOR_EACH_OBSERVER(Observer,
146                    observers_,
147                    OnUserScriptsUpdated(changed_extensions, scripts_.get()));
148  return true;
149}
150
151scoped_ptr<ScriptInjection> UserScriptSet::GetDeclarativeScriptInjection(
152    int script_id,
153    blink::WebFrame* web_frame,
154    int tab_id,
155    UserScript::RunLocation run_location,
156    const GURL& document_url,
157    const Extension* extension) {
158  for (ScopedVector<UserScript>::const_iterator it = scripts_.begin();
159       it != scripts_.end();
160       ++it) {
161    if ((*it)->id() == script_id) {
162      return GetInjectionForScript(*it,
163                                   web_frame,
164                                   tab_id,
165                                   run_location,
166                                   document_url,
167                                   extension,
168                                   true /* is_declarative */);
169    }
170  }
171  return scoped_ptr<ScriptInjection>();
172}
173
174// TODO(dcheng): Scripts can't be injected on a remote frame, so this function
175// signature needs to be updated.
176scoped_ptr<ScriptInjection> UserScriptSet::GetInjectionForScript(
177    UserScript* script,
178    blink::WebFrame* web_frame,
179    int tab_id,
180    UserScript::RunLocation run_location,
181    const GURL& document_url,
182    const Extension* extension,
183    bool is_declarative) {
184  scoped_ptr<ScriptInjection> injection;
185  if (web_frame->parent() && !script->match_all_frames())
186    return injection.Pass();  // Only match subframes if the script declared it.
187
188  GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
189      web_frame, document_url, script->match_about_blank());
190
191  if (!script->MatchesURL(effective_document_url))
192    return injection.Pass();
193
194  scoped_ptr<ScriptInjector> injector(new UserScriptInjector(script,
195                                                             this,
196                                                             is_declarative));
197  if (injector->CanExecuteOnFrame(
198          extension,
199          web_frame,
200          -1,  // Content scripts are not tab-specific.
201          web_frame->top()->document().url()) ==
202      PermissionsData::ACCESS_DENIED) {
203    return injection.Pass();
204  }
205
206  bool inject_css = !script->css_scripts().empty() &&
207                    run_location == UserScript::DOCUMENT_START;
208  bool inject_js =
209      !script->js_scripts().empty() && script->run_location() == run_location;
210  if (inject_css || inject_js) {
211    injection.reset(new ScriptInjection(
212        injector.Pass(),
213        web_frame->toWebLocalFrame(),
214        extension->id(),
215        run_location,
216        tab_id));
217  }
218  return injection.Pass();
219}
220
221}  // namespace extensions
222