|
5 | 5 | #include "third_party/blink/renderer/core/fetch/fetch_manager.h" |
6 | 6 |
|
7 | 7 | #include <optional> |
| 8 | +#include <string> |
| 9 | +#include <string_view> |
| 10 | +#include <utility> |
8 | 11 |
|
9 | 12 | #include "base/memory/scoped_refptr.h" |
10 | 13 | #include "base/strings/strcat.h" |
@@ -56,43 +59,43 @@ using ::testing::Eq; |
56 | 59 | using ::testing::IsNull; |
57 | 60 | using ::testing::Not; |
58 | 61 |
|
59 | | -MATCHER_P(HasRangeError, |
60 | | - expected_message, |
61 | | - base::StrCat({"has ", negation ? "no " : "", "RangeError('", |
62 | | - expected_message, "')"})) { |
| 62 | +MATCHER_P2(HasException, |
| 63 | + error_name, |
| 64 | + expected_message, |
| 65 | + base::StrCat({"has ", negation ? "no " : "", error_name, "('", |
| 66 | + expected_message, "')"})) { |
63 | 67 | const DummyExceptionStateForTesting& exception_state = arg; |
64 | 68 | if (!exception_state.HadException()) { |
65 | 69 | *result_listener << "no exception"; |
66 | 70 | return false; |
67 | 71 | } |
68 | | - if (exception_state.CodeAs<ESErrorType>() != ESErrorType::kRangeError) { |
69 | | - *result_listener << "exception is not RangeError"; |
70 | | - return false; |
71 | | - } |
72 | | - if (exception_state.Message() != expected_message) { |
73 | | - *result_listener << "unexpected message from RangeError: " |
74 | | - << exception_state.Message(); |
75 | | - return false; |
76 | | - } |
77 | | - return true; |
78 | | -} |
79 | 72 |
|
80 | | -MATCHER_P(HasAbortError, |
81 | | - expected_message, |
82 | | - base::StrCat({"has ", negation ? "no " : "", "AbortError('", |
83 | | - expected_message, "')"})) { |
84 | | - const DummyExceptionStateForTesting& exception_state = arg; |
85 | | - if (!exception_state.HadException()) { |
86 | | - *result_listener << "no exception"; |
| 73 | + bool type_matches = false; |
| 74 | + const std::string_view err(error_name); |
| 75 | + if (err == "RangeError") { |
| 76 | + type_matches = |
| 77 | + exception_state.CodeAs<ESErrorType>() == ESErrorType::kRangeError; |
| 78 | + } else if (err == "AbortError") { |
| 79 | + type_matches = exception_state.CodeAs<DOMExceptionCode>() == |
| 80 | + DOMExceptionCode::kAbortError; |
| 81 | + } else if (err == "TypeError") { |
| 82 | + type_matches = |
| 83 | + exception_state.CodeAs<ESErrorType>() == ESErrorType::kTypeError; |
| 84 | + } else if (err == "SecurityError") { |
| 85 | + type_matches = exception_state.CodeAs<DOMExceptionCode>() == |
| 86 | + DOMExceptionCode::kSecurityError; |
| 87 | + } else { |
| 88 | + *result_listener << "unsupported error name in matcher: " << error_name; |
87 | 89 | return false; |
88 | 90 | } |
89 | | - if (exception_state.CodeAs<DOMExceptionCode>() != |
90 | | - DOMExceptionCode::kAbortError) { |
91 | | - *result_listener << "exception is not AbortError"; |
| 91 | + |
| 92 | + if (!type_matches) { |
| 93 | + *result_listener << "exception is not " << error_name; |
92 | 94 | return false; |
93 | 95 | } |
| 96 | + |
94 | 97 | if (exception_state.Message() != expected_message) { |
95 | | - *result_listener << "unexpected message from AbortError: " |
| 98 | + *result_listener << "unexpected message from " << error_name << ": " |
96 | 99 | << exception_state.Message(); |
97 | 100 | return false; |
98 | 101 | } |
@@ -186,9 +189,9 @@ MATCHER_P(MatchNetworkResourceRequest, |
186 | 189 |
|
187 | 190 | } // namespace |
188 | 191 |
|
189 | | -class FetchLaterTest : public testing::Test { |
| 192 | +class FetchLaterTestBase : public testing::Test { |
190 | 193 | public: |
191 | | - FetchLaterTest() |
| 194 | + FetchLaterTestBase() |
192 | 195 | : task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>()) { |
193 | 196 | feature_list_.InitAndEnableFeature(blink::features::kFetchLaterAPI); |
194 | 197 | } |
@@ -261,6 +264,8 @@ class FetchLaterTest : public testing::Test { |
261 | 264 | base::HistogramTester histogram_; |
262 | 265 | }; |
263 | 266 |
|
| 267 | +class FetchLaterTest : public FetchLaterTestBase {}; |
| 268 | + |
264 | 269 | // A FetchLater request where its URL has same-origin as its execution context. |
265 | 270 | TEST_F(FetchLaterTest, CreateSameOriginFetchLaterRequest) { |
266 | 271 | FetchLaterTestingScope scope(FrameClient(), GetSourcePageURL()); |
@@ -317,7 +322,8 @@ TEST_F(FetchLaterTest, NegativeActivateAfterThrowRangeError) { |
317 | 322 |
|
318 | 323 | EXPECT_THAT(result, IsNull()); |
319 | 324 | EXPECT_THAT(exception_state, |
320 | | - HasRangeError("fetchLater's activateAfter cannot be negative.")); |
| 325 | + HasException("RangeError", |
| 326 | + "fetchLater's activateAfter cannot be negative.")); |
321 | 327 | EXPECT_EQ(fetch_later_manager->NumLoadersForTesting(), 0u); |
322 | 328 | Histogram().ExpectTotalCount("FetchLater.Renderer.Total", 0); |
323 | 329 | } |
@@ -345,8 +351,9 @@ TEST_F(FetchLaterTest, AbortBeforeFetchLater) { |
345 | 351 | request->signal(), /*activate_after_ms=*/std::nullopt, exception_state); |
346 | 352 |
|
347 | 353 | EXPECT_THAT(result, IsNull()); |
348 | | - EXPECT_THAT(exception_state, |
349 | | - HasAbortError("The user aborted a fetchLater request.")); |
| 354 | + EXPECT_THAT( |
| 355 | + exception_state, |
| 356 | + HasException("AbortError", "The user aborted a fetchLater request.")); |
350 | 357 | EXPECT_EQ(fetch_later_manager->NumLoadersForTesting(), 0u); |
351 | 358 | Histogram().ExpectTotalCount("FetchLater.Renderer.Total", 0); |
352 | 359 | } |
@@ -495,5 +502,120 @@ TEST_F(FetchLaterTest, ForcedSendingWithBackgroundSyncOff) { |
495 | 502 | 3 /*kActivatedOnEnteredBackForwardCache*/, 1); |
496 | 503 | } |
497 | 504 |
|
| 505 | +// Base class for fetchLater() URL validation tests. |
| 506 | +class FetchLaterUrlTestBase : public FetchLaterTestBase { |
| 507 | + protected: |
| 508 | + std::pair<Persistent<FetchLaterManager>, Persistent<FetchLaterResult>> |
| 509 | + CallFetchLater(V8TestingScope& scope, const std::string& url) { |
| 510 | + auto* manager = |
| 511 | + MakeGarbageCollected<FetchLaterManager>(scope.GetExecutionContext()); |
| 512 | + auto* controller = AbortController::Create(scope.GetScriptState()); |
| 513 | + auto& exception_state = scope.GetExceptionState(); |
| 514 | + auto* request = CreateFetchLaterRequest(scope, String::FromUTF8(url), |
| 515 | + controller->signal()); |
| 516 | + auto* result = manager->FetchLater( |
| 517 | + scope.GetScriptState(), |
| 518 | + request->PassRequestData(scope.GetScriptState(), exception_state), |
| 519 | + controller->signal(), /*activate_after=*/std::nullopt, exception_state); |
| 520 | + return {manager, result}; |
| 521 | + } |
| 522 | +}; |
| 523 | + |
| 524 | +struct UrlTestParam { |
| 525 | + const std::string test_name; |
| 526 | + const std::string url; |
| 527 | +}; |
| 528 | + |
| 529 | +// This test verifies that fetchLater() only accepts URLs with HTTP or HTTPS |
| 530 | +// schemes. It covers various valid and invalid URL schemes, including http(s), |
| 531 | +// localhost, IP addresses, and non-http schemes like data:, file:, etc. |
| 532 | +class FetchLaterWithValidUrlTest |
| 533 | + : public FetchLaterUrlTestBase, |
| 534 | + public testing::WithParamInterface<UrlTestParam> {}; |
| 535 | + |
| 536 | +INSTANTIATE_TEST_SUITE_P(All, |
| 537 | + FetchLaterWithValidUrlTest, |
| 538 | + testing::ValuesIn(std::vector<UrlTestParam>{ |
| 539 | + {"https_example", "https://example.com/"}, |
| 540 | + {"http_localhost", "http://localhost/"}, |
| 541 | + {"https_localhost", "https://localhost/"}, |
| 542 | + {"http_127_0_0_1", "http://127.0.0.1/"}, |
| 543 | + {"https_127_0_0_1", "https://127.0.0.1/"}, |
| 544 | + {"http_ipv6_localhost", "http://[::1]/"}, |
| 545 | + {"https_ipv6_localhost", "https://[::1]/"}, |
| 546 | + }), |
| 547 | + [](const testing::TestParamInfo<UrlTestParam>& info) { |
| 548 | + return info.param.test_name; |
| 549 | + }); |
| 550 | + |
| 551 | +// Verifies that fetchLater() succeeds with valid URLs, including HTTPS URLs and |
| 552 | +// potentially trustworthy HTTP URLs like localhost. |
| 553 | +TEST_P(FetchLaterWithValidUrlTest, Succeeds) { |
| 554 | + FetchLaterTestingScope scope(FrameClient(), GetSourcePageURL()); |
| 555 | + |
| 556 | + auto [manager, result] = CallFetchLater(scope, GetParam().url); |
| 557 | + |
| 558 | + EXPECT_THAT(result, Not(IsNull())); |
| 559 | + EXPECT_FALSE(scope.GetExceptionState().HadException()); |
| 560 | + EXPECT_EQ(manager->NumLoadersForTesting(), 1u); |
| 561 | +} |
| 562 | + |
| 563 | +class FetchLaterWithInsecureUrlTest |
| 564 | + : public FetchLaterUrlTestBase, |
| 565 | + public testing::WithParamInterface<UrlTestParam> {}; |
| 566 | + |
| 567 | +INSTANTIATE_TEST_SUITE_P(All, |
| 568 | + FetchLaterWithInsecureUrlTest, |
| 569 | + testing::ValuesIn(std::vector<UrlTestParam>{ |
| 570 | + {"http_example", "http://example.com/"}, |
| 571 | + }), |
| 572 | + [](const testing::TestParamInfo<UrlTestParam>& info) { |
| 573 | + return info.param.test_name; |
| 574 | + }); |
| 575 | + |
| 576 | +// Verifies that fetchLater() throws a SecurityError for insecure URLs, such as |
| 577 | +// an HTTP URL that is not localhost. |
| 578 | +TEST_P(FetchLaterWithInsecureUrlTest, FailsWithSecurityError) { |
| 579 | + FetchLaterTestingScope scope(FrameClient(), GetSourcePageURL()); |
| 580 | + |
| 581 | + auto [manager, result] = CallFetchLater(scope, GetParam().url); |
| 582 | + |
| 583 | + EXPECT_THAT(result, IsNull()); |
| 584 | + EXPECT_THAT( |
| 585 | + scope.GetExceptionState(), |
| 586 | + HasException("SecurityError", "fetchLater was passed an insecure URL.")); |
| 587 | + EXPECT_EQ(manager->NumLoadersForTesting(), 0u); |
| 588 | +} |
| 589 | + |
| 590 | +class FetchLaterWithInvalidSchemeUrlTest |
| 591 | + : public FetchLaterUrlTestBase, |
| 592 | + public testing::WithParamInterface<UrlTestParam> {}; |
| 593 | + |
| 594 | +INSTANTIATE_TEST_SUITE_P(All, |
| 595 | + FetchLaterWithInvalidSchemeUrlTest, |
| 596 | + testing::ValuesIn(std::vector<UrlTestParam>{ |
| 597 | + {"data", "data:text/plain,Hello"}, |
| 598 | + {"file", "file:///etc/passwd"}, |
| 599 | + {"ftp", "ftp://example.com/"}, |
| 600 | + {"javascript", "javascript:alert(1)"}, |
| 601 | + {"blob", "blob:https://example.com/some-uuid"}, |
| 602 | + }), |
| 603 | + [](const testing::TestParamInfo<UrlTestParam>& info) { |
| 604 | + return info.param.test_name; |
| 605 | + }); |
| 606 | + |
| 607 | +// Verifies that fetchLater() throws a TypeError for URLs with schemes other |
| 608 | +// than HTTP or HTTPS. |
| 609 | +TEST_P(FetchLaterWithInvalidSchemeUrlTest, FailsWithTypeError) { |
| 610 | + FetchLaterTestingScope scope(FrameClient(), GetSourcePageURL()); |
| 611 | + |
| 612 | + auto [manager, result] = CallFetchLater(scope, GetParam().url); |
| 613 | + |
| 614 | + EXPECT_THAT(result, IsNull()); |
| 615 | + EXPECT_THAT( |
| 616 | + scope.GetExceptionState(), |
| 617 | + HasException("TypeError", "fetchLater is only supported over HTTP(S).")); |
| 618 | + EXPECT_EQ(manager->NumLoadersForTesting(), 0u); |
| 619 | +} |
498 | 620 |
|
499 | 621 | } // namespace blink |
0 commit comments