Status.jsm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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. "use strict";
  5. const EXPORTED_SYMBOLS = ["S4EStatusService"];
  6. const CU = Components.utils;
  7. CU.import("resource://gre/modules/Services.jsm");
  8. CU.import("resource://gre/modules/XPCOMUtils.jsm");
  9. function S4EStatusService(window, service, getters)
  10. {
  11. this._window = window;
  12. this._service = service;
  13. this._getters = getters;
  14. this._overLinkService = new S4EOverlinkService(this._window, this._service, this);
  15. }
  16. S4EStatusService.prototype =
  17. {
  18. _window: null,
  19. _service: null,
  20. _getters: null,
  21. _overLinkService: null,
  22. _overLink: { val: "", type: "" },
  23. _network: { val: "", type: "" },
  24. _networkXHR: { val: "", type: "" },
  25. _status: { val: "", type: "" },
  26. _jsStatus: { val: "", type: "" },
  27. _defaultStatus: { val: "", type: "" },
  28. _isFullScreen: false,
  29. _isVideo: false,
  30. _statusText: { val: "", type: "" },
  31. _noUpdate: false,
  32. _statusChromeTimeoutID: 0,
  33. _statusContentTimeoutID: 0,
  34. getCompositeStatusText: function()
  35. {
  36. return this._statusText.val;
  37. },
  38. getStatusText: function()
  39. {
  40. return this._status.val;
  41. },
  42. setNetworkStatus: function(status, busy)
  43. {
  44. if(busy)
  45. {
  46. this._network = { val: status, type: "network" };
  47. this._networkXHR = { val: "", type: "network xhr" };
  48. }
  49. else
  50. {
  51. this._networkXHR = { val: status, type: "network xhr" };
  52. }
  53. this.updateStatusField();
  54. },
  55. setStatusText: function(status)
  56. {
  57. this._status = { val: status, type: "status chrome" };
  58. this.updateStatusField();
  59. },
  60. setJSStatus: function(status)
  61. {
  62. this._jsStatus = { val: status, type: "status content" };
  63. this.updateStatusField();
  64. },
  65. setJSDefaultStatus: function(status)
  66. {
  67. // This was removed from Firefox in Bug 862917
  68. },
  69. setDefaultStatus: function(status)
  70. {
  71. this._defaultStatus = { val: status, type: "status chrome default" };
  72. this.updateStatusField();
  73. },
  74. setOverLink: function(link, aAnchor)
  75. {
  76. this._overLinkService.update(link, aAnchor);
  77. },
  78. setOverLinkInternal: function(link, aAnchor)
  79. {
  80. let status = this._service.status;
  81. let statusLinkOver = this._service.statusLinkOver;
  82. if(statusLinkOver)
  83. {
  84. link = link.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, encodeURIComponent);
  85. if(status == statusLinkOver)
  86. {
  87. this._overLink = { val: link, type: "overLink", anchor: aAnchor };
  88. this.updateStatusField();
  89. }
  90. else
  91. {
  92. this.setStatusField(statusLinkOver, { val: link, type: "overLink", anchor: aAnchor }, true);
  93. }
  94. }
  95. },
  96. setNoUpdate: function(nu)
  97. {
  98. this._noUpdate = nu;
  99. },
  100. buildBinding: function() {
  101. // Object.prototype.watch() shim, based on Eli Grey's polyfill
  102. // object.watch
  103. if (!this._window.XULBrowserWindow.watch) {
  104. Object.defineProperty(this._window.XULBrowserWindow, "watch", {
  105. enumerable: false,
  106. configurable: true,
  107. writable: false,
  108. value: function (prop, handler) {
  109. var oldval = this[prop],
  110. newval = oldval,
  111. getter = function () {
  112. return newval;
  113. },
  114. setter = function (val) {
  115. oldval = newval;
  116. return newval = handler.call(this, prop, oldval, val);
  117. }
  118. ;
  119. try {
  120. if (delete this[prop]) { // can't watch constants
  121. Object.defineProperty(this, prop, {
  122. get: getter,
  123. set: setter,
  124. enumerable: true,
  125. configurable: true
  126. });
  127. }
  128. } catch(e) {
  129. // This fails fatally on non-configurable props, so just
  130. // ignore errors if it does.
  131. }
  132. }
  133. });
  134. }
  135. // object.unwatch
  136. if (!this._window.XULBrowserWindow.unwatch) {
  137. Object.defineProperty(this._window.XULBrowserWindow, "unwatch", {
  138. enumerable: false,
  139. configurable: true,
  140. writable: false,
  141. value: function (prop) {
  142. var val = this[prop];
  143. delete this[prop]; // remove accessors
  144. this[prop] = val;
  145. }
  146. });
  147. }
  148. let XULBWPropHandler = function(prop, oldval, newval) {
  149. CU.reportError("Attempt to modify XULBrowserWindow." + prop);
  150. return oldval;
  151. };
  152. ["updateStatusField", "onStatusChange"].forEach(function(prop)
  153. {
  154. this._window.XULBrowserWindow.unwatch(prop);
  155. this._window.XULBrowserWindow[prop] = function() {};
  156. this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
  157. }, this);
  158. ["getCompositeStatusText", "getStatusText", "setStatusText", "setJSStatus",
  159. "setJSDefaultStatus", "setDefaultStatus", "setOverLink"].forEach(function(prop)
  160. {
  161. this._window.XULBrowserWindow.unwatch(prop);
  162. this._window.XULBrowserWindow[prop] = this[prop].bind(this);
  163. this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
  164. }, this);
  165. },
  166. destroy: function()
  167. {
  168. // No need to unbind from the XULBrowserWindow, it's already null at this point
  169. this.clearTimer("_statusChromeTimeoutID");
  170. this.clearTimer("_statusContentTimeoutID");
  171. this._overLinkService.destroy();
  172. ["_overLink", "_network", "_networkXHR", "_status", "_jsStatus", "_defaultStatus",
  173. "_statusText", "_window", "_service", "_getters", "_overLinkService"].forEach(function(prop)
  174. {
  175. delete this[prop];
  176. }, this);
  177. },
  178. buildTextOrder: function()
  179. {
  180. this.__defineGetter__("_textOrder", function()
  181. {
  182. let textOrder = ["_overLink"];
  183. if(this._service.statusNetwork)
  184. {
  185. textOrder.push("_network");
  186. if(this._service.statusNetworkXHR)
  187. {
  188. textOrder.push("_networkXHR");
  189. }
  190. }
  191. textOrder.push("_status", "_jsStatus");
  192. if(this._service.statusDefault)
  193. {
  194. textOrder.push("_defaultStatus");
  195. }
  196. delete this._textOrder;
  197. return this._textOrder = textOrder;
  198. });
  199. },
  200. updateStatusField: function(force)
  201. {
  202. let text = { val: "", type: "" };
  203. for(let i = 0; !text.val && i < this._textOrder.length; i++)
  204. {
  205. text = this[this._textOrder[i]];
  206. }
  207. if(this._statusText.val != text.val || force)
  208. {
  209. if(this._noUpdate)
  210. {
  211. return;
  212. }
  213. this._statusText = text;
  214. this.setStatusField(this._service.status, text, false);
  215. if(text.val && this._service.statusTimeout)
  216. {
  217. this.setTimer(text.type);
  218. }
  219. }
  220. },
  221. setFullScreenState: function(isFullScreen, isVideo)
  222. {
  223. this._isFullScreen = isFullScreen;
  224. this._isVideo = isFullScreen && isVideo;
  225. this.clearStatusField();
  226. this.updateStatusField(true);
  227. },
  228. setTimer: function(type)
  229. {
  230. let typeArgs = type.split(" ", 3);
  231. if(typeArgs.length < 2 || typeArgs[0] != "status")
  232. {
  233. return;
  234. }
  235. if(typeArgs[1] == "chrome")
  236. {
  237. this.clearTimer("_statusChromeTimeoutID");
  238. this._statusChromeTimeoutID = this._window.setTimeout(function(self, isDefault)
  239. {
  240. self._statusChromeTimeoutID = 0;
  241. if(isDefault)
  242. {
  243. self.setDefaultStatus("");
  244. }
  245. else
  246. {
  247. self.setStatusText("");
  248. }
  249. }, this._service.statusTimeout, this, (typeArgs.length == 3 && typeArgs[2] == "default"));
  250. }
  251. else
  252. {
  253. this.clearTimer("_statusContentTimeoutID");
  254. this._statusContentTimeoutID = this._window.setTimeout(function(self)
  255. {
  256. self._statusContentTimeoutID = 0;
  257. self.setJSStatus("");
  258. }, this._service.statusTimeout, this);
  259. }
  260. },
  261. clearTimer: function(timerName)
  262. {
  263. if(this[timerName] != 0)
  264. {
  265. this._window.clearTimeout(this[timerName]);
  266. this[timerName] = 0;
  267. }
  268. },
  269. clearStatusField: function()
  270. {
  271. this._getters.statusOverlay.value = "";
  272. let status_label = this._getters.statusWidgetLabel;
  273. if(status_label)
  274. {
  275. status_label.value = "";
  276. }
  277. },
  278. setStatusField: function(location, text, allowTooltip)
  279. {
  280. if(!location)
  281. {
  282. return;
  283. }
  284. let label = null;
  285. if(this._isFullScreen)
  286. {
  287. switch(location)
  288. {
  289. case 1: // Toolbar
  290. location = 3
  291. break;
  292. case 2: // URL bar
  293. if(Services.prefs.getBoolPref("browser.fullscreen.autohide"))
  294. {
  295. location = 3
  296. }
  297. break;
  298. }
  299. }
  300. switch(location)
  301. {
  302. case 1: // Toolbar
  303. label = this._getters.statusWidgetLabel;
  304. break;
  305. case 2: // URL Bar
  306. break;
  307. case 3: // Popup
  308. default:
  309. if(this._isVideo)
  310. {
  311. return;
  312. }
  313. label = this._getters.statusOverlay;
  314. break;
  315. }
  316. if(label)
  317. {
  318. label.setAttribute("previoustype", label.getAttribute("type"));
  319. label.setAttribute("type", text.type);
  320. label.value = text.val;
  321. label.setAttribute("crop", text.type == "overLink" ? "center" : "end");
  322. }
  323. }
  324. };
  325. function S4EOverlinkService(window, service, statusService) {
  326. this._window = window;
  327. this._service = service;
  328. this._statusService = statusService;
  329. }
  330. S4EOverlinkService.prototype =
  331. {
  332. _window: null,
  333. _service: null,
  334. _statusService: null,
  335. _timer: 0,
  336. _currentLink: { link: "", anchor: null },
  337. _pendingLink: { link: "", anchor: null },
  338. _listening: false,
  339. update: function(aLink, aAnchor)
  340. {
  341. this.clearTimer();
  342. this.stopListen();
  343. this._pendingLink = { link: aLink, anchor: aAnchor };
  344. if(!aLink)
  345. {
  346. if(this._window.XULBrowserWindow.hideOverLinkImmediately || !this._service.statusLinkOverDelayHide)
  347. {
  348. this._show();
  349. }
  350. else
  351. {
  352. this._showDelayed();
  353. }
  354. }
  355. else if(this._currentLink.link || !this._service.statusLinkOverDelayShow)
  356. {
  357. this._show();
  358. }
  359. else
  360. {
  361. this._showDelayed();
  362. this.startListen();
  363. }
  364. },
  365. destroy: function()
  366. {
  367. this.clearTimer();
  368. this.stopListen();
  369. ["_currentLink", "_pendingLink", "_statusService", "_window"].forEach(function(prop)
  370. {
  371. delete this[prop];
  372. }, this);
  373. },
  374. startListen: function()
  375. {
  376. if(!this._listening)
  377. {
  378. this._window.addEventListener("mousemove", this, true);
  379. this._listening = true;
  380. }
  381. },
  382. stopListen: function()
  383. {
  384. if(this._listening)
  385. {
  386. this._window.removeEventListener("mousemove", this, true);
  387. this._listening = false;
  388. }
  389. },
  390. clearTimer: function()
  391. {
  392. if(this._timer != 0)
  393. {
  394. this._window.clearTimeout(this._timer);
  395. this._timer = 0;
  396. }
  397. },
  398. handleEvent: function(event)
  399. {
  400. switch(event.type)
  401. {
  402. case "mousemove":
  403. this.clearTimer();
  404. this._showDelayed();
  405. }
  406. },
  407. _showDelayed: function()
  408. {
  409. let delay = ((this._pendingLink.link)
  410. ? this._service.statusLinkOverDelayShow
  411. : this._service.statusLinkOverDelayHide);
  412. this._timer = this._window.setTimeout(function(self)
  413. {
  414. self._timer = 0;
  415. self._show();
  416. self.stopListen();
  417. }, delay, this);
  418. },
  419. _show: function()
  420. {
  421. this._currentLink = this._pendingLink;
  422. this._statusService.setOverLinkInternal(this._currentLink.link, this._currentLink.anchor);
  423. }
  424. };