click.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. /* Generic clicking
  2. *
  3. * We can’t just click every clickable object, since there may be side-effects
  4. * like navigating to a different location. Thus whitelist known elements.
  5. */
  6. (function() {
  7. /* Element is visible if itself and all of its parents are
  8. */
  9. function isVisible (o) {
  10. if (o === null || !(o instanceof Element)) {
  11. return true;
  12. }
  13. let style = window.getComputedStyle (o);
  14. if ('parentNode' in o) {
  15. return style.display !== 'none' && isVisible (o.parentNode);
  16. } else {
  17. return style.display !== 'none';
  18. }
  19. }
  20. /* Elements are considered clickable if they are a) visible and b) not
  21. * disabled
  22. */
  23. function isClickable (o) {
  24. return !o.hasAttribute ('disabled') && isVisible (o);
  25. }
  26. const defaultClickThrottle = 50; /* in ms */
  27. const discoverInterval = 1000; /* 1 second */
  28. class Click {
  29. constructor(options) {
  30. /* pick selectors matching current location */
  31. let hostname = document.location.hostname;
  32. this.selector = [];
  33. for (let s of options['sites']) {
  34. let r = new RegExp (s.match, 'i');
  35. if (r.test (hostname)) {
  36. this.selector = this.selector.concat (s.selector);
  37. }
  38. }
  39. /* throttle clicking */
  40. this.queue = [];
  41. this.clickTimeout = null;
  42. /* some sites don’t remove/replace the element immediately, so keep track of
  43. * which ones we already clicked */
  44. this.have = new Set ();
  45. /* XXX: can we use a mutation observer instead? */
  46. this.interval = window.setInterval (this.discover.bind (this), discoverInterval);
  47. }
  48. makeClickEvent () {
  49. return new MouseEvent('click', {
  50. view: window,
  51. bubbles: true,
  52. cancelable: true
  53. });
  54. }
  55. click () {
  56. if (this.queue.length > 0) {
  57. const item = this.queue.shift ();
  58. const o = item.o;
  59. const selector = item.selector;
  60. o.dispatchEvent (this.makeClickEvent ());
  61. if (this.queue.length > 0) {
  62. const nextTimeout = 'throttle' in selector ?
  63. selector.throttle : defaultClickThrottle;
  64. this.clickTimeout = window.setTimeout (this.click.bind (this), nextTimeout);
  65. } else {
  66. this.clickTimeout = null;
  67. }
  68. }
  69. }
  70. discover () {
  71. for (let s of this.selector) {
  72. let obj = document.querySelectorAll (s.selector);
  73. for (let o of obj) {
  74. if (!this.have.has (o) && isClickable (o)) {
  75. this.queue.push ({o: o, selector: s});
  76. if (!s.multi) {
  77. this.have.add (o);
  78. }
  79. }
  80. }
  81. }
  82. if (this.queue.length > 0 && this.clickTimeout === null) {
  83. /* start clicking immediately */
  84. this.clickTimeout = window.setTimeout (this.click.bind (this), 0);
  85. }
  86. return true;
  87. }
  88. stop () {
  89. window.clearInterval (this.interval);
  90. window.clearTimeout (this.clickTimeout);
  91. }
  92. }
  93. return Click;
  94. }())