security-privacy-card.mjs (10732B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { html } from "chrome://global/content/vendor/lit.all.mjs"; 6 import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; 7 8 const lazy = {}; 9 ChromeUtils.defineESModuleGetters(lazy, { 10 AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs", 11 }); 12 13 const L10N_IDS = { 14 okHeader: "security-privacy-status-ok-header", 15 problemHeader: "security-privacy-status-problem-header", 16 okLabel: "security-privacy-status-ok-label", 17 problemLabel: "security-privacy-status-problem-label", 18 problemHelperLabel: "security-privacy-status-problem-helper-label", 19 trackersPendingLabel: "security-privacy-status-pending-trackers-label", 20 trackersLabel: "security-privacy-status-trackers-label", 21 strictEnabledLabel: "security-privacy-status-strict-enabled-label", 22 upToDateLabel: "security-privacy-status-up-to-date-label", 23 updateNeededLabel: "security-privacy-status-update-needed-label", 24 updateErrorLabel: "security-privacy-status-update-error-label", 25 updateCheckingLabel: "security-privacy-status-update-checking-label", 26 updateNeededDescription: "security-privacy-status-update-needed-description", 27 updateButtonLabel: "security-privacy-status-update-button-label", 28 }; 29 30 /** 31 * Custom Element for a card holding configuration issues from the user settings 32 */ 33 export default class SecurityPrivacyCard extends MozLitElement { 34 /** 35 * Private member to check the App Updater status 36 * 37 * @returns {boolean} should we NOT warn the user about their app update status 38 */ 39 #okUpdateStatus() { 40 const okStatuses = [ 41 lazy.AppUpdater.STATUS.NO_UPDATES_FOUND, 42 lazy.AppUpdater.STATUS.CHECKING, 43 lazy.AppUpdater.STATUS.NO_UPDATER, 44 lazy.AppUpdater.STATUS.UPDATE_DISABLED_BY_POLICY, 45 lazy.AppUpdater.STATUS.OTHER_INSTANCE_HANDLING_UPDATES, 46 undefined, 47 ]; 48 49 return okStatuses.includes(this.appUpdateStatus); 50 } 51 52 get strictEnabled() { 53 return this.setting.deps.etpStrictEnabled.value; 54 } 55 56 get trackersBlocked() { 57 return this.setting.deps.trackerCount.value; 58 } 59 60 get appUpdateStatus() { 61 return this.setting.deps.appUpdateStatus.value; 62 } 63 64 // This should really only be used for testing, as it 65 // overrides the reported app updater state 66 set appUpdateStatus(value) { 67 this.requestUpdate(); 68 this.setting.deps.appUpdateStatus.value = value; 69 } 70 71 get configIssueCount() { 72 let filteredWarnings = [ 73 "etpStrictEnabled", 74 "trackerCount", 75 "appUpdateStatus", 76 ]; 77 return Object.values(this.setting.deps).filter( 78 warning => !filteredWarnings.includes(warning.id) && warning.visible 79 ).length; 80 } 81 82 /** 83 * Scrolling to an element in about:preferences is non-trivial because the fragment is controlled 84 * by the panel manager. So we need this logic. 85 * 86 * @param {string} panelHash - the ID of the panel the element we want to scroll to lives on 87 * @param {string} targetId - the ID of the element to scroll to 88 * @returns {Function} a callback that will perform the scroll 89 */ 90 #scrollToTargetOnPanel(panelHash, targetId) { 91 return function () { 92 // This actually scrolls to the target ID, if it exists. 93 // It looks in the document first, then the shadowRoot for that ID. 94 const scrollIntoView = () => { 95 let target = document.getElementById(targetId); 96 if (!target) { 97 target = this.shadowRoot.getElementById(targetId); 98 } 99 if (target) { 100 target.scrollIntoView({ behavior: "smooth" }); 101 } 102 }; 103 if (panelHash !== undefined && document.location.hash != panelHash) { 104 // If we are given a panel to go to, and we aren't already there, 105 // switch to that panel and when it is shown, scrollIntoView. 106 document.addEventListener("paneshown", scrollIntoView, { once: true }); 107 document.location.hash = panelHash; 108 } else { 109 // Here we are already on the panel, so we can just scroll straight to it. 110 scrollIntoView(); 111 } 112 }; 113 } 114 115 #openWarningCardAndScroll() { 116 let accordion = document.getElementById("warningCard"); 117 if (!accordion) { 118 return; 119 } 120 accordion.expanded = true; 121 this.#scrollToTargetOnPanel("#privacy", "warningCard")(); 122 } 123 124 getStatusImage() { 125 if (this.configIssueCount > 0) { 126 return html`<img 127 class="status-image" 128 src="chrome://global/skin/illustrations/kit-looking-left.svg" 129 data-l10n-id="security-privacy-image-warning" 130 />`; 131 } 132 return html`<img 133 class="status-image" 134 src="chrome://global/skin/illustrations/kit-looking-forward.svg" 135 data-l10n-id="security-privacy-image-ok" 136 />`; 137 } 138 139 /** 140 * Create the bullet point for the current count of "issues" in the user profile. 141 * Really only depends on `this.configIssueCount` 142 * 143 * @returns {TemplateResult} the HTML for the "issues" bullet of the custom element 144 */ 145 buildIssuesElement() { 146 if (this.configIssueCount == 0) { 147 return html`<li class="status-ok"> 148 <p data-l10n-id=${L10N_IDS.okLabel}></p> 149 </li>`; 150 } 151 return html`<li class="status-alert"> 152 <div> 153 <p data-l10n-id=${L10N_IDS.problemLabel}></p> 154 <p> 155 <small 156 ><a 157 href="" 158 @click=${() => this.#openWarningCardAndScroll()} 159 data-l10n-id=${L10N_IDS.problemHelperLabel} 160 ></a 161 ></small> 162 </p> 163 </div> 164 </li>`; 165 } 166 167 /** 168 * Create the bullet point for the current count of trackers blocked in the past week. 169 * Really only depends on `this.trackersBlocked`and `this.strictEnabled` 170 * 171 * @returns {TemplateResult} the HTML for the "trackers" bullet of the custom element 172 */ 173 buildTrackersElement() { 174 let trackerData = { 175 trackerCount: this.trackersBlocked, 176 }; 177 let trackerLabelElement = 178 this.trackersBlocked != null 179 ? html`<p 180 data-l10n-id=${L10N_IDS.trackersLabel} 181 data-l10n-args=${JSON.stringify(trackerData)} 182 ></p>` 183 : html`<p data-l10n-id=${L10N_IDS.trackersPendingLabel}></p>`; 184 185 if (this.strictEnabled) { 186 return html`<li class="status-ok"> 187 <div> 188 ${trackerLabelElement} 189 <p> 190 <small 191 data-l10n-id=${L10N_IDS.strictEnabledLabel} 192 id="strictEnabled" 193 @click=${this.#scrollToTargetOnPanel("#privacy", "trackingGroup")} 194 > 195 <a data-l10n-name="strict-tracking-protection" href=""></a 196 ></small> 197 </p> 198 </div> 199 </li>`; 200 } 201 return html`<li class="status-ok">${trackerLabelElement}</li>`; 202 } 203 204 /** 205 * Create the bullet point for the current update status bullet 206 * Really only depends on `this.appUpdateStatus` 207 * 208 * @returns {TemplateResult} the HTML for the "update" bullet of the custom element 209 */ 210 buildUpdateElement() { 211 switch (this.appUpdateStatus) { 212 case lazy.AppUpdater.STATUS.NO_UPDATES_FOUND: 213 return html`<li class="status-ok"> 214 <p data-l10n-id=${L10N_IDS.upToDateLabel}></p> 215 </li>`; 216 case lazy.AppUpdater.STATUS.MANUAL_UPDATE: 217 case lazy.AppUpdater.STATUS.DOWNLOADING: 218 case lazy.AppUpdater.STATUS.DOWNLOAD_AND_INSTALL: 219 case lazy.AppUpdater.STATUS.STAGING: 220 case lazy.AppUpdater.STATUS.READY_FOR_RESTART: 221 return html`<li class="status-alert"> 222 <div> 223 <p data-l10n-id=${L10N_IDS.updateNeededLabel}></p> 224 <p> 225 <small 226 ><span data-l10n-id=${L10N_IDS.updateNeededDescription}></span 227 ></small> 228 </p> 229 <moz-box-link 230 @click=${this.#scrollToTargetOnPanel("#general", "updateApp")} 231 data-l10n-id=${L10N_IDS.updateButtonLabel} 232 ></moz-box-link> 233 </div> 234 </li>`; 235 case lazy.AppUpdater.STATUS.NEVER_CHECKED: 236 case lazy.AppUpdater.STATUS.UNSUPPORTED_SYSTEM: 237 case lazy.AppUpdater.STATUS.DOWNLOAD_FAILED: 238 case lazy.AppUpdater.STATUS.INTERNAL_ERROR: 239 case lazy.AppUpdater.STATUS.CHECKING_FAILED: 240 return html`<li class="status-alert"> 241 <div> 242 <p data-l10n-id=${L10N_IDS.updateErrorLabel}></p> 243 <p> 244 <small 245 ><span data-l10n-id=${L10N_IDS.updateNeededDescription}></span 246 ></small> 247 </p> 248 <moz-box-link 249 href="javascript:void(0)" 250 @click=${this.#scrollToTargetOnPanel("#general", "updateApp")} 251 data-l10n-id=${L10N_IDS.updateButtonLabel} 252 ></moz-box-link> 253 </div> 254 </li>`; 255 case lazy.AppUpdater.STATUS.CHECKING: 256 return html`<li class="status-loading"> 257 <p data-l10n-id=${L10N_IDS.updateCheckingLabel}></p> 258 </li>`; 259 case lazy.AppUpdater.STATUS.NO_UPDATER: 260 case lazy.AppUpdater.STATUS.UPDATE_DISABLED_BY_POLICY: 261 case lazy.AppUpdater.STATUS.OTHER_INSTANCE_HANDLING_UPDATES: 262 case undefined: 263 default: 264 return html``; 265 } 266 } 267 268 /** 269 * Lit invoked callback to render a template for this component. 270 * This creates a card for itself, populates it with bullets and headings, 271 * and nests a <configuration-issue-card>. 272 * 273 * @returns {TemplateResult} the full HTML of this panel, CSS <link> and <moz-card>s included 274 */ 275 render() { 276 // Create l10n fields for the card's header 277 let headerL10nId = L10N_IDS.okHeader; 278 let headerL10nData = { problemCount: 0 }; 279 let trueIssueCount = 280 this.configIssueCount + (this.#okUpdateStatus() ? 0 : 1); 281 if (trueIssueCount > 0) { 282 headerL10nId = L10N_IDS.problemHeader; 283 headerL10nData.problemCount = trueIssueCount; 284 } 285 286 // And render this template! 287 return html` 288 <link 289 rel="stylesheet" 290 href="chrome://browser/content/preferences/widgets/security-privacy-card.css" 291 /> 292 <moz-card aria-labelledby="heading"> 293 <div class="card-contents"> 294 <div class="status-text-container"> 295 <h3 296 id="heading" 297 data-l10n-id=${headerL10nId} 298 data-l10n-args=${JSON.stringify(headerL10nData)} 299 ></h3> 300 <ul> 301 ${this.buildIssuesElement()} ${this.buildTrackersElement()} 302 ${this.buildUpdateElement()} 303 </ul> 304 </div> 305 ${this.getStatusImage()} 306 </div> 307 </moz-card> 308 `; 309 } 310 } 311 customElements.define("security-privacy-card", SecurityPrivacyCard);