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>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
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 " : "";
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; }
26 util.overlayWindow(window, { append: <><statusbar id="status-bar" ordinal="0"/></> });
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;
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;
42 AddonButton:not(:hover) background: transparent !important;
43 ]]>)({ padding: util.OS.isMacOSX ? "padding-right: 10px !important;" : "" }));
45 if (document.getElementById("appmenu-button"))
46 highlight.loadCSS(<![CDATA[
47 AppmenuButton min-width: 0 !important; padding: 0 .5em !important;
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"/>
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"/>
74 <!-- just hide them since other elements expect them -->
75 <statusbarpanel id="statusbar-display" hidden="true"/>
76 <statusbarpanel id="statusbar-progresspanel" hidden="true"/>
80 for each (let attr in prepend..@key)
81 attr.parent().@id = "dactyl-statusline-field-" + attr;
83 util.overlayWindow(window, {
84 objects: this.widgets = { get status() this.container },
85 prepend: prepend.elements()
89 this.security = content.document.dactylSecurity || "insecure";
94 get visible() !this.statusBar.collapsed && !this.statusBar.hidden,
97 "browser.locationChange": function (webProgress, request, uri) {
98 let win = webProgress.DOMWindow;
99 this.status = buffer.uri;
100 this.progress = uri && win && win.dactylProgress || "";
102 // if this is not delayed we get the position of the old buffer
103 this.timeout(function () {
104 this.updateBufferPosition();
105 this.updateZoomLevel();
108 "browser.overLink": function (link) {
109 switch (options["showstatuslinks"]) {
111 this.status = link ? _("status.link", link) : buffer.uri;
115 dactyl.echo(_("status.link", link), commandline.FORCE_SINGLELINE);
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;
126 "browser.securityChange": function onSecurityChange(webProgress, request, state) {
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";
137 if (webProgress && webProgress.DOMWindow)
138 webProgress.DOMWindow.document.dactylSecurity = this.security;
140 "browser.stateChange": function onStateChange(webProgress, request, flags, status) {
141 if (flags & Ci.nsIWebProgressListener.STATE_START)
143 if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
145 this.status = buffer.uri;
148 "browser.statusChange": function onStatusChange(webProgress, request, status, message) {
149 this.status = message || buffer.uri;
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
159 * insecure - Insecure connection.
161 * @param {'extended'|'secure'|'broken'|'insecure'} type
164 this._security = type;
165 const highlightGroup = {
166 extended: "StatusLineExtended",
167 secure: "StatusLineSecure",
168 broken: "StatusLineBroken",
169 insecure: "StatusLineNormal"
172 highlight.highlightNode(this.statusBar, this.baseGroup + highlightGroup[type]);
174 get security() this._security,
176 // update all fields of the statusline
177 update: function update() {
178 this.status = buffer.uri;
179 this.inputBuffer = "";
181 this.updateTabCount();
182 this.updateBufferPosition();
183 this.updateZoomLevel();
186 // ripped from Firefox; modified
187 unsafeURI: util.regexp(String.replace(<![CDATA[
190 // Invisible characters (bug 452979)
191 U001C U001D U001E U001F // file/group/record/unit separator
195 U2062 U2063 // Invisible times/separator
196 U200B UFFFC // Zero-width space/no-break space
198 // Bidi formatting characters. (RFC 3987 sections 3.2 and 4.1 paragraph 6)
199 U200E U200F U202A U202B U202C U202D U202E
203 losslessDecodeURI: function losslessDecodeURI(url) {
204 return url.split("%25").map(function (url) {
205 // Non-UTF-8 compliant URLs cause "malformed URI sequence" errors.
207 return decodeURI(url).replace(this.unsafeURI, encodeURIComponent);
212 }, this).join("%25");
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,
221 * @param {string} url The URL to display.
223 get status() this._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)
234 if (sh && sh.index < sh.count - 1)
238 if (modules.bookmarkcache) {
239 if (bookmarkcache.isBookmarked(uri))
240 modified += UTF8("❤");
243 if (modules.quickmarks)
244 modified += quickmarks.find(uri.spec.replace(/#.*/, "")).join("");
246 url = this.losslessDecodeURI(uri.spec);
249 if (url == "about:blank") {
254 url = url.replace(RegExp("^dactyl://help/(\\S+)#(.*)"), function (m, n1, n2) n1 + " " + decodeURIComponent(n2) + " [Help]")
255 .replace(RegExp("^dactyl://help/(\\S+)"), "$1 [Help]");
259 url += " [" + modified + "]";
261 this.widgets.url.value = url;
266 updateStatus: function updateStatus() { this.status = buffer.uri; },
268 updateUrl: deprecated("statusline.status", function updateUrl(url) { this.status = url || buffer.uri }),
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.
276 * @param {string} buffer
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; }),
284 * Update the page load progress bar.
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.
292 progress: Modes.boundProperty({
293 get: function progress() this._progress,
294 set: function progress(progress) {
295 this._progress = progress || "";
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);
306 + "===================> "
307 .substr(20 - progress, 20)
310 this.widgets.progress.value = progressStr;
314 updateProgress: deprecated("statusline.progress", function updateProgress(progress) {
315 this.progress = progress;
319 * Display the correct tabcount (e.g., [1/5]) on the status bar.
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.
326 updateTabCount: function updateTabCount(delayed) {
327 if (dactyl.has("tabs")) {
329 this.timeout(function () this.updateTabCount(false), 0);
333 this.widgets.tabcount.value = "[" + (tabs.index(null, true) + 1) + "/" + tabs.visibleTabs.length + "]";
338 * Display the main content's vertical scroll position in the status
341 * @param {number} percent The position, as a percentage.
344 updateBufferPosition: function updateBufferPosition(percent) {
345 if (percent == null) {
346 let win = document.commandDispatcher.focusedWindow;
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;
354 percent = Math.round(percent * 100);
357 var position = "All";
358 else if (percent == 0)
360 else if (percent >= 100)
362 else if (percent < 10)
363 position = " " + percent + "%";
365 position = percent + "%";
367 this.widgets.bufferposition.value = position;
371 * Display the main content's zoom level.
373 * @param {number} percent The zoom level, as a percentage. @optional
374 * @param {boolean} full True if full zoom is in operation. @optional
376 updateZoomLevel: function updateZoomLevel(percent, full) {
377 if (arguments.length == 0)
378 [percent, full] = [buffer.zoomLevel, buffer.fullZoom];
381 this.widgets.zoomlevel.value = "";
383 percent = (" " + Math.round(percent)).substr(-3);
385 this.widgets.zoomlevel.value = " [" + percent + "%]";
387 this.widgets.zoomlevel.value = " (" + percent + "%)";
392 // vim: set fdm=marker sw=4 ts=4 et: