@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.
@@ -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