|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const {Cu} = require("chrome"); |
|
8 const EventEmitter = require("devtools/toolkit/event-emitter"); |
|
9 const {Promise: promise} = require("resource://gre/modules/Promise.jsm"); |
|
10 Cu.import("resource://gre/modules/Services.jsm"); |
|
11 Cu.import("resource:///modules/devtools/DOMHelpers.jsm"); |
|
12 |
|
13 /** |
|
14 * A toolbox host represents an object that contains a toolbox (e.g. the |
|
15 * sidebar or a separate window). Any host object should implement the |
|
16 * following functions: |
|
17 * |
|
18 * create() - create the UI and emit a 'ready' event when the UI is ready to use |
|
19 * destroy() - destroy the host's UI |
|
20 */ |
|
21 |
|
22 exports.Hosts = { |
|
23 "bottom": BottomHost, |
|
24 "side": SidebarHost, |
|
25 "window": WindowHost, |
|
26 "custom": CustomHost |
|
27 }; |
|
28 |
|
29 /** |
|
30 * Host object for the dock on the bottom of the browser |
|
31 */ |
|
32 function BottomHost(hostTab) { |
|
33 this.hostTab = hostTab; |
|
34 |
|
35 EventEmitter.decorate(this); |
|
36 } |
|
37 |
|
38 BottomHost.prototype = { |
|
39 type: "bottom", |
|
40 |
|
41 heightPref: "devtools.toolbox.footer.height", |
|
42 |
|
43 /** |
|
44 * Create a box at the bottom of the host tab. |
|
45 */ |
|
46 create: function BH_create() { |
|
47 let deferred = promise.defer(); |
|
48 |
|
49 let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser; |
|
50 let ownerDocument = gBrowser.ownerDocument; |
|
51 |
|
52 this._splitter = ownerDocument.createElement("splitter"); |
|
53 this._splitter.setAttribute("class", "devtools-horizontal-splitter"); |
|
54 |
|
55 this.frame = ownerDocument.createElement("iframe"); |
|
56 this.frame.className = "devtools-toolbox-bottom-iframe"; |
|
57 this.frame.height = Services.prefs.getIntPref(this.heightPref); |
|
58 |
|
59 this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser); |
|
60 this._nbox.appendChild(this._splitter); |
|
61 this._nbox.appendChild(this.frame); |
|
62 |
|
63 let frameLoad = function() { |
|
64 this.emit("ready", this.frame); |
|
65 deferred.resolve(this.frame); |
|
66 }.bind(this); |
|
67 |
|
68 this.frame.tooltip = "aHTMLTooltip"; |
|
69 |
|
70 // we have to load something so we can switch documents if we have to |
|
71 this.frame.setAttribute("src", "about:blank"); |
|
72 |
|
73 let domHelper = new DOMHelpers(this.frame.contentWindow); |
|
74 domHelper.onceDOMReady(frameLoad); |
|
75 |
|
76 focusTab(this.hostTab); |
|
77 |
|
78 return deferred.promise; |
|
79 }, |
|
80 |
|
81 /** |
|
82 * Raise the host. |
|
83 */ |
|
84 raise: function BH_raise() { |
|
85 focusTab(this.hostTab); |
|
86 }, |
|
87 |
|
88 /** |
|
89 * Set the toolbox title. |
|
90 */ |
|
91 setTitle: function BH_setTitle(title) { |
|
92 // Nothing to do for this host type. |
|
93 }, |
|
94 |
|
95 /** |
|
96 * Destroy the bottom dock. |
|
97 */ |
|
98 destroy: function BH_destroy() { |
|
99 if (!this._destroyed) { |
|
100 this._destroyed = true; |
|
101 |
|
102 Services.prefs.setIntPref(this.heightPref, this.frame.height); |
|
103 this._nbox.removeChild(this._splitter); |
|
104 this._nbox.removeChild(this.frame); |
|
105 } |
|
106 |
|
107 return promise.resolve(null); |
|
108 } |
|
109 } |
|
110 |
|
111 |
|
112 /** |
|
113 * Host object for the in-browser sidebar |
|
114 */ |
|
115 function SidebarHost(hostTab) { |
|
116 this.hostTab = hostTab; |
|
117 |
|
118 EventEmitter.decorate(this); |
|
119 } |
|
120 |
|
121 SidebarHost.prototype = { |
|
122 type: "side", |
|
123 |
|
124 widthPref: "devtools.toolbox.sidebar.width", |
|
125 |
|
126 /** |
|
127 * Create a box in the sidebar of the host tab. |
|
128 */ |
|
129 create: function SH_create() { |
|
130 let deferred = promise.defer(); |
|
131 |
|
132 let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser; |
|
133 let ownerDocument = gBrowser.ownerDocument; |
|
134 |
|
135 this._splitter = ownerDocument.createElement("splitter"); |
|
136 this._splitter.setAttribute("class", "devtools-side-splitter"); |
|
137 |
|
138 this.frame = ownerDocument.createElement("iframe"); |
|
139 this.frame.className = "devtools-toolbox-side-iframe"; |
|
140 this.frame.width = Services.prefs.getIntPref(this.widthPref); |
|
141 |
|
142 this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser); |
|
143 this._sidebar.appendChild(this._splitter); |
|
144 this._sidebar.appendChild(this.frame); |
|
145 |
|
146 let frameLoad = function() { |
|
147 this.emit("ready", this.frame); |
|
148 deferred.resolve(this.frame); |
|
149 }.bind(this); |
|
150 |
|
151 this.frame.tooltip = "aHTMLTooltip"; |
|
152 this.frame.setAttribute("src", "about:blank"); |
|
153 |
|
154 let domHelper = new DOMHelpers(this.frame.contentWindow); |
|
155 domHelper.onceDOMReady(frameLoad); |
|
156 |
|
157 focusTab(this.hostTab); |
|
158 |
|
159 return deferred.promise; |
|
160 }, |
|
161 |
|
162 /** |
|
163 * Raise the host. |
|
164 */ |
|
165 raise: function SH_raise() { |
|
166 focusTab(this.hostTab); |
|
167 }, |
|
168 |
|
169 /** |
|
170 * Set the toolbox title. |
|
171 */ |
|
172 setTitle: function SH_setTitle(title) { |
|
173 // Nothing to do for this host type. |
|
174 }, |
|
175 |
|
176 /** |
|
177 * Destroy the sidebar. |
|
178 */ |
|
179 destroy: function SH_destroy() { |
|
180 if (!this._destroyed) { |
|
181 this._destroyed = true; |
|
182 |
|
183 Services.prefs.setIntPref(this.widthPref, this.frame.width); |
|
184 this._sidebar.removeChild(this._splitter); |
|
185 this._sidebar.removeChild(this.frame); |
|
186 } |
|
187 |
|
188 return promise.resolve(null); |
|
189 } |
|
190 } |
|
191 |
|
192 /** |
|
193 * Host object for the toolbox in a separate window |
|
194 */ |
|
195 function WindowHost() { |
|
196 this._boundUnload = this._boundUnload.bind(this); |
|
197 |
|
198 EventEmitter.decorate(this); |
|
199 } |
|
200 |
|
201 WindowHost.prototype = { |
|
202 type: "window", |
|
203 |
|
204 WINDOW_URL: "chrome://browser/content/devtools/framework/toolbox-window.xul", |
|
205 |
|
206 /** |
|
207 * Create a new xul window to contain the toolbox. |
|
208 */ |
|
209 create: function WH_create() { |
|
210 let deferred = promise.defer(); |
|
211 |
|
212 let flags = "chrome,centerscreen,resizable,dialog=no"; |
|
213 let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank", |
|
214 flags, null); |
|
215 |
|
216 let frameLoad = function(event) { |
|
217 win.removeEventListener("load", frameLoad, true); |
|
218 win.focus(); |
|
219 this.frame = win.document.getElementById("toolbox-iframe"); |
|
220 this.emit("ready", this.frame); |
|
221 |
|
222 deferred.resolve(this.frame); |
|
223 }.bind(this); |
|
224 |
|
225 win.addEventListener("load", frameLoad, true); |
|
226 win.addEventListener("unload", this._boundUnload); |
|
227 |
|
228 this._window = win; |
|
229 |
|
230 return deferred.promise; |
|
231 }, |
|
232 |
|
233 /** |
|
234 * Catch the user closing the window. |
|
235 */ |
|
236 _boundUnload: function(event) { |
|
237 if (event.target.location != this.WINDOW_URL) { |
|
238 return; |
|
239 } |
|
240 this._window.removeEventListener("unload", this._boundUnload); |
|
241 |
|
242 this.emit("window-closed"); |
|
243 }, |
|
244 |
|
245 /** |
|
246 * Raise the host. |
|
247 */ |
|
248 raise: function RH_raise() { |
|
249 this._window.focus(); |
|
250 }, |
|
251 |
|
252 /** |
|
253 * Set the toolbox title. |
|
254 */ |
|
255 setTitle: function WH_setTitle(title) { |
|
256 this._window.document.title = title; |
|
257 }, |
|
258 |
|
259 /** |
|
260 * Destroy the window. |
|
261 */ |
|
262 destroy: function WH_destroy() { |
|
263 if (!this._destroyed) { |
|
264 this._destroyed = true; |
|
265 |
|
266 this._window.removeEventListener("unload", this._boundUnload); |
|
267 this._window.close(); |
|
268 } |
|
269 |
|
270 return promise.resolve(null); |
|
271 } |
|
272 }; |
|
273 |
|
274 /** |
|
275 * Host object for the toolbox in its own tab |
|
276 */ |
|
277 function CustomHost(hostTab, options) { |
|
278 this.frame = options.customIframe; |
|
279 this.uid = options.uid; |
|
280 EventEmitter.decorate(this); |
|
281 } |
|
282 |
|
283 CustomHost.prototype = { |
|
284 type: "custom", |
|
285 |
|
286 _sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg, data) { |
|
287 // It's up to the custom frame owner (parent window) to honor |
|
288 // "close" or "raise" instructions. |
|
289 let topWindow = this.frame.ownerDocument.defaultView; |
|
290 let json = {name:"toolbox-" + msg, uid: this.uid}; |
|
291 if (data) { |
|
292 json.data = data; |
|
293 } |
|
294 topWindow.postMessage(JSON.stringify(json), "*"); |
|
295 }, |
|
296 |
|
297 /** |
|
298 * Create a new xul window to contain the toolbox. |
|
299 */ |
|
300 create: function CH_create() { |
|
301 return promise.resolve(this.frame); |
|
302 }, |
|
303 |
|
304 /** |
|
305 * Raise the host. |
|
306 */ |
|
307 raise: function CH_raise() { |
|
308 this._sendMessageToTopWindow("raise"); |
|
309 }, |
|
310 |
|
311 /** |
|
312 * Set the toolbox title. |
|
313 */ |
|
314 setTitle: function CH_setTitle(title) { |
|
315 this._sendMessageToTopWindow("title", { value: title }); |
|
316 }, |
|
317 |
|
318 /** |
|
319 * Destroy the window. |
|
320 */ |
|
321 destroy: function WH_destroy() { |
|
322 if (!this._destroyed) { |
|
323 this._destroyed = true; |
|
324 this._sendMessageToTopWindow("close"); |
|
325 } |
|
326 return promise.resolve(null); |
|
327 } |
|
328 } |
|
329 |
|
330 /** |
|
331 * Switch to the given tab in a browser and focus the browser window |
|
332 */ |
|
333 function focusTab(tab) { |
|
334 let browserWindow = tab.ownerDocument.defaultView; |
|
335 browserWindow.focus(); |
|
336 browserWindow.gBrowser.selectedTab = tab; |
|
337 } |