|
1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 "use strict"; |
|
8 |
|
9 const Cu = Components.utils; |
|
10 Cu.import('resource://gre/modules/XPCOMUtils.jsm'); |
|
11 Cu.import("resource://gre/modules/Services.jsm"); |
|
12 Cu.import("resource://gre/modules/Task.jsm"); |
|
13 Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); |
|
14 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); |
|
15 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); |
|
16 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); |
|
17 |
|
18 let gClient; |
|
19 let gConnectionTimeout; |
|
20 |
|
21 XPCOMUtils.defineLazyGetter(window, 'l10n', function () { |
|
22 return Services.strings.createBundle('chrome://browser/locale/devtools/connection-screen.properties'); |
|
23 }); |
|
24 |
|
25 /** |
|
26 * Once DOM is ready, we prefil the host/port inputs with |
|
27 * pref-stored values. |
|
28 */ |
|
29 window.addEventListener("DOMContentLoaded", function onDOMReady() { |
|
30 window.removeEventListener("DOMContentLoaded", onDOMReady, true); |
|
31 let host = Services.prefs.getCharPref("devtools.debugger.remote-host"); |
|
32 let port = Services.prefs.getIntPref("devtools.debugger.remote-port"); |
|
33 |
|
34 if (host) { |
|
35 document.getElementById("host").value = host; |
|
36 } |
|
37 |
|
38 if (port) { |
|
39 document.getElementById("port").value = port; |
|
40 } |
|
41 |
|
42 let form = document.querySelector("#connection-form form"); |
|
43 form.addEventListener("submit", function() { |
|
44 window.submit(); |
|
45 }); |
|
46 }, true); |
|
47 |
|
48 /** |
|
49 * Called when the "connect" button is clicked. |
|
50 */ |
|
51 function submit() { |
|
52 // Show the "connecting" screen |
|
53 document.body.classList.add("connecting"); |
|
54 |
|
55 // Save the host/port values |
|
56 let host = document.getElementById("host").value; |
|
57 Services.prefs.setCharPref("devtools.debugger.remote-host", host); |
|
58 |
|
59 let port = document.getElementById("port").value; |
|
60 Services.prefs.setIntPref("devtools.debugger.remote-port", port); |
|
61 |
|
62 // Initiate the connection |
|
63 let transport; |
|
64 try { |
|
65 transport = debuggerSocketConnect(host, port); |
|
66 } catch(e) { |
|
67 // Bug 921850: catch rare exception from debuggerSocketConnect |
|
68 showError("unexpected"); |
|
69 return; |
|
70 } |
|
71 gClient = new DebuggerClient(transport); |
|
72 let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout"); |
|
73 gConnectionTimeout = setTimeout(handleConnectionTimeout, delay); |
|
74 gClient.connect(onConnectionReady); |
|
75 } |
|
76 |
|
77 /** |
|
78 * Connection is ready. List actors and build buttons. |
|
79 */ |
|
80 let onConnectionReady = Task.async(function*(aType, aTraits) { |
|
81 clearTimeout(gConnectionTimeout); |
|
82 |
|
83 let deferred = promise.defer(); |
|
84 gClient.listAddons(deferred.resolve); |
|
85 let response = yield deferred.promise; |
|
86 |
|
87 let parent = document.getElementById("addonActors") |
|
88 if (!response.error && response.addons.length > 0) { |
|
89 // Add one entry for each add-on. |
|
90 for (let addon of response.addons) { |
|
91 if (!addon.debuggable) { |
|
92 continue; |
|
93 } |
|
94 buildAddonLink(addon, parent); |
|
95 } |
|
96 } |
|
97 else { |
|
98 // Hide the section when there are no add-ons |
|
99 parent.previousElementSibling.remove(); |
|
100 parent.remove(); |
|
101 } |
|
102 |
|
103 deferred = promise.defer(); |
|
104 gClient.listTabs(deferred.resolve); |
|
105 response = yield deferred.promise; |
|
106 |
|
107 parent = document.getElementById("tabActors"); |
|
108 |
|
109 // Add Global Process debugging... |
|
110 let globals = JSON.parse(JSON.stringify(response)); |
|
111 delete globals.tabs; |
|
112 delete globals.selected; |
|
113 // ...only if there are appropriate actors (a 'from' property will always |
|
114 // be there). |
|
115 |
|
116 // Add one entry for each open tab. |
|
117 for (let i = 0; i < response.tabs.length; i++) { |
|
118 buildTabLink(response.tabs[i], parent, i == response.selected); |
|
119 } |
|
120 |
|
121 let gParent = document.getElementById("globalActors"); |
|
122 |
|
123 // Build the Remote Process button |
|
124 if (Object.keys(globals).length > 1) { |
|
125 let a = document.createElement("a"); |
|
126 a.onclick = function() { |
|
127 openToolbox(globals, true); |
|
128 |
|
129 } |
|
130 a.title = a.textContent = window.l10n.GetStringFromName("mainProcess"); |
|
131 a.className = "remote-process"; |
|
132 a.href = "#"; |
|
133 gParent.appendChild(a); |
|
134 } |
|
135 // Move the selected tab on top |
|
136 let selectedLink = parent.querySelector("a.selected"); |
|
137 if (selectedLink) { |
|
138 parent.insertBefore(selectedLink, parent.firstChild); |
|
139 } |
|
140 |
|
141 document.body.classList.remove("connecting"); |
|
142 document.body.classList.add("actors-mode"); |
|
143 |
|
144 // Ensure the first link is focused |
|
145 let firstLink = parent.querySelector("a:first-of-type"); |
|
146 if (firstLink) { |
|
147 firstLink.focus(); |
|
148 } |
|
149 }); |
|
150 |
|
151 /** |
|
152 * Build one button for an add-on actor. |
|
153 */ |
|
154 function buildAddonLink(addon, parent) { |
|
155 let a = document.createElement("a"); |
|
156 a.onclick = function() { |
|
157 openToolbox({ addonActor: addon.actor, title: addon.name }, true, "jsdebugger"); |
|
158 } |
|
159 |
|
160 a.textContent = addon.name; |
|
161 a.title = addon.id; |
|
162 a.href = "#"; |
|
163 |
|
164 parent.appendChild(a); |
|
165 } |
|
166 |
|
167 /** |
|
168 * Build one button for a tab actor. |
|
169 */ |
|
170 function buildTabLink(tab, parent, selected) { |
|
171 let a = document.createElement("a"); |
|
172 a.onclick = function() { |
|
173 openToolbox(tab); |
|
174 } |
|
175 |
|
176 a.textContent = tab.title; |
|
177 a.title = tab.url; |
|
178 if (!a.textContent) { |
|
179 a.textContent = tab.url; |
|
180 } |
|
181 a.href = "#"; |
|
182 |
|
183 if (selected) { |
|
184 a.classList.add("selected"); |
|
185 } |
|
186 |
|
187 parent.appendChild(a); |
|
188 } |
|
189 |
|
190 /** |
|
191 * An error occured. Let's show it and return to the first screen. |
|
192 */ |
|
193 function showError(type) { |
|
194 document.body.className = "error"; |
|
195 let activeError = document.querySelector(".error-message.active"); |
|
196 if (activeError) { |
|
197 activeError.classList.remove("active"); |
|
198 } |
|
199 activeError = document.querySelector(".error-" + type); |
|
200 if (activeError) { |
|
201 activeError.classList.add("active"); |
|
202 } |
|
203 } |
|
204 |
|
205 /** |
|
206 * Connection timeout. |
|
207 */ |
|
208 function handleConnectionTimeout() { |
|
209 showError("timeout"); |
|
210 } |
|
211 |
|
212 /** |
|
213 * The user clicked on one of the buttons. |
|
214 * Opens the toolbox. |
|
215 */ |
|
216 function openToolbox(form, chrome=false, tool="webconsole") { |
|
217 let options = { |
|
218 form: form, |
|
219 client: gClient, |
|
220 chrome: chrome |
|
221 }; |
|
222 devtools.TargetFactory.forRemoteTab(options).then((target) => { |
|
223 let hostType = devtools.Toolbox.HostType.WINDOW; |
|
224 gDevTools.showToolbox(target, tool, hostType).then((toolbox) => { |
|
225 toolbox.once("destroyed", function() { |
|
226 gClient.close(); |
|
227 }); |
|
228 }); |
|
229 window.close(); |
|
230 }); |
|
231 } |