Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(369)

Side by Side Diff: extensions/renderer/api_last_error.cc

Issue 2953133002: [Extensions Bindings] Support chrome.extension.lastError (Closed)
Patch Set: . Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "extensions/renderer/api_last_error.h" 5 #include "extensions/renderer/api_last_error.h"
6 6
7 #include "gin/converter.h" 7 #include "gin/converter.h"
8 #include "gin/data_object_builder.h"
8 #include "gin/handle.h" 9 #include "gin/handle.h"
9 #include "gin/object_template_builder.h" 10 #include "gin/object_template_builder.h"
10 #include "gin/wrappable.h" 11 #include "gin/wrappable.h"
11 12
12 namespace extensions { 13 namespace extensions {
13 14
14 namespace { 15 namespace {
15 16
16 const char kLastErrorProperty[] = "lastError"; 17 const char kLastErrorProperty[] = "lastError";
17 const char kScriptSuppliedValueKey[] = "script_supplied_value"; 18 const char kScriptSuppliedValueKey[] = "script_supplied_value";
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
125 126
126 // The various accesses/sets on an object could potentially fail if script has 127 // The various accesses/sets on an object could potentially fail if script has
127 // set any crazy interceptors. For the most part, we don't care about behaving 128 // set any crazy interceptors. For the most part, we don't care about behaving
128 // perfectly in these circumstances, but we eat the exception so callers don't 129 // perfectly in these circumstances, but we eat the exception so callers don't
129 // have to worry about it. We also SetVerbose() so that developers will have a 130 // have to worry about it. We also SetVerbose() so that developers will have a
130 // clue what happened if this does arise. 131 // clue what happened if this does arise.
131 // TODO(devlin): Whether or not this needs to be verbose is debatable. 132 // TODO(devlin): Whether or not this needs to be verbose is debatable.
132 v8::TryCatch try_catch(isolate); 133 v8::TryCatch try_catch(isolate);
133 try_catch.SetVerbose(true); 134 try_catch.SetVerbose(true);
134 135
135 v8::Local<v8::Object> parent = get_parent_.Run(context); 136 v8::Local<v8::Object> secondary_parent;
136 if (parent.IsEmpty()) 137 v8::Local<v8::Object> parent = get_parent_.Run(context, &secondary_parent);
137 return;
138 v8::Local<v8::String> key = gin::StringToSymbol(isolate, kLastErrorProperty);
139 v8::Local<v8::Value> v8_error;
140 // Two notes: this Get() is visible to external script, and this will actually
141 // mark the lastError as accessed, if one exists. These shouldn't be a
142 // problem (lastError is meant to be helpful, but isn't designed to handle
143 // crazy chaining, etc). However, if we decide we needed to be fancier, we
144 // could detect the presence of a current error through a GetPrivate(), and
145 // optionally throw it if one exists.
146 if (!parent->Get(context, key).ToLocal(&v8_error))
147 return;
148 138
149 if (!v8_error->IsUndefined()) { 139 SetErrorOnPrimaryParent(context, parent, error);
150 // There may be an existing last error to overwrite. 140 SetErrorOnSecondaryParent(context, secondary_parent, error);
151 LastErrorObject* last_error = nullptr;
152 if (!gin::Converter<LastErrorObject*>::FromV8(isolate, v8_error,
153 &last_error)) {
154 // If it's not a real lastError (e.g. if a script manually set it), don't
155 // do anything. We shouldn't mangle a property set by other script.
156 // TODO(devlin): Or should we? If someone sets chrome.runtime.lastError,
157 // it might be the right course of action to overwrite it.
158 return;
159 }
160 last_error->Reset(error);
161 } else {
162 v8::Local<v8::Value> last_error =
163 gin::CreateHandle(isolate, new LastErrorObject(error)).ToV8();
164 v8::Maybe<bool> set_private = parent->SetPrivate(
165 context, v8::Private::ForApi(isolate, key), last_error);
166 if (!set_private.IsJust() || !set_private.FromJust()) {
167 NOTREACHED();
168 return;
169 }
170 DCHECK(!last_error.IsEmpty());
171 // This Set() can fail, but there's nothing to do if it does (the exception
172 // will be caught by the TryCatch above).
173 ignore_result(parent->SetAccessor(context, key, &LastErrorGetter,
174 &LastErrorSetter, last_error));
175 }
176 } 141 }
177 142
178 void APILastError::ClearError(v8::Local<v8::Context> context, 143 void APILastError::ClearError(v8::Local<v8::Context> context,
179 bool report_if_unchecked) { 144 bool report_if_unchecked) {
180 v8::Isolate* isolate = context->GetIsolate(); 145 v8::Isolate* isolate = context->GetIsolate();
181 v8::HandleScope handle_scope(isolate); 146 v8::HandleScope handle_scope(isolate);
182 147
183 v8::Local<v8::Object> parent; 148 v8::Local<v8::Object> parent;
149 v8::Local<v8::Object> secondary_parent;
184 LastErrorObject* last_error = nullptr; 150 LastErrorObject* last_error = nullptr;
185 v8::Local<v8::String> key; 151 v8::Local<v8::String> key;
186 v8::Local<v8::Private> private_key; 152 v8::Local<v8::Private> private_key;
187 { 153 {
188 // See comment in SetError(). 154 // See comment in SetError().
189 v8::TryCatch try_catch(isolate); 155 v8::TryCatch try_catch(isolate);
190 try_catch.SetVerbose(true); 156 try_catch.SetVerbose(true);
191 157
192 parent = get_parent_.Run(context); 158 parent = get_parent_.Run(context, &secondary_parent);
lazyboy 2017/06/27 17:58:44 It feels a bit odd to return two parents different
Devlin 2017/06/28 17:52:01 I have a slight preference for this, since the pri
193 if (parent.IsEmpty()) 159 if (parent.IsEmpty())
194 return; 160 return;
195 key = gin::StringToSymbol(isolate, kLastErrorProperty); 161 key = gin::StringToSymbol(isolate, kLastErrorProperty);
196 private_key = v8::Private::ForApi(isolate, key); 162 private_key = v8::Private::ForApi(isolate, key);
197 v8::Local<v8::Value> error; 163 v8::Local<v8::Value> error;
198 // Access through GetPrivate() so that we don't trigger accessed(). 164 // Access through GetPrivate() so that we don't trigger accessed().
199 if (!parent->GetPrivate(context, private_key).ToLocal(&error)) 165 if (!parent->GetPrivate(context, private_key).ToLocal(&error) ||
200 return; 166 !gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error,
201 if (!gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error,
202 &last_error)) { 167 &last_error)) {
203 return; 168 return;
204 } 169 }
205 } 170 }
206 171
207 if (report_if_unchecked && !last_error->accessed()) { 172 if (report_if_unchecked && !last_error->accessed()) {
208 add_console_error_.Run( 173 add_console_error_.Run(
209 context, "Unchecked runtime.lastError: " + last_error->error()); 174 context, "Unchecked runtime.lastError: " + last_error->error());
210 } 175 }
211 176
212 // See comment in SetError(). 177 // See comment in SetError().
213 v8::TryCatch try_catch(isolate); 178 v8::TryCatch try_catch(isolate);
214 try_catch.SetVerbose(true); 179 try_catch.SetVerbose(true);
215 180
216 v8::Maybe<bool> delete_private = parent->DeletePrivate(context, private_key); 181 v8::Maybe<bool> delete_private = parent->DeletePrivate(context, private_key);
217 if (!delete_private.IsJust() || !delete_private.FromJust()) { 182 if (!delete_private.IsJust() || !delete_private.FromJust()) {
218 NOTREACHED(); 183 NOTREACHED();
219 return; 184 return;
220 } 185 }
221 // This Delete() can fail, but there's nothing to do if it does (the exception 186 // These Delete()s can fail, but there's nothing to do if it does (the
222 // will be caught by the TryCatch above). 187 // exception will be caught by the TryCatch above).
223 ignore_result(parent->Delete(context, key)); 188 ignore_result(parent->Delete(context, key));
189 if (!secondary_parent.IsEmpty())
190 ignore_result(secondary_parent->Delete(context, key));
224 } 191 }
225 192
226 bool APILastError::HasError(v8::Local<v8::Context> context) { 193 bool APILastError::HasError(v8::Local<v8::Context> context) {
227 v8::Isolate* isolate = context->GetIsolate(); 194 v8::Isolate* isolate = context->GetIsolate();
228 v8::HandleScope handle_scope(isolate); 195 v8::HandleScope handle_scope(isolate);
229 196
230 // See comment in SetError(). 197 // See comment in SetError().
231 v8::TryCatch try_catch(isolate); 198 v8::TryCatch try_catch(isolate);
232 try_catch.SetVerbose(true); 199 try_catch.SetVerbose(true);
233 200
234 v8::Local<v8::Object> parent = get_parent_.Run(context); 201 v8::Local<v8::Object> parent = get_parent_.Run(context, nullptr);
235 if (parent.IsEmpty()) 202 if (parent.IsEmpty())
236 return false; 203 return false;
237 v8::Local<v8::Value> error; 204 v8::Local<v8::Value> error;
238 v8::Local<v8::Private> key = v8::Private::ForApi( 205 v8::Local<v8::Private> key = v8::Private::ForApi(
239 isolate, gin::StringToSymbol(isolate, kLastErrorProperty)); 206 isolate, gin::StringToSymbol(isolate, kLastErrorProperty));
240 // Access through GetPrivate() so we don't trigger accessed(). 207 // Access through GetPrivate() so we don't trigger accessed().
241 if (!parent->GetPrivate(context, key).ToLocal(&error)) 208 if (!parent->GetPrivate(context, key).ToLocal(&error))
242 return false; 209 return false;
243 210
244 LastErrorObject* last_error = nullptr; 211 LastErrorObject* last_error = nullptr;
245 return gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error, 212 return gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error,
246 &last_error); 213 &last_error);
247 } 214 }
248 215
216 void APILastError::SetErrorOnPrimaryParent(v8::Local<v8::Context> context,
217 v8::Local<v8::Object> parent,
218 const std::string& error) {
219 if (parent.IsEmpty())
Devlin 2017/06/24 01:24:17 Note: copy-paste from above.
220 return;
221 v8::Isolate* isolate = context->GetIsolate();
222 v8::Local<v8::String> key = gin::StringToSymbol(isolate, kLastErrorProperty);
223 v8::Local<v8::Value> v8_error;
224 // Two notes: this Get() is visible to external script, and this will actually
225 // mark the lastError as accessed, if one exists. These shouldn't be a
226 // problem (lastError is meant to be helpful, but isn't designed to handle
227 // crazy chaining, etc). However, if we decide we needed to be fancier, we
228 // could detect the presence of a current error through a GetPrivate(), and
229 // optionally throw it if one exists.
230 if (!parent->Get(context, key).ToLocal(&v8_error))
231 return;
232
233 if (!v8_error->IsUndefined()) {
234 // There may be an existing last error to overwrite.
235 LastErrorObject* last_error = nullptr;
236 if (!gin::Converter<LastErrorObject*>::FromV8(isolate, v8_error,
237 &last_error)) {
238 // If it's not a real lastError (e.g. if a script manually set it), don't
239 // do anything. We shouldn't mangle a property set by other script.
240 // TODO(devlin): Or should we? If someone sets chrome.runtime.lastError,
241 // it might be the right course of action to overwrite it.
242 return;
243 }
244 last_error->Reset(error);
245 } else {
246 v8::Local<v8::Value> last_error =
247 gin::CreateHandle(isolate, new LastErrorObject(error)).ToV8();
248 v8::Maybe<bool> set_private = parent->SetPrivate(
249 context, v8::Private::ForApi(isolate, key), last_error);
250 if (!set_private.IsJust() || !set_private.FromJust()) {
251 NOTREACHED();
252 return;
253 }
254 DCHECK(!last_error.IsEmpty());
255 // This Set() can fail, but there's nothing to do if it does (the exception
jbroman 2017/06/25 00:12:49 nit: this is SetAccessor, not Set.
Devlin 2017/06/28 17:52:01 Done.
256 // will be caught by the TryCatch above).
jbroman 2017/06/25 00:12:49 Here and below: is "the TryCatch above" the one in
Devlin 2017/06/28 17:52:01 Yep, clarified in the comments.
257 ignore_result(parent->SetAccessor(context, key, &LastErrorGetter,
258 &LastErrorSetter, last_error));
259 }
260 }
261
262 void APILastError::SetErrorOnSecondaryParent(
263 v8::Local<v8::Context> context,
264 v8::Local<v8::Object> secondary_parent,
265 const std::string& error) {
266 if (secondary_parent.IsEmpty())
267 return;
268
269 // For the secondary parent, simply set chrome.extension.lastError to
270 // {message: <error>}.
271 // TODO(devlin): Gather metrics on how frequently this is checked. It'd be
272 // nice to get rid of it.
273 v8::Isolate* isolate = context->GetIsolate();
274 v8::Local<v8::String> key = gin::StringToSymbol(isolate, kLastErrorProperty);
275 // This Set() can fail, but there's nothing to do if it does (the exception
276 // will be caught by the TryCatch above).
277 ignore_result(secondary_parent->CreateDataProperty(
jbroman 2017/06/25 00:12:49 nit: this is CreateDataProperty, not Set.
Devlin 2017/06/28 17:52:01 Done.
278 context, key,
279 gin::DataObjectBuilder(isolate).Set("message", error).Build()));
280 }
281
249 } // namespace extensions 282 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698