Commit b7e7131a authored by Nick Carter's avatar Nick Carter Committed by Commit Bot

Introduce EvalJs, a replacement for ExecuteScript and friends.

EvalJs has the following improvements over ExecuteScript:
 - One function that works for every return type
   (no AndExtractBool etc variants) and every fancy
   option (isolated worlds, user gestures).
 - No out-params; no out-param variables.
 - |script| doesn't need to call domAutomationController explicitly
   to return a value. Instead, it has eval() semantics -- the statement
   completion value becomes the result.
 - The result of EvalJs can be used directly in EXPECT_EQ; e.g.::
   EXPECT_EQ("about:blank", EvalJs(contents, "window.location.href"));
 - JS exceptions are reliably captured and will appear as C++ assertion
   failures.
 - JS stack traces arising from exceptions are annotated with the
   corresponding source code; this also appears in C++ assertion failures.
 - When a script doesn't produce a result, tests are much less likely
   to hang.
 - Doesn't get confused by crosstalk with other callers
   of domAutomationController.send().
 - Lists, dicts, null values, etc. can be returned as base::Values.

Limitations/warts:
 - The reliance on eval() currently means that a CSP script-src
   directive can interfere with the script runner. There is
   a workaround (use an isolated world), but this limitation
   currently prevents ExecJs from being a drop-in replacement
   for ExecuteScript. Fixing that will be a follow-on.

