For a mouseless future.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

onload.js 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. var conf = {
  2. scroll_speed: 0.3,
  3. scroll_speed_fast: 1.1,
  4. scroll_friction: 0.8,
  5. chars: "SANOTEHUCP",
  6. input_whitelist: ["checkbox", "radio", "hidden", "submit", "reset", "button", "file", "image"],
  7. location_change_check_timeout: 2000
  8. }
  9. var keys = {};
  10. self.port.on("conf", function(c) {
  11. for (var i in c) {
  12. conf[i] = c[i];
  13. }
  14. });
  15. self.port.on("keys", function(k) {
  16. keys = k;
  17. });
  18. function isMatch(k, evt) {
  19. if ((k.code === evt.key)
  20. && (!!k.ctrlKey == evt.ctrlKey)
  21. && (!!k.shiftKey == evt.shiftKey)
  22. && (!!k.altKey == evt.altKey)
  23. && (!!k.metaKey == evt.metaKey)) {
  24. return true;
  25. }
  26. return false;
  27. }
  28. //There's a lot we don't want to do if we're not on an actual webpage, but on
  29. //the "speed dial"-ish pages.
  30. var onWebPage = (document.body !== undefined);
  31. function createKey(n) {
  32. var str = "";
  33. var base = conf.chars.length;
  34. if (n == 0)
  35. return conf.chars[0];
  36. while (n > 0) {
  37. str += conf.chars[n % base];
  38. n = Math.floor(n / base);
  39. }
  40. return str;
  41. }
  42. function getElemPos(elem) {
  43. var curtop = 0;
  44. var curleft = 0;
  45. do {
  46. curtop += elem.offsetTop;
  47. curleft += elem.offsetLeft;
  48. } while (elem = elem.offsetParent);
  49. return {top: curtop, left: curleft};
  50. }
  51. var blobList = {
  52. blobs: {},
  53. container: null,
  54. overview: null,
  55. visible: false,
  56. needLoadBlobs: true,
  57. currentKey: "",
  58. createContainer: function() {
  59. var container = document.createElement("div");
  60. container.style = [
  61. "pointer-events: none",
  62. "display: none",
  63. "position: absolute;",
  64. "top: 0px",
  65. "left: 0px",
  66. "z-index: 2147483647",
  67. "box-sizing: content-box",
  68. ""
  69. ].join(" !important;");
  70. document.body.appendChild(container);
  71. blobList.container = container;
  72. },
  73. createOverview: function() {
  74. var overview = document.createElement("div");
  75. overview.style = [
  76. "position: fixed",
  77. "top: 0px",
  78. "left: 0px",
  79. "background-color: white",
  80. "border-bottom: 2px solid black",
  81. "border-right: 2px solid black",
  82. "color: black",
  83. "font: 12px sans-serif",
  84. "padding: 3px",
  85. "height: 15px",
  86. "line-height: 15px",
  87. "z-index: 2147483647",
  88. "box-sizing: content-box",
  89. ""
  90. ].join(" !important;");
  91. blobList.container.appendChild(overview);
  92. blobList.overview = overview;
  93. },
  94. init: function() {
  95. if (!onWebPage)
  96. return;
  97. blobList.createContainer();
  98. window.addEventListener("scroll", function() {
  99. blobList.needLoadBlobs = true;
  100. });
  101. },
  102. currentIndex: 0,
  103. loadBlobs: function() {
  104. if (!onWebPage)
  105. return;
  106. var linkElems = document.querySelectorAll("a, button, input, textarea");
  107. //Remove old container contents
  108. blobList.container.innerHTML = "";
  109. blobList.createOverview();
  110. //Remove old blobs
  111. blobList.blobs = {};
  112. var i = 0;
  113. var nRealBlobs = 0;
  114. function addBlob() {
  115. var linkElem = linkElems[i];
  116. if (i++ >= linkElems.length)
  117. return false;
  118. if (linkElem === undefined)
  119. return true;
  120. //We don't want hidden elements
  121. if ((linkElem === undefined)
  122. || (linkElem.style.display == "none")
  123. || (linkElem.style.visibility == "hidden")) {
  124. return true;
  125. }
  126. //Get element's absolute position
  127. var pos = getElemPos(linkElem);
  128. //Lots of things which don't really exist have an X and Y value of 0
  129. if (pos.top == 0 && pos.left == 0)
  130. return true;
  131. //We don't need to get things far above our current scroll position
  132. if (pos.top < (window.scrollY - 100))
  133. return true;
  134. //We don't need things below our scroll position either
  135. if (pos.top - 100 > (window.scrollY + window.innerHeight))
  136. return true;
  137. //Create the blob's key
  138. key = createKey(nRealBlobs);
  139. nRealBlobs += 1;
  140. var blobElem = document.createElement("div");
  141. blobElem.innerHTML = key.toUpperCase();
  142. blobElem.style = [
  143. "position: absolute",
  144. "background-color: yellow",
  145. "border: 1px solid black",
  146. "border-radius: 10px",
  147. "padding-left: 3px",
  148. "padding-right: 3px",
  149. "color: black",
  150. "font: 12px sans-serif",
  151. "top: "+pos.top+"px",
  152. "left: "+pos.left+"px",
  153. "line-height: 12px",
  154. "font-size: 12px",
  155. ""
  156. ].join(" !important;");
  157. blobList.container.appendChild(blobElem);
  158. blobList.blobs[key] = {
  159. blobElem: blobElem,
  160. linkElem: linkElem
  161. }
  162. return true;
  163. }
  164. while (addBlob()) {};
  165. },
  166. showBlobs: function() {
  167. blobList.visible = true;
  168. blobList.container.style.display = "block";
  169. },
  170. hideBlobs: function() {
  171. blobList.currentKey = "";
  172. blobList.visible = false;
  173. blobList.container.style.display = "none";
  174. },
  175. click: function() {
  176. if (!blobList.visible)
  177. return;
  178. var blob = blobList.blobs[blobList.currentKey];
  179. if (!blob)
  180. return;
  181. blob.linkElem.click();
  182. blob.linkElem.focus();
  183. blobList.hideBlobs();
  184. },
  185. clickNewTab: function() {
  186. if (!blobList.visible)
  187. return;
  188. var blob = blobList.blobs[blobList.currentKey];
  189. if (!blob)
  190. return;
  191. if (blob.linkElem.tagName == "A" && blob.linkElem.href) {
  192. self.port.emit("tab_open", blob.linkElem.href);
  193. } else {
  194. blob.linkElem.click();
  195. blob.linkElem.focus();
  196. }
  197. blobList.hideBlobs();
  198. },
  199. clickClipboard: function() {
  200. if (!blobList.visible)
  201. return;
  202. var blob = blobList.blobs[blobList.currentKey];
  203. if (!blob)
  204. return;
  205. if (!blob.linkElem.href)
  206. return;
  207. self.port.emit("clipboard_set", blob.linkElem.href);
  208. blobList.hideBlobs();
  209. },
  210. appendKey: function(c) {
  211. blobList.currentKey += c;
  212. blobList.overview.innerHTML = blobList.currentKey;
  213. },
  214. backspace: function() {
  215. blobList.currentKey = blobList.currentKey.substring(0, blobList.currentKey.length - 1);
  216. blobList.overview.innerHTML = blobList.currentKey;
  217. }
  218. }
  219. blobList.init();
  220. //Reload blobs whenever the URL changes
  221. var currentUrl = location.href;
  222. setInterval(function() {
  223. if (currentUrl !== location.href) {
  224. blobList.loadBlobs();
  225. }
  226. currentUrl = location.href;
  227. }, conf.location_change_check_timeout);
  228. var pressedKeys = [];
  229. function inArray(arr, val) {
  230. return (arr.indexOf(val) !== -1);
  231. }
  232. function isValidElem(elem) {
  233. var tag = elem.tagName.toLowerCase();
  234. if (tag === "textarea")
  235. return false;
  236. if (tag === "select")
  237. return false;
  238. if (elem.contentEditable.toLowerCase() === "true")
  239. return false;
  240. if ((tag === "input")
  241. && (!inArray(conf.input_whitelist, elem.type.toLowerCase()))) {
  242. return false;
  243. }
  244. return true;
  245. }
  246. window.addEventListener("keydown", function(evt) {
  247. if (/about:.+/.test(location.href))
  248. return;
  249. var active = document.activeElement;
  250. //We don't want to do anything if the user is tpying in an input field,
  251. //unless the key is to deselect an input field
  252. if (!isValidElem(active)) {
  253. if (isMatch(keys.elem_deselect, evt)) {
  254. active.blur();
  255. blobList.hideBlobs();
  256. return;
  257. } else {
  258. return;
  259. }
  260. }
  261. //User is typing a key to a blob
  262. if (blobList.visible) {
  263. //Hide blobs if appropriate
  264. if (isMatch(keys.blobs_hide, evt)) {
  265. blobList.hideBlobs();
  266. return;
  267. }
  268. //Backspace if appropriate
  269. if (isMatch(keys.blobs_backspace, evt)) {
  270. blobList.backspace();
  271. return;
  272. }
  273. var c = evt.key;
  274. if (conf.chars.indexOf(c) !== -1) {
  275. blobList.appendKey(c);
  276. evt.preventDefault();
  277. evt.stopPropagation();
  278. return false;
  279. }
  280. }
  281. //Handle other key presses
  282. //Deselect element
  283. if (onWebPage && isMatch(keys.elem_deselect, evt)) {
  284. blobList.hideBlobs();
  285. active.blur();
  286. //Show/hide/reload blobs
  287. } else if (onWebPage && !blobList.visible && isMatch(keys.blobs_show, evt)) {
  288. blobList.loadBlobs();
  289. blobList.needLoadBlobs = false;
  290. blobList.showBlobs();
  291. } else if (onWebPage && blobList.visible && isMatch(keys.blobs_hide, evt)) {
  292. blobList.hideBlobs();
  293. //Simulate clicks
  294. } else if (onWebPage && blobList.visible && isMatch(keys.blobs_click, evt)) {
  295. blobList.click();
  296. } else if (onWebPage && blobList.visible && isMatch(keys.blobs_click_new_tab, evt)) {
  297. blobList.clickNewTab();
  298. } else if (onWebPage && blobList.visible && isMatch(keys.blobs_click_clipboard, evt)) {
  299. blobList.clickClipboard();
  300. //Scrolling
  301. } else if (onWebPage && isMatch(keys.scroll_up, evt)) {
  302. scroll.start(-conf.scroll_speed);
  303. } else if (onWebPage && isMatch(keys.scroll_down, evt)) {
  304. scroll.start(conf.scroll_speed);
  305. } else if (onWebPage && isMatch(keys.scroll_up_fast, evt)) {
  306. scroll.start(-conf.scroll_speed_fast);
  307. } else if (onWebPage && isMatch(keys.scroll_down_fast, evt)) {
  308. scroll.start(conf.scroll_speed_fast);
  309. //Back and forwards
  310. } else if (isMatch(keys.history_back, evt)) {
  311. history.back();
  312. } else if (isMatch(keys.history_forward, evt)) {
  313. history.forward();
  314. //Change tab
  315. } else if (isMatch(keys.change_tab_left, evt)) {
  316. self.port.emit("change_tab_left");
  317. } else if (isMatch(keys.change_tab_right, evt)) {
  318. self.port.emit("change_tab_right");
  319. //Move tab
  320. } else if (isMatch(keys.move_tab_left, evt)) {
  321. self.port.emit("move_tab_left");
  322. } else if (isMatch(keys.move_tab_right, evt)) {
  323. self.port.emit("move_tab_right");
  324. //We don't want to stop the event from propagating
  325. //if it hasn't matched anything yet
  326. } else {
  327. return true;
  328. }
  329. evt.preventDefault();
  330. evt.stopPropagation();
  331. return false;
  332. }, true);
  333. window.addEventListener("keyup", function(evt) {
  334. if ((isMatch(keys.scroll_up, evt))
  335. || (isMatch(keys.scroll_down, evt))
  336. || (isMatch(keys.scroll_up_fast, evt))
  337. || (isMatch(keys.scroll_down_fast, evt))) {
  338. scroll.stop();
  339. }
  340. }, true);
  341. var scroll = {
  342. start: function(acceleration) {
  343. scroll.acceleration = acceleration;
  344. if (scroll.raf == null)
  345. scroll.update();
  346. },
  347. stop: function() {
  348. scroll.acceleration = 0;
  349. },
  350. update: function() {
  351. var tdiff = scroll.endTime - scroll.startTime;
  352. if (tdiff < 100) {
  353. scroll.velocity += scroll.acceleration;
  354. window.scrollBy(0, scroll.velocity * tdiff);
  355. scroll.velocity *= conf.scroll_friction;
  356. }
  357. if (tdiff < 100 && scroll.velocity > -0.1 && scroll.velocity < 0.1) {
  358. scroll.velocity = 0;
  359. cancelAnimationFrame(scroll.raf);
  360. scroll.raf = null;
  361. } else {
  362. scroll.startTime = scroll.endTime;
  363. scroll.endTime = new Date().getTime();
  364. scroll.raf = requestAnimationFrame(scroll.update);
  365. }
  366. },
  367. raf: null,
  368. acceleration: 0,
  369. velocity: 0,
  370. startDate: 0,
  371. endDate: 0
  372. }