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->IsUint32()); | |
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"); | |
lazyboy
2017/05/03 18:47:00
declarative_content_constants::kCss
Devlin
2017/05/03 22:55:02
Done. (Note: this meant moving the file, which wa
| |
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( | |
lazyboy
2017/05/03 18:47:00
note: The blink::CanonicalizeSelector call from cs
Devlin
2017/05/03 22:55:02
Added a note.
| |
94 blink::WebString::FromUTF8(*selector, selector.length()), | |
95 blink::kWebSelectorTypeCompound) | |
96 .Utf8(); | |
97 if (parsed.empty()) { | |
98 *error = | |
99 "Invalid css selector: " + std::string(*selector, selector.length()); | |
lazyboy
2017/05/03 18:47:00
nit: s/css/CSS since we're not talking about the "
Devlin
2017/05/03 22:55:01
Done.
| |
100 return false; | |
101 } | |
102 v8::Maybe<bool> set_result = | |
103 css_array->Set(context, i, gin::StringToSymbol(isolate, parsed)); | |
104 if (!set_result.IsJust() || !set_result.FromJust()) | |
105 return false; | |
106 } | |
107 | |
108 return true; | |
109 } | |
110 | |
111 // Validates the source object against the expected spec, and copies over values | |
112 // to |this_object|. Returns true on success. | |
113 bool Validate(const ArgumentSpec* spec, | |
114 const APITypeReferenceMap& type_refs, | |
115 v8::Local<v8::Context> context, | |
116 v8::Local<v8::Object> this_object, | |
117 v8::Local<v8::Object> source_object, | |
118 const std::string& type_name, | |
119 std::string* error) { | |
120 if (!source_object.IsEmpty() && | |
121 !V8Assign(context, source_object, this_object)) { | |
122 return false; | |
123 } | |
124 | |
125 v8::Isolate* isolate = context->GetIsolate(); | |
126 v8::Maybe<bool> set_result = this_object->CreateDataProperty( | |
127 context, gin::StringToSymbol(isolate, "instanceType"), | |
lazyboy
2017/05/03 18:47:01
declarative_content_constants::kInstanceType
Devlin
2017/05/03 22:55:01
Done.
| |
128 gin::StringToSymbol(isolate, type_name)); | |
129 if (!set_result.IsJust() || !set_result.FromJust()) { | |
130 return false; | |
131 } | |
132 | |
133 if (!spec->ParseArgument(context, this_object, type_refs, nullptr, error)) { | |
134 return false; | |
135 } | |
136 | |
137 if (type_name == "declarativeContent.PageStateMatcher" && | |
lazyboy
2017/05/03 18:47:00
The full names match declarative_contents_constant
Devlin
2017/05/03 22:55:02
Done.
| |
138 !CanonicalizeCssSelectors(context, this_object, error)) { | |
139 return false; | |
140 } | |
141 return true; | |
142 } | |
143 | |
144 } // namespace | |
145 | |
146 DeclarativeContentHooksDelegate::DeclarativeContentHooksDelegate() {} | |
147 DeclarativeContentHooksDelegate::~DeclarativeContentHooksDelegate() {} | |
148 | |
149 void DeclarativeContentHooksDelegate::InitializeTemplate( | |
150 v8::Isolate* isolate, | |
151 v8::Local<v8::ObjectTemplate> object_template, | |
152 const APITypeReferenceMap& type_refs) { | |
153 // Add constructors for the API types. | |
154 // TODO(devlin): We'll need to extract out common logic here and share it with | |
155 // declarativeWebRequest. | |
156 struct { | |
157 const char* full_name; | |
158 const char* exposed_name; | |
159 } kTypes[] = { | |
160 {"declarativeContent.PageStateMatcher", "PageStateMatcher"}, | |
161 {"declarativeContent.ShowPageAction", "ShowPageAction"}, | |
162 {"declarativeContent.SetIcon", "SetIcon"}, | |
163 {"declarativeContent.RequestContentScript", "RequestContentScript"}, | |
164 }; | |
165 for (const auto& type : kTypes) { | |
166 const ArgumentSpec* spec = type_refs.GetSpec(type.full_name); | |
167 DCHECK(spec); | |
168 // This object should outlive any calls to the function, so this | |
169 // base::Unretained and the callback itself are safe. Similarly, the same | |
170 // bindings system owns all these objects, so the spec and type refs should | |
171 // also be safe. | |
172 callbacks_.push_back(base::MakeUnique<HandlerCallback>( | |
lazyboy
2017/05/03 18:47:01
At size 4, the vector is going to most likely real
Devlin
2017/05/03 22:55:01
Done.
| |
173 base::Bind(&DeclarativeContentHooksDelegate::HandleCall, | |
174 base::Unretained(this), spec, &type_refs, type.full_name))); | |
175 object_template->Set( | |
176 gin::StringToSymbol(isolate, type.exposed_name), | |
177 v8::FunctionTemplate::New( | |
178 isolate, &CallbackHelper, | |
179 v8::External::New(isolate, callbacks_.back().get()))); | |
180 } | |
181 } | |
182 | |
183 void DeclarativeContentHooksDelegate::HandleCall( | |
184 const ArgumentSpec* spec, | |
185 const APITypeReferenceMap* type_refs, | |
186 const std::string& type_name, | |
187 const v8::FunctionCallbackInfo<v8::Value>& info) { | |
188 gin::Arguments arguments(info); | |
189 v8::Isolate* isolate = arguments.isolate(); | |
190 v8::HandleScope handle_scope(isolate); | |
191 v8::Local<v8::Context> context = isolate->GetCurrentContext(); | |
192 | |
193 // TODO(devlin): It would be pretty nice to be able to throw an error if | |
194 // Arguments::IsConstructCall() is false. That would ensure that the caller | |
195 // used `new declarativeContent.Foo()`, which is a) the documented approach | |
196 // and b) allows us (more) confidence that the |this| object we receive is | |
197 // an unmodified instance. But we don't know how many extensions enforcing | |
198 // that may break, and it's also incompatible with SetIcon(). | |
199 | |
200 v8::Local<v8::Object> this_object = info.This(); | |
201 if (this_object.IsEmpty()) { | |
202 // Crazy script (e.g. declarativeContent.Foo.apply(null, args);). | |
203 NOTREACHED(); | |
204 return; | |
205 } | |
206 | |
207 // TODO(devlin): Find a way to use APISignature here? It's a little awkward | |
208 // because of undocumented expected properties like instanceType and not | |
209 // requiring an argument at all. We may need a better way of expressing these | |
210 // in the JSON schema. | |
211 if (arguments.Length() > 1) { | |
212 arguments.ThrowTypeError("Invalid invocation."); | |
213 return; | |
214 } | |
215 | |
216 v8::Local<v8::Object> properties; | |
217 if (arguments.Length() == 1 && !arguments.GetNext(&properties)) { | |
218 arguments.ThrowTypeError("Invalid invocation."); | |
219 return; | |
220 } | |
221 | |
222 std::string error; | |
223 bool success = false; | |
224 { | |
225 v8::TryCatch try_catch(isolate); | |
226 success = Validate(spec, *type_refs, context, this_object, properties, | |
227 type_name, &error); | |
228 if (try_catch.HasCaught()) { | |
229 try_catch.ReThrow(); | |
230 return; | |
231 } | |
232 } | |
233 | |
234 if (!success) | |
235 arguments.ThrowTypeError("Invalid invocation: " + error); | |
236 } | |
237 | |
238 } // namespace extensions | |
OLD | NEW |