Bug 1317470 Show permission prompts for background webextension updates
MozReview-Commit-ID: I55ePPFPuuE
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -484,33 +484,51 @@ const gExtensionsNotifications = {
if (!this.initialized) {
return;
}
ExtensionsUI.off("change", this.boundUpdate);
},
updateAlerts() {
let sideloaded = ExtensionsUI.sideloaded;
- if (sideloaded.size == 0) {
+ let updates = ExtensionsUI.updates;
+ if (sideloaded.size + updates.size == 0) {
gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_ADDONS);
} else {
gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_ADDONS,
"addon-alert");
}
let container = document.getElementById("PanelUI-footer-addons");
while (container.firstChild) {
container.firstChild.remove();
}
// Strings below to be properly localized in bug 1316996
const DEFAULT_EXTENSION_ICON =
"chrome://mozapps/skin/extensions/extensionGeneric.svg";
let items = 0;
+ for (let update of updates) {
+ if (++items > 4) {
+ break;
+ }
+ let button = document.createElement("toolbarbutton");
+ button.setAttribute("label", `"${update.addon.name}" requires new permissions`);
+
+ let icon = update.addon.iconURL || DEFAULT_EXTENSION_ICON;
+ button.setAttribute("image", icon);
+
+ button.addEventListener("click", evt => {
+ ExtensionsUI.showUpdate(gBrowser, update);
+ });
+
+ container.appendChild(button);
+ }
+
for (let addon of sideloaded) {
if (++items > 4) {
break;
}
let button = document.createElement("toolbarbutton");
button.setAttribute("label", `"${addon.name}" added to Firefox`);
let icon = addon.iconURL || DEFAULT_EXTENSION_ICON;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -115,16 +115,19 @@ support-files =
test_mcb_redirect.sjs
file_bug1045809_1.html
file_bug1045809_2.html
file_csp_block_all_mixedcontent.html
file_csp_block_all_mixedcontent.js
file_install_extensions.html
browser_webext_permissions.xpi
browser_webext_nopermissions.xpi
+ browser_webext_update1.xpi
+ browser_webext_update2.xpi
+ browser_webext_update.json
!/image/test/mochitest/blue.png
!/toolkit/components/passwordmgr/test/browser/form_basic.html
!/toolkit/components/passwordmgr/test/browser/insecure_test.html
!/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
!/toolkit/content/tests/browser/common/mockTransfer.js
!/toolkit/modules/tests/browser/metadata_*.html
!/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi
!/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi
@@ -301,16 +304,17 @@ skip-if = os == "mac" # decoder doctor i
[browser_discovery.js]
[browser_double_close_tab.js]
[browser_documentnavigation.js]
[browser_duplicateIDs.js]
[browser_drag.js]
skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
[browser_extension_permissions.js]
[browser_extension_sideloading.js]
+[browser_extension_update.js]
[browser_favicon_change.js]
[browser_favicon_change_not_in_document.js]
[browser_findbarClose.js]
[browser_focusonkeydown.js]
[browser_fullscreen-window-open.js]
tags = fullscreen
skip-if = os == "linux" # Linux: Intermittent failures - bug 941575.
[browser_fxaccounts.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_extension_update.js
@@ -0,0 +1,192 @@
+const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+
+const URL_BASE = "https://example.com/browser/browser/base/content/test/general";
+const ID = "update@tests.mozilla.org";
+
+function promiseViewLoaded(tab, viewid) {
+ let win = tab.linkedBrowser.contentWindow;
+ if (win.gViewController && !win.gViewController.isLoading &&
+ win.gViewController.currentViewId == viewid) {
+ return Promise.resolve();
+ }
+
+ return new Promise(resolve => {
+ function listener() {
+ if (win.gViewController.currentViewId != viewid) {
+ return;
+ }
+ win.document.removeEventListener("ViewChanged", listener);
+ resolve();
+ }
+ win.document.addEventListener("ViewChanged", listener);
+ });
+}
+
+function promisePopupNotificationShown(name) {
+ return new Promise(resolve => {
+ function popupshown() {
+ let notification = PopupNotifications.getNotification(name);
+ if (!notification) { return; }
+
+ ok(notification, `${name} notification shown`);
+ ok(PopupNotifications.isPanelOpen, "notification panel open");
+
+ PopupNotifications.panel.removeEventListener("popupshown", popupshown);
+ resolve(PopupNotifications.panel.firstChild);
+ }
+
+ PopupNotifications.panel.addEventListener("popupshown", popupshown);
+ });
+}
+
+function getBadgeStatus() {
+ let menuButton = document.getElementById("PanelUI-menu-button");
+ return menuButton.getAttribute("badge-status");
+}
+
+function promiseUpdateDownloaded(addon) {
+ return new Promise(resolve => {
+ let listener = {
+ onDownloadEnded(install) {
+ if (install.addon.id == addon.id) {
+ AddonManager.removeInstallListener(listener);
+ resolve();
+ }
+ },
+ };
+ AddonManager.addInstallListener(listener);
+ });
+}
+
+function promiseUpgrade(addon) {
+ return new Promise(resolve => {
+ let listener = {
+ onInstallEnded(install, newAddon) {
+ if (newAddon.id == addon.id) {
+ AddonManager.removeInstallListener(listener);
+ resolve(newAddon);
+ }
+ },
+ };
+ AddonManager.addInstallListener(listener);
+ });
+}
+
+add_task(function* () {
+ yield SpecialPowers.pushPrefEnv({set: [
+ // Turn on background updates
+ ["extensions.update.enabled", true],
+
+ // Point updates to the local mochitest server
+ ["extensions.update.background.url", `${URL_BASE}/browser_webext_update.json`],
+
+ // We don't have pre-pinned certificates for the local mochitest server
+ ["extensions.install.requireBuiltInCerts", false],
+ ["extensions.update.requireBuiltInCerts", false],
+
+ // XXX remove this when prompts are enabled by default
+ ["extensions.webextPermissionPrompts", true],
+ ]});
+
+ // Install version 1.0 of the test extension
+ let url1 = `${URL_BASE}/browser_webext_update1.xpi`;
+ let install = yield AddonManager.getInstallForURL(url1, null, "application/x-xpinstall");
+ ok(install, "Created install");
+
+ let addon = yield new Promise(resolve => {
+ install.addListener({
+ onInstallEnded(_install, _addon) {
+ resolve(_addon);
+ },
+ });
+ install.install();
+ });
+
+ ok(addon, "Addon was installed");
+ is(getBadgeStatus(), "", "Should not start out with an addon alert badge");
+
+ // Trigger an update check and wait for the update for this addon
+ // to be downloaded.
+ let updatePromise = promiseUpdateDownloaded(addon);
+ AddonManagerPrivate.backgroundUpdateCheck();
+ yield updatePromise;
+
+ is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
+
+ // Find the menu entry for the update
+ yield PanelUI.show();
+
+ let addons = document.getElementById("PanelUI-footer-addons");
+ is(addons.children.length, 1, "Have a menu entry for the update");
+
+ // Click the menu item
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+ let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ addons.children[0].click();
+
+ // about:addons should load and go to the list of extensions
+ let tab = yield tabPromise;
+ is(tab.linkedBrowser.currentURI.spec, "about:addons");
+
+ const VIEW = "addons://list/extension";
+ yield promiseViewLoaded(tab, VIEW);
+ let win = tab.linkedBrowser.contentWindow;
+ ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
+ is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
+
+ // Wait for the permission prompt and cancel it
+ let panel = yield popupPromise;
+ panel.secondaryButton.click();
+
+ addon = yield AddonManager.getAddonByID(ID);
+ is(addon.version, "1.0", "Should still be running the old version");
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Alert badge and hamburger menu items should be gone
+ is(getBadgeStatus(), "", "Addon alert badge should be gone");
+
+ yield PanelUI.show();
+ addons = document.getElementById("PanelUI-footer-addons");
+ is(addons.children.length, 0, "Update menu entries should be gone");
+ yield PanelUI.hide();
+
+ // Re-check for an update
+ updatePromise = promiseUpdateDownloaded(addon);
+ yield AddonManagerPrivate.backgroundUpdateCheck();
+ yield updatePromise;
+
+ is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
+
+ // Find the menu entry for the update
+ yield PanelUI.show();
+
+ addons = document.getElementById("PanelUI-footer-addons");
+ is(addons.children.length, 1, "Have a menu entry for the update");
+
+ // Click the menu item
+ tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+ popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ addons.children[0].click();
+
+ // Wait for about:addons to load
+ tab = yield tabPromise;
+ is(tab.linkedBrowser.currentURI.spec, "about:addons");
+
+ yield promiseViewLoaded(tab, VIEW);
+ win = tab.linkedBrowser.contentWindow;
+ ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
+ is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
+
+ // Wait for the permission prompt and accept it this time
+ updatePromise = promiseUpgrade(addon);
+ panel = yield popupPromise;
+ panel.button.click();
+
+ addon = yield updatePromise;
+ is(addon.version, "2.0", "Should have upgraded to the new version");
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ is(getBadgeStatus(), "", "Addon alert badge should be gone");
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_webext_update.json
@@ -0,0 +1,18 @@
+{
+ "addons": {
+ "update@tests.mozilla.org": {
+ "updates": [
+ {
+ "version": "2.0",
+ "update_link": "https://example.com/browser/browser/base/content/test/general/browser_webext_update2.xpi",
+ "applications": {
+ "gecko": {
+ "strict_min_version": "1",
+ "advisory_max_version": "55.0"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
new file mode 100644
index 0000000000000000000000000000000000000000..8eac9daa01745a2247eb9284b51858546b52f6a1
GIT binary patch
literal 4262
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
new file mode 100644
index 0000000000000000000000000000000000000000..81c7407ba7f17e486ce553318369dc7aaf1b017b
GIT binary patch
literal 4287
zc${@tXH=6}*A1bFKxop7AU(9ut3aqCT|kr~5=ww*La!mxi*!(s&{0AWP?`dv4I<Ke
z6QuVJQe^;nalSQczL_<1o^^hlXWhHk-uLW3+env?hz<Y%kOJB(C3Vcik&TBG0Kk1}
z0N~eOeN9tUk-G+35TuL0^V6p=QFjm6^aDS+m*kAHF`Z3Pm5@+fSeg%Cr~q1EBJQPq
zHv^nW5iA*|mLy_No>%b#BQT*hmQj|>J8mpEQH2?4tU>Rdw4aQh1oR&?1~xXI2=B_T
z%CGul6DCf?+8ids6kwY@x$1Lz5*Z3mxCu!p)JZGorj}3}kl<iw2oS510>#=Fz=QO@
z5hdR8!%~ZpQbYucj!+1u0($}x!AvJ{+GVp@P*)=+wpdBk1Hy)DK?IcJ+(dHq_(@JF
zQVk#A;FoS}(49AcbwV(=)j3shdN#qQMD5xIq)%>;n49-!#YJ`+zGDpzM@6(03zVG+
zI@dmRX_|a|p2vf9<hJuzCL`DdFCMpMPz0|<r4ow;Mrczl&>fu5cbTNm?SPNeFC!Z_
zILy3>WTA;M%pz2|gZyG=38m+nqHqd0hu}G7FjR`o=8{ISwcqzLTao`aHvnY|_K0-G
z7sBdz`%}-v3}Y|;arLfra53icg2T+ywLF5wf{FU~Y_4lHHD~m!5cGWr+VFidjwrE(
zE3iy4DwRZyq-tm@&=K&Wg)=?Uq)c*}5H5uyCk%C`nkCYS)n7*;s<y^x;>LX}M?ff2
z=jv-yi`}mh5+9wLR@8UeRjNrDPgYs3MMkASDzTp=@XC^8d9{yN;6lpL-+Z6;n{HUf
zDZjAxR4}(!fZ2Gr33o!;1XkT2wT>3!;Yb8<bgia`sn;%r%Us(nnQsSNVV5bXYJH)@
zw5c(e{=SAVhIQ4$uwg_t_>GodXKzG_%dFkwQ}9HwF{Tgqv~h<miLK0g!*dq>zys`0
z($6Vv1w-CT**iV0wegyOz9-AadTzZA#HqMc(m#ZWRu6KD1NG5fiN0lS)h4p<UM?7B
zDQq5`I(ypBOZ#Q54nkP07o=C6MRLDS4qvwo298<f{VXKO0Dbk2E*+N(KZpZG3tw2t
zyg<<7mYls%NvcXOTbsUcwDktmOuSrRa$e?;lhOc3CMc}m$5$SrZpJ)I{wz~4#o*gL
z?0@gY=c+`FuF9ECgb{nh$PGE$Q*^-)Bp25umS%M0Iy1hz&dfy7QM$stal;POvXL`Z
zw~lPS#dTgAoqQI)ikvVRLj^_1nD&~}mz0=!8WZGva5Tcv?QH5YDtg=9?+f;t(gEm&
zO;(=Mk1O^yv0O(mP}Vu_$AnReqqs~%Caz1ElZTUayup<W`){2a@VYl0Trj#Z)MM!K
zMqAKpGzz#$Y}JtsX%xv}LT4D8RD>~B)AoqRwj+9*wD31iu~(?E@d}CNcMr5OeF^dO
zA(d+(vYI@cw<j?rt&gvd@lwr(in4%vh$E<CWYRO6f4DnWyqviPVl2F%rR`YCMSOe0
z$X(iOPu)<|S@f0LI%(f)m7dR2c*&Hlv<V`D=n+MAZS{Z$RhPqdB%XYL>(m)r-rPn@
zi6WH0kLe6|xp#fuZX_zwwWl&6MXZ<W;GyFcXEQ?cC%o!lbW%q?b4!P$RGU<Q{v(WN
z(-RAj;(#=kcBJpcj}0lh$gDKSKzLC}Dy@pdmKBcecqOK8e!J-7o`SSN9kRWX*k0`*
z*Y2=yW-<M2)$A<?%HNf#TH^!DrP}K2i(7e5^zos_AC%~pe^#7J%J9$hO6|@>-VvvJ
z&tr_n3grE0sRv5Vd!)&G$p*fYkO~H>aUwQ}mt2E$#|(G*ob|l!?w-~{96%2<uuKIF
zPn!a)X)ye&W1;fVlWncW#0xt8)xaSE3|mH<7V`P~hov*}U0+hLRnPe-schrNZ)DeL
z|7Iwc)_|m&6k3b8=z2ig(CtS7H~LuUb$+{3o+l?C6It&sY#QA6{cw~<`r%{Y7S|8Z
zaz-EE@8TTee9oI)HTa>x7nWLKw28$k$S7&wS??U@Txx#&^w&I3`1JS00zfQjlW+qd
z9lkxLJ<$C;%|>bA##HMq!bF%QfLY-p^wX`UQghAT*N2q@uU$M?w<M+hp*owX0c?4a
zQ;xe-Ta-bC?N-MS$G=$?Z?9l7?;+T}37wdozwGrtHKdrC!UR-DU+(dQ3P^{yi~98O
zWR6Q(+k(F@dbCSrZEsG4GTBF%BE2Sn!Sg;JQo<h`-L%{^!8n>6>7?VVDW7XdG&a>2
zWGmMrU${$L?_uJDJo{Lexw6DiMuqRORzt`(XK~_*F@zj2|8yi?SlFo>fmK>!#qdi)
z9q;DG-C=yiH;_H~a86ACtMYtrv-aS~Y*F3lyO(`uW~Y3+Ttk+5qDeTPS04>m#{7lQ
z%FVa&+~B=R%7o3G+bwcF=4>=zZkiTN`N`%=8Kq|)jmy2vXElbrrp5Jy3U*rKNjvO0
z<x<neWEGSaQI9#hPD|m5==dC6O+rCcM#^xKEvB?H(i7tTL?gQk>8#Eb$o5^YK09v3
z5ZUz8$XXEhoLt^OFWw~7x@w&qWVZh}-%cL5o?lKt8)+8hIyl8u*Y$yPp`%3XMER_?
zs+iKOgG#kRIGC$((Ib2^hZc)U5PG%=(jmR)p8L{DAh1$r^sWYGSo&}v<plH29)p~U
zuX!X-=Jq&(dJ6{Q&F~qT);k+obd2@1=u}tlAw9B0`X^RaEx3(h?<pVfmA#p<wJr_a
zRYYZG#x@|W9x3UF6gjUO9Y#n79je><RN?||_ZHg{6P6;+rwPY*3@vZ*#LzCIsFWu8
zET172vf8|F+xWQ|dNaMPi)Xr#r`}GM^>g4YFDT+S*Zm3mf$KpOpMZL2`d~oh5RjVM
ziONPET6*SUwDyy!70^d0>>Y#!kT7M@QE2F*Nr9)z2%xZ$fk5QAv{3w1@f3QP9#wvx
z0d!BdQlkNYFQKAjybi$pYMc1z2x1RN7tf4gW#&HARBV<KYnr9WwLJBM!vx4o1dMN8
zD{J+H!dN#r1xzy!82KR(A5FTA*+Loz$Iwb{d{^za`is!7P4Amu&llt-jmJ2dH*d20
zu<G&C&aT~*4E)9wOD?)>A2(5ts2Sv6;({P<OA`$mSKL)<yA+?>ad~^1^BdY~S}>1=
zo-plplf$#p1!k2fxLXCV&A0sNL%i2Ql;Aw0K1?>Ha{??w%s`)QaOD1OmAoU;Xh$o_
z_1NW27C$Z-be?0%sQN}1Z`TxothaunLq>{9Uz0uC1D|)1-1_l+vyhc+31qkldDH=Y
zgxQk3_YrZg#8(knbV@p7G<kxtaFY?X?CJ~39R8xDbxZ{t@Rt5cw%A?QZ7{aU6M)!|
z&puAybC6O$_X{}nMP+}@+7{uHDNP<+*I_S7N!`>pZf;2~<m3{Encs(%S$H{oy=FDw
zlJshuUBHfymIEG>W~12iG;Xk1$RNF=qMn>Z@77~z!V?4(2mWx!P=D`xzrj6~1JRXi
zmk(P__dCC~xoZfEn)j(skA;+ctBw7#ZonC>G@Ic39OI$pKM#ARhVs{}jbWOWV0@je
z;@7)%4@H>_DTjcl*~SIg9&`%EM^`+ONpn&unVb-ObWjdmyLl1LZ%AK1OW$2K>=;KJ
z?raZTF?jk^UvuDC;B-ox5qWF|f8cjvYj<e(1!h06ktt<R-ZWb2Q1@oEAA8LOKghcO
zB?F?)M9TX9e#Ek>d^w@2wEhr>@q<$6{n3P%JD)dSE!?)@c9K{+|4D3D8Mpz+Z@PTt
zu)hjEKJf0YCaMio2+6(l?;<!?;jMNWYo3-TV<Z^NF&6O8Q4AU9W8eFzCOFkqyu4Q<
z>B;zRnEPi1KG$AMpUlLm1;;r3wbb1G;3$`2>!DUIdfMhbJXjM8+<p5KKr&g-1Xthk
zW#9m^ZS#3&p*%*M4s&0$<p~Pj5fh<mv_-|d{ixx&xz2c~48BD%tkE2&EPIaq$UHA2
z$d~QlH-fj=-oq^6I&_bk%-J9Ej8QQUYll7AoZ8yZt7y<M!Nm`+Fz1jso;dY75mm~F
ziL2-|Tf`!pGEiW1g+(@cjI#);rFrIeKM>|wYH#uJkJvt}UAMdZaXL$er!7HJ;x`<D
z25(@EOn1xAu*r@&6Wu2rut3YU-1_g6tFzb5s*A)QMm&)lM=8RSts2|N8Ur$l8iExs
z?r_uZPkHdaZs$mA3y8aydZax?CsOM7I}JU@CM!8I!5=CG`*^G^Li*OC%Ory$<R&~o
zhK5I=W*9%h^`R!u%J1+H=RFqu=v=bSn%LkYLczy`qhTh5oUB>?{q8%b&ex6T4YeZ@
z{peWN3|C5Z#y-8;<yKktK(CO3Ez2`d|I$6B<!|X(@G7+6G4k+zLm!<2Mq7rr!N@V2
zHOxNC_q<p_OMA3}UX5PYuRZ>aby(R{D1H5AC0nuSyQnrDE)|ueqAd%mcSC{oDNTiJ
zsTgnT)JYt$Wotzku<cTtS7!OxcQ`3rv1bJ<tMBO1TFQWqHC3B9P5r)7Xg4#?Sa5n7
zp?b1sUvf!qbd{3G{zyq*+$D9W08K_|SJV1cgRUA0Lpj48JUm4a9xf>T*lDs)Vie)K
zCv?2yc2Tdm7SK_#18vM>?{Mox>}a21{gI5sgRGo736k6K0o<M2p~<dH^ESs`;#!CK
zz{>RU<oM5g@qW4&%*}#Z8CjJ;ajkX+U5gQH3V6Gda$bnu^#Q_UOKiAKPiL_;CaaY%
z|K`}&*FF<w+`Ro<IfLX==I+{tMWC<LBz)jqnye><OG`xKh~k+)B>?`A0Qi4Jd(}P;
zwjvI`o+2=JPiHvH&eH>8^tWJ`zk$pPy_#MD{uvDa)Q*H7==XH%>57OBf^<a$`+EAT
zjKxMI=N9&Ld-OjTcZBO~IMA=Iy4BeigwiRj<gYS1q}nerSb~4x(*%joaxCry%y?=)
zp>Vdq!u<TX#7y^tBDRZ$j@+oXbAW!RrKM=1PVHOU*4IxV305wICWhltL$u9%3*}vz
zVyyv}rM8x$qJY1X?s%>&gSeXfuKLEmrCAR+d7oatt}s|cXY_BR_r1hNy*l*_uf!bv
zQbj0HN*66jE2(=sHdIeZXun6oIQG-gfk@8~5?L3b#0C;i;V;0xERb7aVDLXvP_zic
zzVt6h%h$M%!!kL3b9W=SqDBCuBmD2E{bvOc0RAI*|A7GbZyWwqe4u}r@~^A^zW)C!
z>R(`>e>cP5$^X+1f01XV{C9Kw6XBn``3s?s@?SS)q)SZl>j3f92fcbDXR2S*{{V{7
Bo$det
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -21,19 +21,21 @@ XPCOMUtils.defineLazyPreferenceGetter(th
"extensions.webextPermissionPrompts", false);
const DEFAULT_EXENSION_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const HTML_NS = "http://www.w3.org/1999/xhtml";
this.ExtensionsUI = {
sideloaded: new Set(),
+ updates: new Set(),
init() {
Services.obs.addObserver(this, "webextension-permission-prompt", false);
+ Services.obs.addObserver(this, "webextension-update-permissions", false);
this._checkForSideloaded();
},
_checkForSideloaded() {
AddonManager.getAllAddons(addons => {
// Check for any side-loaded addons that the user is allowed
// to enable.
@@ -55,66 +57,98 @@ this.ExtensionsUI = {
let win = RecentWindow.getMostRecentBrowserWindow();
for (let addon of sideloaded) {
win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
}
}
});
},
- showSideloaded(browser, addon) {
- addon.markAsSeen();
- this.sideloaded.delete(addon);
- this.emit("change");
-
+ showAddonsManager(browser, info) {
let loadPromise = new Promise(resolve => {
let listener = (subject, topic) => {
if (subject.location.href == "about:addons") {
Services.obs.removeObserver(listener, topic);
resolve(subject);
}
};
Services.obs.addObserver(listener, "EM-loaded", false);
});
let tab = browser.addTab("about:addons");
browser.selectedTab = tab;
- loadPromise.then(win => {
+
+ return loadPromise.then(win => {
win.loadView("addons://list/extension");
- let info = {
- addon,
- icon: addon.iconURL,
- type: "sideload",
- };
- this.showPermissionsPrompt(browser.selectedBrowser, info).then(answer => {
- addon.userDisabled = !answer;
- });
+ return this.showPermissionsPrompt(browser.selectedBrowser, info);
+ });
+ },
+
+ showSideloaded(browser, addon) {
+ addon.markAsSeen();
+ this.sideloaded.delete(addon);
+ this.emit("change");
+
+ let info = {
+ addon,
+ permissions: addon.userPermissions,
+ icon: addon.iconURL,
+ type: "sideload",
+ };
+ this.showAddonsManager(browser, info).then(answer => {
+ addon.userDisabled = !answer;
+ });
+ },
+
+ showUpdate(browser, info) {
+ info.type = "update";
+ this.showAddonsManager(browser, info).then(answer => {
+ if (answer) {
+ info.resolve();
+ } else {
+ info.reject();
+ }
+ // At the moment, this prompt will re-appear next time we do an update
+ // check. See bug 1332360 for proposal to avoid this.
+ this.updates.delete(info);
+ this.emit("change");
});
},
observe(subject, topic, data) {
if (topic == "webextension-permission-prompt") {
let {target, info} = subject.wrappedJSObject;
// Dismiss the progress notification. Note that this is bad if
// there are multiple simultaneous installs happening, see
// bug 1329884 for a longer explanation.
let progressNotification = target.ownerGlobal.PopupNotifications.getNotification("addon-progress", target);
if (progressNotification) {
progressNotification.remove();
}
- this.showPermissionsPrompt(target, info).then(answer => {
+ let reply = answer => {
Services.obs.notifyObservers(subject, "webextension-permission-response",
JSON.stringify(answer));
- });
+ };
+
+ let perms = info.addon.userPermissions;
+ if (!perms) {
+ reply(true);
+ } else {
+ info.permissions = perms;
+ this.showPermissionsPrompt(target, info).then(reply);
+ }
+ } else if (topic == "webextension-update-permissions") {
+ this.updates.add(subject.wrappedJSObject);
+ this.emit("change");
}
},
showPermissionsPrompt(target, info) {
- let perms = info.addon.userPermissions;
+ let perms = info.permissions;
if (!perms) {
return Promise.resolve();
}
let win = target.ownerGlobal;
let name = info.addon.name;
if (name.length > 50) {
@@ -142,16 +176,21 @@ this.ExtensionsUI = {
if (info.type == "sideload") {
header = `${name} added`;
text = "Another program on your computer installed an add-on that may affect your browser. Please review this add-on's permission requests and choose to Enable or Disable";
acceptText = "Enable";
acceptKey = "E";
cancelText = "Disable";
cancelKey = "D";
+ } else if (info.type == "update") {
+ header = "";
+ text = `${name} has been updated. You must approve new permissions before the updated version will install.`;
+ acceptText = "Update";
+ acceptKey = "U";
}
let formatPermission = perm => {
try {
// return bundle.getString(`webextPerms.description.${perm}`);
return `localized description of permission ${perm}`;
} catch (err) {
// return bundle.getFormattedString("webextPerms.description.unknown",
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -1371,16 +1371,45 @@ var AddonManagerInternal = {
return aMatch;
}
});
// escape() does not properly encode + symbols in any embedded FVF strings.
return uri.replace(/\+/g, "%2B");
},
+ _updatePromptHandler(info) {
+ let oldPerms = info.existingAddon.userPermissions || {hosts: [], permissions: []};
+ let newPerms = info.addon.userPermissions;
+
+ // See bug 1331769: should we do something more complicated to
+ // compare host permissions?
+ // e.g., if we go from <all_urls> to a specific host or from
+ // a *.domain.com to specific-host.domain.com that's actually a
+ // drop in permissions but the simple test below will cause a prompt.
+ let difference = {
+ hosts: newPerms.hosts.filter(perm => !oldPerms.hosts.includes(perm)),
+ permissions: newPerms.permissions.filter(perm => !oldPerms.permissions.includes(perm)),
+ };
+
+ // If there are no new permissions, just go ahead with the update
+ if (difference.hosts.length == 0 && difference.permissions.length == 0) {
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve, reject) => {
+ let subject = {wrappedJSObject: {
+ addon: info.addon,
+ permissions: difference,
+ resolve, reject
+ }};
+ Services.obs.notifyObservers(subject, "webextension-update-permissions", null);
+ });
+ },
+
/**
* Performs a background update check by starting an update for all add-ons
* that can be updated.
* @return Promise{null} Resolves when the background update check is complete
* (the resulting addon installations may still be in progress).
*/
backgroundUpdateCheck() {
if (!gStarted)
@@ -1425,16 +1454,19 @@ var AddonManagerInternal = {
// Start installing updates when the add-on can be updated and
// background updates should be applied.
logger.debug("Found update for add-on ${id}", aAddon);
if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
AddonManager.shouldAutoUpdate(aAddon)) {
// XXX we really should resolve when this install is done,
// not when update-available check completes, no?
logger.debug(`Starting upgrade install of ${aAddon.id}`);
+ if (WEBEXT_PERMISSION_PROMPTS) {
+ aInstall.promptHandler = (...args) => AddonManagerInternal._updatePromptHandler(...args);
+ }
aInstall.install();
}
},
onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); }
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
}));
}