Chromium Code Reviews| Index: extensions/renderer/declarative_content_hooks_delegate.cc |
| diff --git a/extensions/renderer/declarative_content_hooks_delegate.cc b/extensions/renderer/declarative_content_hooks_delegate.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a30621896efd4a12b97a6cbfdc98068db11410df |
| --- /dev/null |
| +++ b/extensions/renderer/declarative_content_hooks_delegate.cc |
| @@ -0,0 +1,243 @@ |
| +// Copyright 2017 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/declarative_content_hooks_delegate.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "extensions/renderer/api_type_reference_map.h" |
| +#include "extensions/renderer/argument_spec.h" |
| +#include "gin/arguments.h" |
| +#include "gin/converter.h" |
| +#include "third_party/WebKit/public/platform/WebString.h" |
| +#include "third_party/WebKit/public/web/WebSelector.h" |
| + |
| +namespace extensions { |
| + |
| +namespace { |
| + |
| +void CallbackHelper(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| + CHECK(info.Data()->IsExternal()); |
| + v8::Local<v8::External> external = info.Data().As<v8::External>(); |
| + auto* callback = |
| + static_cast<DeclarativeContentHooksDelegate::HandlerCallback*>( |
| + external->Value()); |
| + callback->Run(info); |
| +} |
| + |
| +// Copies the properties from src -> dst, similar to Object.assign(). |
|
jbroman
2017/05/01 20:16:40
Don't know how much you care, but at a glance I th
Devlin
2017/05/01 20:46:48
I'm not too worried about those. The goal wasn't
|
| +bool V8Assign(v8::Local<v8::Context> context, |
| + v8::Local<v8::Object> src, |
| + v8::Local<v8::Object> dst) { |
| + v8::Local<v8::Array> own_property_names; |
| + if (!src->GetOwnPropertyNames(context).ToLocal(&own_property_names)) |
| + return false; |
| + |
| + uint32_t length = own_property_names->Length(); |
| + for (uint32_t i = 0; i < length; ++i) { |
| + v8::Local<v8::Value> key; |
| + if (!own_property_names->Get(context, i).ToLocal(&key)) |
| + return false; |
| + DCHECK(key->IsString() || key->IsNumber()); |
|
jbroman
2017/05/01 20:16:40
Note that this is true (without a property filter,
Devlin
2017/05/01 20:46:48
I think the DCHECK is useful only because below we
|
| + |
| + v8::Local<v8::Value> prop_value; |
| + if (!src->Get(context, key).ToLocal(&prop_value)) |
| + return false; |
| + |
| + bool success = false; |
|
jbroman
2017/05/01 20:16:40
super-nit: might as well factor out the result che
Devlin
2017/05/01 20:46:48
Sure. I thought it would be too verbose, but if y
|
| + if (key->IsString()) { |
| + v8::Maybe<bool> set_result = |
| + dst->CreateDataProperty(context, key.As<v8::String>(), prop_value); |
| + success = set_result.IsJust() && set_result.FromJust(); |
| + } else { |
| + v8::Maybe<bool> set_result = dst->CreateDataProperty( |
| + context, key.As<v8::Uint32>()->Value(), prop_value); |
| + success = set_result.IsJust() && set_result.FromJust(); |
| + } |
| + if (!success) |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +// Canonicalizes any css selectors specified in a page state matcher, returning |
| +// true on success. |
| +bool CanonicalizeCssSelectors(v8::Local<v8::Context> context, |
| + v8::Local<v8::Object> object, |
| + std::string* error) { |
| + v8::Isolate* isolate = context->GetIsolate(); |
| + v8::Local<v8::String> key = gin::StringToSymbol(isolate, "css"); |
| + v8::Maybe<bool> has_css = object->HasOwnProperty(context, key); |
| + // Note: don't bother populating |error| if script threw an exception. |
| + if (!has_css.IsJust()) |
| + return false; |
| + |
| + if (!has_css.FromJust()) |
| + return true; |
| + |
| + v8::Local<v8::Value> css; |
| + if (!object->Get(context, key).ToLocal(&css)) |
| + return false; |
| + |
| + if (css->IsUndefined()) |
| + return true; |
| + |
| + if (!css->IsArray()) |
| + return false; |
| + |
| + v8::Local<v8::Array> css_array = css.As<v8::Array>(); |
| + uint32_t length = css_array->Length(); |
| + for (uint32_t i = 0; i < length; ++i) { |
| + v8::Local<v8::Value> val; |
| + if (!css_array->Get(context, i).ToLocal(&val) || !val->IsString()) |
| + return false; |
| + const char* selector = *v8::String::Utf8Value(val.As<v8::String>()); |
|
jbroman
2017/05/01 20:16:40
This is unsafe. The buffer returned by Utf8Value::
Devlin
2017/05/01 20:46:48
Oh, I had no idea. Fixed. Thanks for catching it
|
| + std::string parsed = |
| + blink::CanonicalizeSelector(blink::WebString::FromUTF8(selector), |
| + blink::kWebSelectorTypeCompound) |
| + .Utf8(); |
| + if (parsed.empty()) { |
| + *error = "Invalid css: " + std::string(selector); |
| + return false; |
| + } |
| + v8::Maybe<bool> set_result = |
| + css_array->Set(context, i, gin::StringToSymbol(isolate, parsed)); |
| + if (!set_result.IsJust() || !set_result.FromJust()) |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +// Validates the source object against the expected spec, and copies over values |
| +// to |this|. Returns true on success. |
| +bool Validate(const ArgumentSpec* spec, |
| + const APITypeReferenceMap& type_refs, |
| + v8::Local<v8::Context> context, |
| + v8::Local<v8::Object> this_object, |
| + v8::Local<v8::Object> source_object, |
| + const std::string& type_name, |
| + std::string* error) { |
| + if (!source_object.IsEmpty() && |
| + !V8Assign(context, source_object, this_object)) { |
| + return false; |
| + } |
| + |
| + v8::Isolate* isolate = context->GetIsolate(); |
| + v8::Maybe<bool> set_result = this_object->CreateDataProperty( |
| + context, gin::StringToSymbol(isolate, "instanceType"), |
| + gin::StringToSymbol(isolate, type_name)); |
| + if (!set_result.IsJust() || !set_result.FromJust()) { |
| + return false; |
| + } |
| + |
| + if (!spec->ParseArgument(context, this_object, type_refs, nullptr, error)) { |
| + return false; |
| + } |
| + |
| + if (type_name == "declarativeContent.PageStateMatcher" && |
| + !CanonicalizeCssSelectors(context, this_object, error)) { |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +DeclarativeContentHooksDelegate::DeclarativeContentHooksDelegate() |
| + : weak_factory_(this) {} |
| + |
| +DeclarativeContentHooksDelegate::~DeclarativeContentHooksDelegate() {} |
| + |
| +void DeclarativeContentHooksDelegate::InitializeTemplate( |
| + v8::Isolate* isolate, |
| + v8::Local<v8::ObjectTemplate> object_template, |
| + const APITypeReferenceMap& type_refs) { |
| + // Add constructors for the API types. |
| + // TODO(devlin): We'll need to extract out common logic here and share it with |
| + // declarativeWebRequest. |
| + struct { |
| + const char* full_name; |
| + const char* exposed_name; |
| + } kTypes[] = { |
| + {"declarativeContent.PageStateMatcher", "PageStateMatcher"}, |
| + {"declarativeContent.ShowPageAction", "ShowPageAction"}, |
| + {"declarativeContent.SetIcon", "SetIcon"}, |
| + {"declarativeContent.RequestContentScript", "RequestContentScript"}, |
| + }; |
| + for (const auto type : kTypes) { |
| + const ArgumentSpec* spec = type_refs.GetSpec(type.full_name); |
| + DCHECK(spec); |
| + callbacks_.push_back(base::MakeUnique<HandlerCallback>(base::Bind( |
| + &DeclarativeContentHooksDelegate::HandleCall, |
|
jbroman
2017/05/01 20:16:40
This seems slightly paradoxical. If the Declarativ
Devlin
2017/05/01 20:46:48
Fair enough - habit. Removed.
|
| + weak_factory_.GetWeakPtr(), spec, &type_refs, type.full_name))); |
| + object_template->Set( |
| + gin::StringToSymbol(isolate, type.exposed_name), |
| + v8::FunctionTemplate::New( |
| + isolate, &CallbackHelper, |
| + v8::External::New(isolate, callbacks_.back().get()))); |
| + } |
| +} |
| + |
| +void DeclarativeContentHooksDelegate::HandleCall( |
| + const ArgumentSpec* spec, |
| + const APITypeReferenceMap* type_refs, |
| + const std::string& type_name, |
| + const v8::FunctionCallbackInfo<v8::Value>& info) { |
| + gin::Arguments arguments(info); |
| + v8::Isolate* isolate = arguments.isolate(); |
| + v8::HandleScope handle_scope(isolate); |
| + v8::Local<v8::Context> context = isolate->GetCurrentContext(); |
| + |
| + // TODO(devlin): It would be pretty nice to be able to throw an error if |
| + // Arguments::IsConstructCall() is false. That would ensure that the caller |
| + // used `new declarativeContent.Foo()`, which is a) the documented approach |
| + // and b) allows us (more) confidence that the |this| object we receive is |
| + // an unmodified instance. But we don't know how many extensions enforcing |
| + // that may break, and it's also incompatible with SetIcon(). |
| + |
| + v8::Local<v8::Object> this_object = info.This(); |
| + if (this_object.IsEmpty()) { |
| + // Crazy script (e.g. declarativeContent.Foo.apply(null, args);). |
| + NOTREACHED(); |
| + return; |
| + } |
| + |
| + // TODO(devlin): Find a way to use APISignature here? It's a little awkward |
| + // because of undocumented expected properties like instanceType and not |
| + // requiring an argument at all. We may need a better way of expressing these |
| + // in the JSON schema. |
| + std::vector<v8::Local<v8::Value>> argument_list = arguments.GetAll(); |
| + if (argument_list.size() > 1) { |
| + arguments.ThrowTypeError("Invalid invocation."); |
|
Devlin
2017/05/01 19:29:45
Note: will rebase and use the new API invocation e
|
| + return; |
| + } |
| + |
| + v8::Local<v8::Object> properties; |
| + if (!argument_list.empty()) { |
| + if (!argument_list[0]->IsObject()) { |
| + arguments.ThrowTypeError("Invalid invocation."); |
| + return; |
| + } |
| + properties = argument_list[0].As<v8::Object>(); |
| + } |
| + |
| + std::string error; |
| + bool success = false; |
| + { |
| + v8::TryCatch try_catch(isolate); |
| + success = Validate(spec, *type_refs, context, this_object, properties, |
| + type_name, &error); |
| + if (try_catch.HasCaught()) { |
| + try_catch.ReThrow(); |
| + return; |
| + } |
| + } |
| + |
| + if (!success) |
| + arguments.ThrowTypeError("Invalid invocation: " + error); |
| +} |
| + |
| +} // namespace extensions |