]> git.donarmstrong.com Git - dactyl.git/blob - common/content/statusline.js
54943425f6a650e99a916fd708911faba1dc896c
[dactyl.git] / common / content / statusline.js
1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
4 //
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
7 "use strict";
8
9 /** @scope modules */
10
11 var StatusLine = Module("statusline", {
12     init: function init() {
13         this._statusLine = document.getElementById("status-bar");
14         this.statusBar = document.getElementById("addon-bar") || this._statusLine;
15         this.statusBar.collapsed = true;
16         this.baseGroup = this.statusBar == this._statusLine ? "StatusLine " : "";
17
18         if (this.statusBar.localName == "toolbar") {
19             styles.system.add("addon-bar", config.styleableChrome, <css><![CDATA[
20                 #status-bar { margin-top: 0 !important; }
21                 #addon-bar > statusbar { -moz-box-flex: 1 }
22                 #addon-bar > #addonbar-closebutton { visibility: collapse; }
23                 #addon-bar > xul|toolbarspring { visibility: collapse; }
24             ]]></css>);
25
26             util.overlayWindow(window, { append: <><statusbar id="status-bar" ordinal="0"/></> });
27
28             highlight.loadCSS(util.compileMacro(<![CDATA[
29                 !AddonBar;#addon-bar  {
30                     padding-left: 0 !important;
31                     min-height: 18px !important;
32                     -moz-appearance: none !important;
33                     <padding>
34                 }
35                 !AddonButton;#addon-bar xul|toolbarbutton  {
36                     -moz-appearance: none !important;
37                     padding: 0 !important;
38                     border-width: 0px !important;
39                     min-width: 0 !important;
40                     color: inherit !important;
41                 }
42                 AddonButton:not(:hover)  background: transparent !important;
43             ]]>)({ padding: util.OS.isMacOSX ? "padding-right: 10px !important;" : "" }));
44
45             if (document.getElementById("appmenu-button"))
46                 highlight.loadCSS(<![CDATA[
47                     AppmenuButton       min-width: 0 !important; padding: 0 .5em !important;
48                 ]]>);
49         }
50
51         XML.ignoreWhitespace = true;
52         let _commandline = "if (window.dactyl) return dactyl.modules.commandline";
53         let prepend = <e4x xmlns={XUL} xmlns:dactyl={NS}>
54             <button id="appmenu-button" label="" image="chrome://branding/content/icon16.png" highlight="AppmenuButton" />
55             <toolbarbutton id="appmenu-toolbar-button" label="" image="chrome://branding/content/icon16.png" />
56             <statusbar id="status-bar" highlight="StatusLine">
57                 <!-- insertbefore="dactyl.statusBefore;" insertafter="dactyl.statusAfter;" -->
58                 <hbox key="container" hidden="false" align="center"  flex="1">
59                     <stack orient="horizontal"       align="stretch" flex="1" highlight="CmdLine StatusCmdLine" class="dactyl-container">
60                         <hbox                                                 highlight="CmdLine StatusCmdLine" class="dactyl-container">
61                             <label key="mode"          crop="end"                                               class="plain" collapsed="true"/>
62                             <stack  id="dactyl-statusline-stack"     flex="1" highlight="CmdLine StatusCmdLine" class="dactyl-container">
63                                 <textbox key="url"     crop="end"    flex="1"                                   class="plain dactyl-status-field-url" readonly="true"/>
64                                 <textbox key="message" crop="end"    flex="1" highlight="Normal StatusNormal"   class="plain"                         readonly="true"/>
65                             </stack>
66                         </hbox>
67                     </stack>
68                     <label class="plain" key="inputbuffer"    flex="0"/>
69                     <label class="plain" key="progress"       flex="0"/>
70                     <label class="plain" key="tabcount"       flex="0"/>
71                     <label class="plain" key="bufferposition" flex="0"/>
72                     <label class="plain" key="zoomlevel"      flex="0"/>
73                 </hbox>
74                 <!-- just hide them since other elements expect them -->
75                 <statusbarpanel id="statusbar-display"       hidden="true"/>
76                 <statusbarpanel id="statusbar-progresspanel" hidden="true"/>
77             </statusbar>
78         </e4x>;
79
80         for each (let attr in prepend..@key)
81             attr.parent().@id = "dactyl-statusline-field-" + attr;
82
83         util.overlayWindow(window, {
84             objects: this.widgets = { get status() this.container },
85             prepend: prepend.elements()
86         });
87
88         try {
89             this.security = content.document.dactylSecurity || "insecure";
90         }
91         catch (e) {}
92     },
93
94     get visible() !this.statusBar.collapsed && !this.statusBar.hidden,
95
96     signals: {
97         "browser.locationChange": function (webProgress, request, uri) {
98             let win = webProgress.DOMWindow;
99             this.status = buffer.uri;
100             this.progress = uri && win && win.dactylProgress || "";
101
102             // if this is not delayed we get the position of the old buffer
103             this.timeout(function () {
104                 this.updateBufferPosition();
105                 this.updateZoomLevel();
106             }, 500);
107         },
108         "browser.overLink": function (link) {
109             switch (options["showstatuslinks"]) {
110             case "status":
111                 this.status = link ? _("status.link", link) : buffer.uri;
112                 break;
113             case "command":
114                 if (link)
115                     dactyl.echo(_("status.link", link), commandline.FORCE_SINGLELINE);
116                 else
117                     commandline.clear();
118                 break;
119             }
120         },
121         "browser.progressChange": function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
122             if (webProgress && webProgress.DOMWindow)
123                 webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress;
124             this.progress = curTotalProgress / maxTotalProgress;
125         },
126         "browser.securityChange": function onSecurityChange(webProgress, request, state) {
127
128             if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN)
129                 this.security = "broken";
130             else if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
131                 this.security = "extended";
132             else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
133                 this.security = "secure";
134             else // if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
135                 this.security = "insecure";
136
137             if (webProgress && webProgress.DOMWindow)
138                 webProgress.DOMWindow.document.dactylSecurity = this.security;
139         },
140         "browser.stateChange": function onStateChange(webProgress, request, flags, status) {
141             if (flags & Ci.nsIWebProgressListener.STATE_START)
142                 this.progress = 0;
143             if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
144                 this.progress = "";
145                 this.status = buffer.uri;
146             }
147         },
148         "browser.statusChange": function onStatusChange(webProgress, request, status, message) {
149             this.status = message || buffer.uri;
150         },
151     },
152
153     /**
154      * Update the status bar to indicate how secure the website is:
155      * extended - Secure connection with Extended Validation(EV) certificate.
156      * secure -   Secure connection with valid certificate.
157      * broken -   Secure connection with invalid certificate, or
158      *            mixed content.
159      * insecure - Insecure connection.
160      *
161      * @param {'extended'|'secure'|'broken'|'insecure'} type
162      */
163     set security(type) {
164         this._security = type;
165         const highlightGroup = {
166             extended: "StatusLineExtended",
167             secure:   "StatusLineSecure",
168             broken:   "StatusLineBroken",
169             insecure: "StatusLineNormal"
170         };
171
172         highlight.highlightNode(this.statusBar, this.baseGroup + highlightGroup[type]);
173     },
174     get security() this._security,
175
176     // update all fields of the statusline
177     update: function update() {
178         this.status = buffer.uri;
179         this.inputBuffer = "";
180         this.progress = "";
181         this.updateTabCount();
182         this.updateBufferPosition();
183         this.updateZoomLevel();
184     },
185
186     // ripped from Firefox; modified
187     unsafeURI: util.regexp(String.replace(<![CDATA[
188             [
189                 \s
190                 // Invisible characters (bug 452979)
191                 U001C U001D U001E U001F // file/group/record/unit separator
192                 U00AD // Soft hyphen
193                 UFEFF // BOM
194                 U2060 // Word joiner
195                 U2062 U2063  // Invisible times/separator
196                 U200B UFFFC // Zero-width space/no-break space
197
198                 // Bidi formatting characters. (RFC 3987 sections 3.2 and 4.1 paragraph 6)
199                 U200E U200F U202A U202B U202C U202D U202E
200             ]
201         ]]>, /U/g, "\\u"),
202         "gx"),
203     losslessDecodeURI: function losslessDecodeURI(url) {
204         return url.split("%25").map(function (url) {
205                 // Non-UTF-8 compliant URLs cause "malformed URI sequence" errors.
206                 try {
207                     return decodeURI(url).replace(this.unsafeURI, encodeURIComponent);
208                 }
209                 catch (e) {
210                     return url;
211                 }
212             }, this).join("%25");
213     },
214
215     /**
216      * Update the URL displayed in the status line. Also displays status
217      * icons, [+-♥], when there are next and previous pages in the
218      * current tab's history, and when the current URL is bookmarked,
219      * respectively.
220      *
221      * @param {string} url The URL to display.
222      */
223     get status() this._uri,
224     set status(uri) {
225         let modified = "";
226         let url = uri;
227         if (isinstance(uri, Ci.nsIURI)) {
228             // when session information is available, add [+] when we can go
229             // backwards, [-] when we can go forwards
230             if (uri.equals(buffer.uri) && window.getWebNavigation) {
231                 let sh = window.getWebNavigation().sessionHistory;
232                 if (sh && sh.index > 0)
233                     modified += "+";
234                 if (sh && sh.index < sh.count - 1)
235                     modified += "-";
236             }
237
238             if (modules.bookmarkcache) {
239                 if (bookmarkcache.isBookmarked(uri))
240                     modified += UTF8("❤");
241             }
242
243             if (modules.quickmarks)
244                 modified += quickmarks.find(uri.spec.replace(/#.*/, "")).join("");
245
246             url = this.losslessDecodeURI(uri.spec);
247         }
248
249         if (url == "about:blank") {
250             if (!buffer.title)
251                 url = "[No Name]";
252         }
253         else {
254             url = url.replace(RegExp("^dactyl://help/(\\S+)#(.*)"), function (m, n1, n2) n1 + " " + decodeURIComponent(n2) + " [Help]")
255                      .replace(RegExp("^dactyl://help/(\\S+)"), "$1 [Help]");
256         }
257
258         if (modified)
259             url += " [" + modified + "]";
260
261         this.widgets.url.value = url;
262         this._status = uri;
263
264     },
265
266     updateStatus: function updateStatus() { this.status = buffer.uri; },
267
268     updateUrl: deprecated("statusline.status", function updateUrl(url) { this.status = url || buffer.uri }),
269
270     /**
271      * Set the contents of the status line's input buffer to the given
272      * string. Used primarily when a key press requires further input
273      * before being processed, including mapping counts and arguments,
274      * along with multi-key mappings.
275      *
276      * @param {string} buffer
277      * @optional
278      */
279     get inputBuffer() this.widgets.inputbuffer.value,
280     set inputBuffer(val) this.widgets.inputbuffer.value = val == null ? "" : val,
281     updateInputBuffer: deprecated("statusline.inputBuffer", function updateInputBuffer(val) { this.inputBuffer = val; }),
282
283     /**
284      * Update the page load progress bar.
285      *
286      * @param {string|number} progress The current progress, as follows:
287      *    A string          - Displayed literally.
288      *    A ratio 0 < n < 1 - Displayed as a progress bar.
289      *    A number n <= 0   - Displayed as a "Loading" message.
290      *    Any other number  - The progress is cleared.
291      */
292     progress: Modes.boundProperty({
293         get: function progress() this._progress,
294         set: function  progress(progress) {
295             this._progress = progress || "";
296
297             if (typeof progress == "string")
298                 this.widgets.progress.value = this._progress;
299             else if (typeof progress == "number") {
300                 let progressStr = "";
301                 if (this._progress <= 0)
302                     progressStr = "[ Loading...         ]";
303                 else if (this._progress < 1) {
304                     let progress = Math.round(this._progress * 20);
305                     progressStr = "["
306                         + "===================>                    "
307                             .substr(20 - progress, 20)
308                         + "]";
309                 }
310                 this.widgets.progress.value = progressStr;
311             }
312         }
313     }),
314     updateProgress: deprecated("statusline.progress", function updateProgress(progress) {
315         this.progress = progress;
316     }),
317
318     /**
319      * Display the correct tabcount (e.g., [1/5]) on the status bar.
320      *
321      * @param {boolean} delayed When true, update count after a brief timeout.
322      *     Useful in the many cases when an event that triggers an update is
323      *     broadcast before the tab state is fully updated.
324      * @optional
325      */
326     updateTabCount: function updateTabCount(delayed) {
327         if (dactyl.has("tabs")) {
328             if (delayed) {
329                 this.timeout(function () this.updateTabCount(false), 0);
330                 return;
331             }
332
333             this.widgets.tabcount.value = "[" + (tabs.index(null, true) + 1) + "/" + tabs.visibleTabs.length + "]";
334         }
335     },
336
337     /**
338      * Display the main content's vertical scroll position in the status
339      * bar.
340      *
341      * @param {number} percent The position, as a percentage.
342      * @optional
343      */
344     updateBufferPosition: function updateBufferPosition(percent) {
345         if (percent == null) {
346             let win = document.commandDispatcher.focusedWindow;
347             if (!win)
348                 return;
349             win.scrollY; // intentional - see Kris
350             percent = win.scrollY    == 0 ?  0 : // This prevents a forced rendering
351                       win.scrollMaxY == 0 ? -1 : win.scrollY / win.scrollMaxY;
352         }
353
354         percent = Math.round(percent * 100);
355
356         if (percent < 0)
357             var position = "All";
358         else if (percent == 0)
359             position = "Top";
360         else if (percent >= 100)
361             position = "Bot";
362         else if (percent < 10)
363             position = " " + percent + "%";
364         else
365             position = percent + "%";
366
367         this.widgets.bufferposition.value = position;
368     },
369
370     /**
371      * Display the main content's zoom level.
372      *
373      * @param {number} percent The zoom level, as a percentage. @optional
374      * @param {boolean} full True if full zoom is in operation. @optional
375      */
376     updateZoomLevel: function updateZoomLevel(percent, full) {
377         if (arguments.length == 0)
378             [percent, full] = [buffer.zoomLevel, buffer.fullZoom];
379
380         if (percent == 100)
381             this.widgets.zoomlevel.value = "";
382         else {
383             percent = ("  " + Math.round(percent)).substr(-3);
384             if (full)
385                 this.widgets.zoomlevel.value = " [" + percent + "%]";
386             else
387                 this.widgets.zoomlevel.value = " (" + percent + "%)";
388         }
389     }
390 });
391
392 // vim: set fdm=marker sw=4 ts=4 et: