Mozilla Cross-Reference mozilla-central
mozilla/ browser/ base/ content/ browser-sidebar.js
Hg Log
Hg Blame
Diff file
Raw file
view using tree:
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 /**
6  * SidebarUI controls showing and hiding the browser sidebar.
7  *
8  * @note
9  * Some of these methods take a commandID argument - we expect to find a
10  * xul:broadcaster element with the specified ID.
11  * The following attributes on that element may be used and/or modified:
12  *  - id           (required) the string to match commandID. The convention
13  *                 is to use this naming scheme: 'view<sidebar-name>Sidebar'.
14  *  - sidebarurl   (required) specifies the URL to load in this sidebar.
15  *  - sidebartitle or label (in that order) specify the title to
16  *                 display on the sidebar.
17  *  - checked      indicates whether the sidebar is currently displayed.
18  *                 Note that toggleSidebar updates this attribute when
19  *                 it changes the sidebar's visibility.
20  *  - group        this attribute must be set to "sidebar".
21  */
22 let SidebarUI = {
23   browser: null,
24 
25   _box: null,
26   _title: null,
27   _splitter: null,
28 
29   init() {
30     this._box = document.getElementById("sidebar-box");
31     this.browser = document.getElementById("sidebar");
32     this._title = document.getElementById("sidebar-title");
33     this._splitter = document.getElementById("sidebar-splitter");
34 
35     if (!this.adoptFromWindow(window.opener)) {
36       let commandID = this._box.getAttribute("sidebarcommand");
37       if (commandID) {
38         let command = document.getElementById(commandID);
39         if (command) {
40           this._delayedLoad = true;
41           this._box.hidden = false;
42           this._splitter.hidden = false;
43           command.setAttribute("checked", "true");
44         } else {
45           // Remove the |sidebarcommand| attribute, because the element it
46           // refers to no longer exists, so we should assume this sidebar
47           // panel has been uninstalled. (249883)
48           this._box.removeAttribute("sidebarcommand");
49         }
50       }
51     }
52   },
53 
54   uninit() {
55     let enumerator = Services.wm.getEnumerator(null);
56     enumerator.getNext();
57     if (!enumerator.hasMoreElements()) {
58       document.persist("sidebar-box", "sidebarcommand");
59       document.persist("sidebar-box", "width");
60       document.persist("sidebar-box", "src");
61       document.persist("sidebar-title", "value");
62     }
63   },
64 
65   /**
66    * Try and adopt the status of the sidebar from another window.
67    * @param {Window} sourceWindow - Window to use as a source for sidebar status.
68    * @return true if we adopted the state, or false if the caller should
69    * initialize the state itself.
70    */
71   adoptFromWindow(sourceWindow) {
72     // No source window, or it being closed, or not chrome, or in a different
73     // private-browsing context means we can't adopt.
74     if (!sourceWindow || sourceWindow.closed ||
75         !sourceWindow.document.documentURIObject.schemeIs("chrome") ||
76         PrivateBrowsingUtils.isWindowPrivate(window) != PrivateBrowsingUtils.isWindowPrivate(sourceWindow)) {
77       return false;
78     }
79 
80     // If the opener had a sidebar, open the same sidebar in our window.
81     // The opener can be the hidden window too, if we're coming from the state
82     // where no windows are open, and the hidden window has no sidebar box.
83     let sourceUI = sourceWindow.SidebarUI;
84     if (!sourceUI || !sourceUI._box) {
85       // no source UI or no _box means we also can't adopt the state.
86       return false;
87     }
88     if (sourceUI._box.hidden) {
89       // just hidden means we have adopted the hidden state.
90       return true;
91     }
92 
93     let commandID = sourceUI._box.getAttribute("sidebarcommand");
94     let commandElem = document.getElementById(commandID);
95 
96     // dynamically generated sidebars will fail this check, but we still
97     // consider it adopted.
98     if (!commandElem) {
99       return true;
100     }
101 
102     this._title.setAttribute("value",
103                              sourceUI._title.getAttribute("value"));
104     this._box.setAttribute("width", sourceUI._box.boxObject.width);
105 
106     this._box.setAttribute("sidebarcommand", commandID);
107     // Note: we're setting 'src' on this._box, which is a <vbox>, not on
108     // the <browser id="sidebar">. This lets us delay the actual load until
109     // delayedStartup().
110     this._box.setAttribute("src", sourceUI.browser.getAttribute("src"));
111     this._delayedLoad = true;
112 
113     this._box.hidden = false;
114     this._splitter.hidden = false;
115     commandElem.setAttribute("checked", "true");
116     return true;
117   },
118 
119   /**
120    * If loading a sidebar was delayed on startup, start the load now.
121    */
122   startDelayedLoad() {
123     if (!this._delayedLoad) {
124       return;
125     }
126 
127     this.browser.setAttribute("src", this._box.getAttribute("src"));
128   },
129 
130   /**
131    * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
132    * a chance to adjust focus as needed. An additional event is needed, because
133    * we don't want to focus the sidebar when it's opened on startup or in a new
134    * window, only when the user opens the sidebar.
135    */
136   _fireFocusedEvent() {
137     let event = new CustomEvent("SidebarFocused", {bubbles: true});
138     this.browser.contentWindow.dispatchEvent(event);
139 
140     // Run the original function for backwards compatibility.
141     fireSidebarFocusedEvent();
142   },
143 
144   /**
145    * True if the sidebar is currently open.
146    */
147   get isOpen() {
148     return !this._box.hidden;
149   },
150 
151   /**
152    * The ID of the current sidebar (ie, the ID of the broadcaster being used).
153    * This can be set even if the sidebar is hidden.
154    */
155   get currentID() {
156     return this._box.getAttribute("sidebarcommand");
157   },
158 
159   get title() {
160     return this._title.value;
161   },
162 
163   set title(value) {
164     this._title.value = value;
165   },
166 
167   /**
168    * Toggle the visibility of the sidebar. If the sidebar is hidden or is open
169    * with a different commandID, then the sidebar will be opened using the
170    * specified commandID. Otherwise the sidebar will be hidden.
171    *
172    * @param {string} commandID ID of the xul:broadcaster element to use.
173    * @return {Promise}
174    */
175   toggle(commandID = this.currentID) {
176     if (this.isOpen && commandID == this.currentID) {
177       this.hide();
178       return Promise.resolve();
179     } else {
180       return this.show(commandID);
181     }
182   },
183 
184   /**
185    * Show the sidebar, using the parameters from the specified broadcaster.
186    * @see SidebarUI note.
187    *
188    * @param {string} commandID ID of the xul:broadcaster element to use.
189    */
190   show(commandID) {
191     return new Promise((resolve, reject) => {
192       let sidebarBroadcaster = document.getElementById(commandID);
193       if (!sidebarBroadcaster || sidebarBroadcaster.localName != "broadcaster") {
194         reject(new Error("Invalid sidebar broadcaster specified"));
195         return;
196       }
197 
198       let broadcasters = document.getElementsByAttribute("group", "sidebar");
199       for (let broadcaster of broadcasters) {
200         // skip elements that observe sidebar broadcasters and random
201         // other elements
202         if (broadcaster.localName != "broadcaster") {
203           continue;
204         }
205 
206         if (broadcaster != sidebarBroadcaster) {
207           broadcaster.removeAttribute("checked");
208         } else {
209           sidebarBroadcaster.setAttribute("checked", "true");
210         }
211       }
212 
213       this._box.hidden = false;
214       this._splitter.hidden = false;
215 
216       this._box.setAttribute("sidebarcommand", sidebarBroadcaster.id);
217 
218       let title = sidebarBroadcaster.getAttribute("sidebartitle");
219       if (!title) {
220         title = sidebarBroadcaster.getAttribute("label");
221       }
222       this._title.value = title;
223 
224       let url = sidebarBroadcaster.getAttribute("sidebarurl");
225       this.browser.setAttribute("src", url); // kick off async load
226 
227       // We set this attribute here in addition to setting it on the <browser>
228       // element itself, because the code in SidebarUI.uninit() persists this
229       // attribute, not the "src" of the <browser id="sidebar">. The reason it
230       // does that is that we want to delay sidebar load a bit when a browser
231       // window opens. See delayedStartup() and SidebarUI.startDelayedLoad().
232       this._box.setAttribute("src", url);
233 
234       if (this.browser.contentDocument.location.href != url) {
235         let onLoad = event => {
236           this.browser.removeEventListener("load", onLoad, true);
237 
238           // We're handling the 'load' event before it bubbles up to the usual
239           // (non-capturing) event handlers. Let it bubble up before firing the
240           // SidebarFocused event.
241           setTimeout(() => this._fireFocusedEvent(), 0);
242 
243           // Run the original function for backwards compatibility.
244           sidebarOnLoad(event);
245 
246           resolve();
247         };
248 
249         this.browser.addEventListener("load", onLoad, true);
250       } else {
251         // Older code handled this case, so we do it too.
252         this._fireFocusedEvent();
253         resolve();
254       }
255 
256       let selBrowser = gBrowser.selectedBrowser;
257       selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
258         {commandID: commandID, isOpen: true}
259       );
260     });
261   },
262 
263   /**
264    * Hide the sidebar.
265    */
266   hide() {
267     if (!this.isOpen) {
268       return;
269     }
270 
271     let commandID = this._box.getAttribute("sidebarcommand");
272     let sidebarBroadcaster = document.getElementById(commandID);
273 
274     if (sidebarBroadcaster.getAttribute("checked") != "true") {
275       return;
276     }
277 
278     // Replace the document currently displayed in the sidebar with about:blank
279     // so that we can free memory by unloading the page. We need to explicitly
280     // create a new content viewer because the old one doesn't get destroyed
281     // until about:blank has loaded (which does not happen as long as the
282     // element is hidden).
283     this.browser.setAttribute("src", "about:blank");
284     this.browser.docShell.createAboutBlankContentViewer(null);
285 
286     sidebarBroadcaster.removeAttribute("checked");
287     this._box.setAttribute("sidebarcommand", "");
288     this._title.value = "";
289     this._box.hidden = true;
290     this._splitter.hidden = true;
291 
292     let selBrowser = gBrowser.selectedBrowser;
293     selBrowser.focus();
294     selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
295       {commandID: commandID, isOpen: false}
296     );
297   },
298 };
299 
300 /**
301  * This exists for backards compatibility - it will be called once a sidebar is
302  * ready, following any request to show it.
303  *
304  * @deprecated
305  */
306 function fireSidebarFocusedEvent() {}
307 
308 /**
309  * This exists for backards compatibility - it gets called when a sidebar has
310  * been loaded.
311  *
312  * @deprecated
313  */
314 function sidebarOnLoad(event) {}
315 
316 /**
317  * This exists for backards compatibility, and is equivilent to
318  * SidebarUI.toggle() without the forceOpen param. With forceOpen set to true,
319  * it is equalivent to SidebarUI.show().
320  *
321  * @deprecated
322  */
323 function toggleSidebar(commandID, forceOpen = false) {
324   Deprecated.warning("toggleSidebar() is deprecated, please use SidebarUI.toggle() or SidebarUI.show() instead",
325                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Sidebar");
326 
327   if (forceOpen) {
328     SidebarUI.show(commandID);
329   } else {
330     SidebarUI.toggle(commandID);
331   }
332 }
333 
view http://hg.mozilla.org/mozilla-central/rev/ /browser/base/content/browser-sidebar.js