crbug colorize

Colorize bug list based on status

  1. // ==UserScript==
  2. // @name crbug colorize
  3. // @description Colorize bug list based on status
  4. // @match https://bugs.chromium.org/*
  5. // @version 1.0.4
  6. // @author wOxxOm
  7. // @namespace wOxxOm.scripts
  8. // @license MIT License
  9. // @run-at document-start
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. const prefix = 'wOxxOm-'
  14. const sheet = new CSSStyleSheet();
  15. sheet.replaceSync(`
  16. .${prefix}Starred { font-weight: bold }
  17. .${prefix}Archived { color: gray }
  18. .${prefix}Assigned { color: #3f71b1 }
  19. .${prefix}Available { color: #92479a }
  20. .${prefix}Duplicate,
  21. .${prefix}Invalid { opacity: 0.3 }
  22. .${prefix}ExternalDependency { color: #ababab }
  23. .${prefix}Fixed { color: #227700 }
  24. .${prefix}Started,
  25. .${prefix}FixPending { color: #06908b }
  26. .${prefix}Unconfirmed,
  27. .${prefix}New { color: black }
  28. .${prefix}Untriaged { color: #947911 }
  29. .${prefix}Verified, .${prefix}Accepted { color: #6a846f }
  30. .${prefix}WontFix { color: #d00 }
  31. tr[class*="${prefix}"] td[width="100%"] a {
  32. color: inherit;
  33. text-decoration: underline;
  34. }
  35. `);
  36.  
  37. (async () => {
  38. const app = await added('mr-app');
  39. const main = await added('main', app);
  40. while (true) await colorize(main);
  41. })();
  42.  
  43. async function colorize(main) {
  44. const page = await added('mr-list-page', main);
  45. const list = await shadowOf(await added('mr-issue-list', page));
  46. list.adoptedStyleSheets = [...new Set([...list.adoptedStyleSheets, sheet])];
  47. await added('td', list.host);
  48. const tbody = list.querySelector('tbody');
  49. while (true) {
  50. for (const el of tbody.getElementsByTagName('td')) {
  51. const text = el.textContent.trim();
  52. switch (text) {
  53. case '':
  54. continue;
  55. case 'Accepted':
  56. case 'Archived':
  57. case 'Assigned':
  58. case 'Available':
  59. case 'Duplicate':
  60. case 'ExternalDependency':
  61. case 'FixPending':
  62. case 'Fixed':
  63. case 'Invalid':
  64. case 'New':
  65. case 'Started':
  66. case 'Unconfirmed':
  67. case 'Untriaged':
  68. case 'Verified':
  69. case 'WontFix':
  70. setClass(el, text);
  71. continue;
  72. case '★':
  73. setClass(el, 'Starred');
  74. continue;
  75. }
  76. if (el.align === 'right' && (text === '1' || text === '0')) {
  77. el.textContent = '';
  78. }
  79. if (/% regression in|\b\d(\.\d)?%(-\d(\.\d)?%)? improvement in|test.*?is flaky|^(Android|Chrome)$/.test(text) && el.parentNode) {
  80. el.parentNode.remove();
  81. }
  82. }
  83. const winner = await Promise.race([
  84. removed(page),
  85. added('td', list.host, true),
  86. ]);
  87. if (!winner)
  88. return;
  89. }
  90. }
  91.  
  92. function setClass(el, type) {
  93. const {className} = el.parentNode;
  94. const token = prefix + type;
  95. const allTokens = className
  96. .trim()
  97. .split(/\s+/)
  98. .filter(t => !t.startsWith(prefix) || t === token);
  99. const s = allTokens.join(' ') + (allTokens.includes(token) ? '' : ' ' + token);
  100. if (s !== className)
  101. el.parentNode.className = s;
  102. }
  103.  
  104. async function added(tag, parent = document.documentElement, forceObserver) {
  105. const target =
  106. parent.shadowRoot ||
  107. !parent.localName.includes('-') && parent ||
  108. await shadowOf(parent);
  109. return !forceObserver && target.querySelector(tag) || new Promise(resolve => {
  110. const mo = new MutationObserver(mutations => {
  111. const el = forceObserver ? mutations.some(mutantAdded, {tag}) : target.querySelector(tag);
  112. if (el) {
  113. mo.disconnect();
  114. resolve(el);
  115. }
  116. });
  117. mo.observe(target, {childList: true, subtree: true});
  118. });
  119. }
  120.  
  121. function removed(el) {
  122. const root = el.getRootNode();
  123. return root.contains(el) && new Promise(resolve => {
  124. const mo = new MutationObserver(() => {
  125. if (!root.contains(el)) {
  126. mo.disconnect();
  127. resolve();
  128. }
  129. });
  130. mo.observe(root, {childList: true, subtree: true});
  131. });
  132. }
  133.  
  134. function shadowOf(el) {
  135. return el.shadowRoot || new Promise(resolve => {
  136. el.attachShadow = function (...args) {
  137. delete el.attachShadow;
  138. const root = el.attachShadow(...args);
  139. resolve(root);
  140. return root;
  141. };
  142. });
  143. }
  144.  
  145. function mutantAdded({addedNodes, target}) {
  146. const {tag} = this;
  147. if (target.localName === tag)
  148. return true;
  149. for (const n of addedNodes)
  150. if (n.localName === tag || n.firstElementChild && n.getElementsByTagName(tag)[0])
  151. return true;
  152. }