@duckduckgo/autoconsent 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.github/ISSUE_TEMPLATE/broken-site-issue.md +29 -0
  2. package/CHANGELOG.md +27 -0
  3. package/Jenkinsfile +2 -2
  4. package/api.md +10 -1
  5. package/dist/addon-firefox/background.bundle.js +312 -1
  6. package/dist/addon-firefox/content.bundle.js +1912 -1
  7. package/dist/addon-firefox/manifest.json +2 -2
  8. package/dist/addon-firefox/rules.json +134 -38
  9. package/dist/addon-mv3/background.bundle.js +312 -1
  10. package/dist/addon-mv3/content.bundle.js +1912 -1
  11. package/dist/addon-mv3/devtools/background.html +10 -0
  12. package/dist/addon-mv3/devtools/bulma.min.css +1 -0
  13. package/dist/addon-mv3/devtools/loader.js +2 -0
  14. package/dist/addon-mv3/devtools/panel.html +88 -0
  15. package/dist/addon-mv3/devtools/panel.js +148 -0
  16. package/dist/addon-mv3/devtools/panel.ts +145 -0
  17. package/dist/addon-mv3/manifest.json +5 -3
  18. package/dist/addon-mv3/popup.bundle.js +173 -1
  19. package/dist/addon-mv3/popup.html +14 -0
  20. package/dist/addon-mv3/rules.json +134 -38
  21. package/dist/autoconsent.cjs.js +1 -1
  22. package/dist/autoconsent.esm.js +1 -1
  23. package/dist/autoconsent.playwright.js +1 -1
  24. package/lib/cmps/airbnb.ts +4 -0
  25. package/lib/cmps/base.ts +8 -0
  26. package/lib/cmps/consentmanager.ts +4 -0
  27. package/lib/cmps/consentomatic.ts +1 -0
  28. package/lib/cmps/conversant.ts +4 -0
  29. package/lib/cmps/cookiebot.ts +4 -0
  30. package/lib/cmps/evidon.ts +4 -0
  31. package/lib/cmps/klaro.ts +4 -0
  32. package/lib/cmps/onetrust.ts +4 -0
  33. package/lib/cmps/sourcepoint-frame.ts +4 -0
  34. package/lib/cmps/tiktok.ts +4 -0
  35. package/lib/cmps/trustarc-frame.ts +4 -0
  36. package/lib/cmps/trustarc-top.ts +4 -0
  37. package/lib/cmps/uniconsent.ts +4 -0
  38. package/lib/eval-handler.ts +2 -6
  39. package/lib/messages.ts +44 -13
  40. package/lib/random.ts +6 -0
  41. package/lib/rules.ts +2 -1
  42. package/lib/types.ts +27 -0
  43. package/lib/web.ts +127 -82
  44. package/package.json +4 -3
  45. package/playwright/runner.ts +1 -0
  46. package/rollup.config.js +18 -5
  47. package/rules/autoconsent/ausopen.json +2 -2
  48. package/rules/autoconsent/baden-wuerttemberg-de.json +1 -0
  49. package/rules/autoconsent/cc-banner.json +1 -0
  50. package/rules/autoconsent/complianz-notice.json +1 -0
  51. package/rules/autoconsent/cookie-notice.json +2 -1
  52. package/rules/autoconsent/dsgvo.json +1 -0
  53. package/rules/autoconsent/eu-cookie-compliance.json +1 -1
  54. package/rules/autoconsent/eu-cookie-law.json +1 -0
  55. package/rules/autoconsent/ezoic.json +1 -0
  56. package/rules/autoconsent/generic-cosmetic.json +9 -0
  57. package/rules/autoconsent/indeed-com.json +9 -0
  58. package/rules/autoconsent/jquery-cookiebar.json +1 -0
  59. package/rules/autoconsent/marksandspencer.json +2 -2
  60. package/rules/autoconsent/notice-cookie.json +1 -0
  61. package/rules/autoconsent/osano.json +1 -0
  62. package/rules/autoconsent/pornhub.json +12 -0
  63. package/rules/autoconsent/uk-cookie-consent.json +1 -0
  64. package/rules/autoconsent/xnxx-com.json +9 -0
  65. package/rules/rules.json +134 -38
  66. package/tests/borlabs.spec.ts +1 -1
  67. package/tests/complianz-optin.spec.ts +4 -4
  68. package/tests/cookie-notice.spec.ts +3 -1
  69. package/tests/cookiebot.spec.ts +0 -4
  70. package/tests/cookieinformation.spec.ts +0 -1
  71. package/tests/dsgvo.spec.ts +4 -1
  72. package/tests/evidon.spec.ts +2 -4
  73. package/tests/ezoic.spec.ts +0 -1
  74. package/tests/fundingchoices.spec.ts +0 -1
  75. package/tests/generic-cosmetic.spec.ts +11 -0
  76. package/tests/indeed.spec.ts +7 -0
  77. package/tests/moove.spec.ts +0 -6
  78. package/tests/pornhub.spec.ts +7 -0
  79. package/tests/pubtech.spec.ts +0 -1
  80. package/tests/reddit.spec.ts +1 -1
  81. package/tests/sibbo.spec.ts +0 -1
  82. package/tests/sirdata.spec.ts +0 -1
  83. package/tests/trustarc.spec.ts +0 -2
  84. package/tests/uniconsent.spec.ts +1 -1
  85. package/tests/usercentrics-api.spec.ts +1 -0
  86. package/tests/usercentrics-button.spec.ts +0 -1
  87. package/tests/xnxx.spec.ts +8 -0
  88. package/tsconfig.json +2 -1
  89. package/rules/autoconsent/motor-talk-de.json +0 -24
  90. package/tests/motor-talk.spec.ts +0 -7
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: Broken site issue
3
+ about: Report a site where autoconsent is not working
4
+ title: 'Site breakage: [SITE]'
5
+ labels: broken site
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ## Description
11
+
12
+ The popup on [site]
13
+ - [ ] is not detected
14
+ - [ ] is not dismissed properly
15
+ - [ ] other (elaborate below)
16
+
17
+ **Tested on**
18
+ - [ ] DuckDuckGo macOS browser
19
+ - [ ] DuckDuckGo Windows browser
20
+ - [ ] DuckDuckGo Android browser
21
+ - [ ] DuckDuckGo iOS browser
22
+ - [ ] Chrome browser extension
23
+ - [ ] Firefox browser extension
24
+
25
+ **Tested from country**:
26
+
27
+ **Autoconsent version**:
28
+
29
+ **Screenshot (optional)**
package/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ # v4.1.0 (Tue Jan 17 2023)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - Cosmetic rules [#67](https://github.com/duckduckgo/autoconsent/pull/67) ([@muodov](https://github.com/muodov))
6
+ - Extension Devtools [#42](https://github.com/duckduckgo/autoconsent/pull/42) ([@sammacbeth](https://github.com/sammacbeth) [@muodov](https://github.com/muodov))
7
+
8
+ #### 🐛 Bug Fix
9
+
10
+ - Cleaning up invalid tests [#49](https://github.com/duckduckgo/autoconsent/pull/49) ([@sammacbeth](https://github.com/sammacbeth))
11
+ - Fix type warning on bundle. [#83](https://github.com/duckduckgo/autoconsent/pull/83) ([@sammacbeth](https://github.com/sammacbeth))
12
+ - Add broken site issue template [#69](https://github.com/duckduckgo/autoconsent/pull/69) ([@sammacbeth](https://github.com/sammacbeth))
13
+
14
+ #### 🔩 Dependency Updates
15
+
16
+ - Bump @rollup/plugin-json from 5.0.2 to 6.0.0 [#72](https://github.com/duckduckgo/autoconsent/pull/72) ([@dependabot[bot]](https://github.com/dependabot[bot]))
17
+ - Bump @types/chrome from 0.0.204 to 0.0.206 [#85](https://github.com/duckduckgo/autoconsent/pull/85) ([@dependabot[bot]](https://github.com/dependabot[bot]))
18
+ - Bump eslint from 8.29.0 to 8.31.0 [#84](https://github.com/duckduckgo/autoconsent/pull/84) ([@dependabot[bot]](https://github.com/dependabot[bot]))
19
+
20
+ #### Authors: 3
21
+
22
+ - [@dependabot[bot]](https://github.com/dependabot[bot])
23
+ - Maxim Tsoy ([@muodov](https://github.com/muodov))
24
+ - Sam Macbeth ([@sammacbeth](https://github.com/sammacbeth))
25
+
26
+ ---
27
+
1
28
  # v4.0.0 (Wed Dec 14 2022)
2
29
 
3
30
  #### 💥 Breaking Change
package/Jenkinsfile CHANGED
@@ -3,7 +3,7 @@ def runPlaywrightTests(resultDir, browser, grep) {
3
3
  timeout(20) {
4
4
  sh 'mkdir -p ./test-results'
5
5
  sh """
6
- PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml npx playwright test --project $browser --reporter=junit --workers 10 --grep "$grep"|| true
6
+ PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml npx playwright test --project $browser --reporter=junit --grep "$grep"|| true
7
7
  """
8
8
  }
9
9
  } finally {
@@ -27,7 +27,7 @@ pipeline {
27
27
  agent { label 'autoconsent-crawler' }
28
28
  parameters {
29
29
  string(name: 'TEST_RESULT_ROOT', defaultValue: '/mnt/efs/users/smacbeth/autoconsent/ci', description: 'Where test results and configuration are stored')
30
- choice(name: 'BROWSER', choices: ['webkit', 'iphoneSE', 'chrome', 'firefox'], description: 'Browser')
30
+ choice(name: 'BROWSER', choices: ['chrome', 'webkit', 'iphoneSE', 'firefox'], description: 'Browser')
31
31
  string(name: 'GREP', defaultValue: '', description: 'filter for tests matching a specific string')
32
32
  }
33
33
  environment {
package/api.md CHANGED
@@ -77,6 +77,10 @@ sequenceDiagram
77
77
  Note right of CS: execute self-test rules
78
78
  CS -->>- BG: selfTestResult
79
79
  end
80
+
81
+ opt periodically
82
+ CS ->> BG: report
83
+ end
80
84
  ```
81
85
 
82
86
  ### Asynchronous eval rules
@@ -101,4 +105,9 @@ sequenceDiagram
101
105
  deactivate BG
102
106
 
103
107
  Note over CS: Continue rule execution (or fail with timeout)
104
- ```
108
+ ```
109
+
110
+ ### Report messages
111
+
112
+ The content-script periodically sends `report` messages which give a summary of the status of what has run
113
+ so far in a particular frame. The exact contents of the report messages can be found in [type definitions](/lib/messages.ts).
@@ -1 +1,312 @@
1
- !function(){"use strict";const e=chrome.runtime.getManifest().manifest_version,t={};async function o(o){if(2!==e)return chrome.storage.local.set(o);Object.assign(t,o)}async function a(o){return null===o?2===e?t:await chrome.storage.local.get(null):2===e?t[o]:(await chrome.storage.local.get(o))?.[o]}async function s(o){if(2!==e)return chrome.storage.local.remove(o);delete t[o]}async function n(e,t,o=""){let a="",s="icons/cookie-idle.png";"success"===t?(a=`Opt out successful! (${o})`,s="icons/party.png"):"complete"===t?(a=`Opt out complete! (${o})`,s="icons/tick.png"):"working"===t?(a=`Processing... (${o})`,s="icons/cog.png"):"verified"===t?(a=`Verified (${o})`,s="icons/verified.png"):"idle"===t?(a="Idle",s="icons/cookie-idle.png"):"available"===t&&(a=`Click to opt out (${o})`,s="icons/cookie.png");const n=chrome.action||chrome.pageAction;chrome.pageAction&&chrome.pageAction.show(e),await n.setTitle({tabId:e,title:a}),await n.setIcon({tabId:e,path:s})}async function c(){const e=await fetch("./rules.json");o({rules:await e.json()})}async function i(){console.log("init sw");const e=await a("config");if(console.log("storedConfig",e),!e){console.log("init config");const e={enabled:!0,autoAction:"optOut",disabledCmps:[],enablePrehide:!0,detectRetries:20};await o({config:e})}}chrome.runtime.onInstalled.addListener((()=>{c(),i()})),2===e&&(c(),i()),chrome.tabs.onRemoved.addListener((e=>{s(`detected${e}`)})),chrome.runtime.onMessage.addListener((async(t,c)=>{const i=c.tab.id,r=c.frameId,l=await a("rules"),d=await a("config");switch(t.type){case"init":0===r&&await n(i,"idle"),chrome.tabs.sendMessage(i,{type:"initResp",rules:l,config:d},{frameId:r});break;case"eval":(async function(t,o,a){return 2===e?new Promise((e=>{chrome.tabs.executeScript(t,{frameId:o,code:`!!window.eval(decodeURIComponent("${encodeURIComponent(a)}"))`},(t=>{e([{result:t[0],frameId:o}])}))})):chrome.scripting.executeScript({target:{tabId:t,frameIds:[o]},world:"MAIN",args:[a],func:e=>{try{return window.eval(e)}catch(t){return void console.warn("eval error",e,t)}}})})(i,r,t.code).then((([e])=>{chrome.tabs.sendMessage(i,{id:t.id,type:"evalResp",result:e.result},{frameId:r})}));break;case"popupFound":await n(i,"available",t.cmp),o({[`detected${i}`]:r});break;case"optOutResult":case"optInResult":t.result&&(await n(i,"working",t.cmp),t.scheduleSelfTest&&await o({[`selfTest${i}`]:r}));break;case"selfTestResult":t.result&&await n(i,"verified",t.cmp);break;case"autoconsentDone":{await n(i,"success",t.cmp);const e=`selfTest${i}`,o=(await chrome.storage.local.get(e))?.[e];"number"==typeof o&&(s(e),chrome.tabs.sendMessage(i,{type:"selfTest"},{frameId:o}));break}case"autoconsentError":console.error("Error:",t.details)}})),2===e&&chrome.pageAction.onClicked.addListener((async e=>{const t=e.id,o=`detected${t}`,c=await a(o);"number"==typeof c&&(s(o),await n(t,"working"),chrome.tabs.sendMessage(t,{type:"optOut"},{frameId:c}))}))}();
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const manifestVersion = chrome.runtime.getManifest().manifest_version;
5
+ // Storage abstraction: MV2 keeps everything in memory, MV3 uses chrome.storage
6
+ const storage = {};
7
+ async function storageSet(obj) {
8
+ if (manifestVersion === 2) {
9
+ Object.assign(storage, obj);
10
+ return;
11
+ }
12
+ return chrome.storage.local.set(obj);
13
+ }
14
+ async function storageGet(key) {
15
+ if (key === null) {
16
+ if (manifestVersion === 2) {
17
+ return storage;
18
+ }
19
+ return await chrome.storage.local.get(null);
20
+ }
21
+ if (manifestVersion === 2) {
22
+ return storage[key];
23
+ }
24
+ return (await chrome.storage.local.get(key))?.[key];
25
+ }
26
+ async function storageRemove(key) {
27
+ if (manifestVersion === 2) {
28
+ delete storage[key];
29
+ return;
30
+ }
31
+ return chrome.storage.local.remove(key);
32
+ }
33
+
34
+ async function showOptOutStatus(tabId, status, cmp = '') {
35
+ let title = "";
36
+ let icon = "icons/cookie-idle.png";
37
+ if (status === "success") {
38
+ title = `Opt out successful! (${cmp})`;
39
+ icon = "icons/party.png";
40
+ }
41
+ else if (status === "complete") {
42
+ title = `Opt out complete! (${cmp})`;
43
+ icon = "icons/tick.png";
44
+ }
45
+ else if (status === "working") {
46
+ title = `Processing... (${cmp})`;
47
+ icon = "icons/cog.png";
48
+ }
49
+ else if (status === "verified") {
50
+ title = `Verified (${cmp})`;
51
+ icon = "icons/verified.png";
52
+ }
53
+ else if (status === "idle") {
54
+ title = "Idle";
55
+ icon = "icons/cookie-idle.png";
56
+ }
57
+ else if (status === "available") {
58
+ title = `Click to opt out (${cmp})`;
59
+ icon = "icons/cookie.png";
60
+ }
61
+ const action = chrome.action || chrome.pageAction;
62
+ if (chrome.pageAction) {
63
+ chrome.pageAction.show(tabId);
64
+ }
65
+ await action.setTitle({
66
+ tabId,
67
+ title,
68
+ });
69
+ await action.setIcon({
70
+ tabId,
71
+ path: icon,
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Mapping of tabIds to Port connections to open devtools panels.
77
+ * This is kept in memory, as the values are only relevant while the background service worker is
78
+ * alive. Once the service worker stops, Ports to devtools will be severed, and the panel will
79
+ * reestablish the connection.
80
+ */
81
+ const openDevToolsPanels = new Map();
82
+ async function loadRules() {
83
+ const res = await fetch("./rules.json");
84
+ storageSet({
85
+ rules: await res.json(),
86
+ });
87
+ }
88
+ async function initConfig() {
89
+ console.log('init sw');
90
+ const storedConfig = await storageGet('config');
91
+ console.log('storedConfig', storedConfig);
92
+ if (!storedConfig) {
93
+ console.log('init config');
94
+ const defaultConfig = {
95
+ enabled: true,
96
+ autoAction: 'optOut',
97
+ disabledCmps: [],
98
+ enablePrehide: true,
99
+ enableCosmeticRules: true,
100
+ detectRetries: 20,
101
+ };
102
+ await storageSet({
103
+ config: defaultConfig,
104
+ });
105
+ }
106
+ else if (typeof storedConfig.enableCosmeticRules === 'undefined') { // upgrade from old versions
107
+ storedConfig.enableCosmeticRules = true;
108
+ await storageSet({
109
+ config: storedConfig,
110
+ });
111
+ }
112
+ }
113
+ async function evalInTab(tabId, frameId, code) {
114
+ if (manifestVersion === 2) {
115
+ return new Promise((resolve) => {
116
+ chrome.tabs.executeScript(tabId, {
117
+ frameId,
118
+ code: `!!window.eval(decodeURIComponent("${encodeURIComponent(code)}"))`
119
+ }, (resultArr) => {
120
+ resolve([{
121
+ result: resultArr[0],
122
+ frameId,
123
+ }]);
124
+ });
125
+ });
126
+ }
127
+ return chrome.scripting.executeScript({
128
+ target: {
129
+ tabId,
130
+ frameIds: [frameId],
131
+ },
132
+ world: "MAIN",
133
+ args: [code],
134
+ func: (code) => {
135
+ try {
136
+ return window.eval(code);
137
+ }
138
+ catch (e) {
139
+ // ignore CSP errors
140
+ console.warn('eval error', code, e);
141
+ return;
142
+ }
143
+ },
144
+ });
145
+ }
146
+ async function getTabReports(tabId) {
147
+ const storageKey = `reports-${tabId}`;
148
+ return (await chrome.storage.session.get(storageKey))[storageKey] || {};
149
+ }
150
+ async function updateTabReports(tabId, frameId, msg) {
151
+ const reportsForTab = await getTabReports(tabId);
152
+ reportsForTab[frameId] = msg;
153
+ await chrome.storage.session.set({ [`reports-${tabId}`]: reportsForTab });
154
+ }
155
+ chrome.runtime.onInstalled.addListener(() => {
156
+ loadRules();
157
+ initConfig();
158
+ });
159
+ if (manifestVersion === 2) {
160
+ // always load rules on startup in MV2
161
+ loadRules();
162
+ initConfig();
163
+ }
164
+ chrome.tabs.onRemoved.addListener((tabId) => {
165
+ storageRemove(`detected${tabId}`);
166
+ });
167
+ chrome.runtime.onMessage.addListener(async (msg, sender) => {
168
+ const tabId = sender.tab.id;
169
+ const frameId = sender.frameId;
170
+ const rules = await storageGet("rules");
171
+ const autoconsentConfig = await storageGet('config');
172
+ switch (msg.type) {
173
+ case "init":
174
+ if (frameId === 0) {
175
+ await showOptOutStatus(tabId, 'idle');
176
+ }
177
+ chrome.tabs.sendMessage(tabId, {
178
+ type: "initResp",
179
+ rules,
180
+ config: autoconsentConfig,
181
+ }, {
182
+ frameId,
183
+ });
184
+ break;
185
+ case "eval":
186
+ evalInTab(tabId, frameId, msg.code).then(([result]) => {
187
+ chrome.tabs.sendMessage(tabId, {
188
+ id: msg.id,
189
+ type: "evalResp",
190
+ result: result.result,
191
+ }, {
192
+ frameId,
193
+ });
194
+ });
195
+ break;
196
+ case "popupFound":
197
+ await showOptOutStatus(tabId, "available", msg.cmp);
198
+ storageSet({
199
+ [`detected${tabId}`]: frameId,
200
+ });
201
+ break;
202
+ case "optOutResult":
203
+ case "optInResult":
204
+ if (msg.result) {
205
+ await showOptOutStatus(tabId, "working", msg.cmp);
206
+ if (msg.scheduleSelfTest) {
207
+ await storageSet({
208
+ [`selfTest${tabId}`]: frameId,
209
+ });
210
+ }
211
+ }
212
+ break;
213
+ case "selfTestResult":
214
+ if (msg.result) {
215
+ await showOptOutStatus(tabId, "verified", msg.cmp);
216
+ }
217
+ break;
218
+ case "autoconsentDone": {
219
+ await showOptOutStatus(tabId, "success", msg.cmp);
220
+ // sometimes self-test needs to be done in another frame
221
+ const selfTestKey = `selfTest${tabId}`;
222
+ const selfTestFrameId = (await chrome.storage.local.get(selfTestKey))?.[selfTestKey];
223
+ if (typeof selfTestFrameId === 'number') {
224
+ storageRemove(selfTestKey);
225
+ chrome.tabs.sendMessage(tabId, {
226
+ type: "selfTest",
227
+ }, {
228
+ frameId: selfTestFrameId,
229
+ });
230
+ }
231
+ break;
232
+ }
233
+ case "autoconsentError":
234
+ console.error('Error:', msg.details);
235
+ break;
236
+ case "report":
237
+ if (sender.tab && openDevToolsPanels.has(sender.tab.id)) {
238
+ openDevToolsPanels.get(sender.tab.id).postMessage({
239
+ tabId: sender.tab.id,
240
+ frameId: sender.frameId,
241
+ ...msg,
242
+ });
243
+ }
244
+ updateTabReports(sender.tab.id, sender.frameId, msg);
245
+ break;
246
+ }
247
+ });
248
+ if (manifestVersion === 2) { // MV3 handles this inside the popup
249
+ chrome.pageAction.onClicked.addListener(async (tab) => {
250
+ const tabId = tab.id;
251
+ const detectedKey = `detected${tabId}`;
252
+ const frameId = await storageGet(detectedKey);
253
+ if (typeof frameId === 'number') {
254
+ storageRemove(detectedKey);
255
+ await showOptOutStatus(tabId, "working");
256
+ chrome.tabs.sendMessage(tabId, {
257
+ type: "optOut",
258
+ }, {
259
+ frameId,
260
+ });
261
+ }
262
+ });
263
+ }
264
+ // Communicate with devtools panels
265
+ chrome.runtime.onConnect.addListener(function (devToolsConnection) {
266
+ if (devToolsConnection.name.startsWith('instance-')) {
267
+ // connection from an autoconsent instance - used to detect frame destruction
268
+ const tabId = devToolsConnection.sender?.tab?.id;
269
+ const instanceId = devToolsConnection.name.slice('instance-'.length);
270
+ if (tabId && instanceId) {
271
+ devToolsConnection.onDisconnect.addListener(() => {
272
+ if (openDevToolsPanels.has(tabId)) {
273
+ openDevToolsPanels.get(tabId).postMessage({
274
+ type: 'instanceTerminated',
275
+ tabId,
276
+ instanceId,
277
+ });
278
+ }
279
+ // remove stored frame data
280
+ updateTabReports(tabId, devToolsConnection.sender.frameId, undefined);
281
+ });
282
+ }
283
+ }
284
+ else if (devToolsConnection.name === 'devtools-panel') {
285
+ let tabId = -1;
286
+ // add the listener
287
+ devToolsConnection.onMessage.addListener(async (message) => {
288
+ tabId = message.tabId;
289
+ if (message.type === 'init') {
290
+ // save the message channel for this tab
291
+ openDevToolsPanels.set(tabId, devToolsConnection);
292
+ // dump data cached in bg to the panel
293
+ const reportsForTab = await getTabReports(tabId);
294
+ Object.keys(reportsForTab || {}).forEach((frameId) => {
295
+ devToolsConnection.postMessage({
296
+ tabId,
297
+ frameId,
298
+ ...reportsForTab[parseInt(frameId, 10)]
299
+ });
300
+ });
301
+ }
302
+ });
303
+ devToolsConnection.onDisconnect.addListener(function () {
304
+ openDevToolsPanels.delete(tabId);
305
+ });
306
+ }
307
+ });
308
+ chrome.tabs.onRemoved.addListener((tabId) => {
309
+ chrome.storage.session.remove(`reports-${tabId}`);
310
+ });
311
+
312
+ })();