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