|
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style license that can be |
|
3 // found in the LICENSE file. |
|
4 |
|
5 package org.chromium.sdk.internal.tools.devtools; |
|
6 |
|
7 import java.util.ArrayList; |
|
8 import java.util.Collections; |
|
9 import java.util.List; |
|
10 import java.util.concurrent.Semaphore; |
|
11 import java.util.concurrent.TimeUnit; |
|
12 import java.util.concurrent.TimeoutException; |
|
13 import java.util.logging.Level; |
|
14 import java.util.logging.Logger; |
|
15 |
|
16 import org.chromium.sdk.internal.JsonUtil; |
|
17 import org.chromium.sdk.internal.tools.ChromeDevToolsProtocol; |
|
18 import org.chromium.sdk.internal.tools.ToolHandler; |
|
19 import org.chromium.sdk.internal.tools.ToolOutput; |
|
20 import org.chromium.sdk.internal.transport.Message; |
|
21 import org.json.simple.JSONArray; |
|
22 import org.json.simple.JSONObject; |
|
23 import org.json.simple.parser.ParseException; |
|
24 |
|
25 /** |
|
26 * Handles the interaction with the "DevToolsService" tool. |
|
27 */ |
|
28 public class DevToolsServiceHandler implements ToolHandler { |
|
29 |
|
30 /** |
|
31 * The debugger connection to use. |
|
32 */ |
|
33 private final ToolOutput toolOutput; |
|
34 |
|
35 /** |
|
36 * A "list_tabs" command callback. Is accessed in a synchronized way. |
|
37 */ |
|
38 private ListTabsCallback listTabsCallback; |
|
39 |
|
40 /** |
|
41 * A "version" command callback. Is accessed in a synchronized way. |
|
42 */ |
|
43 private VersionCallback versionCallback; |
|
44 |
|
45 /** |
|
46 * An access/modification lock for the callback fields. |
|
47 */ |
|
48 private final Object lock = new Object(); |
|
49 |
|
50 public static class TabIdAndUrl { |
|
51 public final int id; |
|
52 |
|
53 public final String url; |
|
54 |
|
55 private TabIdAndUrl(int id, String url) { |
|
56 this.id = id; |
|
57 this.url = url; |
|
58 } |
|
59 |
|
60 @Override |
|
61 public String toString() { |
|
62 return new StringBuilder() |
|
63 .append('[') |
|
64 .append(id) |
|
65 .append('=') |
|
66 .append(url) |
|
67 .append(']') |
|
68 .toString(); |
|
69 } |
|
70 } |
|
71 |
|
72 /** |
|
73 * A callback that will be invoked when the ChromeDevTools protocol version |
|
74 * is available. |
|
75 */ |
|
76 private interface VersionCallback { |
|
77 void versionReceived(String versionString); |
|
78 } |
|
79 |
|
80 /** |
|
81 * A callback that will be invoked when the tabs from the associated browser |
|
82 * instance are ready (or not...) |
|
83 */ |
|
84 private interface ListTabsCallback { |
|
85 void tabsReceived(List<TabIdAndUrl> tabs); |
|
86 |
|
87 void failure(int result); |
|
88 } |
|
89 |
|
90 public DevToolsServiceHandler(ToolOutput toolOutput) { |
|
91 this.toolOutput = toolOutput; |
|
92 } |
|
93 |
|
94 public void onDebuggerDetached() { |
|
95 } |
|
96 |
|
97 public void handleMessage(Message message) { |
|
98 JSONObject json; |
|
99 try { |
|
100 json = JsonUtil.jsonObjectFromJson(message.getContent()); |
|
101 } catch (ParseException e) { |
|
102 Logger.getLogger(DevToolsServiceHandler.class.getName()).log( |
|
103 Level.SEVERE, "Invalid JSON received: {0}", message.getContent()); |
|
104 return; |
|
105 } |
|
106 String commandString = JsonUtil.getAsString(json, ChromeDevToolsProtocol.COMMAND.key); |
|
107 DevToolsServiceCommand command = DevToolsServiceCommand.forString(commandString); |
|
108 if (command != null) { |
|
109 switch (command) { |
|
110 case LIST_TABS: |
|
111 handleListTabs(json); |
|
112 break; |
|
113 case VERSION: |
|
114 handleVersion(json); |
|
115 break; |
|
116 default: |
|
117 break; |
|
118 } |
|
119 } |
|
120 } |
|
121 |
|
122 public void handleEos() { |
|
123 // ignore this event, we do not close browser in any way; but clients should dismiss |
|
124 // all tickets |
|
125 } |
|
126 |
|
127 private void handleVersion(JSONObject json) { |
|
128 VersionCallback callback; |
|
129 synchronized (lock) { |
|
130 callback = versionCallback; |
|
131 versionCallback = null; |
|
132 } |
|
133 if (callback != null) { |
|
134 String versionString = JsonUtil.getAsString(json, ChromeDevToolsProtocol.DATA.key); |
|
135 callback.versionReceived(versionString); |
|
136 } |
|
137 } |
|
138 |
|
139 private void handleListTabs(JSONObject json) { |
|
140 ListTabsCallback callback; |
|
141 synchronized (lock) { |
|
142 callback = listTabsCallback; |
|
143 listTabsCallback = null; |
|
144 } |
|
145 if (callback != null) { |
|
146 int result = JsonUtil.getAsLong(json, ChromeDevToolsProtocol.RESULT.key).intValue(); |
|
147 if (result != 0) { |
|
148 callback.failure(result); |
|
149 return; |
|
150 } |
|
151 JSONArray data = JsonUtil.getAsJSONArray(json, ChromeDevToolsProtocol.DATA.key); |
|
152 List<TabIdAndUrl> tabs = new ArrayList<TabIdAndUrl>(data.size()); |
|
153 for (int i = 0; i < data.size(); ++i) { |
|
154 JSONArray idAndUrl = (JSONArray) data.get(i); |
|
155 int id = ((Long) idAndUrl.get(0)).intValue(); |
|
156 String url = (String) idAndUrl.get(1); |
|
157 tabs.add(new TabIdAndUrl(id, url)); |
|
158 } |
|
159 callback.tabsReceived(tabs); |
|
160 } |
|
161 } |
|
162 |
|
163 @SuppressWarnings("unchecked") |
|
164 public List<TabIdAndUrl> listTabs(int timeout) { |
|
165 final Semaphore sem = new Semaphore(0); |
|
166 final List<TabIdAndUrl>[] output = new List[1]; |
|
167 synchronized (lock) { |
|
168 if (listTabsCallback != null) { |
|
169 throw new IllegalStateException("list_tabs request is pending"); |
|
170 } |
|
171 listTabsCallback = new ListTabsCallback() { |
|
172 public void failure(int result) { |
|
173 sem.release(); |
|
174 } |
|
175 |
|
176 public void tabsReceived(List<TabIdAndUrl> tabs) { |
|
177 output[0] = tabs; |
|
178 sem.release(); |
|
179 } |
|
180 }; |
|
181 } |
|
182 toolOutput.send(CommandFactory.listTabs()); |
|
183 try { |
|
184 if (!sem.tryAcquire(timeout, TimeUnit.MILLISECONDS)) { |
|
185 resetListTabsHandler(); |
|
186 } |
|
187 } catch (InterruptedException e) { |
|
188 // Fall through |
|
189 } |
|
190 |
|
191 if (output[0] == null) { |
|
192 return Collections.emptyList(); |
|
193 } |
|
194 |
|
195 return output[0]; |
|
196 } |
|
197 |
|
198 public String version(int timeout) throws TimeoutException { |
|
199 final Semaphore sem = new Semaphore(0); |
|
200 final String[] output = new String[1]; |
|
201 synchronized (lock) { |
|
202 if (versionCallback != null) { |
|
203 throw new IllegalStateException("version request is pending"); |
|
204 } |
|
205 versionCallback = new VersionCallback() { |
|
206 public void versionReceived(String versionString) { |
|
207 output[0] = versionString; |
|
208 sem.release(); |
|
209 } |
|
210 }; |
|
211 } |
|
212 toolOutput.send(CommandFactory.version()); |
|
213 boolean res; |
|
214 try { |
|
215 res = sem.tryAcquire(timeout, TimeUnit.MILLISECONDS); |
|
216 } catch (InterruptedException e) { |
|
217 throw new RuntimeException(e); |
|
218 } |
|
219 if (!res) { |
|
220 throw new TimeoutException("Failed to get version response in " + timeout + " ms"); |
|
221 } |
|
222 return output[0]; |
|
223 } |
|
224 |
|
225 /** |
|
226 * This can get called asynchronously. |
|
227 */ |
|
228 public void resetListTabsHandler() { |
|
229 synchronized (lock) { |
|
230 listTabsCallback = null; |
|
231 } |
|
232 } |
|
233 |
|
234 private static class CommandFactory { |
|
235 |
|
236 public static String ping() { |
|
237 return createDevToolsMessage(DevToolsServiceCommand.PING); |
|
238 } |
|
239 |
|
240 public static String version() { |
|
241 return createDevToolsMessage(DevToolsServiceCommand.VERSION); |
|
242 } |
|
243 public static String listTabs() { |
|
244 return createDevToolsMessage(DevToolsServiceCommand.LIST_TABS); |
|
245 } |
|
246 |
|
247 private static String createDevToolsMessage(DevToolsServiceCommand command) { |
|
248 return "{\"command\":" + JsonUtil.quoteString(command.commandName) + "}"; |
|
249 } |
|
250 } |
|
251 } |