diff --git a/toolkit/locales/en-US/toolkit/about/aboutAddons.ftl b/toolkit/locales/en-US/toolkit/about/aboutAddons.ftl index f7e1ef7bb17f..98f61eb5404c 100644 --- a/toolkit/locales/en-US/toolkit/about/aboutAddons.ftl +++ b/toolkit/locales/en-US/toolkit/about/aboutAddons.ftl @@ -391,6 +391,7 @@ preferences-addon-button = } details-addon-button = Details release-notes-addon-button = Release Notes +permissions-addon-button = Permissions addons-enabled-heading = Enabled addons-disabled-heading = Disabled @@ -452,3 +453,5 @@ recent-updates-heading = Recent Updates release-notes-loading = Loading… release-notes-error = Sorry, but there was an error loading the release notes. + +addon-permissions-empty = This extension doesn’t require any permissions diff --git a/toolkit/mozapps/extensions/content/aboutaddons.css b/toolkit/mozapps/extensions/content/aboutaddons.css index a5b377c72f07..9e3c7c24b2e1 100644 --- a/toolkit/mozapps/extensions/content/aboutaddons.css +++ b/toolkit/mozapps/extensions/content/aboutaddons.css @@ -362,6 +362,10 @@ panel-item-separator[hidden] { text-decoration: none; } +addon-permissions-list > .addon-detail-row:first-of-type { + border-top: none; +} + .deck-tab-group { border-bottom: 1px solid var(--grey-90-a20); border-top: 1px solid var(--grey-90-a20); diff --git a/toolkit/mozapps/extensions/content/aboutaddons.html b/toolkit/mozapps/extensions/content/aboutaddons.html index 07a73c457b06..159d3b25dd37 100644 --- a/toolkit/mozapps/extensions/content/aboutaddons.html +++ b/toolkit/mozapps/extensions/content/aboutaddons.html @@ -87,6 +87,7 @@ diff --git a/toolkit/mozapps/extensions/content/aboutaddons.js b/toolkit/mozapps/extensions/content/aboutaddons.js index 65dc975d6610..ac442851de45 100644 --- a/toolkit/mozapps/extensions/content/aboutaddons.js +++ b/toolkit/mozapps/extensions/content/aboutaddons.js @@ -18,6 +18,15 @@ XPCOMUtils.defineLazyModuleGetters(this, { PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", }); +XPCOMUtils.defineLazyGetter(this, "browserBundle", () => { + return Services.strings.createBundle( + "chrome://browser/locale/browser.properties"); +}); +XPCOMUtils.defineLazyGetter(this, "brandBundle", () => { + return Services.strings.createBundle( + "chrome://branding/locale/brand.properties"); +}); + XPCOMUtils.defineLazyPreferenceGetter( this, "allowPrivateBrowsingByDefault", "extensions.allowPrivateBrowsingByDefault", true); @@ -753,6 +762,49 @@ class UpdateReleaseNotes extends HTMLElement { } customElements.define("update-release-notes", UpdateReleaseNotes); +class AddonPermissionsList extends HTMLElement { + setAddon(addon) { + this.addon = addon; + this.render(); + } + + render() { + let appName = brandBundle.GetStringFromName("brandShortName"); + let {msgs} = Extension.formatPermissionStrings({ + permissions: this.addon.userPermissions, + appName, + }, browserBundle); + + this.textContent = ""; + + if (msgs.length > 0) { + // Add a row for each permission message. + for (let msg of msgs) { + let row = document.createElement("div"); + row.classList.add("addon-detail-row", "permission-info"); + row.textContent = msg; + this.appendChild(row); + } + } else { + let emptyMessage = document.createElement("div"); + emptyMessage.classList.add("addon-detail-row"); + document.l10n.setAttributes(emptyMessage, "addon-permissions-empty"); + this.appendChild(emptyMessage); + } + + // Add a learn more link. + let learnMoreRow = document.createElement("div"); + learnMoreRow.classList.add("addon-detail-row"); + let learnMoreLink = document.createElement("a"); + learnMoreLink.setAttribute("target", "_blank"); + learnMoreLink.href = SUPPORT_URL + "extension-permissions"; + learnMoreLink.textContent = + browserBundle.GetStringFromName("webextPerms.learnMore"); + learnMoreRow.appendChild(learnMoreLink); + this.appendChild(learnMoreRow); + } +} +customElements.define("addon-permissions-list", AddonPermissionsList); class AddonDetails extends HTMLElement { connectedCallback() { @@ -793,6 +845,8 @@ class AddonDetails extends HTMLElement { // Hide tab buttons that won't have any content. let getButtonByName = name => this.tabGroup.querySelector(`[name="${name}"]`); + let permsBtn = getButtonByName("permissions"); + permsBtn.hidden = addon.type != "extension"; let notesBtn = getButtonByName("release-notes"); notesBtn.hidden = !this.releaseNotesUri; @@ -828,6 +882,10 @@ class AddonDetails extends HTMLElement { this.deck = this.querySelector("named-deck"); this.tabGroup = this.querySelector(".deck-tab-group"); + // Set the add-on for the permissions section. + this.permissionsList = this.querySelector("addon-permissions-list"); + this.permissionsList.setAddon(addon); + // Full description. let description = this.querySelector(".addon-detail-description"); if (addon.getFullDescription) { diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js b/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js index 74b6eab651a1..241ec642165a 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js +++ b/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js @@ -61,6 +61,26 @@ function checkOptions(doc, options, expectedOptions) { } } +function assertDeckHeadingHidden(group) { + ok(group.hidden, "The tab group is hidden"); + for (let button of group.children) { + ok(button.offsetHeight == 0, `The ${button.name} is hidden`); + } +} + +function assertDeckHeadingButtons(group, visibleButtons) { + ok(!group.hidden, "The tab group is shown"); + ok(group.children.length >= visibleButtons.length, + `There should be at least ${visibleButtons.length} buttons`); + for (let button of group.children) { + if (visibleButtons.includes(button.name)) { + ok(!button.hidden, `The ${button.name} is shown`); + } else { + ok(button.hidden, `The ${button.name} is hidden`); + } + } +} + async function hasPrivateAllowed(id) { let perms = await ExtensionPermissions.get(id); return perms.permissions.includes("internal:privateBrowsingAllowed"); @@ -83,6 +103,10 @@ add_task(async function enableHtmlViews() { type: "extension", contributionURL: "http://foo.com", averageRating: 4.279, + userPermissions: { + origins: ["", "file://*/*"], + permissions: ["alarms", "contextMenus", "tabs", "webNavigation"], + }, reviewCount: 5, reviewURL: "http://example.com/reviews", homepageURL: "http://example.com/addon1", @@ -93,6 +117,10 @@ add_task(async function enableHtmlViews() { name: "Test add-on 2", creator: {name: "I made it"}, description: "Short description", + userPermissions: { + origins: [], + permissions: ["alarms", "contextMenus"], + }, type: "extension", }, { id: "theme1@mochi.test", @@ -284,6 +312,10 @@ add_task(async function testFullDetails() { is(preview.hidden, true, "The preview is hidden"); let details = card.querySelector("addon-details"); + + // Check all the deck buttons are hidden. + assertDeckHeadingButtons(details.tabGroup, ["details", "permissions"]); + let desc = details.querySelector(".addon-detail-description"); is(desc.innerHTML, "Longer description
With brs!", "The full description replaces newlines with
"); @@ -291,7 +323,8 @@ add_task(async function testFullDetails() { let contrib = details.querySelector(".addon-detail-contribute"); ok(contrib, "The contribution section is visible"); - let rows = Array.from(details.querySelectorAll(".addon-detail-row")); + let rows = Array.from( + card.querySelectorAll('[name="details"] .addon-detail-row')); // Auto updates. let row = rows.shift(); @@ -396,13 +429,17 @@ add_task(async function testMinimalExtension() { card = getAddonCard(doc, "addon2@mochi.test"); let details = card.querySelector("addon-details"); + // Check all the deck buttons are hidden. + assertDeckHeadingButtons(details.tabGroup, ["details", "permissions"]); + let desc = details.querySelector(".addon-detail-description"); is(desc.textContent, "", "There is no full description"); let contrib = details.querySelector(".addon-detail-contribute"); ok(!contrib, "There is no contribution element"); - let rows = Array.from(details.querySelectorAll(".addon-detail-row")); + let rows = Array.from( + card.querySelectorAll('[name="details"] .addon-detail-row')); // Automatic updates. let row = rows.shift(); @@ -454,7 +491,11 @@ add_task(async function testDefaultTheme() { ok(preview, "There is a preview"); is(preview.hidden, true, "The preview is hidden"); - let rows = Array.from(card.querySelectorAll(".addon-detail-row")); + // Check all the deck buttons are hidden. + assertDeckHeadingHidden(card.details.tabGroup); + + let rows = Array.from( + card.querySelectorAll('[name="details"] .addon-detail-row')); // Author. let author = rows.shift(); @@ -508,7 +549,11 @@ add_task(async function testStaticTheme() { is(preview.height, "90", "The height is set"); is(preview.hidden, false, "The preview is visible"); - let rows = Array.from(card.querySelectorAll(".addon-detail-row")); + // Check all the deck buttons are hidden. + assertDeckHeadingHidden(card.details.tabGroup); + + let rows = Array.from( + card.querySelectorAll('[name="details"] .addon-detail-row')); // Automatic updates. let row = rows.shift(); @@ -621,3 +666,61 @@ add_task(async function testPrivateBrowsingAllowedListView() { await extension.unload(); await closeView(win); }); + +add_task(async function testPermissions() { + async function runTest(id, permissions) { + let win = await loadInitialView("extension"); + let doc = win.document; + + let card = getAddonCard(doc, id); + ok(!card.hasAttribute("expanded"), "The list card is not expanded"); + let loaded = waitForViewLoad(win); + card.querySelector('[action="expand"]').click(); + await loaded; + + card = getAddonCard(doc, id); + let {deck, tabGroup} = card.details; + + // Check all the deck buttons are hidden. + assertDeckHeadingButtons(tabGroup, ["details", "permissions"]); + + let permsBtn = tabGroup.querySelector('[name="permissions"]'); + let permsShown = BrowserTestUtils.waitForEvent(deck, "view-changed"); + permsBtn.click(); + await permsShown; + + let permsSection = card.querySelector("addon-permissions-list"); + let rows = Array.from(permsSection.querySelectorAll(".addon-detail-row")); + + info("Check displayed permissions"); + if (permissions) { + for (let name in permissions) { + // Check the permission-info class to make sure it's for a permission. + let row = rows.shift(); + ok(row.classList.contains("permission-info"), + `There's a row for ${name}`); + } + } else { + let row = rows.shift(); + is(doc.l10n.getAttributes(row).id, "addon-permissions-empty", + "There's a message when no permissions are shown"); + } + + info("Check learn more link"); + let row = rows.shift(); + is(row.children.length, 1, "There's one child for learn more"); + let link = row.firstElementChild; + let rootUrl = Services.urlFormatter.formatURLPref("app.support.baseURL"); + let url = rootUrl + "extension-permissions"; + is(link.href, url, "The URL is set"); + is(link.getAttribute("target"), "_blank", "The link opens in a new tab"); + + await closeView(win); + } + + info("Check permissions for add-on with permission message"); + await runTest("addon1@mochi.test", ["", "tabs", "webNavigation"]); + + info("Check permissions for add-on without permission messages"); + await runTest("addon2@mochi.test"); +}); diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_updates.js b/toolkit/mozapps/extensions/test/browser/browser_html_updates.js index 1a51c2277578..b25ee2b514e9 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_html_updates.js +++ b/toolkit/mozapps/extensions/test/browser/browser_html_updates.js @@ -218,8 +218,7 @@ function assertUpdateState({ `The update check button is ${shown ? "hidden" : "shown"}`); let {tabGroup} = card.details; - is(tabGroup.hidden, !releaseNotes, - `The tab group is ${releaseNotes ? "shown" : "hidden"}`); + is(tabGroup.hidden, false, "The tab group is shown"); let notesBtn = tabGroup.querySelector('[name="release-notes"]'); is(notesBtn.hidden, !releaseNotes, `The release notes button is ${releaseNotes ? "shown" : "hidden"}`);