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/bindings/api_last_error.h"
6
7 #include "gin/converter.h"
8 #include "gin/data_object_builder.h"
9 #include "gin/handle.h"
10 #include "gin/object_template_builder.h"
11 #include "gin/wrappable.h"
12
13 namespace extensions {
14
15 namespace {
16
17 constexpr char kLastErrorProperty[] = "lastError";
18 constexpr char kScriptSuppliedValueKey[] = "script_supplied_value";
19 constexpr char kUncheckedErrorPrefix[] = "Unchecked runtime.lastError: ";
20
21 // The object corresponding to the lastError property, containing a single
22 // property ('message') with the last error. This object is stored on the parent
23 // (chrome.runtime in production) as a private property, and is returned via an
24 // accessor which marks the error as accessed.
25 class LastErrorObject final : public gin::Wrappable<LastErrorObject> {
26 public:
LastErrorObject(const std::string & error)27 explicit LastErrorObject(const std::string& error) : error_(error) {}
28
29 static gin::WrapperInfo kWrapperInfo;
30
31 // gin::Wrappable:
GetObjectTemplateBuilder(v8::Isolate * isolate)32 gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
33 v8::Isolate* isolate) override {
34 DCHECK(isolate);
35 return Wrappable<LastErrorObject>::GetObjectTemplateBuilder(isolate)
36 .SetProperty("message", &LastErrorObject::error);
37 }
38
Reset(const std::string & error)39 void Reset(const std::string& error) {
40 error_ = error;
41 accessed_ = false;
42 }
43
error() const44 const std::string& error() const { return error_; }
accessed() const45 bool accessed() const { return accessed_; }
set_accessed()46 void set_accessed() { accessed_ = true; }
47
48 private:
49 std::string error_;
50 bool accessed_ = false;
51
52 DISALLOW_COPY_AND_ASSIGN(LastErrorObject);
53 };
54
55 gin::WrapperInfo LastErrorObject::kWrapperInfo = {gin::kEmbedderNativeGin};
56
57 // An accessor to retrieve the last error property (curried in through data),
58 // and mark it as accessed.
LastErrorGetter(v8::Local<v8::Name> property,const v8::PropertyCallbackInfo<v8::Value> & info)59 void LastErrorGetter(v8::Local<v8::Name> property,
60 const v8::PropertyCallbackInfo<v8::Value>& info) {
61 v8::Isolate* isolate = info.GetIsolate();
62 v8::HandleScope handle_scope(isolate);
63 v8::Local<v8::Object> holder = info.Holder();
64 v8::Local<v8::Context> context = holder->CreationContext();
65
66 v8::Local<v8::Value> last_error;
67 v8::Local<v8::Private> last_error_key = v8::Private::ForApi(
68 isolate, gin::StringToSymbol(isolate, kLastErrorProperty));
69 if (!holder->GetPrivate(context, last_error_key).ToLocal(&last_error) ||
70 last_error != info.Data()) {
71 // Something funny happened - our private properties aren't set right.
72 NOTREACHED();
73 return;
74 }
75
76 v8::Local<v8::Value> return_value;
77
78 // It's possible that some script has set their own value for the last error
79 // property. If so, return that. Otherwise, return the real last error.
80 v8::Local<v8::Private> script_value_key = v8::Private::ForApi(
81 isolate, gin::StringToSymbol(isolate, kScriptSuppliedValueKey));
82 v8::Local<v8::Value> script_value;
83 if (holder->GetPrivate(context, script_value_key).ToLocal(&script_value) &&
84 !script_value->IsUndefined()) {
85 return_value = script_value;
86 } else {
87 LastErrorObject* last_error_obj = nullptr;
88 CHECK(gin::Converter<LastErrorObject*>::FromV8(isolate, last_error,
89 &last_error_obj));
90 last_error_obj->set_accessed();
91 return_value = last_error;
92 }
93
94 info.GetReturnValue().Set(return_value);
95 }
96
97 // Allow script to set the last error property.
LastErrorSetter(v8::Local<v8::Name> property,v8::Local<v8::Value> value,const v8::PropertyCallbackInfo<void> & info)98 void LastErrorSetter(v8::Local<v8::Name> property,
99 v8::Local<v8::Value> value,
100 const v8::PropertyCallbackInfo<void>& info) {
101 v8::Isolate* isolate = info.GetIsolate();
102 v8::HandleScope handle_scope(isolate);
103 v8::Local<v8::Object> holder = info.Holder();
104 v8::Local<v8::Context> context = holder->CreationContext();
105
106 v8::Local<v8::Private> script_value_key = v8::Private::ForApi(
107 isolate, gin::StringToSymbol(isolate, kScriptSuppliedValueKey));
108 v8::Maybe<bool> set_private =
109 holder->SetPrivate(context, script_value_key, value);
110 if (!set_private.IsJust() || !set_private.FromJust())
111 NOTREACHED();
112 }
113
114 } // namespace
115
APILastError(GetParent get_parent,binding::AddConsoleError add_console_error)116 APILastError::APILastError(GetParent get_parent,
117 binding::AddConsoleError add_console_error)
118 : get_parent_(std::move(get_parent)),
119 add_console_error_(std::move(add_console_error)) {}
120 APILastError::APILastError(APILastError&& other) = default;
121 APILastError::~APILastError() = default;
122
SetError(v8::Local<v8::Context> context,const std::string & error)123 void APILastError::SetError(v8::Local<v8::Context> context,
124 const std::string& error) {
125 v8::Isolate* isolate = context->GetIsolate();
126 DCHECK(isolate);
127 v8::HandleScope handle_scope(isolate);
128
129 // The various accesses/sets on an object could potentially fail if script has
130 // set any crazy interceptors. For the most part, we don't care about behaving
131 // perfectly in these circumstances, but we eat the exception so callers don't
132 // have to worry about it. We also SetVerbose() so that developers will have a
133 // clue what happened if this does arise.
134 // TODO(devlin): Whether or not this needs to be verbose is debatable.
135 v8::TryCatch try_catch(isolate);
136 try_catch.SetVerbose(true);
137
138 v8::Local<v8::Object> secondary_parent;
139 v8::Local<v8::Object> parent = get_parent_.Run(context, &secondary_parent);
140
141 SetErrorOnPrimaryParent(context, parent, error);
142 SetErrorOnSecondaryParent(context, secondary_parent, error);
143 }
144
ClearError(v8::Local<v8::Context> context,bool report_if_unchecked)145 void APILastError::ClearError(v8::Local<v8::Context> context,
146 bool report_if_unchecked) {
147 v8::Isolate* isolate = context->GetIsolate();
148 v8::HandleScope handle_scope(isolate);
149
150 v8::Local<v8::Object> parent;
151 v8::Local<v8::Object> secondary_parent;
152 LastErrorObject* last_error = nullptr;
153 v8::Local<v8::String> key;
154 v8::Local<v8::Private> private_key;
155 {
156 // See comment in SetError().
157 v8::TryCatch try_catch(isolate);
158 try_catch.SetVerbose(true);
159
160 parent = get_parent_.Run(context, &secondary_parent);
161 if (parent.IsEmpty())
162 return;
163 key = gin::StringToSymbol(isolate, kLastErrorProperty);
164 private_key = v8::Private::ForApi(isolate, key);
165 v8::Local<v8::Value> error;
166 // Access through GetPrivate() so that we don't trigger accessed().
167 if (!parent->GetPrivate(context, private_key).ToLocal(&error) ||
168 !gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error,
169 &last_error)) {
170 return;
171 }
172 }
173
174 if (report_if_unchecked && !last_error->accessed())
175 ReportUncheckedError(context, last_error->error());
176
177 // See comment in SetError().
178 v8::TryCatch try_catch(isolate);
179 try_catch.SetVerbose(true);
180
181 v8::Maybe<bool> delete_private = parent->DeletePrivate(context, private_key);
182 if (!delete_private.IsJust() || !delete_private.FromJust()) {
183 NOTREACHED();
184 return;
185 }
186 // These Delete()s can fail, but there's nothing to do if it does (the
187 // exception will be caught by the TryCatch above).
188 ignore_result(parent->Delete(context, key));
189 if (!secondary_parent.IsEmpty())
190 ignore_result(secondary_parent->Delete(context, key));
191 }
192
HasError(v8::Local<v8::Context> context)193 bool APILastError::HasError(v8::Local<v8::Context> context) {
194 v8::Isolate* isolate = context->GetIsolate();
195 v8::HandleScope handle_scope(isolate);
196
197 // See comment in SetError().
198 v8::TryCatch try_catch(isolate);
199 try_catch.SetVerbose(true);
200
201 v8::Local<v8::Object> parent = get_parent_.Run(context, nullptr);
202 if (parent.IsEmpty())
203 return false;
204 v8::Local<v8::Value> error;
205 v8::Local<v8::Private> key = v8::Private::ForApi(
206 isolate, gin::StringToSymbol(isolate, kLastErrorProperty));
207 // Access through GetPrivate() so we don't trigger accessed().
208 if (!parent->GetPrivate(context, key).ToLocal(&error))
209 return false;
210
211 LastErrorObject* last_error = nullptr;
212 return gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error,
213 &last_error);
214 }
215
ReportUncheckedError(v8::Local<v8::Context> context,const std::string & error)216 void APILastError::ReportUncheckedError(v8::Local<v8::Context> context,
217 const std::string& error) {
218 add_console_error_.Run(context, kUncheckedErrorPrefix + error);
219 }
220
SetErrorOnPrimaryParent(v8::Local<v8::Context> context,v8::Local<v8::Object> parent,const std::string & error)221 void APILastError::SetErrorOnPrimaryParent(v8::Local<v8::Context> context,
222 v8::Local<v8::Object> parent,
223 const std::string& error) {
224 if (parent.IsEmpty())
225 return;
226 v8::Isolate* isolate = context->GetIsolate();
227 v8::Local<v8::String> key = gin::StringToSymbol(isolate, kLastErrorProperty);
228 v8::Local<v8::Value> v8_error;
229 // Two notes: this Get() is visible to external script, and this will actually
230 // mark the lastError as accessed, if one exists. These shouldn't be a
231 // problem (lastError is meant to be helpful, but isn't designed to handle
232 // crazy chaining, etc). However, if we decide we needed to be fancier, we
233 // could detect the presence of a current error through a GetPrivate(), and
234 // optionally throw it if one exists.
235 if (!parent->Get(context, key).ToLocal(&v8_error))
236 return;
237
238 if (!v8_error->IsUndefined()) {
239 // There may be an existing last error to overwrite.
240 LastErrorObject* last_error = nullptr;
241 if (!gin::Converter<LastErrorObject*>::FromV8(isolate, v8_error,
242 &last_error)) {
243 // If it's not a real lastError (e.g. if a script manually set it), don't
244 // do anything. We shouldn't mangle a property set by other script.
245 // TODO(devlin): Or should we? If someone sets chrome.runtime.lastError,
246 // it might be the right course of action to overwrite it.
247 return;
248 }
249 last_error->Reset(error);
250 } else {
251 v8::Local<v8::Value> last_error =
252 gin::CreateHandle(isolate, new LastErrorObject(error)).ToV8();
253 v8::Maybe<bool> set_private = parent->SetPrivate(
254 context, v8::Private::ForApi(isolate, key), last_error);
255 if (!set_private.IsJust() || !set_private.FromJust()) {
256 NOTREACHED();
257 return;
258 }
259 DCHECK(!last_error.IsEmpty());
260 // This SetAccessor() can fail, but there's nothing to do if it does (the
261 // exception will be caught by the TryCatch in SetError()).
262 ignore_result(parent->SetAccessor(context, key, &LastErrorGetter,
263 &LastErrorSetter, last_error));
264 }
265 }
266
SetErrorOnSecondaryParent(v8::Local<v8::Context> context,v8::Local<v8::Object> secondary_parent,const std::string & error)267 void APILastError::SetErrorOnSecondaryParent(
268 v8::Local<v8::Context> context,
269 v8::Local<v8::Object> secondary_parent,
270 const std::string& error) {
271 if (secondary_parent.IsEmpty())
272 return;
273
274 // For the secondary parent, simply set chrome.extension.lastError to
275 // {message: <error>}.
276 // TODO(devlin): Gather metrics on how frequently this is checked. It'd be
277 // nice to get rid of it.
278 v8::Isolate* isolate = context->GetIsolate();
279 v8::Local<v8::String> key = gin::StringToSymbol(isolate, kLastErrorProperty);
280 // This CreateDataProperty() can fail, but there's nothing to do if it does
281 // (the exception will be caught by the TryCatch in SetError()).
282 ignore_result(secondary_parent->CreateDataProperty(
283 context, key,
284 gin::DataObjectBuilder(isolate).Set("message", error).Build()));
285 }
286
287 } // namespace extensions
288