@duckduckgo/autoconsent 15.0.0 → 15.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # v15.1.0 (Tue Jun 23 2026)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - Test extension improvements [#1372](https://github.com/duckduckgo/autoconsent/pull/1372) ([@muodov](https://github.com/muodov))
6
+
7
+ #### Authors: 1
8
+
9
+ - Maxim Tsoy ([@muodov](https://github.com/muodov))
10
+
11
+ ---
12
+
1
13
  # v15.0.0 (Sat Jun 20 2026)
2
14
 
3
15
  #### 💥 Breaking Change
package/build.sh CHANGED
@@ -4,6 +4,7 @@ set -ex
4
4
  ESBUILD="node_modules/.bin/esbuild --bundle"
5
5
 
6
6
  $ESBUILD --format=iife --target=es2021 playwright/content.ts --outfile=dist/autoconsent.playwright.js
7
+ $ESBUILD --format=iife --target=es2021 standalone/content.ts --outfile=dist/autoconsent.standalone.js
7
8
  $ESBUILD --format=esm --target=es2021 lib/web-extra.ts --outfile=dist/autoconsent.extra.esm.js
8
9
  $ESBUILD --format=cjs --target=es2021 --platform=node lib/web-extra.ts --outfile=dist/autoconsent.extra.cjs.js
9
10
  $ESBUILD --format=esm --target=es2021 lib/web.ts --outfile=dist/autoconsent.esm.js
@@ -25,8 +26,10 @@ cp -r addon/icons dist/addon-mv3/
25
26
  cp -r addon/icons dist/addon-firefox/
26
27
  cp rules/rules.json dist/addon-mv3/
27
28
  cp rules/compact-rules.json dist/addon-mv3/
29
+ cp rules/rule-index.json dist/addon-mv3/
28
30
  cp rules/rules.json dist/addon-firefox/
29
31
  cp rules/compact-rules.json dist/addon-firefox/
32
+ cp rules/rule-index.json dist/addon-firefox/
30
33
  cp addon/popup.html dist/addon-mv3/
31
34
  cp addon/popup.html dist/addon-firefox/
32
35
  cp -r addon/devtools dist/addon-mv3/
@@ -434,6 +434,9 @@
434
434
  sendContentMessage: null
435
435
  };
436
436
  function requestEval(code, snippetId) {
437
+ if (!evalState.sendContentMessage) {
438
+ return Promise.reject(new Error("AutoConsent is not initialized yet"));
439
+ }
437
440
  const id = getRandomID();
438
441
  evalState.sendContentMessage({
439
442
  type: "eval",
@@ -736,12 +739,13 @@
736
739
  }
737
740
  }
738
741
  function highlightNode(node) {
742
+ const highlightedNode = node;
739
743
  if (!node.style) return;
740
- if (node.__oldStyles !== void 0) {
744
+ if (highlightedNode.__oldStyles !== void 0) {
741
745
  return;
742
746
  }
743
747
  if (node.hasAttribute("style")) {
744
- node.__oldStyles = node.style.cssText;
748
+ highlightedNode.__oldStyles = node.style.cssText;
745
749
  }
746
750
  node.style.animation = "pulsate .5s infinite";
747
751
  node.style.outline = "solid red";
@@ -769,10 +773,11 @@
769
773
  document.head.appendChild(styleTag);
770
774
  }
771
775
  function unhighlightNode(node) {
776
+ const highlightedNode = node;
772
777
  if (!node.style || !node.hasAttribute("style")) return;
773
- if (node.__oldStyles !== void 0) {
774
- node.style.cssText = node.__oldStyles;
775
- delete node.__oldStyles;
778
+ if (highlightedNode.__oldStyles !== void 0) {
779
+ node.style.cssText = highlightedNode.__oldStyles;
780
+ delete highlightedNode.__oldStyles;
776
781
  } else {
777
782
  node.removeAttribute("style");
778
783
  }
@@ -2818,7 +2823,7 @@
2818
2823
  async optOut() {
2819
2824
  await this.waitForElement(".unic button", 1e3);
2820
2825
  document.querySelectorAll(".unic button").forEach((button) => {
2821
- const text = button.textContent;
2826
+ const text = button.textContent || "";
2822
2827
  if (text.includes("Manage Options") || text.includes("Optionen verwalten")) {
2823
2828
  button.click();
2824
2829
  }
@@ -2831,7 +2836,7 @@
2831
2836
  }
2832
2837
  });
2833
2838
  for (const b of document.querySelectorAll(".unic button")) {
2834
- const text = b.textContent;
2839
+ const text = b.textContent || "";
2835
2840
  for (const pattern of ["Confirm Choices", "Save Choices", "Auswahl speichern"]) {
2836
2841
  if (text.includes(pattern)) {
2837
2842
  b.click();
@@ -2888,6 +2893,9 @@
2888
2893
  item.querySelector(".cmp-accordion-item-title").click();
2889
2894
  await waitFor(() => !!item.querySelector(".cmp-accordion-item-content.cmp-active"), 10, 50);
2890
2895
  const content = item.querySelector(".cmp-accordion-item-content.cmp-active");
2896
+ if (!content) {
2897
+ return false;
2898
+ }
2891
2899
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e) => e.click());
2892
2900
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e) => e.click());
2893
2901
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.6.19",
4
+ "version": "2026.6.20",
5
5
  "background": {
6
6
  "scripts": [
7
7
  "background.bundle.js"
@@ -913,6 +913,7 @@
913
913
  }
914
914
 
915
915
  // addon/popup.ts
916
+ var maxRenderedRules = 100;
916
917
  async function init() {
917
918
  const autoconsentConfig = await storageGet("config");
918
919
  const enabledCheckbox = document.querySelector("input#enabled");
@@ -926,8 +927,7 @@
926
927
  const cosmeticOffRadio = document.querySelector("input#cosmetic-off");
927
928
  const generatedOnRadio = document.querySelector("input#generated-on");
928
929
  const generatedOffRadio = document.querySelector("input#generated-off");
929
- const heuristicActionOnRadio = document.querySelector("input#heuristic-action-on");
930
- const heuristicActionOffRadio = document.querySelector("input#heuristic-action-off");
930
+ const heuristicActionCheckbox = document.querySelector("input#heuristic-action");
931
931
  const visualTestOnRadio = document.querySelector("input#visual-test-on");
932
932
  const visualTestOffRadio = document.querySelector("input#visual-test-off");
933
933
  const popupMutationOnRadio = document.querySelector("input#popup-mutation-on");
@@ -942,6 +942,47 @@
942
942
  const logsMessagesCheckbox = document.querySelector("input#logs-messages");
943
943
  const ruleReloadButton = document.querySelector("#reload");
944
944
  const resetButton = document.querySelector("#reset");
945
+ const ruleSectionControls = {
946
+ generated: {
947
+ details: document.querySelector("#generated-rules-section"),
948
+ count: document.querySelector("#generated-rules-count"),
949
+ search: document.querySelector("#generated-rules-search"),
950
+ enableAll: document.querySelector("#generated-rules-enable-all"),
951
+ disableAll: document.querySelector("#generated-rules-disable-all"),
952
+ status: document.querySelector("#generated-rules-status"),
953
+ list: document.querySelector("#generated-rules-list")
954
+ },
955
+ generic: {
956
+ details: document.querySelector("#generic-rules-section"),
957
+ count: document.querySelector("#generic-rules-count"),
958
+ search: document.querySelector("#generic-rules-search"),
959
+ enableAll: document.querySelector("#generic-rules-enable-all"),
960
+ disableAll: document.querySelector("#generic-rules-disable-all"),
961
+ status: document.querySelector("#generic-rules-status"),
962
+ list: document.querySelector("#generic-rules-list")
963
+ },
964
+ code: {
965
+ details: document.querySelector("#code-rules-section"),
966
+ count: document.querySelector("#code-rules-count"),
967
+ search: document.querySelector("#code-rules-search"),
968
+ enableAll: document.querySelector("#code-rules-enable-all"),
969
+ disableAll: document.querySelector("#code-rules-disable-all"),
970
+ status: document.querySelector("#code-rules-status"),
971
+ list: document.querySelector("#code-rules-list")
972
+ },
973
+ consentomatic: {
974
+ details: document.querySelector("#consentomatic-rules-section"),
975
+ count: document.querySelector("#consentomatic-rules-count"),
976
+ search: document.querySelector("#consentomatic-rules-search"),
977
+ enableAll: document.querySelector("#consentomatic-rules-enable-all"),
978
+ disableAll: document.querySelector("#consentomatic-rules-disable-all"),
979
+ status: document.querySelector("#consentomatic-rules-status"),
980
+ list: document.querySelector("#consentomatic-rules-list")
981
+ }
982
+ };
983
+ let ruleIndexPromise = null;
984
+ let ruleIndex = null;
985
+ autoconsentConfig.disabledCmps = autoconsentConfig.disabledCmps || [];
945
986
  const [currentTab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
946
987
  if (!currentTab.url || !currentTab.id) {
947
988
  console.error("Current tab is not valid");
@@ -1007,11 +1048,7 @@
1007
1048
  } else {
1008
1049
  generatedOffRadio.checked = true;
1009
1050
  }
1010
- if (autoconsentConfig.enableHeuristicAction) {
1011
- heuristicActionOnRadio.checked = true;
1012
- } else {
1013
- heuristicActionOffRadio.checked = true;
1014
- }
1051
+ heuristicActionCheckbox.checked = autoconsentConfig.enableHeuristicAction;
1015
1052
  if (autoconsentConfig.visualTest) {
1016
1053
  visualTestOnRadio.checked = true;
1017
1054
  } else {
@@ -1061,11 +1098,10 @@
1061
1098
  generatedOnRadio.addEventListener("change", generatedChange);
1062
1099
  generatedOffRadio.addEventListener("change", generatedChange);
1063
1100
  function heuristicActionChange() {
1064
- autoconsentConfig.enableHeuristicAction = heuristicActionOnRadio.checked;
1101
+ autoconsentConfig.enableHeuristicAction = heuristicActionCheckbox.checked;
1065
1102
  storageSet({ config: autoconsentConfig });
1066
1103
  }
1067
- heuristicActionOnRadio.addEventListener("change", heuristicActionChange);
1068
- heuristicActionOffRadio.addEventListener("change", heuristicActionChange);
1104
+ heuristicActionCheckbox.addEventListener("change", heuristicActionChange);
1069
1105
  function visualTestChange() {
1070
1106
  autoconsentConfig.visualTest = visualTestOnRadio.checked;
1071
1107
  storageSet({ config: autoconsentConfig });
@@ -1111,10 +1147,141 @@
1111
1147
  logsMessagesCheckbox.addEventListener("change", () => {
1112
1148
  updateLogsConfig();
1113
1149
  });
1150
+ async function loadRuleIndex() {
1151
+ if (!ruleIndexPromise) {
1152
+ ruleIndexPromise = fetch("./rule-index.json").then(async (res) => await res.json());
1153
+ }
1154
+ ruleIndex = await ruleIndexPromise;
1155
+ updateRuleCounts();
1156
+ return ruleIndex;
1157
+ }
1158
+ function updateRuleCounts() {
1159
+ if (!ruleIndex) {
1160
+ return;
1161
+ }
1162
+ const disabledCmps = autoconsentConfig.disabledCmps || [];
1163
+ Object.keys(ruleSectionControls).forEach((section) => {
1164
+ const sectionRules = ruleIndex?.filter((rule) => rule.section === section) || [];
1165
+ const disabledInSection = sectionRules.filter((rule) => disabledCmps.includes(rule.name)).length;
1166
+ const count = ruleSectionControls[section].count;
1167
+ count.textContent = `(${disabledInSection}/${sectionRules.length} disabled)`;
1168
+ count.classList.toggle("rule-count-disabled", disabledInSection > 0);
1169
+ });
1170
+ }
1171
+ function setRuleEnabled(ruleName, enabled) {
1172
+ const disabledCmps = new Set(autoconsentConfig.disabledCmps || []);
1173
+ if (enabled) {
1174
+ disabledCmps.delete(ruleName);
1175
+ } else {
1176
+ disabledCmps.add(ruleName);
1177
+ }
1178
+ autoconsentConfig.disabledCmps = Array.from(disabledCmps).sort();
1179
+ storageSet({ config: autoconsentConfig });
1180
+ updateRuleCounts();
1181
+ }
1182
+ function renderOpenRuleSections() {
1183
+ Object.keys(ruleSectionControls).forEach((section) => {
1184
+ if (ruleSectionControls[section].details.open) {
1185
+ renderRuleSection(section);
1186
+ }
1187
+ });
1188
+ }
1189
+ function setRuleSectionEnabled(section, enabled) {
1190
+ if (!ruleIndex) {
1191
+ return;
1192
+ }
1193
+ const disabledCmps = new Set(autoconsentConfig.disabledCmps || []);
1194
+ ruleIndex.filter((rule) => rule.section === section).forEach((rule) => {
1195
+ if (enabled) {
1196
+ disabledCmps.delete(rule.name);
1197
+ } else {
1198
+ disabledCmps.add(rule.name);
1199
+ }
1200
+ });
1201
+ autoconsentConfig.disabledCmps = Array.from(disabledCmps).sort();
1202
+ storageSet({ config: autoconsentConfig });
1203
+ updateRuleCounts();
1204
+ renderOpenRuleSections();
1205
+ }
1206
+ function renderRuleSection(section) {
1207
+ const controls = ruleSectionControls[section];
1208
+ if (!ruleIndex) {
1209
+ controls.status.textContent = "Rules are not loaded yet.";
1210
+ return;
1211
+ }
1212
+ const disabledCmps = autoconsentConfig.disabledCmps || [];
1213
+ const searchQuery = controls.search.value.trim().toLowerCase();
1214
+ const sectionRules = ruleIndex.filter((rule) => rule.section === section);
1215
+ const matchingRules = sectionRules.filter((rule) => {
1216
+ if (!searchQuery) {
1217
+ return disabledCmps.includes(rule.name);
1218
+ }
1219
+ return rule.name.toLowerCase().includes(searchQuery) || !!rule.urlPattern?.toLowerCase().includes(searchQuery);
1220
+ });
1221
+ const renderedRules = matchingRules.slice(0, maxRenderedRules);
1222
+ const disabledInSection = sectionRules.filter((rule) => disabledCmps.includes(rule.name)).length;
1223
+ controls.status.textContent = searchQuery ? `Showing ${renderedRules.length} of ${matchingRules.length} matches. ${disabledInSection} disabled.` : `Showing ${renderedRules.length} of ${disabledInSection} disabled rules. Search to find enabled rules.`;
1224
+ controls.list.replaceChildren(
1225
+ ...renderedRules.map((rule) => {
1226
+ const checkbox = document.createElement("input");
1227
+ checkbox.type = "checkbox";
1228
+ checkbox.checked = !disabledCmps.includes(rule.name);
1229
+ checkbox.addEventListener("change", () => {
1230
+ setRuleEnabled(rule.name, checkbox.checked);
1231
+ renderRuleSection(section);
1232
+ });
1233
+ const label = document.createElement("label");
1234
+ label.className = "rule-row";
1235
+ label.title = rule.urlPattern ? `${rule.name}
1236
+ ${rule.urlPattern}` : rule.name;
1237
+ label.append(checkbox, document.createTextNode(` ${rule.name}${rule.cosmetic ? " (cosmetic)" : ""}`));
1238
+ return label;
1239
+ })
1240
+ );
1241
+ }
1242
+ async function loadAndRenderRuleSection(section) {
1243
+ ruleSectionControls[section].status.textContent = "Loading rules...";
1244
+ try {
1245
+ await loadRuleIndex();
1246
+ renderRuleSection(section);
1247
+ } catch (e) {
1248
+ console.error("Failed to load rule index", e);
1249
+ ruleSectionControls[section].status.textContent = "Failed to load rules.";
1250
+ }
1251
+ }
1252
+ Object.keys(ruleSectionControls).forEach((section) => {
1253
+ const controls = ruleSectionControls[section];
1254
+ controls.details.addEventListener("toggle", () => {
1255
+ if (controls.details.open) {
1256
+ loadAndRenderRuleSection(section);
1257
+ }
1258
+ });
1259
+ controls.search.addEventListener("input", () => renderRuleSection(section));
1260
+ controls.enableAll.addEventListener("click", async () => {
1261
+ await loadRuleIndex();
1262
+ setRuleSectionEnabled(section, true);
1263
+ });
1264
+ controls.disableAll.addEventListener("click", async () => {
1265
+ await loadRuleIndex();
1266
+ setRuleSectionEnabled(section, false);
1267
+ });
1268
+ });
1269
+ loadRuleIndex().catch((e) => {
1270
+ console.error("Failed to load rule index", e);
1271
+ });
1114
1272
  ruleReloadButton.addEventListener("click", async () => {
1115
- const res = await fetch("./rules.json");
1116
- storageSet({
1117
- rules: await res.json()
1273
+ const [compactRulesRes, fullRulesRes] = await Promise.all([fetch("./compact-rules.json"), fetch("./rules.json")]);
1274
+ const fullRules = await fullRulesRes.json();
1275
+ await storageSet({
1276
+ rules: await compactRulesRes.json(),
1277
+ fullRules: fullRules.autoconsent
1278
+ });
1279
+ ruleIndexPromise = null;
1280
+ ruleIndex = null;
1281
+ Object.keys(ruleSectionControls).forEach((section) => {
1282
+ if (ruleSectionControls[section].details.open) {
1283
+ loadAndRenderRuleSection(section);
1284
+ }
1118
1285
  });
1119
1286
  });
1120
1287
  resetButton.addEventListener("click", async () => {
@@ -23,6 +23,52 @@
23
23
  cursor: pointer;
24
24
  border-radius: 2px;
25
25
  }
26
+
27
+ .rule-section {
28
+ margin: 0.5em 0;
29
+ }
30
+
31
+ .rule-search {
32
+ box-sizing: border-box;
33
+ margin: 0.5em 0;
34
+ width: 100%;
35
+ }
36
+
37
+ .rule-actions {
38
+ display: flex;
39
+ gap: 0.5em;
40
+ margin: 0.5em 0;
41
+ }
42
+
43
+ .rule-actions button {
44
+ flex: 1;
45
+ }
46
+
47
+ .rule-list {
48
+ border: 1px solid #ddd;
49
+ max-height: 12em;
50
+ overflow-y: auto;
51
+ padding: 0.25em;
52
+ }
53
+
54
+ .rule-row {
55
+ display: block;
56
+ overflow: hidden;
57
+ padding: 0.15em 0;
58
+ text-overflow: ellipsis;
59
+ white-space: nowrap;
60
+ }
61
+
62
+ .rule-list-status {
63
+ color: #666;
64
+ font-size: 0.9em;
65
+ margin: 0.25em 0;
66
+ }
67
+
68
+ .rule-count-disabled {
69
+ color: #b00020;
70
+ font-weight: bold;
71
+ }
26
72
  </style>
27
73
  </head>
28
74
 
@@ -96,16 +142,55 @@
96
142
  </fieldset>
97
143
 
98
144
  <fieldset>
99
- <legend>Handle unknown popups (beta)</legend>
145
+ <legend>Rules</legend>
100
146
 
101
- <div>
102
- <input type="radio" id="heuristic-action-on" name="heuristic-action" value="true" />
103
- <label for="heuristic-action-on">On</label>
104
- </div>
147
+ <details class="rule-section" id="generated-rules-section">
148
+ <summary>Generated rules <span id="generated-rules-count"></span></summary>
149
+ <input class="rule-search" type="search" id="generated-rules-search" placeholder="Search generated rules" />
150
+ <div class="rule-actions">
151
+ <button id="generated-rules-enable-all">Enable all</button>
152
+ <button id="generated-rules-disable-all">Disable all</button>
153
+ </div>
154
+ <div class="rule-list-status" id="generated-rules-status"></div>
155
+ <div class="rule-list" id="generated-rules-list"></div>
156
+ </details>
105
157
 
106
- <div>
107
- <input type="radio" id="heuristic-action-off" name="heuristic-action" value="false" checked />
108
- <label for="heuristic-action-off">Off</label>
158
+ <details class="rule-section" id="generic-rules-section">
159
+ <summary>Generic rules <span id="generic-rules-count"></span></summary>
160
+ <input class="rule-search" type="search" id="generic-rules-search" placeholder="Search generic rules" />
161
+ <div class="rule-actions">
162
+ <button id="generic-rules-enable-all">Enable all</button>
163
+ <button id="generic-rules-disable-all">Disable all</button>
164
+ </div>
165
+ <div class="rule-list-status" id="generic-rules-status"></div>
166
+ <div class="rule-list" id="generic-rules-list"></div>
167
+ </details>
168
+
169
+ <details class="rule-section" id="code-rules-section">
170
+ <summary>Code-based rules <span id="code-rules-count"></span></summary>
171
+ <input class="rule-search" type="search" id="code-rules-search" placeholder="Search code-based rules" />
172
+ <div class="rule-actions">
173
+ <button id="code-rules-enable-all">Enable all</button>
174
+ <button id="code-rules-disable-all">Disable all</button>
175
+ </div>
176
+ <div class="rule-list-status" id="code-rules-status"></div>
177
+ <div class="rule-list" id="code-rules-list"></div>
178
+ </details>
179
+
180
+ <details class="rule-section" id="consentomatic-rules-section">
181
+ <summary>Consent-O-Matic rules <span id="consentomatic-rules-count"></span></summary>
182
+ <input class="rule-search" type="search" id="consentomatic-rules-search" placeholder="Search Consent-O-Matic rules" />
183
+ <div class="rule-actions">
184
+ <button id="consentomatic-rules-enable-all">Enable all</button>
185
+ <button id="consentomatic-rules-disable-all">Disable all</button>
186
+ </div>
187
+ <div class="rule-list-status" id="consentomatic-rules-status"></div>
188
+ <div class="rule-list" id="consentomatic-rules-list"></div>
189
+ </details>
190
+
191
+ <div class="rule-section">
192
+ <input type="checkbox" id="heuristic-action" name="heuristic-action" />
193
+ <label for="heuristic-action">Handle unknown popups (heuristic rule)</label>
109
194
  </div>
110
195
  </fieldset>
111
196