api_last_error_unittest.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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 "base/bind.h"
  6. #include "base/callback_helpers.h"
  7. #include "extensions/renderer/bindings/api_binding_test.h"
  8. #include "extensions/renderer/bindings/api_binding_test_util.h"
  9. #include "gin/converter.h"
  10. #include "gin/public/context_holder.h"
  11. #include "third_party/abseil-cpp/absl/types/optional.h"
  12. namespace extensions {
  13. namespace {
  14. // Returns the v8 object for the lastError.
  15. v8::Local<v8::Value> GetLastError(v8::Local<v8::Object> parent,
  16. v8::Local<v8::Context> context) {
  17. return GetPropertyFromObject(parent, context, "lastError");
  18. }
  19. // Returns a stringified version of the lastError message, if one exists, and
  20. // otherwise a stringified version of whatever the lastError property is (e.g.
  21. // undefined).
  22. std::string GetLastErrorMessage(v8::Local<v8::Object> parent,
  23. v8::Local<v8::Context> context) {
  24. v8::Local<v8::Value> last_error = GetLastError(parent, context);
  25. if (last_error.IsEmpty() || !last_error->IsObject())
  26. return V8ToString(last_error, context);
  27. v8::Local<v8::Value> message =
  28. GetPropertyFromObject(last_error.As<v8::Object>(), context, "message");
  29. return V8ToString(message, context);
  30. }
  31. using ParentList =
  32. std::vector<std::pair<v8::Local<v8::Context>, v8::Local<v8::Object>>>;
  33. v8::Local<v8::Object> GetParent(const ParentList& parents,
  34. v8::Local<v8::Context> context,
  35. v8::Local<v8::Object>* secondary_parent) {
  36. // This would be simpler with a map<context, object>, but Local<> doesn't
  37. // define an operator<.
  38. for (const auto& parent : parents) {
  39. if (parent.first == context)
  40. return parent.second;
  41. }
  42. return v8::Local<v8::Object>();
  43. }
  44. } // namespace
  45. using APILastErrorTest = APIBindingTest;
  46. // Test basic functionality of the lastError object.
  47. TEST_F(APILastErrorTest, TestLastError) {
  48. v8::HandleScope handle_scope(isolate());
  49. v8::Local<v8::Context> context = MainContext();
  50. v8::Local<v8::Object> parent_object = v8::Object::New(isolate());
  51. ParentList parents = {{context, parent_object}};
  52. APILastError last_error(base::BindRepeating(&GetParent, parents),
  53. base::DoNothing());
  54. EXPECT_FALSE(last_error.HasError(context));
  55. EXPECT_EQ("undefined", GetLastErrorMessage(parent_object, context));
  56. // Check that the key isn't present on the object (as opposed to simply being
  57. // undefined).
  58. EXPECT_FALSE(
  59. parent_object->Has(context, gin::StringToV8(isolate(), "lastError"))
  60. .ToChecked());
  61. last_error.SetError(context, "Some last error");
  62. EXPECT_TRUE(last_error.HasError(context));
  63. EXPECT_EQ("\"Some last error\"", GetLastErrorMessage(parent_object, context));
  64. last_error.ClearError(context, false);
  65. EXPECT_FALSE(last_error.HasError(context));
  66. EXPECT_EQ("undefined", GetLastErrorMessage(parent_object, context));
  67. EXPECT_FALSE(
  68. parent_object->Has(context, gin::StringToV8(isolate(), "lastError"))
  69. .ToChecked());
  70. }
  71. // Test throwing an error if the last error wasn't checked.
  72. TEST_F(APILastErrorTest, ReportIfUnchecked) {
  73. v8::HandleScope handle_scope(isolate());
  74. v8::Local<v8::Context> context = MainContext();
  75. v8::Local<v8::Object> parent_object = v8::Object::New(isolate());
  76. absl::optional<std::string> console_error;
  77. auto log_error = [](absl::optional<std::string>* console_error,
  78. v8::Local<v8::Context> context,
  79. const std::string& error) { *console_error = error; };
  80. ParentList parents = {{context, parent_object}};
  81. APILastError last_error(base::BindRepeating(&GetParent, parents),
  82. base::BindRepeating(log_error, &console_error));
  83. {
  84. v8::TryCatch try_catch(isolate());
  85. last_error.SetError(context, "foo");
  86. // GetLastErrorMessage() will count as accessing the error property, so we
  87. // shouldn't throw an exception.
  88. EXPECT_EQ("\"foo\"", GetLastErrorMessage(parent_object, context));
  89. last_error.ClearError(context, true);
  90. EXPECT_FALSE(console_error);
  91. EXPECT_FALSE(try_catch.HasCaught());
  92. }
  93. {
  94. v8::TryCatch try_catch(isolate());
  95. last_error.SetError(context, "foo");
  96. // GetLastError() only accesses the error object, and not the message
  97. // directly (e.g. chrome.runtime.lastError vs
  98. // chrome.runtime.lastError.message), but should still count as access and
  99. // shouldn't throw an exception.
  100. v8::Local<v8::Value> v8_error = GetLastError(parent_object, context);
  101. ASSERT_FALSE(v8_error.IsEmpty());
  102. EXPECT_TRUE(v8_error->IsObject());
  103. last_error.ClearError(context, true);
  104. EXPECT_FALSE(console_error);
  105. EXPECT_FALSE(try_catch.HasCaught());
  106. }
  107. {
  108. v8::TryCatch try_catch(isolate());
  109. // This time, we should log an error.
  110. last_error.SetError(context, "A last error");
  111. last_error.ClearError(context, true);
  112. ASSERT_TRUE(console_error);
  113. EXPECT_EQ("Unchecked runtime.lastError: A last error", *console_error);
  114. // We shouldn't have thrown an exception in order to prevent disrupting
  115. // JS execution.
  116. EXPECT_FALSE(try_catch.HasCaught());
  117. }
  118. {
  119. v8::TryCatch try_catch(isolate());
  120. last_error.SetError(context, "A last error");
  121. // Access through the internal HasError() should not count as access.
  122. EXPECT_TRUE(last_error.HasError(context));
  123. last_error.ClearError(context, true);
  124. ASSERT_TRUE(console_error);
  125. EXPECT_EQ("Unchecked runtime.lastError: A last error", *console_error);
  126. EXPECT_FALSE(try_catch.HasCaught());
  127. }
  128. }
  129. TEST_F(APILastErrorTest, ReportUncheckedError) {
  130. v8::HandleScope handle_scope(isolate());
  131. v8::Local<v8::Context> context = MainContext();
  132. v8::Local<v8::Object> parent_object = v8::Object::New(isolate());
  133. absl::optional<std::string> console_error;
  134. auto log_error = [](absl::optional<std::string>* console_error,
  135. v8::Local<v8::Context> context,
  136. const std::string& error) { *console_error = error; };
  137. ParentList parents = {{context, parent_object}};
  138. APILastError last_error(base::BindRepeating(&GetParent, parents),
  139. base::BindRepeating(log_error, &console_error));
  140. // lastError should start unset.
  141. EXPECT_FALSE(last_error.HasError(context));
  142. EXPECT_EQ("undefined", GetLastErrorMessage(parent_object, context));
  143. EXPECT_FALSE(
  144. parent_object->Has(context, gin::StringToV8(isolate(), "lastError"))
  145. .ToChecked());
  146. {
  147. v8::TryCatch try_catch(isolate());
  148. // Report an unchecked error. We should log the error, but not throw an
  149. // exception to avoid disrupting JS execution.
  150. last_error.ReportUncheckedError(context, "A last error");
  151. ASSERT_TRUE(console_error);
  152. EXPECT_EQ("Unchecked runtime.lastError: A last error", *console_error);
  153. EXPECT_FALSE(try_catch.HasCaught());
  154. }
  155. // lastError should remain unset.
  156. EXPECT_FALSE(last_error.HasError(context));
  157. EXPECT_EQ("undefined", GetLastErrorMessage(parent_object, context));
  158. EXPECT_FALSE(
  159. parent_object->Has(context, gin::StringToV8(isolate(), "lastError"))
  160. .ToChecked());
  161. }
  162. // Test behavior when something else sets a lastError property on the parent
  163. // object.
  164. TEST_F(APILastErrorTest, NonLastErrorObject) {
  165. v8::HandleScope handle_scope(isolate());
  166. v8::Local<v8::Context> context = MainContext();
  167. v8::Local<v8::Object> parent_object = v8::Object::New(isolate());
  168. ParentList parents = {{context, parent_object}};
  169. APILastError last_error(base::BindRepeating(&GetParent, parents),
  170. base::DoNothing());
  171. auto checked_set = [context](v8::Local<v8::Object> object,
  172. base::StringPiece key,
  173. v8::Local<v8::Value> value) {
  174. v8::Maybe<bool> success = object->Set(
  175. context, gin::StringToSymbol(context->GetIsolate(), key), value);
  176. ASSERT_TRUE(success.IsJust());
  177. ASSERT_TRUE(success.FromJust());
  178. };
  179. // Set a "fake" lastError property on the parent.
  180. v8::Local<v8::Object> fake_last_error = v8::Object::New(isolate());
  181. checked_set(fake_last_error, "message",
  182. gin::StringToV8(isolate(), "fake error"));
  183. checked_set(parent_object, "lastError", fake_last_error);
  184. EXPECT_EQ("\"fake error\"", GetLastErrorMessage(parent_object, context));
  185. // The bindings shouldn't mangle an existing property (or maybe we should -
  186. // see the TODO in api_last_error.cc).
  187. last_error.SetError(context, "Real last error");
  188. EXPECT_EQ("\"fake error\"", GetLastErrorMessage(parent_object, context));
  189. last_error.ClearError(context, false);
  190. EXPECT_EQ("\"fake error\"", GetLastErrorMessage(parent_object, context));
  191. checked_set(parent_object, "lastError", v8::Undefined(isolate()));
  192. EXPECT_EQ("undefined", GetLastErrorMessage(parent_object, context));
  193. last_error.SetError(context, "a last error");
  194. EXPECT_EQ("\"a last error\"", GetLastErrorMessage(parent_object, context));
  195. checked_set(parent_object, "lastError", fake_last_error);
  196. EXPECT_EQ("\"fake error\"", GetLastErrorMessage(parent_object, context));
  197. }
  198. // Test lastError in multiple different contexts.
  199. TEST_F(APILastErrorTest, MultipleContexts) {
  200. v8::HandleScope handle_scope(isolate());
  201. v8::Local<v8::Context> context_a = MainContext();
  202. v8::Local<v8::Context> context_b = AddContext();
  203. v8::Local<v8::Object> parent_a = v8::Object::New(isolate());
  204. v8::Local<v8::Object> parent_b = v8::Object::New(isolate());
  205. ParentList parents = {{context_a, parent_a}, {context_b, parent_b}};
  206. APILastError last_error(base::BindRepeating(&GetParent, parents),
  207. base::DoNothing());
  208. last_error.SetError(context_a, "Last error a");
  209. EXPECT_EQ("\"Last error a\"", GetLastErrorMessage(parent_a, context_a));
  210. EXPECT_EQ("undefined", GetLastErrorMessage(parent_b, context_b));
  211. last_error.SetError(context_b, "Last error b");
  212. EXPECT_EQ("\"Last error a\"", GetLastErrorMessage(parent_a, context_a));
  213. EXPECT_EQ("\"Last error b\"", GetLastErrorMessage(parent_b, context_b));
  214. last_error.ClearError(context_b, false);
  215. EXPECT_EQ("\"Last error a\"", GetLastErrorMessage(parent_a, context_a));
  216. EXPECT_EQ("undefined", GetLastErrorMessage(parent_b, context_b));
  217. last_error.ClearError(context_a, false);
  218. EXPECT_EQ("undefined", GetLastErrorMessage(parent_a, context_a));
  219. EXPECT_EQ("undefined", GetLastErrorMessage(parent_b, context_b));
  220. }
  221. TEST_F(APILastErrorTest, SecondaryParent) {
  222. auto get_parents = [](v8::Local<v8::Object> primary_parent,
  223. v8::Local<v8::Object> secondary_parent,
  224. v8::Local<v8::Context> context,
  225. v8::Local<v8::Object>* secondary_parent_out) {
  226. if (secondary_parent_out)
  227. *secondary_parent_out = secondary_parent;
  228. return primary_parent;
  229. };
  230. absl::optional<std::string> console_error;
  231. auto log_error = [](absl::optional<std::string>* console_error,
  232. v8::Local<v8::Context> context,
  233. const std::string& error) { *console_error = error; };
  234. v8::HandleScope handle_scope(isolate());
  235. v8::Local<v8::Context> context = MainContext();
  236. v8::Local<v8::Object> primary_parent = v8::Object::New(isolate());
  237. v8::Local<v8::Object> secondary_parent = v8::Object::New(isolate());
  238. APILastError last_error(
  239. base::BindRepeating(get_parents, primary_parent, secondary_parent),
  240. base::BindRepeating(log_error, &console_error));
  241. last_error.SetError(context, "error");
  242. EXPECT_TRUE(last_error.HasError(context));
  243. EXPECT_EQ("\"error\"", GetLastErrorMessage(primary_parent, context));
  244. EXPECT_EQ("\"error\"", GetLastErrorMessage(secondary_parent, context));
  245. EXPECT_FALSE(console_error);
  246. last_error.ClearError(context, true);
  247. EXPECT_FALSE(console_error);
  248. EXPECT_EQ("undefined", GetLastErrorMessage(primary_parent, context));
  249. EXPECT_EQ("undefined", GetLastErrorMessage(secondary_parent, context));
  250. // Accessing the primary parent's error should be sufficient to not log the
  251. // error in the console.
  252. last_error.SetError(context, "error");
  253. EXPECT_EQ("\"error\"", GetLastErrorMessage(primary_parent, context));
  254. last_error.ClearError(context, true);
  255. EXPECT_FALSE(console_error);
  256. // Accessing only the secondary parent's error shouldn't count as access on
  257. // the main error, and we should log it.
  258. last_error.SetError(context, "error");
  259. EXPECT_EQ("\"error\"", GetLastErrorMessage(secondary_parent, context));
  260. last_error.ClearError(context, true);
  261. ASSERT_TRUE(console_error);
  262. EXPECT_EQ("Unchecked runtime.lastError: error", *console_error);
  263. }
  264. } // namespace extensions