File: js_extension_bindings_system.cc

package info (click to toggle)
chromium-browser 57.0.2987.98-1~deb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 2,637,852 kB
  • ctags: 2,544,394
  • sloc: cpp: 12,815,961; ansic: 3,676,222; python: 1,147,112; asm: 526,608; java: 523,212; xml: 286,794; perl: 92,654; sh: 86,408; objc: 73,271; makefile: 27,698; cs: 18,487; yacc: 13,031; tcl: 12,957; pascal: 4,875; ml: 4,716; lex: 3,904; sql: 3,862; ruby: 1,982; lisp: 1,508; php: 1,368; exp: 404; awk: 325; csh: 117; jsp: 39; sed: 37
file content (319 lines) | stat: -rw-r--r-- 12,859 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/renderer/js_extension_bindings_system.h"

#include "base/command_line.h"
#include "base/strings/string_split.h"
#include "content/public/child/v8_value_converter.h"
#include "content/public/common/content_switches.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_api.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/externally_connectable.h"
#include "extensions/renderer/binding_generating_native_handler.h"
#include "extensions/renderer/renderer_extension_registry.h"
#include "extensions/renderer/resource_bundle_source_map.h"
#include "extensions/renderer/script_context.h"
#include "gin/converter.h"
#include "v8/include/v8.h"

