1#!/usr/bin/env python
2
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import cgi
8import json
9import logging
10import logging.handlers
11import os
12import sys
13
14import common
15from autotest_lib.client.bin import utils
16from autotest_lib.client.common_lib.cros import chrome, xmlrpc_server
17from autotest_lib.client.cros import constants
18
19
20class InteractiveXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
21    """Exposes methods called remotely to create interactive tests.
22
23    All instance methods of this object without a preceding '_' are exposed via
24    an XML-RPC server. This is not a stateless handler object, which means that
25    if you store state inside the delegate, that state will remain around for
26    future calls.
27    """
28
29    def login(self):
30        """Login to the system and open a tab.
31
32        The tab opened is used by other methods on this server to interact
33        with the user.
34
35        @return True.
36
37        """
38        self._chrome = chrome.Chrome()
39        self._chrome.browser.platform.SetHTTPServerDirectories(
40                os.path.dirname(sys.argv[0]))
41        self._tab = self._chrome.browser.tabs[0]
42        self._tab.Navigate(
43                self._chrome.browser.platform.http_server.UrlOf('shell.html'))
44
45        return True
46
47
48    def set_output(self, html):
49        """Replace the contents of the tab.
50
51        @param html: HTML document to replace tab contents with.
52
53        @return True.
54
55        """
56        # JSON does a better job of escaping HTML for JavaScript than we could
57        # with string.replace().
58        html_escaped = json.dumps(html)
59        # Use JavaScript to append the output and scroll to the bottom of the
60        # open tab.
61        self._tab.ExecuteJavaScript('document.body.innerHTML = %s; ' %
62                                    html_escaped)
63        self._tab.Activate()
64        self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
65        return True
66
67
68    def append_output(self, html):
69        """Append HTML to the contents of the tab.
70
71        @param html: HTML to append to the existing tab contents.
72
73        @return True.
74
75        """
76        # JSON does a better job of escaping HTML for JavaScript than we could
77        # with string.replace().
78        html_escaped = json.dumps(html)
79        # Use JavaScript to append the output and scroll to the bottom of the
80        # open tab.
81        self._tab.ExecuteJavaScript(
82                ('document.body.innerHTML += %s; ' % html_escaped) +
83                'window.scrollTo(0, document.body.scrollHeight);')
84        self._tab.Activate()
85        self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
86        return True
87
88
89    def append_buttons(self, *args):
90        """Append confirmation buttons to the tab.
91
92        Each button is given an index, 0 for the first button, 1 for the second,
93        and so on.
94
95        @param title...: Title of button to append.
96
97        @return True.
98
99        """
100        html = ''
101        index = 0
102        for title in args:
103            onclick = 'submit_button(%d)' % index
104            html += ('<input type="button" value="%s" onclick="%s">' % (
105                     cgi.escape(title),
106                     cgi.escape(onclick)))
107            index += 1
108        return self.append_output(html)
109
110
111    def wait_for_button(self, timeout):
112        """Wait for a button to be clicked.
113
114        Call append_buttons() before this to add buttons to the document.
115
116        @param timeout: Maximum time, in seconds, to wait for a click.
117
118        @return index of button that was clicked.
119
120        """
121        # Wait for the button to be clicked.
122        utils.poll_for_condition(
123                condition=lambda:
124                    self._tab.EvaluateJavaScript('window.__ready') == 1,
125                desc='User clicked on button.',
126                timeout=timeout)
127        # Fetch the result.
128        result = self._tab.EvaluateJavaScript('window.__result')
129        # Reset for the next button.
130        self._tab.ExecuteJavaScript(
131                'window.__ready = 0; '
132                'window.__result = null;')
133        return result
134
135
136    def check_for_button(self):
137        """Check whether a button has been clicked.
138
139        Call append_buttons() before this to add buttons to the document.
140
141        @return index of button that was clicked or -1 if no button
142            has been clicked.
143
144        """
145        if not self._tab.EvaluateJavaScript('window.__ready'):
146            return -1
147        # Fetch the result.
148        result = self._tab.EvaluateJavaScript('window.__result')
149        # Reset for the next button.
150        self._tab.ExecuteJavaScript(
151                'window.__ready = 0; '
152                'window.__result = null;')
153        return result
154
155
156    def append_list(self, name):
157        """Append a results list to the contents of the tab.
158
159        @param name: Name to use for making modifications to the list.
160
161        @return True.
162
163        """
164        html = '<div id="%s"></div>' % cgi.escape(name)
165        return self.append_output(html)
166
167
168    def append_list_item(self, list_name, item_name, html):
169        """Append an item to a results list.
170
171        @param list_name: Name of list provided to append_list().
172        @param item_name: Name to use for making modifications to the item.
173        @param html: HTML to place in the list item.
174
175        @return True.
176
177        """
178        # JSON does a better job of escaping HTML for JavaScript than we could
179        # with string.replace().
180        item_html = '"<div id=\\"%s\\"></div>"' % cgi.escape(item_name)
181        # Use JavaScript to append the output.
182        self._tab.ExecuteJavaScript(
183                'document.getElementById("%s").innerHTML += %s; ' % (
184                        cgi.escape(list_name),
185                        item_html))
186        self._tab.Activate()
187        self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
188        return self.replace_list_item(item_name, html)
189
190
191    def replace_list_item(self, item_name, html):
192        """Replace an item in a results list.
193
194        @param item_name: Name of item provided to append_list_item().
195        @param html: HTML to place in the list item.
196
197        @return True.
198
199        """
200        # JSON does a better job of escaping HTML for JavaScript than we could
201        # with string.replace().
202        html_escaped = json.dumps(html)
203        # Use JavaScript to append the output.
204        self._tab.ExecuteJavaScript(
205                'document.getElementById("%s").innerHTML = %s; ' % (
206                        cgi.escape(item_name),
207                        html_escaped))
208        self._tab.Activate()
209        self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
210        return True
211
212
213    def close(self):
214        """Close the browser.
215
216        @return True.
217
218        """
219        if hasattr(self, '_chrome'):
220            self._chrome.browser.Close()
221        return True
222
223
224if __name__ == '__main__':
225    logging.basicConfig(level=logging.DEBUG)
226    handler = logging.handlers.SysLogHandler(address='/dev/log')
227    formatter = logging.Formatter(
228            'interactive_xmlrpc_server: [%(levelname)s] %(message)s')
229    handler.setFormatter(formatter)
230    logging.getLogger().addHandler(handler)
231    logging.debug('interactive_xmlrpc_server main...')
232    server = xmlrpc_server.XmlRpcServer(
233            'localhost',
234            constants.INTERACTIVE_XMLRPC_SERVER_PORT)
235    server.register_delegate(InteractiveXmlRpcDelegate())
236    server.run()
237