Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 |
| OLD | NEW |