| 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/api_last_error.h" | |
| 6 | |
| 7 #include "gin/converter.h" | |
| 8 #include "gin/handle.h" | |
| 9 #include "gin/object_template_builder.h" | |
| 10 #include "gin/wrappable.h" | |
| 11 | |
| 12 namespace extensions { | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 const char kLastErrorProperty[] = "lastError"; | |
| 17 const char kScriptSuppliedValueKey[] = "script_supplied_value"; | |
| 18 | |
| 19 // The object corresponding to the lastError property, containing a single | |
| 20 // property ('message') with the last error. This object is stored on the parent | |
| 21 // (chrome.runtime in production) as a private property, and is returned via an | |
| 22 // accessor which marks the error as accessed. | |
| 23 class LastErrorObject final : public gin::Wrappable<LastErrorObject> { | |
| 24 public: | |
| 25 explicit LastErrorObject(const std::string& error) : error_(error) {} | |
| 26 | |
| 27 static gin::WrapperInfo kWrapperInfo; | |
| 28 | |
| 29 // gin::Wrappable: | |
| 30 gin::ObjectTemplateBuilder GetObjectTemplateBuilder( | |
| 31 v8::Isolate* isolate) override { | |
| 32 DCHECK(isolate); | |
| 33 return Wrappable<LastErrorObject>::GetObjectTemplateBuilder(isolate) | |
| 34 .SetProperty("message", &LastErrorObject::error); | |
| 35 } | |
| 36 | |
| 37 void Reset(const std::string& error) { | |
| 38 error_ = error; | |
| 39 accessed_ = false; | |
| 40 } | |
| 41 | |
| 42 const std::string& error() const { return error_; } | |
| 43 bool accessed() const { return accessed_; } | |
| 44 void set_accessed() { accessed_ = true; } | |
| 45 | |
| 46 private: | |
| 47 std::string error_; | |
| 48 bool accessed_ = false; | |
| 49 | |
| 50 DISALLOW_COPY_AND_ASSIGN(LastErrorObject); | |
| 51 }; | |
| 52 | |
| 53 gin::WrapperInfo LastErrorObject::kWrapperInfo = {gin::kEmbedderNativeGin}; | |
| 54 | |
| 55 // An accessor to retrieve the last error property (curried in through data), | |
| 56 // and mark it as accessed. | |
| 57 void LastErrorGetter(v8::Local<v8::Name> property, | |
| 58 const v8::PropertyCallbackInfo<v8::Value>& info) { | |
| 59 v8::Isolate* isolate = info.GetIsolate(); | |
| 60 v8::HandleScope handle_scope(isolate); | |
| 61 v8::Local<v8::Object> holder = info.Holder(); | |
| 62 v8::Local<v8::Context> context = holder->CreationContext(); | |
| 63 | |
| 64 v8::Local<v8::Value> last_error; | |
| 65 v8::Local<v8::Private> last_error_key = v8::Private::ForApi( | |
| 66 isolate, gin::StringToSymbol(isolate, kLastErrorProperty)); | |
| 67 if (!holder->GetPrivate(context, last_error_key).ToLocal(&last_error) || | |
| 68 last_error != info.Data()) { | |
| 69 // Something funny happened - our private properties aren't set right. | |
| 70 NOTREACHED(); | |
| 71 return; | |
| 72 } | |
| 73 | |
| 74 v8::Local<v8::Value> return_value; | |
| 75 | |
| 76 // It's possible that some script has set their own value for the last error | |
| 77 // property. If so, return that. Otherwise, return the real last error. | |
| 78 v8::Local<v8::Private> script_value_key = v8::Private::ForApi( | |
| 79 isolate, gin::StringToSymbol(isolate, kScriptSuppliedValueKey)); | |
| 80 v8::Local<v8::Value> script_value; | |
| 81 if (holder->GetPrivate(context, script_value_key).ToLocal(&script_value) && | |
| 82 !script_value->IsUndefined()) { | |
| 83 return_value = script_value; | |
| 84 } else { | |
| 85 LastErrorObject* last_error_obj = nullptr; | |
| 86 CHECK(gin::Converter<LastErrorObject*>::FromV8(isolate, last_error, | |
| 87 &last_error_obj)); | |
| 88 last_error_obj->set_accessed(); | |
| 89 return_value = last_error; | |
| 90 } | |
| 91 | |
| 92 info.GetReturnValue().Set(return_value); | |
| 93 } | |
| 94 | |
| 95 // Allow script to set the last error property. | |
| 96 void LastErrorSetter(v8::Local<v8::Name> property, | |
| 97 v8::Local<v8::Value> value, | |
| 98 const v8::PropertyCallbackInfo<void>& info) { | |
| 99 v8::Isolate* isolate = info.GetIsolate(); | |
| 100 v8::HandleScope handle_scope(isolate); | |
| 101 v8::Local<v8::Object> holder = info.Holder(); | |
| 102 v8::Local<v8::Context> context = holder->CreationContext(); | |
| 103 | |
| 104 v8::Local<v8::Private> script_value_key = v8::Private::ForApi( | |
| 105 isolate, gin::StringToSymbol(isolate, kScriptSuppliedValueKey)); | |
| 106 v8::Maybe<bool> set_private = | |
| 107 holder->SetPrivate(context, script_value_key, value); | |
| 108 if (!set_private.IsJust() || !set_private.FromJust()) | |
| 109 NOTREACHED(); | |
| 110 } | |
| 111 | |
| 112 } // namespace | |
| 113 | |
| 114 APILastError::APILastError(const GetParent& get_parent, | |
| 115 const AddConsoleError& add_console_error) | |
| 116 : get_parent_(get_parent), add_console_error_(add_console_error) {} | |
| 117 APILastError::APILastError(APILastError&& other) = default; | |
| 118 APILastError::~APILastError() = default; | |
| 119 | |
| 120 void APILastError::SetError(v8::Local<v8::Context> context, | |
| 121 const std::string& error) { | |
| 122 v8::Isolate* isolate = context->GetIsolate(); | |
| 123 DCHECK(isolate); | |
| 124 v8::HandleScope handle_scope(isolate); | |
| 125 | |
| 126 // 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 // 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 // clue what happened if this does arise. | |
| 131 // TODO(devlin): Whether or not this needs to be verbose is debatable. | |
| 132 v8::TryCatch try_catch(isolate); | |
| 133 try_catch.SetVerbose(true); | |
| 134 | |
| 135 v8::Local<v8::Object> parent = get_parent_.Run(context); | |
| 136 if (parent.IsEmpty()) | |
| 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 | |
| 149 if (!v8_error->IsUndefined()) { | |
| 150 // There may be an existing last error to overwrite. | |
| 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 } | |
| 177 | |
| 178 void APILastError::ClearError(v8::Local<v8::Context> context, | |
| 179 bool report_if_unchecked) { | |
| 180 v8::Isolate* isolate = context->GetIsolate(); | |
| 181 v8::HandleScope handle_scope(isolate); | |
| 182 | |
| 183 v8::Local<v8::Object> parent; | |
| 184 LastErrorObject* last_error = nullptr; | |
| 185 v8::Local<v8::String> key; | |
| 186 v8::Local<v8::Private> private_key; | |
| 187 { | |
| 188 // See comment in SetError(). | |
| 189 v8::TryCatch try_catch(isolate); | |
| 190 try_catch.SetVerbose(true); | |
| 191 | |
| 192 parent = get_parent_.Run(context); | |
| 193 if (parent.IsEmpty()) | |
| 194 return; | |
| 195 key = gin::StringToSymbol(isolate, kLastErrorProperty); | |
| 196 private_key = v8::Private::ForApi(isolate, key); | |
| 197 v8::Local<v8::Value> error; | |
| 198 // Access through GetPrivate() so that we don't trigger accessed(). | |
| 199 if (!parent->GetPrivate(context, private_key).ToLocal(&error)) | |
| 200 return; | |
| 201 if (!gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error, | |
| 202 &last_error)) { | |
| 203 return; | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 if (report_if_unchecked && !last_error->accessed()) { | |
| 208 add_console_error_.Run( | |
| 209 context, "Unchecked runtime.lastError: " + last_error->error()); | |
| 210 } | |
| 211 | |
| 212 // See comment in SetError(). | |
| 213 v8::TryCatch try_catch(isolate); | |
| 214 try_catch.SetVerbose(true); | |
| 215 | |
| 216 v8::Maybe<bool> delete_private = parent->DeletePrivate(context, private_key); | |
| 217 if (!delete_private.IsJust() || !delete_private.FromJust()) { | |
| 218 NOTREACHED(); | |
| 219 return; | |
| 220 } | |
| 221 // This Delete() can fail, but there's nothing to do if it does (the exception | |
| 222 // will be caught by the TryCatch above). | |
| 223 ignore_result(parent->Delete(context, key)); | |
| 224 } | |
| 225 | |
| 226 bool APILastError::HasError(v8::Local<v8::Context> context) { | |
| 227 v8::Isolate* isolate = context->GetIsolate(); | |
| 228 v8::HandleScope handle_scope(isolate); | |
| 229 | |
| 230 // See comment in SetError(). | |
| 231 v8::TryCatch try_catch(isolate); | |
| 232 try_catch.SetVerbose(true); | |
| 233 | |
| 234 v8::Local<v8::Object> parent = get_parent_.Run(context); | |
| 235 if (parent.IsEmpty()) | |
| 236 return false; | |
| 237 v8::Local<v8::Value> error; | |
| 238 v8::Local<v8::Private> key = v8::Private::ForApi( | |
| 239 isolate, gin::StringToSymbol(isolate, kLastErrorProperty)); | |
| 240 // Access through GetPrivate() so we don't trigger accessed(). | |
| 241 if (!parent->GetPrivate(context, key).ToLocal(&error)) | |
| 242 return false; | |
| 243 | |
| 244 LastErrorObject* last_error = nullptr; | |
| 245 return gin::Converter<LastErrorObject*>::FromV8(context->GetIsolate(), error, | |
| 246 &last_error); | |
| 247 } | |
| 248 | |
| 249 } // namespace extensions | |
| OLD | NEW |