Commit 6a2260c4 authored by Nick Carter's avatar Nick Carter Committed by Commit Bot

Introduce JsReplace, for getting values from C++ to ExecuteScript scripts

JsReplace(script, args...) works by replacing $1, $2, $3, etc in |script| with
JS literal values constructed from |args|, similar to
base::ReplaceStringPlaceholders, except that args can be a variadic mix of
strings, ints, base::Values, GURLs and more.

Unlike StringPrintf or manual concatenation, this utility will properly
escape string content, even if it contains slashes or quotation marks.

Example 1:

  GURL page_url("http://example.com");
  EXPECT_TRUE(ExecuteScript(
      shell(), JsReplace("window.open($1, '_blank');", page_url)));

$1 is replaced with a double-quoted JS string literal: "http://example.com".
Note that quotes around $1 are not required.

Example 2:

  bool forced_reload = true;
  EXPECT_TRUE(ExecuteScript(
      shell(), JsReplace("window.location.reload($1);", forced_reload)));

This becomes "window.location.reload(true);" -- because bool values are
supported by base::Value. Numbers, lists, and dicts also work.

Change-Id: I11041b6d2db299949266290b940e2d855522f2ea
Reviewed-on: https://chromium-review.googlesource.com/1125098
Commit-Queue: Nick Carter <nick@chromium.org>
Reviewed-by: default avatarAlbert J. Wong <ajwong@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577262}
parent b249c686
......@@ -688,7 +688,7 @@ IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest, SubframeOpenerSetForNewWindow) {
ShellAddedObserver new_shell_observer;
GURL popup_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(ExecuteScript(root->child_at(0),
"window.open('" + popup_url.spec() + "');"));
JsReplace("window.open($1);", popup_url)));
Shell* new_shell = new_shell_observer.GetShell();
WebContents* new_contents = new_shell->web_contents();
WaitForLoadStop(new_contents);
......
......@@ -325,8 +325,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
// Navigate with Javascript.
{
GURL navigate_url = embedded_test_server()->base_url();
std::string script = "document.location = '" +
navigate_url.spec() + "';";
std::string script = JsReplace("document.location = $1", navigate_url);
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
EXPECT_TRUE(ExecuteScript(shell(), script));
same_tab_observer.Wait();
......@@ -508,7 +507,7 @@ bool RendererLocationReplace(Shell* shell, const GURL& url) {
WaitForLoadStop(web_contents);
TestNavigationObserver same_tab_observer(web_contents, 1);
EXPECT_TRUE(
ExecuteScript(shell, "window.location.replace('" + url.spec() + "');"));
ExecuteScript(shell, JsReplace("window.location.replace($1)", url)));
same_tab_observer.Wait();
if (!IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL))
return false;
......@@ -576,10 +575,12 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
shell()->web_contents()->GetMainFrame()->GetEnabledBindings());
ShellAddedObserver observer;
std::string page_url = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html").spec();
EXPECT_TRUE(
ExecuteScript(shell(), "window.open('" + page_url + "', '_blank')"));
GURL page_url = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
{
EXPECT_TRUE(ExecuteScript(
shell(), JsReplace("window.open($1, '_blank');", page_url)));
}
Shell* shell2 = observer.GetShell();
EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));
......@@ -879,7 +880,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, SubframeOnEmptyPage) {
"foo.com", "/navigation_controller/simple_page_2.html");
{
LoadCommittedCapturer capturer(new_shell->web_contents());
std::string script = "location.assign('" + frame_url.spec() + "')";
std::string script = JsReplace("location.assign($1);", frame_url);
EXPECT_TRUE(ExecuteScript(new_root->child_at(0), script));
capturer.Wait();
}
......@@ -930,9 +931,11 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
// Make a new iframe in it using document.write from the opener.
{
LoadCommittedCapturer capturer(new_shell->web_contents());
std::string script = "w.document.write("
"\"<iframe src='" + url1.spec() + "'></iframe>\");"
"w.document.close();";
std::string html = "<iframe src='" + url1.spec() + "'></iframe>";
std::string script = JsReplace(
"w.document.write($1);"
"w.document.close();",
html);
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
capturer.Wait();
}
......@@ -1118,7 +1121,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
std::string script = "location.assign('" + frame_url.spec() + "')";
std::string script = JsReplace("location.assign($1);", frame_url);
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
......@@ -1148,7 +1151,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
std::string script = "location.replace('" + frame_url.spec() + "')";
std::string script = JsReplace("location.replace($1);", frame_url);
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
......@@ -1288,7 +1291,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
std::string script = "location.replace('" + frame_url.spec() + "')";
std::string script = JsReplace("location.replace($1);", frame_url);
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
......@@ -1611,7 +1614,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigateParamsCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
std::string script = "location.assign('" + frame_url.spec() + "')";
std::string script = JsReplace("location.assign($1);", frame_url);
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
......@@ -1624,7 +1627,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
LoadCommittedCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
std::string script = "location.replace('" + frame_url.spec() + "')";
std::string script = JsReplace("location.replace($1);", frame_url);
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
......@@ -1875,10 +1878,13 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
// 3. A real same-site navigation in the nested iframe should be AUTO.
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(root->child_at(0)->child_at(0));
std::string script = "var frames = document.getElementsByTagName('iframe');"
"frames[0].src = '" + frame_url.spec() + "';";
std::string script = JsReplace(
"var frames = document.getElementsByTagName('iframe');"
"frames[0].src = $1;",
frame_url);
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
......@@ -1903,8 +1909,10 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
"foo.com", "/navigation_controller/simple_page_2.html"));
{
LoadCommittedCapturer capturer(root->child_at(1));
std::string script = "var frames = document.getElementsByTagName('iframe');"
"frames[1].src = '" + foo_url.spec() + "';";
std::string script = JsReplace(
"var frames = document.getElementsByTagName('iframe');"
"frames[1].src = $1;",
foo_url);
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
......@@ -1980,12 +1988,12 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
GURL slow_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
TestNavigationManager subframe_delayer(shell()->web_contents(), slow_url);
{
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + slow_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
}
std::string script_template =
"var iframe = document.createElement('iframe');"
"iframe.src = $1;"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, JsReplace(script_template, slow_url)));
EXPECT_TRUE(subframe_delayer.WaitForRequestStart());
// Stop the request so that we can wait for load stop below, without ending up
......@@ -1995,13 +2003,9 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
// 2. A nested iframe with a cross-site URL should be able to commit.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
{
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
WaitForLoadStopWithoutSuccessCheck(shell()->web_contents());
}
EXPECT_TRUE(
ExecuteScript(root->child_at(0), JsReplace(script_template, foo_url)));
WaitForLoadStopWithoutSuccessCheck(shell()->web_contents());
// TODO(creis): Check subframe entries once we create them in this case.
// See https://crbug.com/608402.
......@@ -5915,11 +5919,12 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
EXPECT_EQ(blank_url, frame->current_url());
// Do a document.write in the subframe to create a link to click.
std::string document_write_script =
std::string html = "<a id='fraglink' href='#frag'>fragment link</a>";
std::string document_write_script = JsReplace(
"var iframe = document.getElementById('frame');"
"iframe.contentWindow.document.write("
" \"<a id='fraglink' href='#frag'>fragment link</a>\");"
"iframe.contentWindow.document.close();";
"iframe.contentWindow.document.write($1);"
"iframe.contentWindow.document.close();",
html);
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), document_write_script));
// Click the link to do a same document navigation. Due to the
......
......@@ -126,20 +126,19 @@ class NetworkServiceBrowserTest : public ContentBrowserTest {
bool FetchResource(const GURL& url) {
if (!url.is_valid())
return false;
std::string script(
std::string script = JsReplace(
"var xhr = new XMLHttpRequest();"
"xhr.open('GET', '");
script += url.spec() +
"', true);"
"xhr.onload = function (e) {"
" if (xhr.readyState === 4) {"
" window.domAutomationController.send(xhr.status === 200);"
" }"
"};"
"xhr.onerror = function () {"
" window.domAutomationController.send(false);"
"};"
"xhr.send(null)";
"xhr.open('GET', $1, true);"
"xhr.onload = function (e) {"
" if (xhr.readyState === 4) {"
" window.domAutomationController.send(xhr.status === 200);"
" }"
"};"
"xhr.onerror = function () {"
" window.domAutomationController.send(false);"
"};"
"xhr.send(null);",
url);
return ExecuteScript(script);
}
......
......@@ -7,6 +7,7 @@
#include "build/build_config.h"
#include "content/browser/webrtc/webrtc_content_browsertest_base.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/shell/common/shell_switches.h"
#include "media/base/media_switches.h"
#include "media/base/test_data_util.h"
......@@ -132,13 +133,10 @@ IN_PROC_BROWSER_TEST_P(WebRtcCaptureFromElementBrowserTest,
}
#endif
MakeTypicalCall(
base::StringPrintf("testCaptureFromMediaElement(\"%s\", %d, %d, %d);",
GetParam().filename.c_str(),
GetParam().has_video,
GetParam().has_audio,
GetParam().use_audio_tag),
kVideoAudioHtmlFile);
MakeTypicalCall(JsReplace("testCaptureFromMediaElement($1, $2, $3, $4)",
GetParam().filename, GetParam().has_video,
GetParam().has_audio, GetParam().use_audio_tag),
kVideoAudioHtmlFile);
}
IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest,
......
......@@ -95,13 +95,12 @@ double GetMainframeWindowBorder(const ToRenderFrameHost& adapter) {
}
double GetMainFrameZoomFactor(const ToRenderFrameHost& adapter, double border) {
const char kGetMainFrameZoomLevel[] =
"window.domAutomationController.send("
"(window.outerWidth - %f)/window.innerWidth"
");";
double zoom_factor;
EXPECT_TRUE(ExecuteScriptAndExtractDouble(
adapter, base::StringPrintf(kGetMainFrameZoomLevel, border),
adapter,
JsReplace("window.domAutomationController.send("
" (window.outerWidth - $1) / window.innerWidth);",
border),
&zoom_factor));
return zoom_factor;
}
......
......@@ -14,6 +14,7 @@
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
......@@ -371,6 +372,101 @@ bool ExecuteScriptAndExtractString(const ToRenderFrameHost& adapter,
const std::string& script,
std::string* result) WARN_UNUSED_RESULT;
// JsLiteralHelper is a helper class that determines what types are legal to
// pass to StringifyJsLiteral. Legal types include int, string, StringPiece,
// char*, bool, double, GURL, url::Origin, and base::Value&&.
template <typename T>
struct JsLiteralHelper {
// This generic version enables passing any type from which base::Value can be
// instantiated. This covers int, string, double, bool, base::Value&&, etc.
template <typename U>
static base::Value Convert(U&& arg) {
return base::Value(std::forward<U>(arg));
}
};
// Specialization allowing GURL to be passed to StringifyJsLiteral.
template <>
struct JsLiteralHelper<GURL> {
static base::Value Convert(const GURL& url) {
return base::Value(url.spec());
}
};
// Specialization allowing url::Origin to be passed to StringifyJsLiteral.
template <>
struct JsLiteralHelper<url::Origin> {
static base::Value Convert(const url::Origin& url) {
return base::Value(url.Serialize());
}
};
// Convert a value to a corresponding JS literal.
//
// |value| can be any type explicitly convertible to base::Value
// (including int/string/StringPiece/char*/double/bool), or any type that
// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
// string literals.
template <typename T>
std::string StringifyJsLiteral(T&& value) {
using ValueType = std::remove_cv_t<std::remove_reference_t<T>>;
base::Value value_as_base_value =
JsLiteralHelper<ValueType>::Convert(std::forward<T>(value));
std::string value_as_json;
CHECK(base::JSONWriter::Write(value_as_base_value, &value_as_json));
return value_as_json;
}
// Base case for StringifyJsLiterals() variadic template (see below).
inline void StringifyJsLiterals(std::vector<std::string>* list) {}
// Call StringifyJsLiteral() on an arbitrary mix of values, appending the
// results to |list|. |first| and |rest...| can have any type accepted by
// StringifyJsLiteral.
template <typename T, typename... Args>
void StringifyJsLiterals(std::vector<std::string>* list,
T&& first,
Args&&... rest) {
list->push_back(StringifyJsLiteral(std::forward<T>(first)));
StringifyJsLiterals(list, std::forward<Args>(rest)...);
}
// Replaces $1, $2, $3, etc in |script_template| with JS literal values
// constructed from |args|, similar to base::ReplaceStringPlaceholders.
//
// Unlike StringPrintf or manual concatenation, this version will properly
// escape string content, even if it contains slashes or quotation marks.
//
// Each |arg| can be any type explicitly convertible to base::Value
// (including int/string/StringPiece/char*/double/bool), or any type that
// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
// string literals. |args| can be a mix of different types.
//
// Example 1:
//
// GURL page_url("http://example.com");
// EXPECT_TRUE(ExecuteScript(
// shell(), JsReplace("window.open($1, '_blank');", page_url)));
//
// $1 is replaced with a double-quoted JS string literal: "http://example.com".
// Note that quotes around $1 are not required.
//
// Example 2:
//
// bool forced_reload = true;
// EXPECT_TRUE(ExecuteScript(
// shell(), JsReplace("window.location.reload($1);", forced_reload)));
//
// This becomes "window.location.reload(true);" -- because bool values are
// supported by base::Value. Numbers, lists, and dicts also work.
template <typename... Args>
std::string JsReplace(base::StringPiece script_template, Args&&... args) {
std::vector<std::string> replacements;
StringifyJsLiterals(&replacements, std::forward<Args>(args)...);
return base::ReplaceStringPlaceholders(script_template, replacements,
nullptr);
}
// Same as above but the script executed without user gesture.
bool ExecuteScriptWithoutUserGestureAndExtractDouble(
const ToRenderFrameHost& adapter,
......
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