Change-Id: I260595094f71e7734d29a78e9ffd90b409d74f37
Reviewed-on: https://chromium-review.googlesource.com/1125121
Commit-Queue: Nick Carter <nick@chromium.org>
Reviewed-by: default avatarBen Greenstein <bengr@chromium.org>
Reviewed-by: default avatarŁukasz Anforowicz <lukasza@chromium.org>
Cr-Commit-Position: refs/heads/master@{#580702}
parent 6caf54f2
......@@ -376,12 +376,11 @@ IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
// Wait for the page load to complete (this will be a distiller error page).
content::WaitForLoadStop(contents);
bool result;
// Execute in isolated world; where all distiller scripts are run.
EXPECT_TRUE(content::ExecuteScriptInIsolatedWorldAndExtractBool(
contents, ISOLATED_WORLD_ID_CHROME_INTERNAL, kTestDistillerObject,
&result));
EXPECT_TRUE(result);
EXPECT_EQ(true, content::EvalJsWithManualReply(
contents, kTestDistillerObject,
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
ISOLATED_WORLD_ID_CHROME_INTERNAL));
}
IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
......
This diff is collapsed.
......@@ -487,23 +487,15 @@ IN_PROC_BROWSER_TEST_F(SessionHistoryScrollAnchorTest,
// http://code.google.com/p/chromium/issues/detail?id=56267
IN_PROC_BROWSER_TEST_F(SessionHistoryTest, HistoryLength) {
int length;
ASSERT_TRUE(ExecuteScriptAndExtractInt(
shell(), "domAutomationController.send(history.length)", &length));
EXPECT_EQ(1, length);
EXPECT_EQ(1, EvalJs(shell(), "history.length"));
NavigateToURL(shell(), GetURL("title1.html"));
ASSERT_TRUE(ExecuteScriptAndExtractInt(
shell(), "domAutomationController.send(history.length)", &length));
EXPECT_EQ(2, length);
EXPECT_EQ(2, EvalJs(shell(), "history.length"));
// Now test that history.length is updated when the navigation is committed.
NavigateToURL(shell(), GetURL("record_length.html"));
ASSERT_TRUE(ExecuteScriptAndExtractInt(
shell(), "domAutomationController.send(history.length)", &length));
EXPECT_EQ(3, length);
EXPECT_EQ(3, EvalJs(shell(), "history.length"));
GoBack();
GoBack();
......@@ -511,9 +503,7 @@ IN_PROC_BROWSER_TEST_F(SessionHistoryTest, HistoryLength) {
// Ensure history.length is properly truncated.
NavigateToURL(shell(), GetURL("title2.html"));
ASSERT_TRUE(ExecuteScriptAndExtractInt(
shell(), "domAutomationController.send(history.length)", &length));
EXPECT_EQ(2, length);
EXPECT_EQ(2, EvalJs(shell(), "history.length"));
}
// Test that verifies that a cross-process transfer doesn't lose session
......@@ -537,8 +527,7 @@ IN_PROC_BROWSER_TEST_F(SessionHistoryTest, GoBackToCrossSitePostWithRedirect) {
// Submit the form.
TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
EXPECT_TRUE(
ExecuteScript(shell(), "document.getElementById('text-form').submit();"));
EXPECT_TRUE(ExecJs(shell(), "document.getElementById('text-form').submit()"));
form_post_observer.Wait();
// Verify that we arrived at the expected, redirected location.
......@@ -547,13 +536,9 @@ IN_PROC_BROWSER_TEST_F(SessionHistoryTest, GoBackToCrossSitePostWithRedirect) {
// Verify that POST body got preserved by 307 redirect. This expectation
// comes from: https://tools.ietf.org/html/rfc7231#section-6.4.7
std::string body;
EXPECT_TRUE(ExecuteScriptAndExtractString(
shell(),
"window.domAutomationController.send("
"document.getElementsByTagName('pre')[0].innerText);",
&body));
EXPECT_EQ("text=value\n", body);
EXPECT_EQ(
"text=value\n",
EvalJs(shell(), "document.getElementsByTagName('pre')[0].innerText"));
// Navigate to a page from yet another site.
EXPECT_TRUE(NavigateToURL(shell(), page_to_go_back_from));
......@@ -568,13 +553,9 @@ IN_PROC_BROWSER_TEST_F(SessionHistoryTest, GoBackToCrossSitePostWithRedirect) {
shell()->web_contents()->GetLastCommittedURL());
// Again verify that POST body got preserved by 307 redirect.
std::string body_after_back_navigation;
EXPECT_TRUE(ExecuteScriptAndExtractString(
shell(),
"window.domAutomationController.send("
"document.getElementsByTagName('pre')[0].innerText);",
&body_after_back_navigation));
EXPECT_EQ("text=value\n", body_after_back_navigation);
EXPECT_EQ(
"text=value\n",
EvalJs(shell(), "document.getElementsByTagName('pre')[0].innerText"));
}
} // namespace content
......@@ -551,8 +551,7 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
NavigateToURL(shell(), kWebUIUrl);
bool js_executed = content::ExecuteScript(shell(), kJSCodeForAppendingFrame);
EXPECT_TRUE(js_executed);
EXPECT_TRUE(content::ExecuteScript(shell(), kJSCodeForAppendingFrame));
}
// Observer class to track the creation of RenderFrameHost objects. It is used
......
This diff is collapsed.
This diff is collapsed.
......@@ -10,6 +10,7 @@
#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"
namespace content {
......@@ -79,4 +80,99 @@ IN_PROC_BROWSER_TEST_F(CrossSiteRedirectorBrowserTest,
EXPECT_EQ(observer.redirect_url(), observer.navigation_url());
}
using EvalJsBrowserTest = ContentBrowserTest;
IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsErrors) {
ASSERT_TRUE(embedded_test_server()->Start());
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_FALSE("}}" != result); // As should EXPECT_NE
EXPECT_FALSE(nullptr == result);
std::string expected_error = R"(a JavaScript error:
SyntaxError: Unexpected token }
at eval (<anonymous>)
at Promise.resolve.then.script (EvalJs-runner.js:2:34)
)";
EXPECT_FALSE(expected_error == result);
EXPECT_EQ(expected_error, result.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 eval (__const_std::string&_script__:1:11):
55; throw new Error('whoops');
^^^^^
at eval (<anonymous>)
at Promise.resolve.then.script (EvalJs-runner.js:2:34)
)";
EXPECT_FALSE(expected_error == result);
EXPECT_EQ(expected_error, result.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 = R"(a JavaScript error:
ReferenceError: z is not defined
at eval (__const_std::string&_script__:4:13):
var y = z + x;
^^^^^
at eval (<anonymous>)
at Promise.resolve.then.script (EvalJs-runner.js:2:34)
)";
EXPECT_FALSE(expected_error == result);
EXPECT_EQ(expected_error, result.error);
}
}
IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsWithManualReply) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
std::string script = "window.domAutomationController.send(20); 'hi';";
// Calling domAutomationController is required for EvalJsWithManualReply.
EXPECT_EQ(20, EvalJsWithManualReply(shell(), script));
// Calling domAutomationController is an error with EvalJs.
auto result = EvalJs(shell(), script);
EXPECT_FALSE(20 == result);
EXPECT_FALSE("hi" == result);
EXPECT_THAT(result.error,
::testing::StartsWith(
"Internal Error: expected a 2-element list of the form "));
EXPECT_THAT(
result.error,
::testing::EndsWith("This is potentially because a script tried to call "
"domAutomationController.send itself -- that is only "
"allowed when using EvalJsWithManualReply(). When "
"using EvalJs(), result values are just the result "
"of calling eval() on the script -- the completion "
"value is the value of the last executed statement. "
"When using ExecJs(), there is no result value."));
}
} // namespace content
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment