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 10KB

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