Skip to content

Commit c17c513

Browse files
committed
Hardened popup page and improved accessibility
1 parent 93b7938 commit c17c513

File tree

2 files changed

+82
-91
lines changed

2 files changed

+82
-91
lines changed

src/main/pages/popup/PopupPage.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ <h3 id="popupTitle" class="white">
4848
</h3>
4949
<div class="pagination">
5050
<span id="prevPage" class="arrow" tabindex="0">&#9664;</span>
51-
<span id="pageIndicator" class="page-indicator"></span>
51+
<span id="pageIndicator" class="page-indicator" aria-live="polite"></span>
5252
<span id="nextPage" class="arrow" tabindex="0">&#9654;</span>
5353
</div>
5454
</div>
@@ -58,7 +58,7 @@ <h3 id="popupTitle" class="white">
5858
<span class="starSymbol">&#9733;</span>
5959
<img class="adGuardLogo" src="../../assets/images/adguard.png" width="16" height="16" alt="AdGuard Logo">
6060
<a class="partnerLabel" href="https://adguard-dns.io/?utm_source=osprey" target="_blank"
61-
rel="noopener noreferrer" referrerpolicy="no-referrer">AdGuard Security DNS</a>
61+
rel="noopener noreferrer">AdGuard Security DNS</a>
6262
<div id="adGuardSecurityDivSwitch">
6363
<span id="adGuardSecuritySwitch" class="switch on leftMargin" role="switch" aria-checked="true"
6464
tabindex="0"></span>
@@ -69,7 +69,7 @@ <h3 id="popupTitle" class="white">
6969
<span class="starSymbol">&#9733;</span>
7070
<img class="adGuardLogo" src="../../assets/images/adguard.png" width="16" height="16" alt="AdGuard Logo">
7171
<a class="partnerLabel" href="https://adguard-dns.io/?utm_source=osprey" target="_blank"
72-
rel="noopener noreferrer" referrerpolicy="no-referrer">AdGuard Family DNS</a>
72+
rel="noopener noreferrer">AdGuard Family DNS</a>
7373
<div id="adGuardFamilyDivSwitch">
7474
<span id="adGuardFamilySwitch" class="switch on leftMargin" role="switch" aria-checked="true"
7575
tabindex="0"></span>
@@ -81,7 +81,7 @@ <h3 id="popupTitle" class="white">
8181
<img class="alphaMountainLogo" src="../../assets/images/alphamountain.png" width="16" height="16"
8282
alt="alphaMountain Logo">
8383
<a class="partnerLabel" href="https://alphamountain.ai/?utm_source=osprey" target="_blank"
84-
rel="noopener noreferrer" referrerpolicy="no-referrer">alphaMountain Web Protection</a>
84+
rel="noopener noreferrer">alphaMountain Web Protection</a>
8585
<div id="alphaMountainDivSwitch">
8686
<span id="alphaMountainSwitch" class="switch on leftMargin" role="switch" aria-checked="true"
8787
tabindex="0"></span>
@@ -93,7 +93,7 @@ <h3 id="popupTitle" class="white">
9393
<img class="precisionSecLogo" src="../../assets/images/precisionsec.png" width="16" height="16"
9494
alt="PrecisionSec Logo">
9595
<a class="partnerLabel" href="https://precisionsec.com/?utm_source=osprey" target="_blank"
96-
rel="noopener noreferrer" referrerpolicy="no-referrer">PrecisionSec Web Protection</a>
96+
rel="noopener noreferrer">PrecisionSec Web Protection</a>
9797
<div id="precisionSecDivSwitch">
9898
<span id="precisionSecSwitch" class="switch on leftMargin" role="switch" aria-checked="true"
9999
tabindex="0"></span>
@@ -210,16 +210,16 @@ <h3 id="popupTitle" class="white">
210210

