summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rwxr-xr-xchrome/chrome_renderer.gypi2
-rw-r--r--chrome/chrome_tests.gypi5
-rw-r--r--chrome/renderer/automation/dom_automation_controller.cc31
-rw-r--r--chrome/renderer/automation/dom_automation_controller.h5
-rw-r--r--chrome/renderer/automation/dom_automation_v8_extension.cc19
-rw-r--r--chrome/renderer/automation/dom_automation_v8_extension.h16
-rw-r--r--chrome/renderer/render_thread.cc5
-rw-r--r--chrome/renderer/renderer_resources.grd1
-rw-r--r--chrome/renderer/resources/dom_automation.js234
-rw-r--r--chrome/test/automation/dom_automation_browsertest.cc246
-rw-r--r--chrome/test/automation/dom_element_proxy.cc299
-rw-r--r--chrome/test/automation/dom_element_proxy.h126
-rw-r--r--chrome/test/automation/javascript_execution_controller.cc127
-rw-r--r--chrome/test/automation/javascript_execution_controller.h146
-rw-r--r--chrome/test/data/dom_automation/events/test.html19
-rw-r--r--chrome/test/data/dom_automation/find_elements/test.html36
-rw-r--r--chrome/test/data/dom_automation/frames/frame1.html6
-rw-r--r--chrome/test/data/dom_automation/frames/frame2.html6
-rw-r--r--chrome/test/data/dom_automation/frames/iframe1.html5
-rw-r--r--chrome/test/data/dom_automation/frames/iframe2.html5
-rw-r--r--chrome/test/data/dom_automation/frames/test.html12
-rw-r--r--chrome/test/data/dom_automation/string_escape/test.html6
-rw-r--r--chrome/test/ui_test_utils.cc51
-rw-r--r--chrome/test/ui_test_utils.h5
24 files changed, 1412 insertions, 1 deletions
diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi
index 0bcd077..67df617 100755
--- a/chrome/chrome_renderer.gypi
+++ b/chrome/chrome_renderer.gypi
@@ -45,6 +45,8 @@
# short term I'd like the build to work.
'renderer/automation/dom_automation_controller.cc',
'renderer/automation/dom_automation_controller.h',
+ 'renderer/automation/dom_automation_v8_extension.cc',
+ 'renderer/automation/dom_automation_v8_extension.h',
'renderer/extensions/bindings_utils.cc',
'renderer/extensions/bindings_utils.h',
'renderer/extensions/event_bindings.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index e1933dd..a465999 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -100,6 +100,10 @@
'test/automation/automation_proxy.h',
'test/automation/browser_proxy.cc',
'test/automation/browser_proxy.h',
+ 'test/automation/dom_element_proxy.cc',
+ 'test/automation/dom_element_proxy.h',
+ 'test/automation/javascript_execution_controller.cc',
+ 'test/automation/javascript_execution_controller.h',
'test/automation/tab_proxy.cc',
'test/automation/tab_proxy.h',
'test/automation/window_proxy.cc',
@@ -1247,6 +1251,7 @@
'browser/ssl/ssl_browser_tests.cc',
'browser/task_manager_browsertest.cc',
'renderer/form_autocomplete_unittest.cc',
+ 'test/automation/dom_automation_browsertest.cc',
'test/render_view_test.cc',
'test/render_view_test.h',
],
diff --git a/chrome/renderer/automation/dom_automation_controller.cc b/chrome/renderer/automation/dom_automation_controller.cc
index 3b7ac43..6c3e8a5 100644
--- a/chrome/renderer/automation/dom_automation_controller.cc
+++ b/chrome/renderer/automation/dom_automation_controller.cc
@@ -14,6 +14,7 @@ DomAutomationController::DomAutomationController()
automation_id_(MSG_ROUTING_NONE) {
BindMethod("send", &DomAutomationController::Send);
BindMethod("setAutomationId", &DomAutomationController::SetAutomationId);
+ BindMethod("sendJSON", &DomAutomationController::SendJSON);
}
void DomAutomationController::Send(const CppArgumentList& args,
@@ -86,6 +87,36 @@ void DomAutomationController::Send(const CppArgumentList& args,
return;
}
+void DomAutomationController::SendJSON(const CppArgumentList& args,
+ CppVariant* result) {
+ if (args.size() != 1) {
+ result->SetNull();
+ return;
+ }
+
+ if (automation_id_ == MSG_ROUTING_NONE) {
+ result->SetNull();
+ return;
+ }
+
+ if (!sender_) {
+ NOTREACHED();
+ result->SetNull();
+ return;
+ }
+
+ if (args[0].type != NPVariantType_String) {
+ result->SetNull();
+ return;
+ }
+
+ std::string json = args[0].ToString();
+ result->Set(sender_->Send(
+ new ViewHostMsg_DomOperationResponse(routing_id_, json, automation_id_)));
+
+ automation_id_ = MSG_ROUTING_NONE;
+}
+
void DomAutomationController::SetAutomationId(
const CppArgumentList& args, CppVariant* result) {
if (args.size() != 1) {
diff --git a/chrome/renderer/automation/dom_automation_controller.h b/chrome/renderer/automation/dom_automation_controller.h
index bc6f5fe..c815418 100644
--- a/chrome/renderer/automation/dom_automation_controller.h
+++ b/chrome/renderer/automation/dom_automation_controller.h
@@ -84,6 +84,11 @@ class DomAutomationController : public CppBoundClass {
// IPC. It sets the return value to null on unexpected errors or arguments.
void Send(const CppArgumentList& args, CppVariant* result);
+ // Makes the renderer send a javascript value to the app.
+ // The value must be a NPString and should be properly formed JSON.
+ // This function does not modify/escape the returned string in any way.
+ void SendJSON(const CppArgumentList& args, CppVariant* result);
+
void SetAutomationId(const CppArgumentList& args, CppVariant* result);
// TODO(vibhor): Implement later
diff --git a/chrome/renderer/automation/dom_automation_v8_extension.cc b/chrome/renderer/automation/dom_automation_v8_extension.cc
new file mode 100644
index 0000000..efe5ada
--- /dev/null
+++ b/chrome/renderer/automation/dom_automation_v8_extension.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/automation/dom_automation_v8_extension.h"
+
+#include "chrome/renderer/extensions/bindings_utils.h"
+#include "grit/renderer_resources.h"
+
+using bindings_utils::GetStringResource;
+
+const char* DomAutomationV8Extension::kName = "chrome/domautomation";
+
+v8::Extension* DomAutomationV8Extension::Get() {
+ static v8::Extension* extension =
+ new bindings_utils::ExtensionBase(
+ kName, GetStringResource<IDR_DOM_AUTOMATION_JS>(), 0, NULL);
+ return extension;
+}
diff --git a/chrome/renderer/automation/dom_automation_v8_extension.h b/chrome/renderer/automation/dom_automation_v8_extension.h
new file mode 100644
index 0000000..94201e7
--- /dev/null
+++ b/chrome/renderer/automation/dom_automation_v8_extension.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_AUTOMATION_DOM_AUTOMATION_V8_EXTENSION_H_
+#define CHROME_RENDERER_AUTOMATION_DOM_AUTOMATION_V8_EXTENSION_H_
+
+#include "v8/include/v8.h"
+
+class DomAutomationV8Extension {
+ public:
+ static const char* kName;
+ static v8::Extension* Get();
+};
+
+#endif // CHROME_RENDERER_AUTOMATION_DOM_AUTOMATION_V8_EXTENSION_H_
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index ad4ec2a..6448cf5 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -42,6 +42,7 @@
#include "base/scoped_handle.h"
#include "chrome/plugin/plugin_channel_base.h"
#endif
+#include "chrome/renderer/automation/dom_automation_v8_extension.h"
#include "chrome/renderer/cookie_message_filter.h"
#include "chrome/renderer/devtools_agent_filter.h"
#include "chrome/renderer/extension_groups.h"
@@ -789,6 +790,10 @@ void RenderThread::EnsureWebKitInitialized() {
extensions_v8::PlaybackExtension::Get());
}
+ if (command_line.HasSwitch(switches::kDomAutomationController)) {
+ WebScriptController::registerExtension(DomAutomationV8Extension::Get());
+ }
+
WebRuntimeFeatures::enableMediaPlayer(
RenderProcess::current()->HasInitializedMediaLibrary());
diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd
index 15c8f90..ac2f91c 100644
--- a/chrome/renderer/renderer_resources.grd
+++ b/chrome/renderer/renderer_resources.grd
@@ -22,6 +22,7 @@ without changes to the corresponding grd file. fb9 -->
<include name="IDR_SAD_PLUGIN" file="resources\sadplugin.png" type="BINDATA" />
<include name="IDR_EXTENSION_APITEST_JS" file="resources\extension_apitest.js" type="BINDATA" />
<include name="IDR_EXTENSION_TOOLSTRIP_CSS" file="resources\extension_toolstrip.css" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_DOM_AUTOMATION_JS" file="resources\dom_automation.js" type="BINDATA" />
<include name="IDR_BASE_JS" file="resources\base.js" type="BINDATA" />
</includes>
</release>
diff --git a/chrome/renderer/resources/dom_automation.js b/chrome/renderer/resources/dom_automation.js
new file mode 100644
index 0000000..16a4dd0
--- /dev/null
+++ b/chrome/renderer/resources/dom_automation.js
@@ -0,0 +1,234 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// dom_automation.js
+// Methods for performing common DOM operations. Used in Chrome testing
+// involving the DomAutomationController.
+
+var domAutomation = domAutomation || {};
+
+(function() {
+ // |objects| is used to track objects which are sent back to the
+ // DomAutomationController. Since JavaScript does not have a map type,
+ // |objects| is simply an object in which the property name and
+ // property value serve as the key-value pair. The key is the handle number
+ // and the value is the tracked object.
+ domAutomation.objects = {};
+ domAutomation.nextHandle = 1;
+
+ // Returns |value| after converting it to an acceptable type for return, if
+ // necessary.
+ function getConvertedValue(value) {
+ if (typeof value == "undefined" || !value) {
+ return "";
+ }
+ if (value instanceof Array) {
+ var result = [];
+ for (var i = 0; i < value.length; i++) {
+ result.push(getConvertedValue(value[i]));
+ }
+ return result;
+ }
+ if (typeof(value) == "object") {
+ var handle = getHandleForObject(value);
+ if (handle == -1) {
+ // Track this object.
+ var handle = domAutomation.nextHandle++;
+ domAutomation.objects[handle] = value;
+ }
+ return handle;
+ }
+ return value;
+ }
+
+ // Returns the handle for |obj|, or -1 if no handle exists.
+ function getHandleForObject(obj) {
+ for (var property in domAutomation.objects) {
+ if (domAutomation.objects[property] == obj)
+ return parseInt(property);
+ }
+ return -1;
+ }
+
+ // Safely evaluates |javascript| and sends a response back via the
+ // DomAutomationController. See javascript_execution_controller.cc
+ // for more details.
+ domAutomation.evaluateJavaScript = function(javascript) {
+ try {
+ var result = [true, "", getConvertedValue(eval(javascript))];
+ }
+ catch (exception) {
+ var message = exception.message;
+ if (typeof message != "string")
+ message = JSON.stringify(message);
+ var result = [false, message, exception];
+ }
+ domAutomationController.sendJSON(JSON.stringify(result));
+ }
+
+ // Stops tracking the object associated with |handle|.
+ domAutomation.removeObject = function(handle) {
+ delete domAutomation.objects[handle];
+ }
+
+ // Stops tracking all objects.
+ domAutomation.removeAll = function() {
+ domAutomation.objects = {};
+ domAutomation.nextHandle = 1;
+ }
+
+ // Gets the object associated with this |handle|.
+ domAutomation.getObject = function(handle) {
+ return domAutomation.objects[handle];
+ }
+
+ // Converts an indexable list with a length property to an array.
+ function getArray(list) {
+ var arr = [];
+ for (var i = 0; i < list.length; i++) {
+ arr.push(list[i]);
+ }
+ return arr;
+ }
+
+ // Removes whitespace at the beginning and end of |text|.
+ function trim(text) {
+ return text.replace(/^\s+|\s+$/g, "");
+ }
+
+ // Returns the window (which is a sub window of |win|) which
+ // directly contains |doc|. May return null.
+ function findWindowForDocument(win, doc) {
+ if (win.document == doc)
+ return win;
+ for (var i = 0; i < win.frames.length; i++) {
+ if (findWindowForDocument(win.frames[i], doc))
+ return win.frames[i];
+ }
+ return null;
+ }
+
+ //// DOM Element automation methods
+ //// See dom_element_proxy.h for method details.
+
+ domAutomation.getDocumentFromFrame = function(element, frameNames) {
+ // Find the window this element is in.
+ var containingDocument = element.ownerDocument || element;
+ var frame = findWindowForDocument(window, containingDocument);
+
+ for (var i = 0; i < frameNames.length; i++) {
+ frame = frame.frames[frameNames[i]];
+ if (typeof frame == "undefined" || !frame) {
+ return null;
+ }
+ }
+ return frame.document;
+ }
+
+ domAutomation.findByXPath = function(context, xpath) {
+ var xpathResult =
+ document.evaluate(xpath, context, null,
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ var elements = [];
+ for (var i = 0; i < xpathResult.snapshotLength; i++) {
+ elements.push(xpathResult.snapshotItem(i));
+ }
+ return elements;
+ }
+
+ domAutomation.find1ByXPath = function(context, xpath) {
+ var xpathResult =
+ document.evaluate(xpath, context, null,
+ XPathResult.FIRST_ORDERED_NODE_TYPE, null);
+ return xpathResult.singleNodeValue;
+ }
+
+ domAutomation.findBySelectors = function(context, selectors) {
+ return getArray(context.querySelectorAll(selectors));
+ }
+
+ domAutomation.find1BySelectors = function(context, selectors) {
+ return context.querySelector(selectors);
+ }
+
+ domAutomation.findByText = function(context, text) {
+ // Find all elements containing this text and all inputs containing
+ // this text.
+ var xpath = ".//*[contains(text(), '" + text + "')] | " +
+ ".//input[contains(@value, '" + text + "')]";
+ var elements = domAutomation.findByXPath(context, xpath);
+
+ // Limit to what is visible.
+ var final_list = [];
+ for (var i = 0; i < elements.length; i++) {
+ if (domAutomation.isVisible(elements[i]))
+ final_list.push(elements[i]);
+ }
+ return final_list;
+ }
+
+ domAutomation.find1ByText = function(context, text) {
+ return domAutomation.findByText(context, text)[0];
+ }
+
+ domAutomation.click = function(element) {
+ var evt = document.createEvent('MouseEvents');
+ evt.initMouseEvent('click', true, true, window,
+ 0, 0, 0, 0, 0, false, false,
+ false, false, 0, null);
+ while (element) {
+ element.dispatchEvent(evt);
+ element = element.parentNode;
+ }
+ }
+
+ domAutomation.type = function(element, text) {
+ if (element instanceof HTMLTextAreaElement ||
+ (element instanceof HTMLInputElement && element.type == "text")) {
+ element.value += text;
+ return true;
+ }
+ return false;
+ }
+
+ domAutomation.setText = function(element, text) {
+ if (element instanceof HTMLTextAreaElement ||
+ (element instanceof HTMLInputElement && element.type == "text")) {
+ element.value = text;
+ return true;
+ }
+ return false;
+ }
+
+ domAutomation.getText = function(element) {
+ if (element instanceof Text) {
+ return trim(element.textContent);
+ }
+ else if (element instanceof HTMLTextAreaElement ||
+ (element instanceof HTMLInputElement)) {
+ return element.value || "";
+ }
+ var childrenText = "";
+ for (var i = 0; i < element.childNodes.length; i++) {
+ childrenText += domAutomation.getText(element.childNodes[i]);
+ }
+ return childrenText;
+ }
+
+ domAutomation.getInnerHTML = function(element) {
+ return trim(element.innerHTML);
+ }
+
+ domAutomation.isVisible = function(element) {
+ while (element.style) {
+ if (element.style.display == 'none' ||
+ element.style.visibility == 'hidden' ||
+ element.style.visibility == 'collapse') {
+ return false;
+ }
+ element = element.parentNode;
+ }
+ return true;
+ }
+})();
diff --git a/chrome/test/automation/dom_automation_browsertest.cc b/chrome/test/automation/dom_automation_browsertest.cc
new file mode 100644
index 0000000..c861f89
--- /dev/null
+++ b/chrome/test/automation/dom_automation_browsertest.cc
@@ -0,0 +1,246 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/ref_counted.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/test/automation/dom_element_proxy.h"
+#include "chrome/browser/browser.h"
+#include "chrome/test/in_process_browser_test.h"
+#include "chrome/test/ui_test_utils.h"
+
+namespace {
+
+class DOMAutomationTest : public InProcessBrowserTest {
+ public:
+ DOMAutomationTest() {
+ EnableDOMAutomation();
+ }
+
+ GURL GetTestURL(const char* path) {
+ std::string url("http://localhost:1337/files/dom_automation/");
+ url.append(path);
+ return GURL(url);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindByXPath) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(),
+ GetTestURL("find_elements/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ // Find first element.
+ DOMElementProxyRef first_div = main_doc->FindByXPath("//div");
+ ASSERT_TRUE(first_div);
+ ASSERT_NO_FATAL_FAILURE(first_div->EnsureNameMatches("0"));
+
+ // Find many elements.
+ std::vector<DOMElementProxyRef> elements;
+ ASSERT_TRUE(main_doc->FindByXPath("//div", &elements));
+ ASSERT_EQ(2u, elements.size());
+ for (size_t i = 0; i < elements.size(); i++)
+ ASSERT_NO_FATAL_FAILURE(elements[i]->EnsureNameMatches(UintToString(i)));
+
+ // Find 0 elements.
+ ASSERT_FALSE(main_doc->FindByXPath("//nosuchtag"));
+ elements.clear();
+ ASSERT_TRUE(main_doc->FindByXPath("//nosuchtag", &elements));
+ elements.clear();
+ ASSERT_EQ(0u, elements.size());
+
+ // Find with invalid xpath.
+ ASSERT_FALSE(main_doc->FindByXPath("'invalid'"));
+ ASSERT_FALSE(main_doc->FindByXPath(" / / "));
+ ASSERT_FALSE(main_doc->FindByXPath("'invalid'", &elements));
+ ASSERT_FALSE(main_doc->FindByXPath(" / / ", &elements));
+
+ // Find nested elements.
+ int nested_count = 0;
+ std::string span_name;
+ DOMElementProxyRef node = main_doc->FindByXPath("/html/body/span");
+ while (node) {
+ nested_count++;
+ span_name.append("span");
+ ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name));
+ node = node->FindByXPath("./span");
+ }
+ ASSERT_EQ(3, nested_count);
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindBySelectors) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(),
+ GetTestURL("find_elements/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ // Find first element.
+ DOMElementProxyRef first_myclass =
+ main_doc->FindBySelectors(".myclass");
+ ASSERT_TRUE(first_myclass);
+ ASSERT_NO_FATAL_FAILURE(first_myclass->EnsureNameMatches("0"));
+
+ // Find many elements.
+ std::vector<DOMElementProxyRef> elements;
+ ASSERT_TRUE(main_doc->FindBySelectors(".myclass", &elements));
+ ASSERT_EQ(2u, elements.size());
+ for (size_t i = 0; i < elements.size(); i++)
+ ASSERT_NO_FATAL_FAILURE(elements[i]->EnsureNameMatches(UintToString(i)));
+
+ // Find 0 elements.
+ ASSERT_FALSE(main_doc->FindBySelectors("#nosuchid"));
+ elements.clear();
+ ASSERT_TRUE(main_doc->FindBySelectors("#nosuchid", &elements));
+ ASSERT_EQ(0u, elements.size());
+
+ // Find with invalid selectors.
+ ASSERT_FALSE(main_doc->FindBySelectors("1#2"));
+ ASSERT_FALSE(main_doc->FindBySelectors("1#2", &elements));
+
+ // Find nested elements.
+ int nested_count = 0;
+ std::string span_name;
+ DOMElementProxyRef node = main_doc->FindBySelectors("span");
+ while (node) {
+ nested_count++;
+ span_name.append("span");
+ ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name));
+ node = node->FindBySelectors("span");
+ }
+ ASSERT_EQ(3, nested_count);
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindByText) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(),
+ GetTestURL("find_elements/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ // Find first element.
+ DOMElementProxyRef first_text = main_doc->FindByText("div_text");
+ ASSERT_TRUE(first_text);
+ ASSERT_NO_FATAL_FAILURE(first_text->EnsureNameMatches("0"));
+
+ // Find many elements.
+ std::vector<DOMElementProxyRef> elements;
+ ASSERT_TRUE(main_doc->FindByText("div_text", &elements));
+ ASSERT_EQ(2u, elements.size());
+ for (size_t i = 0; i < elements.size(); i++)
+ ASSERT_NO_FATAL_FAILURE(elements[i]->EnsureNameMatches(UintToString(i)));
+
+ // Find 0 elements.
+ ASSERT_FALSE(main_doc->FindByText("nosuchtext"));
+ elements.clear();
+ ASSERT_TRUE(main_doc->FindByText("nosuchtext", &elements));
+ ASSERT_EQ(0u, elements.size());
+
+ // Find nested elements.
+ int nested_count = 0;
+ std::string span_name;
+ DOMElementProxyRef node = main_doc->FindByText("span_text");
+ while (node) {
+ nested_count++;
+ span_name.append("span");
+ ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name));
+ node = node->FindByText("span_text");
+ }
+ ASSERT_EQ(3, nested_count);
+
+ // Find only visible text.
+ DOMElementProxyRef shown_td = main_doc->FindByText("table_text");
+ ASSERT_TRUE(shown_td);
+ ASSERT_NO_FATAL_FAILURE(shown_td->EnsureNameMatches("shown"));
+
+ // Find text in inputs.
+ ASSERT_TRUE(main_doc->FindByText("textarea_text"));
+ ASSERT_TRUE(main_doc->FindByText("input_text"));
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Frames) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(), GetTestURL("frames/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ // Get both frame elements.
+ std::vector<DOMElementProxyRef> frame_elements;
+ ASSERT_TRUE(main_doc->FindByXPath("//frame", &frame_elements));
+ ASSERT_EQ(2u, frame_elements.size());
+
+ // Get both frames, checking their contents are correct.
+ DOMElementProxyRef frame1 = frame_elements[0]->GetContentDocument();
+ DOMElementProxyRef frame2 = frame_elements[1]->GetContentDocument();
+ ASSERT_TRUE(frame1 && frame2);
+ DOMElementProxyRef frame_div = frame1->FindByXPath("/html/body/div");
+ ASSERT_TRUE(frame_div);
+ ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("frame 1"));
+ frame_div = frame2->FindByXPath("/html/body/div");
+ ASSERT_TRUE(frame_div);
+ ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("frame 2"));
+
+ // Get both inner iframes, checking their contents are correct.
+ DOMElementProxyRef iframe1 =
+ frame1->GetDocumentFromFrame("0");
+ DOMElementProxyRef iframe2 =
+ frame2->GetDocumentFromFrame("0");
+ ASSERT_TRUE(iframe1 && iframe2);
+ frame_div = iframe1->FindByXPath("/html/body/div");
+ ASSERT_TRUE(frame_div);
+ ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("iframe 1"));
+ frame_div = iframe2->FindByXPath("/html/body/div");
+ ASSERT_TRUE(frame_div);
+ ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("iframe 2"));
+
+ // Get nested frame.
+ ASSERT_EQ(iframe1.get(), main_doc->GetDocumentFromFrame("0", "0").get());
+ ASSERT_EQ(iframe2.get(), main_doc->GetDocumentFromFrame("1", "0").get());
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Events) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(), GetTestURL("events/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ // Click link and make sure text changes.
+ DOMElementProxyRef link = main_doc->FindBySelectors("a");
+ ASSERT_TRUE(link && link->Click());
+ ASSERT_NO_FATAL_FAILURE(link->EnsureTextMatches("clicked"));
+
+ // Click input button and make sure textfield changes.
+ DOMElementProxyRef button = main_doc->FindBySelectors("#button");
+ DOMElementProxyRef textfield = main_doc->FindBySelectors("#textfield");
+ ASSERT_TRUE(textfield && button && button->Click());
+ ASSERT_NO_FATAL_FAILURE(textfield->EnsureTextMatches("clicked"));
+
+ // Type in the textfield.
+ ASSERT_TRUE(textfield->SetText("test"));
+ ASSERT_NO_FATAL_FAILURE(textfield->EnsureTextMatches("test"));
+
+ // Type in the textarea.
+ DOMElementProxyRef textarea = main_doc->FindBySelectors("textarea");
+ ASSERT_TRUE(textarea && textarea->Type("test"));
+ ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches("textareatest"));
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, StringEscape) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(),
+ GetTestURL("string_escape/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ DOMElementProxyRef textarea = main_doc->FindBySelectors("textarea");
+ ASSERT_TRUE(textarea);
+ ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches(WideToUTF8(L"\u00FF")));
+
+ const wchar_t* set_and_expect_strings[] = {
+ L"\u00FF and \u00FF",
+ L"\n \t \\",
+ L"' \""
+ };
+ for (size_t i = 0; i < 3; i++) {
+ ASSERT_TRUE(textarea->SetText(WideToUTF8(set_and_expect_strings[i])));
+ ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches(
+ WideToUTF8(set_and_expect_strings[i])));
+ }
+}
+
+} // namespace
diff --git a/chrome/test/automation/dom_element_proxy.cc b/chrome/test/automation/dom_element_proxy.cc
new file mode 100644
index 0000000..c7bc6da
--- /dev/null
+++ b/chrome/test/automation/dom_element_proxy.cc
@@ -0,0 +1,299 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/automation/dom_element_proxy.h"
+
+#include "base/json/string_escape.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Convenience wrapper for GetDoubleQuotedJson function.
+std::string GetDoubleQuotedJson(std::string utf8_string) {
+ return base::GetDoubleQuotedJson(UTF8ToUTF16(utf8_string));
+}
+
+} // namespace
+
+DOMElementProxyRef DOMElementProxy::GetContentDocument() {
+ const char* script = "%s.contentDocument;";
+ DOMElementProxy* element = NULL;
+ executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str()), &element);
+ return element;
+}
+
+DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
+ const std::vector<std::string>& frame_names) {
+ if (!is_valid())
+ return NULL;
+
+ const char* script = "domAutomation.getDocumentFromFrame(%s, %s);";
+ std::string string_script = StringPrintf(
+ script, this->GetReferenceJavaScript().c_str(),
+ JavaScriptExecutionController::Serialize(frame_names).c_str());
+ DOMElementProxy* element = NULL;
+ executor_->ExecuteJavaScriptAndParse(string_script, &element);
+ return element;
+}
+
+DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
+ const std::string& frame_name) {
+ if (!is_valid())
+ return NULL;
+
+ std::vector<std::string> frame_names;
+ frame_names.push_back(frame_name);
+ return GetDocumentFromFrame(frame_names);
+}
+
+DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
+ const std::string& frame_name1, const std::string& frame_name2) {
+ if (!is_valid())
+ return NULL;
+
+ std::vector<std::string> frame_names;
+ frame_names.push_back(frame_name1);
+ frame_names.push_back(frame_name2);
+ return GetDocumentFromFrame(frame_names);
+}
+
+DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
+ const std::string& frame_name1, const std::string& frame_name2,
+ const std::string& frame_name3) {
+ if (!is_valid())
+ return NULL;
+
+ std::vector<std::string> frame_names;
+ frame_names.push_back(frame_name1);
+ frame_names.push_back(frame_name2);
+ frame_names.push_back(frame_name3);
+ return GetDocumentFromFrame(frame_names);
+}
+
+bool DOMElementProxy::FindByXPath(const std::string& xpath,
+ std::vector<DOMElementProxyRef>* elements) {
+ DCHECK(elements);
+ if (!is_valid())
+ return false;
+
+ const char* script = "domAutomation.findByXPath(%s, %s);";
+ std::vector<DOMElementProxy*> element_pointers;
+ if (!executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(xpath).c_str()),
+ &element_pointers))
+ return false;
+ for (size_t i = 0; i < element_pointers.size(); i++)
+ elements->push_back(element_pointers[i]);
+ return true;
+}
+
+DOMElementProxyRef DOMElementProxy::FindByXPath(const std::string& xpath) {
+ if (!is_valid())
+ return NULL;
+
+ const char* script = "domAutomation.find1ByXPath(%s, %s);";
+ DOMElementProxy* element = NULL;
+ executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(xpath).c_str()),
+ &element);
+ return element;
+}
+
+bool DOMElementProxy::FindBySelectors(
+ const std::string& selectors, std::vector<DOMElementProxyRef>* elements) {
+ DCHECK(elements);
+ if (!is_valid())
+ return false;
+
+ const char* script = "domAutomation.findBySelectors(%s, %s);";
+ std::vector<DOMElementProxy*> element_pointers;
+ if (!executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(selectors).c_str()),
+ &element_pointers))
+ return false;
+ for (size_t i = 0; i < element_pointers.size(); i++)
+ elements->push_back(element_pointers[i]);
+ return true;
+}
+
+DOMElementProxyRef DOMElementProxy::FindBySelectors(
+ const std::string& selectors) {
+ if (!is_valid())
+ return NULL;
+
+ const char* script = "domAutomation.find1BySelectors(%s, %s);";
+ DOMElementProxy* element = NULL;
+ executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(selectors).c_str()),
+ &element);
+ return element;
+}
+
+bool DOMElementProxy::FindByText(const std::string& text,
+ std::vector<DOMElementProxyRef>* elements) {
+ DCHECK(elements);
+ if (!is_valid())
+ return false;
+
+ const char* script = "domAutomation.findByText(%s, %s);";
+ std::vector<DOMElementProxy*> element_pointers;
+ if (!executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(text).c_str()),
+ &element_pointers))
+ return false;
+ for (size_t i = 0; i < element_pointers.size(); i++)
+ elements->push_back(element_pointers[i]);
+ return true;
+}
+
+DOMElementProxyRef DOMElementProxy::FindByText(const std::string& text) {
+ if (!is_valid())
+ return NULL;
+
+ const char* script = "domAutomation.find1ByText(%s, %s);";
+ DOMElementProxy* element = NULL;
+ executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(text).c_str()),
+ &element);
+ return element;
+}
+
+bool DOMElementProxy::Click() {
+ const char* script = "domAutomation.click(%s);";
+ if (!is_valid())
+ return false;
+
+ return executor_->ExecuteJavaScript(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str()));
+}
+
+bool DOMElementProxy::Type(const std::string& text) {
+ const char* script = "domAutomation.type(%s, %s);";
+ if (!is_valid())
+ return false;
+
+ bool success = false;
+ executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(text).c_str()),
+ &success);
+ return success;
+}
+
+bool DOMElementProxy::SetText(const std::string& text) {
+ const char* script = "domAutomation.setText(%s, %s);";
+ if (!is_valid())
+ return false;
+
+ bool success = false;
+ executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(text).c_str()),
+ &success);
+ return success;
+}
+
+bool DOMElementProxy::GetProperty(const std::string& property,
+ std::string* out) {
+ DCHECK(out);
+ if (!is_valid())
+ return false;
+
+ const char* script = "%s.%s;";
+ return executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(property).c_str()),
+ out);
+}
+
+bool DOMElementProxy::GetAttribute(const std::string& attribute,
+ std::string* out) {
+ DCHECK(out);
+ if (!is_valid())
+ return false;
+
+ const char* script = "%s.getAttribute(%s);";
+ return executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str(),
+ GetDoubleQuotedJson(attribute).c_str()),
+ out);
+}
+
+bool DOMElementProxy::GetText(std::string* text) {
+ DCHECK(text);
+ if (!is_valid())
+ return false;
+
+ const char* script = "domAutomation.getText(%s);";
+ return executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str()), text);
+}
+
+bool DOMElementProxy::GetInnerHTML(std::string* html) {
+ DCHECK(html);
+ if (!is_valid())
+ return false;
+
+ const char* script = "domAutomation.getInnerHTML(%s);";
+ return executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str()), html);
+}
+
+bool DOMElementProxy::GetId(std::string* id) {
+ DCHECK(id);
+ if (!is_valid())
+ return false;
+
+ const char* script = "%s.id;";
+ return executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str()), id);
+}
+
+bool DOMElementProxy::GetName(std::string* name) {
+ return GetAttribute("name", name);
+}
+
+bool DOMElementProxy::GetVisibility(bool* visibility) {
+ DCHECK(visibility);
+ if (!is_valid())
+ return false;
+
+ const char* script = "domAutomation.isVisible(%s);";
+ return executor_->ExecuteJavaScriptAndParse(
+ StringPrintf(script, this->GetReferenceJavaScript().c_str()),
+ visibility);
+}
+
+void DOMElementProxy::EnsureTextMatches(const std::string& expected_text) {
+ std::string text;
+ ASSERT_TRUE(GetText(&text));
+ ASSERT_EQ(expected_text, text);
+}
+
+void DOMElementProxy::EnsureInnerHTMLMatches(const std::string& expected_html) {
+ std::string html;
+ ASSERT_TRUE(GetInnerHTML(&html));
+ ASSERT_EQ(expected_html, html);
+}
+
+void DOMElementProxy::EnsureNameMatches(const std::string& expected_name) {
+ std::string name;
+ ASSERT_TRUE(GetName(&name));
+ ASSERT_EQ(expected_name, name);
+}
+
+void DOMElementProxy::EnsureVisibilityMatches(bool expected_visibility) {
+ bool visibility;
+ ASSERT_TRUE(GetVisibility(&visibility));
+ ASSERT_EQ(expected_visibility, visibility);
+}
diff --git a/chrome/test/automation/dom_element_proxy.h b/chrome/test/automation/dom_element_proxy.h
new file mode 100644
index 0000000..2057e0c0
--- /dev/null
+++ b/chrome/test/automation/dom_element_proxy.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_TEST_AUTOMATION_DOM_ELEMENT_PROXY_H_
+#define CHROME_TEST_AUTOMATION_DOM_ELEMENT_PROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "chrome/test/automation/javascript_execution_controller.h"
+
+class DOMElementProxy;
+
+typedef scoped_refptr<DOMElementProxy> DOMElementProxyRef;
+
+// This class presents the interface to actions that can be performed on
+// a given DOM element. Note that this object can be invalidated at any
+// time. In that case, any subsequent calls will return false immediately.
+class DOMElementProxy : public JavaScriptObjectProxy {
+ public:
+ DOMElementProxy(JavaScriptExecutionController* executor, int handle)
+ : JavaScriptObjectProxy(executor, handle) {}
+
+ // Returns the document for this element, which must be of type frame.
+ // Returns NULL on failure.
+ DOMElementProxyRef GetContentDocument();
+
+ // Finds the frame which matches the list of given names, starting from
+ // the window that contains this element. Each name in the list is used to
+ // select the next sub frame. Returns NULL on failure.
+ // A vector of "2" and "ad" is equivalent to the javascript:
+ // frame.frames["2"].frames["ad"].
+ DOMElementProxyRef GetDocumentFromFrame(
+ const std::vector<std::string>& frame_names);
+
+ // Same as above but with different argument for convenience.
+ DOMElementProxyRef GetDocumentFromFrame(const std::string& frame_name);
+
+ // Same as above but with different argument for convenience.
+ DOMElementProxyRef GetDocumentFromFrame(const std::string& frame_name1,
+ const std::string& frame_name2);
+
+ // Same as above but with different argument for convenience.
+ DOMElementProxyRef GetDocumentFromFrame(const std::string& frame_name1,
+ const std::string& frame_name2, const std::string& frame_name3);
+
+ // Adds the elements from this element's descendants that satisfy the
+ // XPath query |xpath| to the vector |elements|.
+ // Returns true on success.
+ bool FindByXPath(const std::string& xpath,
+ std::vector<DOMElementProxyRef>* elements);
+
+ // Same as above, but returns the first element, or NULL if none.
+ DOMElementProxyRef FindByXPath(const std::string& xpath);
+
+ // Adds the elements from this element's descendants that match the
+ // CSS Selectors |selectors| to the vector |elements|.
+ // Returns true on success.
+ bool FindBySelectors(const std::string& selectors,
+ std::vector<DOMElementProxyRef>* elements);
+
+ // Same as above, but returns the first element, or NULL if none.
+ DOMElementProxyRef FindBySelectors(const std::string& selectors);
+
+ // Adds the elements from this element's descendants which have text that
+ // matches |text|. This includes text from input elements.
+ // Returns true on success.
+ bool FindByText(const std::string& text,
+ std::vector<DOMElementProxyRef>* elements);
+
+ // Same as above, but returns the first element, or NULL if none.
+ DOMElementProxyRef FindByText(const std::string& text);
+
+ // Dispatches a click MouseEvent to the element and all its parents.
+ // Returns true on success.
+ bool Click();
+
+ // Adds |text| to this element. Only valid for textareas and textfields.
+ // Returns true on success.
+ bool Type(const std::string& text);
+
+ // Sets the input text to |text|. Only valid for textareas and textfields.
+ // Returns true on success.
+ bool SetText(const std::string& text);
+
+ // Gets the element's value for its |property|. Returns true on success.
+ bool GetProperty(const std::string& property,
+ std::string* out);
+
+ // Gets the element's value for its |attribute|. Returns true on success.
+ bool GetAttribute(const std::string& attribute,
+ std::string* out);
+
+ // Retrieves all the text in this element. This includes the value
+ // of textfields and inputs. Returns true on success.
+ bool GetText(std::string* text);
+
+ // Retrieves the element's inner HTML. Returns true on success.
+ bool GetInnerHTML(std::string* html);
+
+ // Retrieves the element's id. Returns true on success.
+ bool GetId(std::string* id);
+
+ // Retrieves the element's name. Returns true on success.
+ bool GetName(std::string* name);
+
+ // Retrieves the element's visibility. Returns true on success.
+ bool GetVisibility(bool* visilibity);
+
+ // Asserts that |expected_text| matches all the text in this element. This
+ // includes the value of textfields and inputs.
+ void EnsureTextMatches(const std::string& expected_text);
+
+ // Asserts that |expected_html| matches the element's inner html.
+ void EnsureInnerHTMLMatches(const std::string& expected_html);
+
+ // Asserts that |expected_name| matches the element's name.
+ void EnsureNameMatches(const std::string& expected_name);
+
+ // Asserts that |expected_visibility| matches the element's visibility.
+ void EnsureVisibilityMatches(bool expected_visibility);
+};
+
+#endif // CHROME_TEST_AUTOMATION_DOM_ELEMENT_PROXY_H_
diff --git a/chrome/test/automation/javascript_execution_controller.cc b/chrome/test/automation/javascript_execution_controller.cc
new file mode 100644
index 0000000..26d32d0
--- /dev/null
+++ b/chrome/test/automation/javascript_execution_controller.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/automation/javascript_execution_controller.h"
+
+#include "base/json/string_escape.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/json_value_serializer.h"
+
+using base::GetDoubleQuotedJson;
+
+// JavaScriptObjectProxy methods
+JavaScriptObjectProxy::JavaScriptObjectProxy(
+ JavaScriptExecutionController* executor, int handle)
+ : executor_(executor->AsWeakPtr()), handle_(handle) {}
+
+JavaScriptObjectProxy::~JavaScriptObjectProxy() {
+ if (is_valid())
+ executor_->Remove(handle_);
+}
+
+std::string JavaScriptObjectProxy::GetReferenceJavaScript() {
+ return JavaScriptExecutionController::GetReferenceJavaScript(this);
+}
+
+// JavaScriptExecutionController methods
+bool JavaScriptExecutionController::ExecuteJavaScript(
+ const std::string& script) {
+ std::string json;
+ return ExecuteJavaScript(script, &json);
+}
+
+// static
+std::string JavaScriptExecutionController::GetReferenceJavaScript(
+ JavaScriptObjectProxy* object) {
+ return StringPrintf("domAutomation.getObject(%d)", object->handle());
+}
+
+// static
+std::string JavaScriptExecutionController::Serialize(
+ const std::vector<std::string>& vector) {
+ std::string javascript = "[";
+ for (size_t i = 0; i < vector.size(); i++) {
+ javascript.append(GetDoubleQuotedJson(UTF8ToUTF16(vector[i])));
+ if (i < vector.size() - 1)
+ javascript.append(",");
+ }
+ javascript.append("]");
+ return javascript;
+}
+
+void JavaScriptExecutionController::Remove(int handle) {
+ ExecuteJavaScript(StringPrintf("domAutomation.removeObject(%d);", handle));
+ handle_to_object_.erase(handle);
+ if (handle_to_object_.empty())
+ LastObjectRemoved();
+}
+
+bool JavaScriptExecutionController::ExecuteJavaScript(
+ const std::string& original_script, std::string* json) {
+ std::string script =
+ "domAutomationController.setAutomationId(0);"
+ "domAutomation.evaluateJavaScript(";
+ script.append(GetDoubleQuotedJson(UTF8ToUTF16(original_script)));
+ script.append(");");
+ return ExecuteJavaScriptAndGetJSON(script, json);
+}
+
+bool JavaScriptExecutionController::ParseJSON(const std::string& json,
+ scoped_ptr<Value>* result) {
+ JSONStringValueSerializer parse(json);
+ std::string parsing_error;
+ scoped_ptr<Value> root_value(parse.Deserialize(&parsing_error));
+
+ if (!root_value.get()) {
+ if (parsing_error.length())
+ LOG(ERROR) << "Cannot parse JSON response: " << parsing_error;
+ else
+ LOG(ERROR) << "JSON response is empty";
+ return false;
+ }
+
+ // The response must be a list of 3 components:
+ // -success(boolean): whether the javascript was evaluated with no errors
+ // -error(string): the evaluation error message or the empty string if
+ // no error occurred
+ // -result(string): the result of the evaluation (in JSON), or the
+ // exact error if an error occurred (in JSON)
+ bool success;
+ std::string evaluation_error;
+ Value* evaluation_result_value;
+ if (!root_value->IsType(Value::TYPE_LIST)) {
+ LOG(ERROR) << "JSON response was not in correct format";
+ return false;
+ }
+ ListValue* list = static_cast<ListValue*>(root_value.get());
+ if (!list->GetBoolean(0, &success) ||
+ !list->GetString(1, &evaluation_error) ||
+ !list->Remove(2, &evaluation_result_value)) {
+ LOG(ERROR) << "JSON response was not in correct format";
+ return false;
+ }
+ if (!success) {
+ LOG(WARNING) << "JavaScript evaluation did not complete successfully."
+ << evaluation_error;
+ return false;
+ }
+ result->reset(evaluation_result_value);
+ return true;
+}
+
+bool JavaScriptExecutionController::ConvertResponse(Value* value,
+ bool* result) {
+ return value->GetAsBoolean(result);
+}
+
+bool JavaScriptExecutionController::ConvertResponse(Value* value,
+ int* result) {
+ return value->GetAsInteger(result);
+}
+
+bool JavaScriptExecutionController::ConvertResponse(Value* value,
+ std::string* result) {
+ return value->GetAsString(result);
+}
diff --git a/chrome/test/automation/javascript_execution_controller.h b/chrome/test/automation/javascript_execution_controller.h
new file mode 100644
index 0000000..0472eee
--- /dev/null
+++ b/chrome/test/automation/javascript_execution_controller.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_
+#define CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "base/weak_ptr.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+class JavaScriptExecutionController;
+
+// This class is a proxy to an object in JavaScript. It holds a handle which
+// can be used to retrieve the actual object in JavaScript scripts.
+class JavaScriptObjectProxy
+ : public base::RefCountedThreadSafe<JavaScriptObjectProxy> {
+ public:
+ JavaScriptObjectProxy(JavaScriptExecutionController* executor, int handle);
+ virtual ~JavaScriptObjectProxy();
+
+ // Returns JavaScript which can be used for retrieving the actual object
+ // associated with this proxy.
+ std::string GetReferenceJavaScript();
+
+ int handle() const { return handle_; }
+ bool is_valid() const { return executor_; }
+
+ protected:
+ base::WeakPtr<JavaScriptExecutionController> executor_;
+ int handle_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(JavaScriptObjectProxy);
+};
+
+// This class handles the execution of arbitrary JavaScript, preparing it for
+// execution, and parsing its result (in JSON). It keeps track of all returned
+// JavaScript objects.
+class JavaScriptExecutionController
+ : public base::SupportsWeakPtr<JavaScriptExecutionController> {
+ public:
+ JavaScriptExecutionController() {}
+ virtual ~JavaScriptExecutionController() {}
+
+ // Executes |script| and parse return value.
+ // A corresponding ConvertResponse(Value* value, T* result) must exist
+ // for type T.
+ template <typename T>
+ bool ExecuteJavaScriptAndParse(const std::string& script, T* result) {
+ std::string json;
+ if (!ExecuteJavaScript(script, &json))
+ return false;
+ scoped_ptr<Value> value;
+ if (!ParseJSON(json, &value))
+ return false;
+ return ConvertResponse(value.get(), result);
+ }
+
+ // Executes |script| with no return.
+ bool ExecuteJavaScript(const std::string& script);
+
+ // Returns JavaScript which can be used for retrieving the actual object
+ // associated with the proxy |object|.
+ static std::string GetReferenceJavaScript(JavaScriptObjectProxy* object);
+
+ // Returns the equivalent JSON for |vector|.
+ static std::string Serialize(const std::vector<std::string>& vector);
+
+ protected:
+ // Executes |script| and sets the JSON response |json|. Returns true
+ // on success.
+ virtual bool ExecuteJavaScriptAndGetJSON(const std::string& script,
+ std::string* json) = 0;
+
+ // Called when this controller is tracking its first object. Used by
+ // reference counted subclasses.
+ virtual void FirstObjectAdded() {}
+
+ // Called when this controller is no longer tracking any objects. Used by
+ // reference counted subclasses.
+ virtual void LastObjectRemoved() {}
+
+ private:
+ typedef std::map<int, JavaScriptObjectProxy*> HandleToObjectMap;
+
+ friend class JavaScriptObjectProxy;
+ // Called by JavaScriptObjectProxy on destruct.
+ void Remove(int handle);
+
+ bool ParseJSON(const std::string& json, scoped_ptr<Value>* result);
+
+ bool ExecuteJavaScript(const std::string& script, std::string* json);
+
+ bool ConvertResponse(Value* value, bool* result);
+ bool ConvertResponse(Value* value, int* result);
+ bool ConvertResponse(Value* value, std::string* result);
+
+ template<class JavaScriptObject>
+ bool ConvertResponse(Value* value, JavaScriptObject** result) {
+ int handle;
+ if (!value->GetAsInteger(&handle))
+ return false;
+
+ HandleToObjectMap::const_iterator iter = handle_to_object_.find(handle);
+ if (iter == handle_to_object_.end()) {
+ *result = new JavaScriptObject(this, handle);
+ if (handle_to_object_.empty())
+ FirstObjectAdded();
+ handle_to_object_.insert(std::make_pair(handle, *result));
+ } else {
+ *result = static_cast<JavaScriptObject*>(iter->second);
+ }
+ return true;
+ }
+
+ template<typename T>
+ bool ConvertResponse(Value* value, std::vector<T>* result) {
+ if (!value->IsType(Value::TYPE_LIST))
+ return false;
+
+ ListValue* list = static_cast<ListValue*>(value);
+ for (size_t i = 0; i < list->GetSize(); i++) {
+ Value* inner_value;
+ if (!list->Get(i, &inner_value))
+ return false;
+ T item;
+ ConvertResponse(inner_value, &item);
+ result->push_back(item);
+ }
+ return true;
+ }
+
+ // Weak pointer to all the object proxies that we create.
+ HandleToObjectMap handle_to_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavaScriptExecutionController);
+};
+
+#endif // CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_
diff --git a/chrome/test/data/dom_automation/events/test.html b/chrome/test/data/dom_automation/events/test.html
new file mode 100644
index 0000000..022a3a0
--- /dev/null
+++ b/chrome/test/data/dom_automation/events/test.html
@@ -0,0 +1,19 @@
+<html>
+<script>
+function renameLink() {
+ document.links[0].innerHTML = "clicked";
+}
+function changeTextfield() {
+ document.getElementById("textfield").value = "clicked";
+}
+</script>
+<body>
+<a href="javascript:renameLink();">link</a>
+<textarea>textarea</textarea>
+<form action="">
+ <input id="textfield" type='text' value='textfield'></input>
+ <input id="button" type='button' value='button' onclick="changeTextfield()">
+ </input>
+</form>
+</body>
+</html>
diff --git a/chrome/test/data/dom_automation/find_elements/test.html b/chrome/test/data/dom_automation/find_elements/test.html
new file mode 100644
index 0000000..858677d
--- /dev/null
+++ b/chrome/test/data/dom_automation/find_elements/test.html
@@ -0,0 +1,36 @@
+<html>
+<head>
+</head>
+
+<div class="myclass" name="0">div_text</div>
+<div class="myclass" name="1">div_text</div>
+
+35957
+<span name="span">span_text
+ <span name="spanspan">span_text
+ <span name="spanspanspan">span_text
+ </span>
+ </span>
+</span>
+
+<table border=1>
+ <tr style="display:none">
+ <td>table_text</td>
+ </tr>
+ <tr>
+ <td style="visibility:collapse">
+ table_text
+ </td>
+ <td style="visibility:hidden">
+ table_text
+ </td>
+ <td name="shown">
+ table_text
+ </td>
+ </tr>
+</table>
+
+<textarea >textarea_text</textarea>
+<input type="text" value="input_text"></input>
+
+</html>
diff --git a/chrome/test/data/dom_automation/frames/frame1.html b/chrome/test/data/dom_automation/frames/frame1.html
new file mode 100644
index 0000000..4e02aab
--- /dev/null
+++ b/chrome/test/data/dom_automation/frames/frame1.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+<iframe src="iframe1.html"></iframe>
+<div>frame 1</div>
+</body>
+</html>
diff --git a/chrome/test/data/dom_automation/frames/frame2.html b/chrome/test/data/dom_automation/frames/frame2.html
new file mode 100644
index 0000000..d1e8bdf
--- /dev/null
+++ b/chrome/test/data/dom_automation/frames/frame2.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+<iframe src="iframe2.html"></iframe>
+<div>frame 2</div>
+</body>
+</html>
diff --git a/chrome/test/data/dom_automation/frames/iframe1.html b/chrome/test/data/dom_automation/frames/iframe1.html
new file mode 100644
index 0000000..7fccbfd
--- /dev/null
+++ b/chrome/test/data/dom_automation/frames/iframe1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<div>iframe 1</div>
+</body>
+</html>
diff --git a/chrome/test/data/dom_automation/frames/iframe2.html b/chrome/test/data/dom_automation/frames/iframe2.html
new file mode 100644
index 0000000..908303e
--- /dev/null
+++ b/chrome/test/data/dom_automation/frames/iframe2.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<div>iframe 2</div>
+</body>
+</html>
diff --git a/chrome/test/data/dom_automation/frames/test.html b/chrome/test/data/dom_automation/frames/test.html
new file mode 100644
index 0000000..004c3e9
--- /dev/null
+++ b/chrome/test/data/dom_automation/frames/test.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script>
+
+</script>
+</head>
+
+<frameset id="set" cols="25%,75%">
+<frame src="frame1.html">
+<frame src="frame2.html">
+</frameset>
+</html>
diff --git a/chrome/test/data/dom_automation/string_escape/test.html b/chrome/test/data/dom_automation/string_escape/test.html
new file mode 100644
index 0000000..6ffea56
--- /dev/null
+++ b/chrome/test/data/dom_automation/string_escape/test.html
@@ -0,0 +1,6 @@
+<html>
+<head>
+<meta http-equiv="content-type" content="charset=UTF-8">
+</head>
+<textarea>&#x00FF</textarea>
+</html>
diff --git a/chrome/test/ui_test_utils.cc b/chrome/test/ui_test_utils.cc
index c93ec1c..f04b02e 100644
--- a/chrome/test/ui_test_utils.cc
+++ b/chrome/test/ui_test_utils.cc
@@ -10,6 +10,7 @@
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/process_util.h"
+#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
@@ -25,6 +26,7 @@
#include "chrome/common/extensions/extension_action.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
+#include "chrome/test/automation/javascript_execution_controller.h"
#if defined(TOOLKIT_VIEWS)
#include "views/focus/accelerator_handler.h"
#endif
@@ -85,7 +87,8 @@ class NavigationNotificationObserver : public NotificationObserver {
class DOMOperationObserver : public NotificationObserver {
public:
- explicit DOMOperationObserver(RenderViewHost* render_view_host) {
+ explicit DOMOperationObserver(RenderViewHost* render_view_host)
+ : did_respond_(false) {
registrar_.Add(this, NotificationType::DOM_OPERATION_RESPONSE,
Source<RenderViewHost>(render_view_host));
ui_test_utils::RunMessageLoop();
@@ -97,14 +100,21 @@ class DOMOperationObserver : public NotificationObserver {
DCHECK(type == NotificationType::DOM_OPERATION_RESPONSE);
Details<DomOperationNotificationDetails> dom_op_details(details);
response_ = dom_op_details->json();
+ did_respond_ = true;
MessageLoopForUI::current()->Quit();
}
+ bool GetResponse(std::string* response) {
+ *response = response_;
+ return did_respond_;
+ }
+
std::string response() const { return response_; }
private:
NotificationRegistrar registrar_;
std::string response_;
+ bool did_respond_;
DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver);
};
@@ -337,6 +347,36 @@ class FindInPageNotificationObserver : public NotificationObserver {
DISALLOW_COPY_AND_ASSIGN(FindInPageNotificationObserver);
};
+class InProcessJavaScriptExecutionController
+ : public base::RefCounted<InProcessJavaScriptExecutionController>,
+ public JavaScriptExecutionController {
+ public:
+ explicit InProcessJavaScriptExecutionController(
+ RenderViewHost* render_view_host)
+ : render_view_host_(render_view_host) {}
+
+ protected:
+ // Executes |script| and sets the JSON response |json|.
+ bool ExecuteJavaScriptAndGetJSON(const std::string& script,
+ std::string* json) {
+ render_view_host_->ExecuteJavascriptInWebFrame(L"", UTF8ToWide(script));
+ DOMOperationObserver dom_op_observer(render_view_host_);
+ return dom_op_observer.GetResponse(json);
+ }
+
+ void FirstObjectAdded() {
+ AddRef();
+ }
+
+ void LastObjectRemoved() {
+ Release();
+ }
+
+ private:
+ // Weak pointer to the associated RenderViewHost.
+ RenderViewHost* render_view_host_;
+};
+
} // namespace
void RunMessageLoop() {
@@ -427,6 +467,15 @@ void NavigateToURLBlockUntilNavigationsComplete(Browser* browser,
WaitForNavigations(controller, number_of_navigations);
}
+DOMElementProxyRef GetActiveDOMDocument(Browser* browser) {
+ JavaScriptExecutionController* executor =
+ new InProcessJavaScriptExecutionController(
+ browser->GetSelectedTabContents()->render_view_host());
+ DOMElementProxy* main_doc = NULL;
+ executor->ExecuteJavaScriptAndParse("document;", &main_doc);
+ return main_doc;
+}
+
Value* ExecuteJavaScript(RenderViewHost* render_view_host,
const std::wstring& frame_xpath,
const std::wstring& original_script) {
diff --git a/chrome/test/ui_test_utils.h b/chrome/test/ui_test_utils.h
index 68e7103..eca6fe5 100644
--- a/chrome/test/ui_test_utils.h
+++ b/chrome/test/ui_test_utils.h
@@ -17,6 +17,7 @@
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_type.h"
#include "chrome/common/notification_service.h"
+#include "chrome/test/automation/dom_element_proxy.h"
class AppModalDialog;
class Browser;
@@ -86,6 +87,10 @@ void NavigateToURLBlockUntilNavigationsComplete(Browser* browser,
const GURL& url,
int number_of_navigations);
+// Gets the DOMDocument for the active tab in |browser|.
+// Returns a NULL reference on failure.
+DOMElementProxyRef GetActiveDOMDocument(Browser* browser);
+
// Executes the passed |script| in the frame pointed to by |frame_xpath| (use
// empty string for main frame) and returns the value the evaluation of the
// script returned. The caller owns the returned value.