api_last_error.cc 11 KB

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