namespace extensions {

namespace {

static const char kEventDispatchFunction[] = "dispatchEvent";

// Gets |field| from |object| or creates it as an empty object if it doesn't
// exist.
v8::Local<v8::Object> GetOrCreateObject(const v8::Local<v8::Object>& object,
                                        const std::string& field,
                                        v8::Isolate* isolate) {
  v8::Local<v8::String> key = v8::String::NewFromUtf8(isolate, field.c_str());
  // If the object has a callback property, it is assumed it is an unavailable
  // API, so it is safe to delete. This is checked before GetOrCreateObject is
  // called.
  if (object->HasRealNamedCallbackProperty(key)) {
    object->Delete(key);
  } else if (object->HasRealNamedProperty(key)) {
    v8::Local<v8::Value> value = object->Get(key);
    CHECK(value->IsObject());
    return v8::Local<v8::Object>::Cast(value);
  }

  v8::Local<v8::Object> new_object = v8::Object::New(isolate);
  object->Set(key, new_object);
  return new_object;
}

// Returns the global value for "chrome" from |context|. If one doesn't exist
// creates a new object for it. If a chrome property exists on the window
// already (as in the case when a script did `window.chrome = true`), returns
// an empty object.
v8::Local<v8::Object> GetOrCreateChrome(ScriptContext* context) {
  v8::Local<v8::String> chrome_string(
      v8::String::NewFromUtf8(context->isolate(), "chrome"));
  v8::Local<v8::Object> global(context->v8_context()->Global());
  v8::Local<v8::Value> chrome(global->Get(chrome_string));
  if (chrome->IsUndefined()) {
    chrome = v8::Object::New(context->isolate());
    global->Set(chrome_string, chrome);
  }
  return chrome->IsObject() ? chrome.As<v8::Object>() : v8::Local<v8::Object>();
}

v8::Local<v8::Object> GetOrCreateBindObjectIfAvailable(
    const std::string& api_name,
    std::string* bind_name,
    ScriptContext* context) {
  std::vector<std::string> split = base::SplitString(
      api_name, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  v8::Local<v8::Object> bind_object;

  // Check if this API has an ancestor. If the API's ancestor is available and
  // the API is not available, don't install the bindings for this API. If
  // the API is available and its ancestor is not, delete the ancestor and
  // install the bindings for the API. This is to prevent loading the ancestor
  // API schema if it will not be needed.
  //
  // For example:
  //  If app is available and app.window is not, just install app.
  //  If app.window is available and app is not, delete app and install
  //  app.window on a new object so app does not have to be loaded.
  const FeatureProvider* api_feature_provider =
      FeatureProvider::GetAPIFeatures();
  std::string ancestor_name;
  bool only_ancestor_available = false;

  for (size_t i = 0; i < split.size() - 1; ++i) {
    ancestor_name += (i ? "." : "") + split[i];
    if (api_feature_provider->GetFeature(ancestor_name) &&
        context->GetAvailability(ancestor_name).is_available() &&
        !context->GetAvailability(api_name).is_available()) {
      only_ancestor_available = true;
      break;
    }

    if (bind_object.IsEmpty()) {
      bind_object = GetOrCreateChrome(context);
      if (bind_object.IsEmpty())
        return v8::Local<v8::Object>();
    }
    bind_object = GetOrCreateObject(bind_object, split[i], context->isolate());
  }

  if (only_ancestor_available)
    return v8::Local<v8::Object>();

  DCHECK(bind_name);
  *bind_name = split.back();

  return bind_object.IsEmpty() ? GetOrCreateChrome(context) : bind_object;
}

// Determines if a ScriptContext can connect to any externally_connectable-
// enabled extension.
bool IsRuntimeAvailableToContext(ScriptContext* context) {
  for (const auto& extension :
       *RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
    ExternallyConnectableInfo* info = static_cast<ExternallyConnectableInfo*>(
        extension->GetManifestData(manifest_keys::kExternallyConnectable));
    if (info && info->matches.MatchesURL(context->url()))
      return true;
  }
  return false;
}

// Creates the event bindings if necessary for the given |context|.
void MaybeCreateEventBindings(ScriptContext* context) {
  // chrome.Event is part of the public API (although undocumented). Make it
  // lazily evalulate to Event from event_bindings.js. For extensions only
  // though, not all webpages!
  if (!context->extension())
    return;
  v8::Local<v8::Object> chrome = GetOrCreateChrome(context);
  if (chrome.IsEmpty())
    return;
  context->module_system()->SetLazyField(chrome, "Event", kEventBindings,
                                         "Event");
}

}  // namespace

JsExtensionBindingsSystem::JsExtensionBindingsSystem(
    ResourceBundleSourceMap* source_map,
    std::unique_ptr<RequestSender> request_sender)
    : source_map_(source_map), request_sender_(std::move(request_sender)) {}

JsExtensionBindingsSystem::~JsExtensionBindingsSystem() {}

void JsExtensionBindingsSystem::DidCreateScriptContext(ScriptContext* context) {
  MaybeCreateEventBindings(context);
}

void JsExtensionBindingsSystem::WillReleaseScriptContext(
    ScriptContext* context) {
  // TODO(kalman): Make |request_sender| use |context->AddInvalidationObserver|.
  // In fact |request_sender_| should really be owned by ScriptContext.
  request_sender_->InvalidateSource(context);
}

void JsExtensionBindingsSystem::UpdateBindingsForContext(
    ScriptContext* context) {
  v8::HandleScope handle_scope(context->isolate());
  v8::Context::Scope context_scope(context->v8_context());

  // TODO(kalman): Make the bindings registration have zero overhead then run
  // the same code regardless of context type.
  switch (context->context_type()) {
    case Feature::UNSPECIFIED_CONTEXT:
    case Feature::WEB_PAGE_CONTEXT:
    case Feature::BLESSED_WEB_PAGE_CONTEXT:
      // Hard-code registration of any APIs that are exposed to webpage-like
      // contexts, because it's too expensive to run the full bindings code.
      // All of the same permission checks will still apply.
      if (context->GetAvailability("app").is_available())
        RegisterBinding("app", "app", context);
      if (context->GetAvailability("webstore").is_available())
        RegisterBinding("webstore", "webstore", context);
      if (context->GetAvailability("dashboardPrivate").is_available())
        RegisterBinding("dashboardPrivate", "dashboardPrivate", context);
      if (IsRuntimeAvailableToContext(context))
        RegisterBinding("runtime", "runtime", context);
      break;

    case Feature::SERVICE_WORKER_CONTEXT:
      DCHECK(ExtensionsClient::Get()
                 ->ExtensionAPIEnabledInExtensionServiceWorkers());
    // Intentional fallthrough.
    case Feature::BLESSED_EXTENSION_CONTEXT:
    case Feature::UNBLESSED_EXTENSION_CONTEXT:
    case Feature::CONTENT_SCRIPT_CONTEXT:
    case Feature::WEBUI_CONTEXT: {
      // Extension context; iterate through all the APIs and bind the available
      // ones.
      const FeatureProvider* api_feature_provider =
          FeatureProvider::GetAPIFeatures();
      for (const auto& map_entry : api_feature_provider->GetAllFeatures()) {
        // Internal APIs are included via require(api_name) from internal code
        // rather than chrome[api_name].
        if (map_entry.second->IsInternal())
          continue;

        // If this API has a parent feature (and isn't marked 'noparent'),
        // then this must be a function or event, so we should not register.
        if (api_feature_provider->GetParent(map_entry.second.get()) != nullptr)
          continue;

        // Skip chrome.test if this isn't a test.
        if (map_entry.first == "test" &&
            !base::CommandLine::ForCurrentProcess()->HasSwitch(
                ::switches::kTestType)) {
          continue;
        }

        if (context->IsAnyFeatureAvailableToContext(
                *map_entry.second, CheckAliasStatus::NOT_ALLOWED)) {
          // Check if the API feature is indeed an alias. If it is, the API
          // should use source API bindings as its own.
          const std::string& source = map_entry.second->source();
          // TODO(lazyboy): RegisterBinding() uses |source_map_|, any thread
          // safety issue?
          RegisterBinding(source.empty() ? map_entry.first : source,
                          map_entry.first, context);
        }
      }
      break;
    }
  }
}

void JsExtensionBindingsSystem::HandleResponse(int request_id,
                                               bool success,
                                               const base::ListValue& response,
                                               const std::string& error) {
  request_sender_->HandleResponse(request_id, success, response, error);
}

RequestSender* JsExtensionBindingsSystem::GetRequestSender() {
  return request_sender_.get();
}

void JsExtensionBindingsSystem::DispatchEventInContext(
    const std::string& event_name,
    const base::ListValue* event_args,
    const base::DictionaryValue* filtering_info,
    ScriptContext* context) {
  v8::HandleScope handle_scope(context->isolate());
  v8::Context::Scope context_scope(context->v8_context());

  std::vector<v8::Local<v8::Value>> arguments;
  arguments.push_back(gin::StringToSymbol(context->isolate(), event_name));

  {
    std::unique_ptr<content::V8ValueConverter> converter(
        content::V8ValueConverter::create());
    arguments.push_back(
        converter->ToV8Value(event_args, context->v8_context()));
    if (!filtering_info->empty()) {
      arguments.push_back(
          converter->ToV8Value(filtering_info, context->v8_context()));
    }
  }

  context->module_system()->CallModuleMethodSafe(
      kEventBindings, kEventDispatchFunction, arguments.size(),
      arguments.data());
}

void JsExtensionBindingsSystem::RegisterBinding(
    const std::string& api_name,
    const std::string& api_bind_name,
    ScriptContext* context) {
  std::string bind_name;
  v8::Local<v8::Object> bind_object =
      GetOrCreateBindObjectIfAvailable(api_bind_name, &bind_name, context);

  // Empty if the bind object failed to be created, probably because the
  // extension overrode chrome with a non-object, e.g. window.chrome = true.
  if (bind_object.IsEmpty())
    return;

  v8::Local<v8::String> v8_bind_name =
      v8::String::NewFromUtf8(context->isolate(), bind_name.c_str());
  if (bind_object->HasRealNamedProperty(v8_bind_name)) {
    // The bind object may already have the property if the API has been
    // registered before (or if the extension has put something there already,
    // but, whatevs).
    //
    // In the former case, we need to re-register the bindings for the APIs
    // which the extension now has permissions for (if any), but not touch any
    // others so that we don't destroy state such as event listeners.
    //
    // TODO(kalman): Only register available APIs to make this all moot.
    if (bind_object->HasRealNamedCallbackProperty(v8_bind_name))
      return;  // lazy binding still there, nothing to do
    if (bind_object->Get(v8_bind_name)->IsObject())
      return;  // binding has already been fully installed
  }

  ModuleSystem* module_system = context->module_system();
  if (!source_map_->Contains(api_name)) {
    module_system->RegisterNativeHandler(
        api_bind_name,
        std::unique_ptr<NativeHandler>(
            new BindingGeneratingNativeHandler(context, api_name, "binding")));
    module_system->SetNativeLazyField(bind_object, bind_name, api_bind_name,
                                      "binding");
  } else {
    module_system->SetLazyField(bind_object, bind_name, api_name, "binding");
  }
}

}  // namespace extensions