211211
<div class="links">
212212
<a id="githubLink" href="https://osprey.ac" target="_blank"
213-
rel="noopener noreferrer" referrerpolicy="no-referrer">
213+
rel="noopener noreferrer">
214214
<!-- Automatically translated -->
215215
</a>
216-
<a>|</a>
216+
<span aria-hidden="true"> | </span>
217217
<a id="version">
218218
<!-- Automatically translated -->
219219
</a>
220-
<a>|</a>
220+
<span aria-hidden="true"> | </span>
221221
<a id="privacyPolicy" href="https://osprey.ac/privacy" target="_blank"
222-
rel="noopener noreferrer" referrerpolicy="no-referrer">
222+
rel="noopener noreferrer">
223223
<!-- Automatically translated -->
224224
</a>
225225
</div>

src/main/pages/popup/PopupPage.js

Lines changed: 73 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
4545
const makeSystem = (origin, name, labelElementId, switchElementId, messageType) => Object.freeze({
4646
origin,
4747
name,
48-
title: ProtectionResult.FullName[origin],
4948
labelElementId,
5049
switchElementId,
5150
messageType,
@@ -70,9 +69,6 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
7069
makeSystem(ProtectionResult.Origin.SWITCH_CH, "switchCHEnabled", "switchCHStatus", "switchCHSwitch", Messages.SWITCH_CH_TOGGLED),
7170
]);
7271

