| // Copyright 2014 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/v8_schema_registry.h" |
| |
| #include <stddef.h> |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/values.h" |
| #include "content/public/renderer/v8_value_converter.h" |
| #include "extensions/common/extension_api.h" |
| #include "extensions/renderer/object_backed_native_handler.h" |
| #include "extensions/renderer/script_context.h" |
| #include "extensions/renderer/static_v8_external_one_byte_string_resource.h" |
| #include "extensions/renderer/v8_helpers.h" |
| |
| using content::V8ValueConverter; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // Recursively freezes every v8 object on |object|. |
| void DeepFreeze(const v8::Local<v8::Object>& object, |
| const v8::Local<v8::Context>& context) { |
| // Don't let the object trace upwards via the prototype. |
| v8::Maybe<bool> maybe = |
| object->SetPrototype(context, v8::Null(context->GetIsolate())); |
| CHECK(maybe.IsJust() && maybe.FromJust()); |
| v8::Local<v8::Array> property_names = |
| object->GetOwnPropertyNames(context).ToLocalChecked(); |
| for (uint32_t i = 0; i < property_names->Length(); ++i) { |
| v8::Local<v8::Value> child = |
| object->Get(context, property_names->Get(context, i).ToLocalChecked()) |
| .ToLocalChecked(); |
| if (child->IsObject()) |
| DeepFreeze(v8::Local<v8::Object>::Cast(child), context); |
| } |
| object->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen); |
| } |
| |
| class SchemaRegistryNativeHandler : public ObjectBackedNativeHandler { |
| public: |
| SchemaRegistryNativeHandler(V8SchemaRegistry* registry, |
| std::unique_ptr<ScriptContext> context) |
| : ObjectBackedNativeHandler(context.get()), |
| context_(std::move(context)), |
| registry_(registry) {} |
| |
| // ObjectBackedNativeHandler: |
| void AddRoutes() override { |
| RouteHandlerFunction( |
| "GetSchema", |
| base::BindRepeating(&SchemaRegistryNativeHandler::GetSchema, |
| base::Unretained(this))); |
| RouteHandlerFunction( |
| "GetObjectType", |
| base::BindRepeating(&SchemaRegistryNativeHandler::GetObjectType, |
| base::Unretained(this))); |
| } |
| |
| ~SchemaRegistryNativeHandler() override { context_->Invalidate(); } |
| |
| private: |
| void GetSchema(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| args.GetReturnValue().Set(registry_->GetSchema( |
| *v8::String::Utf8Value(args.GetIsolate(), args[0]))); |
| } |
| |
| void GetObjectType(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| CHECK(args.Length() == 1 && args[0]->IsObject()); |
| std::string type; |
| if (args[0]->IsArray()) |
| type = "array"; |
| else if (args[0]->IsArrayBuffer() || args[0]->IsArrayBufferView()) |
| type = "binary"; |
| else |
| type = "object"; |
| args.GetReturnValue().Set( |
| v8_helpers::ToV8StringUnsafe(context()->isolate(), type.c_str())); |
| } |
| |
| std::unique_ptr<ScriptContext> context_; |
| V8SchemaRegistry* registry_; |
| }; |
| |
| } // namespace |
| |
| V8SchemaRegistry::V8SchemaRegistry() { |
| } |
| |
| V8SchemaRegistry::~V8SchemaRegistry() { |
| } |
| |
| std::unique_ptr<NativeHandler> V8SchemaRegistry::AsNativeHandler() { |
| std::unique_ptr<ScriptContext> context( |
| new ScriptContext(GetOrCreateContext(v8::Isolate::GetCurrent()), |
| NULL, // no frame |
| NULL, // no extension |
| Feature::UNSPECIFIED_CONTEXT, |
| NULL, // no effective extension |
| Feature::UNSPECIFIED_CONTEXT)); |
| return std::unique_ptr<NativeHandler>( |
| new SchemaRegistryNativeHandler(this, std::move(context))); |
| } |
| |
| v8::Local<v8::Array> V8SchemaRegistry::GetSchemas( |
| const std::vector<std::string>& apis) { |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| v8::EscapableHandleScope handle_scope(isolate); |
| v8::Local<v8::Context> context = GetOrCreateContext(isolate); |
| v8::Context::Scope context_scope(context); |
| |
| v8::Local<v8::Array> v8_apis(v8::Array::New(isolate, apis.size())); |
| size_t api_index = 0; |
| for (auto i = apis.cbegin(); i != apis.cend(); ++i) { |
| bool set_result = |
| v8_apis->Set(context, api_index++, GetSchema(*i)).ToChecked(); |
| // Set() should never return false without throwing an exception (which |
| // would be caught by the ToChecked() above). |
| DCHECK(set_result); |
| } |
| return handle_scope.Escape(v8_apis); |
| } |
| |
| v8::Local<v8::Object> V8SchemaRegistry::GetSchema(const std::string& api) { |
| if (schema_cache_ != NULL) { |
| v8::Local<v8::Object> cached_schema = schema_cache_->Get(api); |
| if (!cached_schema.IsEmpty()) { |
| return cached_schema; |
| } |
| } |
| |
| // Slow path: Need to build schema first. |
| |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| v8::EscapableHandleScope handle_scope(isolate); |
| v8::Local<v8::Context> context = GetOrCreateContext(isolate); |
| v8::Context::Scope context_scope(context); |
| |
| base::StringPiece schema_string = |
| ExtensionAPI::GetSharedInstance()->GetSchemaStringPiece(api); |
| CHECK(!schema_string.empty()); |
| v8::MaybeLocal<v8::String> v8_maybe_string = v8::String::NewExternalOneByte( |
| isolate, new StaticV8ExternalOneByteStringResource(schema_string)); |
| v8::Local<v8::String> v8_schema_string; |
| CHECK(v8_maybe_string.ToLocal(&v8_schema_string)); |
| |
| v8::MaybeLocal<v8::Value> v8_maybe_schema_value = |
| v8::JSON::Parse(context, v8_schema_string); |
| v8::Local<v8::Value> v8_schema_value; |
| CHECK(v8_maybe_schema_value.ToLocal(&v8_schema_value)); |
| CHECK(v8_schema_value->IsObject()); |
| |
| v8::Local<v8::Object> v8_schema_object( |
| v8::Local<v8::Object>::Cast(v8_schema_value)); |
| DeepFreeze(v8_schema_object, context); |
| |
| schema_cache_->Set(api, v8_schema_object); |
| |
| return handle_scope.Escape(v8_schema_object); |
| } |
| |
| v8::Local<v8::Context> V8SchemaRegistry::GetOrCreateContext( |
| v8::Isolate* isolate) { |
| // It's ok to create local handles in this function, since this is only called |
| // when we have a HandleScope. |
| if (!context_holder_) { |
| context_holder_.reset(new gin::ContextHolder(isolate)); |
| context_holder_->SetContext(v8::Context::New(isolate)); |
| schema_cache_.reset(new SchemaCache(isolate)); |
| return context_holder_->context(); |
| } |
| return context_holder_->context(); |
| } |
| |
| } // namespace extensions |