values.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { isEmpty, makePause, sendTabCmd } from '@/common';
  2. import { forEachEntry, forEachKey, objectSet } from '@/common/object';
  3. import { getScript, getValueStoresByIds, dumpValueStores } from './db';
  4. import { commands } from './message';
  5. const openers = {}; // { scriptId: { tabId: { frameId: 1, ... }, ... } }
  6. let cache = {}; // { scriptId: { key: { last: value, tabId: { frameId: value } } } }
  7. let cacheUpd;
  8. Object.assign(commands, {
  9. /** @param {{ where, store }[]} data
  10. * @return {Promise<void>} */
  11. async SetValueStores(data) {
  12. // Value store will be replaced soon.
  13. const stores = data.reduce((res, { where, store }) => {
  14. const id = where.id || getScript(where)?.props.id;
  15. if (id) res[id] = store;
  16. return res;
  17. }, {});
  18. await Promise.all([
  19. dumpValueStores(stores),
  20. broadcastValueStores(groupStoresByFrame(stores)),
  21. ]);
  22. },
  23. /** @return {void} */
  24. UpdateValue({ id, key, value = null }, src) {
  25. objectSet(cache, [id, key, 'last'], value);
  26. objectSet(cache, [id, key, src.tab.id, src.frameId], value);
  27. updateLater();
  28. },
  29. });
  30. browser.tabs.onRemoved.addListener(resetValueOpener);
  31. browser.tabs.onReplaced.addListener((addedId, removedId) => resetValueOpener(removedId));
  32. export function resetValueOpener(tabId) {
  33. openers::forEachEntry(([id, openerTabs]) => {
  34. if (tabId in openerTabs) {
  35. delete openerTabs[tabId];
  36. if (isEmpty(openerTabs)) delete openers[id];
  37. }
  38. });
  39. }
  40. export function addValueOpener(tabId, frameId, scriptIds) {
  41. scriptIds.forEach((id) => {
  42. objectSet(openers, [id, tabId, frameId], 1);
  43. });
  44. }
  45. async function updateLater() {
  46. while (!cacheUpd) {
  47. await makePause(0);
  48. cacheUpd = cache;
  49. cache = {};
  50. await doUpdate();
  51. cacheUpd = null;
  52. if (isEmpty(cache)) break;
  53. }
  54. }
  55. async function doUpdate() {
  56. const toSend = {};
  57. const valueStores = await getValueStoresByIds(Object.keys(cacheUpd));
  58. cacheUpd::forEachEntry(([id, scriptData]) => {
  59. scriptData::forEachEntry(([key, history]) => {
  60. const { last } = history;
  61. objectSet(valueStores, [id, key], last || undefined);
  62. openers[id]::forEachEntry(([tabId, frames]) => {
  63. const tabHistory = history[tabId] || {};
  64. frames::forEachKey((frameId) => {
  65. if (tabHistory[frameId] !== last) {
  66. objectSet(toSend, [tabId, frameId, id, key], last);
  67. }
  68. });
  69. });
  70. });
  71. });
  72. await Promise.all([
  73. dumpValueStores(valueStores),
  74. broadcastValueStores(toSend, { partial: true }),
  75. ]);
  76. }
  77. async function broadcastValueStores(tabFrameData, { partial } = {}) {
  78. const tasks = [];
  79. for (const [tabId, frames] of Object.entries(tabFrameData)) {
  80. for (const [frameId, frameData] of Object.entries(frames)) {
  81. if (partial) frameData.partial = true;
  82. tasks.push(sendTabCmd(+tabId, 'UpdatedValues', frameData, { frameId: +frameId }));
  83. if (tasks.length === 20) await Promise.all(tasks.splice(0)); // throttling
  84. }
  85. }
  86. await Promise.all(tasks);
  87. }
  88. // Returns per tab/frame data
  89. function groupStoresByFrame(stores) {
  90. const toSend = {};
  91. stores::forEachEntry(([id, store]) => {
  92. openers[id]::forEachEntry(([tabId, frames]) => {
  93. frames::forEachKey(frameId => {
  94. objectSet(toSend, [tabId, frameId, id], store);
  95. });
  96. });
  97. });
  98. return toSend;
  99. }