73-
// Cached manifest data
74-
const manifest = browserAPI.runtime.getManifest();
75-
7672
/**
7773
* Gets DOM elements for a system, caching them for future use.
7874
*
@@ -109,40 +105,29 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
109105
return;
110106
}
111107

112-
const updates = [];
113-
114-
updates.push(() => {
115-
if (elements.label) {
116-
if (isLocked) {
117-
elements.label.textContent = isOn ? LangUtil.ON_LOCKED_TEXT : LangUtil.OFF_LOCKED_TEXT;
118-
} else {
119-
elements.label.textContent = isOn ? LangUtil.ON_TEXT : LangUtil.OFF_TEXT;
120-
}
108+
if (elements.label) {
109+
if (isLocked) {
110+
elements.label.textContent = isOn ? LangUtil.ON_LOCKED_TEXT : LangUtil.OFF_LOCKED_TEXT;
121111
} else {
122-
console.warn(`'label' element not found for ${system.name} in the PopupPage DOM.`);
112+
elements.label.textContent = isOn ? LangUtil.ON_TEXT : LangUtil.OFF_TEXT;
123113
}
114+
} else {
115+
console.warn(`'label' element not found for ${system.name} in the PopupPage DOM.`);
116+
}
124117

125-
if (elements.switchElement) {
126-
if (isOn) {
127-
elements.switchElement.classList.add("on");
128-
elements.switchElement.classList.remove("off");
129-
elements.switchElement.setAttribute("aria-checked", "true");
130-
} else {
131-
elements.switchElement.classList.remove("on");
132-
elements.switchElement.classList.add("off");
133-
elements.switchElement.setAttribute("aria-checked", "false");
134-
}
118+
if (elements.switchElement) {
119+
if (isOn) {
120+
elements.switchElement.classList.add("on");
121+
elements.switchElement.classList.remove("off");
122+
elements.switchElement.setAttribute("aria-checked", "true");
135123
} else {
136-
console.warn(`'switchElement' not found for ${system.name} in the PopupPage DOM.`);
137-
}
138-
});
139-
140-
// Batches the DOM updates for performance
141-
globalThis.requestAnimationFrame(() => {
142-
for (const update of updates) {
143-
update();
124+
elements.switchElement.classList.remove("on");
125+
elements.switchElement.classList.add("off");
126+
elements.switchElement.setAttribute("aria-checked", "false");
144127
}
145-
});
128+
} else {
129+
console.warn(`'switchElement' not found for ${system.name} in the PopupPage DOM.`);
130+
}
146131
};
147132

148133
/**
@@ -152,18 +137,34 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
152137
*/
153138
const toggleProtection = system => {
154139
Settings.get(settings => {
155-
const currentState = settings[system.name];
156-
const newState = !currentState;
140+
if (!settings || typeof settings !== 'object') {
141+
console.error(`PopupPage: Settings.get returned invalid settings in toggleProtection for ${system.name}; aborting toggle.`);
142+
return;
143+
}
144+
145+
const newState = !settings[system.name];
157146

158147
Settings.set({[system.name]: newState}, () => {
159-
updateProtectionStatusUI(system, newState, settings.lockProtectionOptions);
160-
161-
browserAPI.runtime.sendMessage({
162-
messageType: system.messageType,
163-
title: ProtectionResult.FullName[system.origin],
164-
toggleState: newState,
165-
}).catch(error => {
166-
console.error(`Failed to send message for ${system.name}:`, error);
148+
Settings.get(verified => {
149+
if (!verified || typeof verified !== 'object') {
150+
console.error(`PopupPage: Could not verify settings write for ${system.name}; aborting UI update.`);
151+
return;
152+
}
153+
154+
if (verified[system.name] !== newState) {
155+
console.error(`PopupPage: Settings write verification failed for ${system.name}; expected ${newState}, got ${verified[system.name]}.`);
156+
return;
157+
}
158+
159+
updateProtectionStatusUI(system, newState, verified.lockProtectionOptions);
160+
161+
browserAPI.runtime.sendMessage({
162+
messageType: system.messageType,
163+
title: ProtectionResult.FullName[system.origin],
164+
toggleState: newState,
165+
}).catch(error => {
166+
console.error(`Failed to send message for ${system.name}:`, error);
167+
});
167168
});
168169
});
169170
});
@@ -201,17 +202,17 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
201202
* Initializes the popup or refresh if already initialized.
202203
*/
203204
const initialize = () => {
205+
// If already initialized, reset first
206+
if (isInitialized) {
207+
reset();
208+
}
209+
204210
// Initializes the DOM element cache
205211
domElements = Object.fromEntries(
206212
["popupTitle", "githubLink", "version", "privacyPolicy", "logo", "prevPage", "nextPage", "pageIndicator"]
207213
.map(id => [id, document.getElementById(id)])
208214
);
209215

210-
// If already initialized, reset first
211-
if (isInitialized) {
212-
reset();
213-
}
214-
215216
// Marks initialized as true
216217
isInitialized = true;
217218

@@ -222,11 +223,7 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
222223
const bannerText = document.querySelector('.bannerText');
223224

224225
// Sets the document title text
225-
if (document.title) {
226-
document.title = LangUtil.TITLE;
227-
} else {
228-
console.warn("Document title not found in the PopupPage DOM.");
229-
}
226+
document.title = LangUtil.TITLE;
230227

231228
// Sets the banner text
232229
if (bannerText) {
@@ -236,9 +233,10 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
236233
}
237234

238235
// Sets titles and aria-labels for star symbols and partner labels
236+
const officialPartnerTitle = LangUtil.OFFICIAL_PARTNER_TITLE;
239237
for (const element of document.querySelectorAll('.starSymbol, .partnerLabel')) {
240-
element.setAttribute('title', LangUtil.OFFICIAL_PARTNER_TITLE);
241-
element.setAttribute('aria-label', LangUtil.OFFICIAL_PARTNER_TITLE);
238+
element.setAttribute('title', officialPartnerTitle);
239+
element.setAttribute('aria-label', officialPartnerTitle);
242240
}
243241

244242
// Sets the alt text for the Osprey logo
@@ -262,13 +260,6 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
262260
console.warn("'githubLink' element not found in the PopupPage DOM.");
263261
}
264262

265-
// Sets the version text
266-
if (domElements.version) {
267-
domElements.version.textContent = LangUtil.VERSION;
268-
} else {
269-
console.warn("'version' element not found in the PopupPage DOM.");
270-
}
271-
272263
// Sets the Privacy Policy text
273264
if (domElements.privacyPolicy) {
274265
domElements.privacyPolicy.textContent = LangUtil.PRIVACY_POLICY;
@@ -291,6 +282,11 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
291282
if (elements.switchElement) {
292283
elements.switchElement.onclick = () => {
293284
Settings.get(settings => {
285+
if (!settings || typeof settings !== 'object') {
286+
console.error("PopupPage: Settings.get returned invalid settings in switch handler; aborting toggle.");
287+
return;
288+
}
289+
294290
if (settings.lockProtectionOptions) {
295291
console.debug("Protections are locked; cannot toggle.");
296292
} else {
@@ -302,7 +298,7 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
302298
elements.switchElement.onkeydown = e => {
303299
if (e.key === " " || e.key === "Enter") {
304300
e.preventDefault();
305-
elements.switchElement.onclick();
301+
elements.switchElement.click();
306302
}
307303
};
308304
} else {
@@ -319,8 +315,7 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
319315

320316
// Updates the version display
321317
if (domElements.version) {
322-
const {version} = manifest;
323-
domElements.version.textContent = LangUtil.VERSION + version;
318+
domElements.version.textContent = LangUtil.VERSION + browserAPI.runtime.getManifest().version;
324319
}
325320

326321
// Get all elements with the class 'page'
@@ -334,35 +329,32 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
334329
return;
335330
}
336331

337-
const updatePageDisplay = () => {
338-
// Checks for invalid current page numbers
339-
if (currentPage < 1 || currentPage > totalPages) {
340-
currentPage = 1;
332+
const updatePageDisplay = (newPage) => {
333+
if (newPage < 1 || newPage > totalPages) {
334+
return;
341335
}
342336

343-
// Toggles the active status
344-
for (let i = 0; i < pages.length; i++) {
345-
pages[i].classList.toggle('active', i + 1 === currentPage);
346-
}
337+
pages[currentPage - 1].classList.remove('active');
338+
currentPage = newPage;
339+
pages[currentPage - 1].classList.add('active');
347340

348-
// Updates the page indicator
349341
if (domElements.pageIndicator) {
350342
domElements.pageIndicator.textContent = `${currentPage}/${totalPages}`;
343+
domElements.pageIndicator.setAttribute('aria-label', `${LangUtil.PAGE_INDICATOR_LABEL} ${currentPage} ${LangUtil.PAGE_INDICATOR_OF} ${totalPages}`);
351344
} else {
352345
console.warn("'pageIndicator' element not found in the PopupPage DOM.");
353346
}
354347
};
355348

356349
if (domElements.prevPage) {
357350
domElements.prevPage.onclick = () => {
358-
currentPage = currentPage === 1 ? totalPages : currentPage - 1;
359-
updatePageDisplay();
351+
updatePageDisplay(currentPage === 1 ? totalPages : currentPage - 1);
360352
};
361353

362354
domElements.prevPage.onkeydown = e => {
363355
if (e.key === " " || e.key === "Enter") {
364356
e.preventDefault();
365-
domElements.prevPage.onclick();
357+
domElements.prevPage.click();
366358
}
367359
};
368360
} else {
@@ -371,22 +363,21 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
371363

372364
if (domElements.nextPage) {
373365
domElements.nextPage.onclick = () => {
374-
currentPage = currentPage === totalPages ? 1 : currentPage + 1;
375-
updatePageDisplay();
366+
updatePageDisplay(currentPage === totalPages ? 1 : currentPage + 1);
376367
};
377368

378369
domElements.nextPage.onkeydown = e => {
379370
if (e.key === " " || e.key === "Enter") {
380371
e.preventDefault();
381-
domElements.nextPage.onclick();
372+
domElements.nextPage.click();
382373
}
383374
};
384375
} else {
385376
console.warn("'nextPage' element not found in the PopupPage DOM.");
386377
}
387378

388379
// Initializes the page display
389-
updatePageDisplay();
380+
updatePageDisplay(1);
390381
};
391382

392383
return Object.freeze({
@@ -398,7 +389,7 @@ globalThis.PopupSingleton = globalThis.PopupSingleton || (() => {
398389
document.addEventListener("DOMContentLoaded", () => {
399390
try {
400391
Settings.get(settings => {
401-
if (settings.hideProtectionOptions) {
392+
if (!settings || typeof settings !== 'object' || settings.hideProtectionOptions) {
402393
globalThis.close();
403394
} else {
404395
globalThis.PopupSingleton.initialize();

0 commit comments

Comments
 (0)