Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 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/declarative_content_hooks_delegate.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/memory/ptr_util.h" | |
| 9 #include "extensions/renderer/api_type_reference_map.h" | |
| 10 #include "extensions/renderer/argument_spec.h" | |
| 11 #include "gin/arguments.h" | |
| 12 #include "gin/converter.h" | |
| 13 #include "third_party/WebKit/public/platform/WebString.h" | |
| 14 #include "third_party/WebKit/public/web/WebSelector.h" | |
| 15 | |
| 16 namespace extensions { | |
| 17 | |
| 18 namespace { | |
| 19 | |
| 20 void CallbackHelper(const v8::FunctionCallbackInfo<v8::Value>& info) { | |
| 21 CHECK(info.Data()->IsExternal()); | |
| 22 v8::Local<v8::External> external = info.Data().As<v8::External>(); | |
| 23 auto* callback = | |
| 24 static_cast<DeclarativeContentHooksDelegate::HandlerCallback*>( | |
| 25 external->Value()); | |
| 26 callback->Run(info); | |
| 27 } | |
| 28 | |
| 29 // Copies the 'own' properties from src -> dst. | |
| 30 bool V8Assign(v8::Local<v8::Context> context, | |
| 31 v8::Local<v8::Object> src, | |
| 32 v8::Local<v8::Object> dst) { | |
| 33 v8::Local<v8::Array> own_property_names; | |
| 34 if (!src->GetOwnPropertyNames(context).ToLocal(&own_property_names)) | |
| 35 return false; | |
| 36 | |
| 37 uint32_t length = own_property_names->Length(); | |
| 38 for (uint32_t i = 0; i < length; ++i) { | |
| 39 v8::Local<v8::Value> key; | |
| 40 if (!own_property_names->Get(context, i).ToLocal(&key)) | |
| 41 return false; | |
| 42 DCHECK(key->IsString() || key->IsNumber()); | |
|
jbroman
2017/05/02 18:36:34
nit: might as well narrow IsNumber to IsUint32, si
Devlin
2017/05/02 20:51:44
Done.
| |
| 43 | |
| 44 v8::Local<v8::Value> prop_value; | |
| 45 if (!src->Get(context, key).ToLocal(&prop_value)) | |
| 46 return false; | |
| 47 | |
| 48 v8::Maybe<bool> success = | |
| 49 key->IsString() | |
| 50 ? dst->CreateDataProperty(context, key.As<v8::String>(), prop_value) | |
| 51 : dst->CreateDataProperty(context, key.As<v8::Uint32>()->Value(), | |
| 52 prop_value); | |
| 53 if (!success.IsJust() || !success.FromJust()) | |
| 54 return false; | |
| 55 } | |
| 56 | |
| 57 return true; | |
| 58 } | |
| 59 | |
| 60 // Canonicalizes any css selectors specified in a page state matcher, returning | |
| 61 // true on success. | |
| 62 bool CanonicalizeCssSelectors(v8::Local<v8::Context> context, | |
| 63 v8::Local<v8::Object> object, | |
| 64 std::string* error) { | |
| 65 v8::Isolate* isolate = context->GetIsolate(); | |
| 66 v8::Local<v8::String> key = gin::StringToSymbol(isolate, "css"); | |
| 67 v8::Maybe<bool> has_css = object->HasOwnProperty(context, key); | |
| 68 // Note: don't bother populating |error| if script threw an exception. | |
| 69 if (!has_css.IsJust()) | |
| 70 return false; | |
| 71 | |
| 72 if (!has_css.FromJust()) | |
| 73 return true; | |
| 74 | |
| 75 v8::Local<v8::Value> css; | |
| 76 if (!object->Get(context, key).ToLocal(&css)) | |
| 77 return false; | |
| 78 | |
| 79 if (css->IsUndefined()) | |
| 80 return true; | |
| 81 | |
| 82 if (!css->IsArray()) | |
| 83 return false; | |
| 84 | |
| 85 v8::Local<v8::Array> css_array = css.As<v8::Array>(); | |
| 86 uint32_t length = css_array->Length(); | |
| 87 for (uint32_t i = 0; i < length; ++i) { | |
| 88 v8::Local<v8::Value> val; | |
| 89 if (!css_array->Get(context, i).ToLocal(&val) || !val->IsString()) | |
| 90 return false; | |
| 91 v8::String::Utf8Value selector(val.As<v8::String>()); | |
| 92 std::string parsed = | |
| 93 blink::CanonicalizeSelector( | |
| 94 blink::WebString::FromUTF8(*selector, selector.length()), | |
| 95 blink::kWebSelectorTypeCompound) | |
| 96 .Utf8(); | |
| 97 if (parsed.empty()) { | |
| 98 *error = "Invalid css: " + std::string(*selector, selector.length()); | |
|
jbroman
2017/05/02 18:36:34
nit: "Invalid CSS selector", maybe?
Devlin
2017/05/02 20:51:44
Done.
| |
| 99 return false; | |
| 100 } | |
| 101 v8::Maybe<bool> set_result = | |
| 102 css_array->Set(context, i, gin::StringToSymbol(isolate, parsed)); | |
| 103 if (!set_result.IsJust() || !set_result.FromJust()) | |
| 104 return false; | |
| 105 } | |
| 106 | |
| 107 return true; | |
| 108 } | |
| 109 | |
| 110 // Validates the source object against the expected spec, and copies over values | |
| 111 // to |this|. Returns true on success. | |
|
jbroman
2017/05/02 18:36:34
nit: |this_object|, since that's what it's called
Devlin
2017/05/02 20:51:44
Whoops, done.
| |
| 112 bool Validate(const ArgumentSpec* spec, | |
| 113 const APITypeReferenceMap& type_refs, | |
| 114 v8::Local<v8::Context> context, | |
| 115 v8::Local<v8::Object> this_object, | |
| 116 v8::Local<v8::Object> source_object, | |
| 117 const std::string& type_name, | |
| 118 std::string* error) { | |
| 119 if (!source_object.IsEmpty() && | |
| 120 !V8Assign(context, source_object, this_object)) { | |
| 121 return false; | |
| 122 } | |
| 123 | |
| 124 v8::Isolate* isolate = context->GetIsolate(); | |
| 125 v8::Maybe<bool> set_result = this_object->CreateDataProperty( | |
| 126 context, gin::StringToSymbol(isolate, "instanceType"), | |
| 127 gin::StringToSymbol(isolate, type_name)); | |
| 128 if (!set_result.IsJust() || !set_result.FromJust()) { | |
| 129 return false; | |
| 130 } | |
| 131 | |
| 132 if (!spec->ParseArgument(context, this_object, type_refs, nullptr, error)) { | |
| 133 return false; | |
| 134 } | |
| 135 | |
| 136 if (type_name == "declarativeContent.PageStateMatcher" && | |
| 137 !CanonicalizeCssSelectors(context, this_object, error)) { | |
|
jbroman
2017/05/02 18:36:34
Is there test coverage for this? I guess it will b
Devlin
2017/05/02 20:51:43
Yep, that was my plan. Since this is more just an
| |
| 138 return false; | |
| 139 } | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 } // namespace | |
| 144 | |
| 145 DeclarativeContentHooksDelegate::DeclarativeContentHooksDelegate() {} | |
| 146 DeclarativeContentHooksDelegate::~DeclarativeContentHooksDelegate() {} | |
| 147 | |
| 148 void DeclarativeContentHooksDelegate::InitializeTemplate( | |
| 149 v8::Isolate* isolate, | |
| 150 v8::Local<v8::ObjectTemplate> object_template, | |
| 151 const APITypeReferenceMap& type_refs) { | |
| 152 // Add constructors for the API types. | |
| 153 // TODO(devlin): We'll need to extract out common logic here and share it with | |
| 154 // declarativeWebRequest. | |
| 155 struct { | |
| 156 const char* full_name; | |
| 157 const char* exposed_name; | |
| 158 } kTypes[] = { | |
| 159 {"declarativeContent.PageStateMatcher", "PageStateMatcher"}, | |
| 160 {"declarativeContent.ShowPageAction", "ShowPageAction"}, | |
| 161 {"declarativeContent.SetIcon", "SetIcon"}, | |
| 162 {"declarativeContent.RequestContentScript", "RequestContentScript"}, | |
| 163 }; | |
| 164 for (const auto type : kTypes) { | |
|
jbroman
2017/05/02 18:36:33
nit: const auto&
Devlin
2017/05/02 20:51:44
Whoops, done.
| |
| 165 const ArgumentSpec* spec = type_refs.GetSpec(type.full_name); | |
| 166 DCHECK(spec); | |
| 167 // This object should outlive any calls to the function, so this | |
| 168 // base::Unretained is safe. | |
|
jbroman
2017/05/02 18:36:33
And spec, type_refs, and type, and the callback it
Devlin
2017/05/02 20:51:43
Done.
| |
| 169 callbacks_.push_back(base::MakeUnique<HandlerCallback>( | |
| 170 base::Bind(&DeclarativeContentHooksDelegate::HandleCall, | |
| 171 base::Unretained(this), spec, &type_refs, type.full_name))); | |
| 172 object_template->Set( | |
| 173 gin::StringToSymbol(isolate, type.exposed_name), | |
| 174 v8::FunctionTemplate::New( | |
| 175 isolate, &CallbackHelper, | |
| 176 v8::External::New(isolate, callbacks_.back().get()))); | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 void DeclarativeContentHooksDelegate::HandleCall( | |
| 181 const ArgumentSpec* spec, | |
| 182 const APITypeReferenceMap* type_refs, | |
| 183 const std::string& type_name, | |
| 184 const v8::FunctionCallbackInfo<v8::Value>& info) { | |
| 185 gin::Arguments arguments(info); | |
| 186 v8::Isolate* isolate = arguments.isolate(); | |
| 187 v8::HandleScope handle_scope(isolate); | |
| 188 v8::Local<v8::Context> context = isolate->GetCurrentContext(); | |
| 189 | |
| 190 // TODO(devlin): It would be pretty nice to be able to throw an error if | |
| 191 // Arguments::IsConstructCall() is false. That would ensure that the caller | |
| 192 // used `new declarativeContent.Foo()`, which is a) the documented approach | |
| 193 // and b) allows us (more) confidence that the |this| object we receive is | |
| 194 // an unmodified instance. But we don't know how many extensions enforcing | |
|
jbroman
2017/05/02 18:36:34
Script can still break that, e.g. by using Reflect
Devlin
2017/05/02 20:51:43
Hmm... interesting. Could we just check info.NewT
jbroman
2017/05/02 23:09:08
I think you can use v8::Signature to ensure that i
| |
| 195 // that may break, and it's also incompatible with SetIcon(). | |
| 196 | |
| 197 v8::Local<v8::Object> this_object = info.This(); | |
| 198 if (this_object.IsEmpty()) { | |
| 199 // Crazy script (e.g. declarativeContent.Foo.apply(null, args);). | |
| 200 NOTREACHED(); | |
| 201 return; | |
| 202 } | |
| 203 | |
| 204 // TODO(devlin): Find a way to use APISignature here? It's a little awkward | |
| 205 // because of undocumented expected properties like instanceType and not | |
| 206 // requiring an argument at all. We may need a better way of expressing these | |
| 207 // in the JSON schema. | |
| 208 std::vector<v8::Local<v8::Value>> argument_list = arguments.GetAll(); | |
|
jbroman
2017/05/02 18:36:34
nit: slightly shorter (don't need the vector for a
Devlin
2017/05/02 20:51:43
Nah, that should work. Done.
| |
| 209 if (argument_list.size() > 1) { | |
| 210 arguments.ThrowTypeError("Invalid invocation."); | |
| 211 return; | |
| 212 } | |
| 213 | |
| 214 v8::Local<v8::Object> properties; | |
| 215 if (!argument_list.empty()) { | |
| 216 if (!argument_list[0]->IsObject()) { | |
| 217 arguments.ThrowTypeError("Invalid invocation."); | |
| 218 return; | |
| 219 } | |
| 220 properties = argument_list[0].As<v8::Object>(); | |
| 221 } | |
| 222 | |
| 223 std::string error; | |
| 224 bool success = false; | |
| 225 { | |
| 226 v8::TryCatch try_catch(isolate); | |
| 227 success = Validate(spec, *type_refs, context, this_object, properties, | |
| 228 type_name, &error); | |
| 229 if (try_catch.HasCaught()) { | |
| 230 try_catch.ReThrow(); | |
| 231 return; | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 if (!success) | |
| 236 arguments.ThrowTypeError("Invalid invocation: " + error); | |
| 237 } | |
| 238 | |
| 239 } // namespace extensions | |
| OLD | NEW |