| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/public/test/browser_test_utils.h" |
| |
| #include "base/test/scoped_run_loop_timeout.h" |
| #include "base/values.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest-spi.h" |
| |
| namespace content { |
| |
| using ::testing::Eq; |
| using ::testing::Optional; |
| |
| class NavigationObserver: public WebContentsObserver { |
| public: |
| explicit NavigationObserver(WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| NavigationObserver(const NavigationObserver&) = delete; |
| NavigationObserver& operator=(const NavigationObserver&) = delete; |
| |
| ~NavigationObserver() override {} |
| |
| void DidFinishNavigation(NavigationHandle* navigation_handle) override { |
| if (navigation_handle->HasCommitted()) |
| navigation_url_ = navigation_handle->GetURL(); |
| } |
| |
| void DidRedirectNavigation(NavigationHandle* handle) override { |
| redirect_url_ = handle->GetURL(); |
| } |
| |
| const GURL& navigation_url() const { |
| return navigation_url_; |
| } |
| |
| const GURL& redirect_url() const { |
| return redirect_url_; |
| } |
| |
| private: |
| GURL redirect_url_; |
| GURL navigation_url_; |
| }; |
| |
| class CrossSiteRedirectorBrowserTest : public ContentBrowserTest { |
| public: |
| CrossSiteRedirectorBrowserTest() {} |
| |
| void SetUpOnMainThread() override { |
| // Map all hosts to localhost and setup the EmbeddedTestServer for |
| // redirects. |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CrossSiteRedirectorBrowserTest, |
| VerifyCrossSiteRedirectURL) { |
| SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to http://localhost:<port>/cross-site/foo.com/title2.html and |
| // ensure that the redirector forwards the navigation to |
| // http://foo.com:<port>/title2.html. The expectation is that the cross-site |
| // redirector will take the hostname supplied in the URL and rewrite the URL. |
| GURL expected_url(embedded_test_server()->GetURL("foo.com", "/title2.html")); |
| NavigationObserver observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), |
| embedded_test_server()->GetURL("/cross-site/foo.com/title2.html"), |
| expected_url /* expected_commit_url */)); |
| |
| EXPECT_EQ(expected_url, observer.navigation_url()); |
| EXPECT_EQ(observer.redirect_url(), observer.navigation_url()); |
| } |
| |
| using EvalJsBrowserTest = ContentBrowserTest; |
| |
| IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsErrors) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"))); |
| |
| { |
| // Test syntax errors. |
| auto result = EvalJs(shell(), "}}"); |
| EXPECT_FALSE(true == result); |
| EXPECT_FALSE(false == result); // EXPECT_FALSE(EvalJs()) shouldn't compile. |
| EXPECT_FALSE(0 == result); |
| EXPECT_FALSE(1 == result); |
| EXPECT_FALSE("}}" == result); // EXPECT_EQ should fail |
| EXPECT_TRUE("}}" != result); |
| EXPECT_FALSE(base::Value() == result); |
| |
| std::string expected_error = |
| "a JavaScript error: \"SyntaxError: Unexpected token '}'\"\n"; |
| EXPECT_FALSE(expected_error == result); |
| EXPECT_THAT(result, EvalJsResult::ErrorIs(expected_error)); |
| } |
| |
| { |
| // Test throwing exceptions. |
| auto result = EvalJs(shell(), "55; throw new Error('whoops');"); |
| EXPECT_FALSE(55 == result); |
| EXPECT_FALSE(1 == result); |
| EXPECT_FALSE("whoops" == result); |
| |
| std::string expected_error = R"(a JavaScript error: "Error: whoops |
| at __const_std::string&_script__:1:12): |
| {55; throw new Error('whoops'); |
| ^^^^^ |
| )"; |
| EXPECT_FALSE(expected_error == result); |
| EXPECT_THAT(result, EvalJsResult::ErrorIs(expected_error)); |
| } |
| |
| { |
| // Test reference errors in a multi-line script. |
| auto result = EvalJs(shell(), R"( |
| 22; |
| var x = 200 + 300; |
| var y = z + x; |
| 'sweet';)"); |
| EXPECT_FALSE(22 == result); |
| EXPECT_FALSE("sweet" == result); |
| |
| std::string expected_error = |
| "a JavaScript error: \"ReferenceError: z is not defined\n" |
| " at __const_std::string&_script__:4:13):\n" |
| " var y = z + x;\n" |
| " ^^^^^\n"; |
| EXPECT_FALSE(expected_error == result); |
| EXPECT_THAT(result, EvalJsResult::ErrorIs(expected_error)); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsAfterLifecycleUpdateErrors) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"))); |
| |
| { |
| // Test syntax errors. |
| auto result = EvalJsAfterLifecycleUpdate(shell(), "}}", "'hi'"); |
| |
| EXPECT_FALSE(result.is_ok()); |
| EXPECT_THAT( |
| result, |
| EvalJsResult::ErrorIs( |
| Eq("a JavaScript error: \"SyntaxError: Unexpected token '}'\n" |
| " at eval (<anonymous>)\n" |
| " at \"__const_std::string&_EvalJsAfterLifecycleUpdate__\"" |
| ":3:27\"\n"))); |
| |
| auto result2 = EvalJsAfterLifecycleUpdate(shell(), "'hi'", "]]"); |
| |
| EXPECT_FALSE(result.is_ok()); |
| EXPECT_THAT( |
| result2, |
| EvalJsResult::ErrorIs( |
| Eq("a JavaScript error: \"SyntaxError: Unexpected token ']'\n" |
| " at eval (<anonymous>)\n" |
| " at \"__const_std::string&_EvalJsAfterLifecycleUpdate__\"" |
| ":5:37\"\n"))); |
| } |
| |
| { |
| // Test throwing exceptions. |
| auto result = EvalJsAfterLifecycleUpdate( |
| shell(), "55; throw new Error('whoops');", "'hi'"); |
| |
| EXPECT_FALSE(result.is_ok()); |
| EXPECT_THAT( |
| result, |
| EvalJsResult::ErrorIs( |
| Eq("a JavaScript error: \"Error: whoops\n" |
| " at eval (__const_std::string&_script__:1:11)\n" |
| " at eval (<anonymous>)\n" |
| " at \"__const_std::string&_EvalJsAfterLifecycleUpdate__\"" |
| ":3:27\"\n"))); |
| |
| auto result2 = EvalJsAfterLifecycleUpdate( |
| shell(), "'hi'", "55; throw new Error('whoopsie');"); |
| |
| EXPECT_FALSE(result.is_ok()); |
| EXPECT_THAT( |
| result2, |
| EvalJsResult::ErrorIs( |
| Eq("a JavaScript error: \"Error: whoopsie\n" |
| " at eval (__const_std::string&_script__:1:11)\n" |
| " at eval (<anonymous>)\n" |
| " at \"__const_std::string&_EvalJsAfterLifecycleUpdate__\"" |
| ":5:37\"\n"))); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsWithDomAutomationController) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"))); |
| |
| std::string script = "window.domAutomationController.send(20); 'hi';"; |
| |
| // Calling domAutomationController is allowed with EvalJs, but doesn't |
| // influence the completion value. |
| EvalJsResult result = EvalJs(shell(), script); |
| EXPECT_NE(20, result); |
| EXPECT_EQ("hi", result); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsTimeout) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"))); |
| |
| base::test::ScopedRunLoopTimeout scoped_run_timeout(FROM_HERE, |
| base::Milliseconds(1)); |
| |
| // Store the promise resolve function so it doesn't get GC'd. |
| static std::string script = "new Promise(resolve => {window.r = resolve})"; |
| static std::optional<EvalJsResult> result; |
| static Shell* shell_ptr = shell(); |
| EXPECT_NONFATAL_FAILURE(result.emplace(EvalJs(shell_ptr, script)), |
| "RunLoop::Run() timed out."); |
| |
| EXPECT_THAT(result, Optional(EvalJsResult::ErrorIs( |
| Eq("Timeout waiting for Javascript to execute.")))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsNotBlockedByCSP) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL( |
| "/set-header?Content-Security-Policy: script-src 'self'"))); |
| |
| auto result = EvalJs(shell(), "'hi'"); |
| EXPECT_EQ("hi", result); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, |
| EvalJsAfterLifecycleUpdateBlockedByCSP) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL( |
| "/set-header?Content-Security-Policy: script-src 'self'"))); |
| |
| auto result = EvalJsAfterLifecycleUpdate(shell(), "'hi'", ""); |
| EXPECT_FALSE(result.is_ok()); |
| EXPECT_THAT( |
| result, |
| EvalJsResult::ErrorIs(::testing::StartsWith( |
| "EvalJsAfterLifecycleUpdate encountered an EvalError, because eval() " |
| "is blocked by the document's CSP on this page. To test content that " |
| "is protected by CSP, consider using EvalJsAfterLifecycleUpdate in " |
| "an isolated world. Details:"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, ExecJsWithDomAutomationController) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"))); |
| |
| std::string script = "window.domAutomationController.send(20); 'hi';"; |
| |
| // Calling domAutomationController is allowed with ExecJs. |
| EXPECT_TRUE(ExecJs(shell(), script)); |
| } |
| |
| } // namespace content |