From efe7fba0b4577d27644e95bd2659e78263980c83 Mon Sep 17 00:00:00 2001 From: nielm Date: Thu, 7 Mar 2024 17:08:32 +0100 Subject: [PATCH 1/5] Move original to keepAwake Advanced --- .../power/{ => keepAwake Advanced}/README.md | 0 .../_locales/en/messages.json | 0 .../power/{ => keepAwake Advanced}/background.js | 0 .../{ => keepAwake Advanced}/images/day-19.png | Bin .../{ => keepAwake Advanced}/images/day-38.png | Bin .../{ => keepAwake Advanced}/images/icon-128.png | Bin .../{ => keepAwake Advanced}/images/icon-16.png | Bin .../{ => keepAwake Advanced}/images/icon-48.png | Bin .../{ => keepAwake Advanced}/images/night-19.png | Bin .../{ => keepAwake Advanced}/images/night-38.png | Bin .../{ => keepAwake Advanced}/images/sunset-19.png | Bin .../{ => keepAwake Advanced}/images/sunset-38.png | Bin .../power/{ => keepAwake Advanced}/manifest.json | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename api-samples/power/{ => keepAwake Advanced}/README.md (100%) rename api-samples/power/{ => keepAwake Advanced}/_locales/en/messages.json (100%) rename api-samples/power/{ => keepAwake Advanced}/background.js (100%) rename api-samples/power/{ => keepAwake Advanced}/images/day-19.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/day-38.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/icon-128.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/icon-16.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/icon-48.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/night-19.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/night-38.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/sunset-19.png (100%) rename api-samples/power/{ => keepAwake Advanced}/images/sunset-38.png (100%) rename api-samples/power/{ => keepAwake Advanced}/manifest.json (100%) diff --git a/api-samples/power/README.md b/api-samples/power/keepAwake Advanced/README.md similarity index 100% rename from api-samples/power/README.md rename to api-samples/power/keepAwake Advanced/README.md diff --git a/api-samples/power/_locales/en/messages.json b/api-samples/power/keepAwake Advanced/_locales/en/messages.json similarity index 100% rename from api-samples/power/_locales/en/messages.json rename to api-samples/power/keepAwake Advanced/_locales/en/messages.json diff --git a/api-samples/power/background.js b/api-samples/power/keepAwake Advanced/background.js similarity index 100% rename from api-samples/power/background.js rename to api-samples/power/keepAwake Advanced/background.js diff --git a/api-samples/power/images/day-19.png b/api-samples/power/keepAwake Advanced/images/day-19.png similarity index 100% rename from api-samples/power/images/day-19.png rename to api-samples/power/keepAwake Advanced/images/day-19.png diff --git a/api-samples/power/images/day-38.png b/api-samples/power/keepAwake Advanced/images/day-38.png similarity index 100% rename from api-samples/power/images/day-38.png rename to api-samples/power/keepAwake Advanced/images/day-38.png diff --git a/api-samples/power/images/icon-128.png b/api-samples/power/keepAwake Advanced/images/icon-128.png similarity index 100% rename from api-samples/power/images/icon-128.png rename to api-samples/power/keepAwake Advanced/images/icon-128.png diff --git a/api-samples/power/images/icon-16.png b/api-samples/power/keepAwake Advanced/images/icon-16.png similarity index 100% rename from api-samples/power/images/icon-16.png rename to api-samples/power/keepAwake Advanced/images/icon-16.png diff --git a/api-samples/power/images/icon-48.png b/api-samples/power/keepAwake Advanced/images/icon-48.png similarity index 100% rename from api-samples/power/images/icon-48.png rename to api-samples/power/keepAwake Advanced/images/icon-48.png diff --git a/api-samples/power/images/night-19.png b/api-samples/power/keepAwake Advanced/images/night-19.png similarity index 100% rename from api-samples/power/images/night-19.png rename to api-samples/power/keepAwake Advanced/images/night-19.png diff --git a/api-samples/power/images/night-38.png b/api-samples/power/keepAwake Advanced/images/night-38.png similarity index 100% rename from api-samples/power/images/night-38.png rename to api-samples/power/keepAwake Advanced/images/night-38.png diff --git a/api-samples/power/images/sunset-19.png b/api-samples/power/keepAwake Advanced/images/sunset-19.png similarity index 100% rename from api-samples/power/images/sunset-19.png rename to api-samples/power/keepAwake Advanced/images/sunset-19.png diff --git a/api-samples/power/images/sunset-38.png b/api-samples/power/keepAwake Advanced/images/sunset-38.png similarity index 100% rename from api-samples/power/images/sunset-38.png rename to api-samples/power/keepAwake Advanced/images/sunset-38.png diff --git a/api-samples/power/manifest.json b/api-samples/power/keepAwake Advanced/manifest.json similarity index 100% rename from api-samples/power/manifest.json rename to api-samples/power/keepAwake Advanced/manifest.json From 1df0f471e3d8b4093939b58de9e50bd5b6841ed1 Mon Sep 17 00:00:00 2001 From: nielm Date: Thu, 7 Mar 2024 17:05:17 +0100 Subject: [PATCH 2/5] Move original to keepAwake Basic --- api-samples/power/keepAwake Basic/README.md | 13 +++ .../keepAwake Basic/_locales/en/messages.json | 22 ++++ .../power/keepAwake Basic/background.js | 99 ++++++++++++++++++ .../power/keepAwake Basic/images/day-19.png | Bin 0 -> 966 bytes .../power/keepAwake Basic/images/day-38.png | Bin 0 -> 2511 bytes .../power/keepAwake Basic/images/icon-128.png | Bin 0 -> 10687 bytes .../power/keepAwake Basic/images/icon-16.png | Bin 0 -> 654 bytes .../power/keepAwake Basic/images/icon-48.png | Bin 0 -> 3067 bytes .../power/keepAwake Basic/images/night-19.png | Bin 0 -> 931 bytes .../power/keepAwake Basic/images/night-38.png | Bin 0 -> 2433 bytes .../keepAwake Basic/images/sunset-19.png | Bin 0 -> 1071 bytes .../keepAwake Basic/images/sunset-38.png | Bin 0 -> 3010 bytes .../power/keepAwake Basic/manifest.json | 26 +++++ 13 files changed, 160 insertions(+) create mode 100644 api-samples/power/keepAwake Basic/README.md create mode 100644 api-samples/power/keepAwake Basic/_locales/en/messages.json create mode 100644 api-samples/power/keepAwake Basic/background.js create mode 100644 api-samples/power/keepAwake Basic/images/day-19.png create mode 100644 api-samples/power/keepAwake Basic/images/day-38.png create mode 100644 api-samples/power/keepAwake Basic/images/icon-128.png create mode 100644 api-samples/power/keepAwake Basic/images/icon-16.png create mode 100644 api-samples/power/keepAwake Basic/images/icon-48.png create mode 100644 api-samples/power/keepAwake Basic/images/night-19.png create mode 100644 api-samples/power/keepAwake Basic/images/night-38.png create mode 100644 api-samples/power/keepAwake Basic/images/sunset-19.png create mode 100644 api-samples/power/keepAwake Basic/images/sunset-38.png create mode 100644 api-samples/power/keepAwake Basic/manifest.json diff --git a/api-samples/power/keepAwake Basic/README.md b/api-samples/power/keepAwake Basic/README.md new file mode 100644 index 0000000000..0ef26affe2 --- /dev/null +++ b/api-samples/power/keepAwake Basic/README.md @@ -0,0 +1,13 @@ +# chrome.power + +This extension demonstrates the `chrome.power` API by allowing users to override their system's power management features. + +## Overview + +The extension adds a popup that cycles different states when clicked. It will go though a mode that prevents the display from dimming or going to sleep, a mode that keeps the system awake but allows the screen to dim/go to sleep, and a mode that uses the system's default. + +## Running this extension + +1. Clone this repository. +2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked). +3. Pin the extension and click the action button. diff --git a/api-samples/power/keepAwake Basic/_locales/en/messages.json b/api-samples/power/keepAwake Basic/_locales/en/messages.json new file mode 100644 index 0000000000..ef50944de1 --- /dev/null +++ b/api-samples/power/keepAwake Basic/_locales/en/messages.json @@ -0,0 +1,22 @@ +{ + "extensionName": { + "message": "Keep Awake", + "description": "Extension name." + }, + "extensionDescription": { + "message": "Override system power-saving settings.", + "description": "Extension description." + }, + "disabledTitle": { + "message": "Default power-saving settings", + "description": "Browser action title when disabled." + }, + "displayTitle": { + "message": "Screen will be kept on", + "description": "Browser action title when preventing screen-off." + }, + "systemTitle": { + "message": "System will stay awake", + "description": "Browser action title when preventing system sleep." + } +} diff --git a/api-samples/power/keepAwake Basic/background.js b/api-samples/power/keepAwake Basic/background.js new file mode 100644 index 0000000000..59dd9b62a5 --- /dev/null +++ b/api-samples/power/keepAwake Basic/background.js @@ -0,0 +1,99 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * States that the extension can be in. + */ +let StateEnum = { + DISABLED: 'disabled', + DISPLAY: 'display', + SYSTEM: 'system' +}; + +/** + * Key used for storing the current state in {localStorage}. + */ +let STATE_KEY = 'state'; + +/** + * Loads the locally-saved state asynchronously. + * @param {function} callback Callback invoked with the loaded {StateEnum}. + */ +function loadSavedState(callback) { + chrome.storage.local.get(STATE_KEY, function (items) { + let savedState = items[STATE_KEY]; + for (let key in StateEnum) { + if (savedState == StateEnum[key]) { + callback(savedState); + return; + } + } + callback(StateEnum.DISABLED); + }); +} + +/** + * Switches to a new state. + * @param {string} newState New {StateEnum} to use. + */ +function setState(newState) { + let imagePrefix = 'night'; + let title = ''; + + switch (newState) { + case StateEnum.DISABLED: + chrome.power.releaseKeepAwake(); + imagePrefix = 'night'; + title = chrome.i18n.getMessage('disabledTitle'); + break; + case StateEnum.DISPLAY: + chrome.power.requestKeepAwake('display'); + imagePrefix = 'day'; + title = chrome.i18n.getMessage('displayTitle'); + break; + case StateEnum.SYSTEM: + chrome.power.requestKeepAwake('system'); + imagePrefix = 'sunset'; + title = chrome.i18n.getMessage('systemTitle'); + break; + default: + throw 'Invalid state "' + newState + '"'; + } + + let items = {}; + items[STATE_KEY] = newState; + chrome.storage.local.set(items); + + chrome.action.setIcon({ + path: { + 19: 'images/' + imagePrefix + '-19.png', + 38: 'images/' + imagePrefix + '-38.png' + } + }); + chrome.action.setTitle({ title: title }); +} + +chrome.action.onClicked.addListener(function () { + loadSavedState(function (state) { + switch (state) { + case StateEnum.DISABLED: + setState(StateEnum.DISPLAY); + break; + case StateEnum.DISPLAY: + setState(StateEnum.SYSTEM); + break; + case StateEnum.SYSTEM: + setState(StateEnum.DISABLED); + break; + default: + throw 'Invalid state "' + state + '"'; + } + }); +}); + +chrome.runtime.onStartup.addListener(function () { + loadSavedState(function (state) { + setState(state); + }); +}); diff --git a/api-samples/power/keepAwake Basic/images/day-19.png b/api-samples/power/keepAwake Basic/images/day-19.png new file mode 100644 index 0000000000000000000000000000000000000000..04965f76eab80cd9b9492099e1ca01a2d45a7e7d GIT binary patch literal 966 zcmV;%13CPOP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*e^ z7XvWa>^b28000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0009aNkl~U&L3g!zaTIH5g7@Q zB?2c%qz){|j0n!gHom*Pz1_W;>28AA!*eZ{KZ|~m!&c&VMz5sUkZa8WJN(8??lQJDs<&ChD?HX$9P}S@mJ2)2AeSH{^jQB&s5Q7ncnxo_tj4A+tfGF@$=$ZP0 znLpu%et^l>(0mO3E)XHEQFw#5Pk&{l+x)%SA*cZYC?E!8Zs6N7ab>jRy6%B4K=mHz zA_}`e4P{NK_E0)uxBY`>ewnt<^nk1YqB3X#cq)aU%!q#|HbeF%gq;oe0#rZ)+NIJN z6BjK|1bHJ7E8qh{u}*;%S3wgAFn?UOqd#7qW}N^07*qoM6N<$f>v^y>Hq)$ literal 0 HcmV?d00001 diff --git a/api-samples/power/keepAwake Basic/images/day-38.png b/api-samples/power/keepAwake Basic/images/day-38.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7484218c21b65e5808bc88f3b628ec2adf7d45 GIT binary patch literal 2511 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

ycsAVEmiL-zJQATze=}T8l%o02})rRi^(1$ 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 JPDHLkV1m-0?Dqfw literal 0 HcmV?d00001 diff --git a/api-samples/power/keepAwake Basic/images/night-19.png b/api-samples/power/keepAwake Basic/images/night-19.png new file mode 100644 index 0000000000000000000000000000000000000000..1fee02d8dc52abf96c46c5a67f19e6888a2be18c GIT binary patch literal 931 zcmV;U16=%xP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*e^ z7X&rt$6KiY000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}00091Nklwi)4`yf+EZyLpF{G{ta$jx{2dDe?Z*1 z$<9TB2(A(^ZX^VHFytoR0 z_<-}h4yhUm8yf z;-_DJLrA#srJtY7El4a8l*N(Bg_im1h^8uE084O{-=QZ+<; z8D8t=z^h^aN;>_k%nUXAqxjXmrHcN)(#ld@7j`jb|8FDo#Z}Ey4z>UQ002ovPDHLk FV1k9$t?U2* literal 0 HcmV?d00001 diff --git a/api-samples/power/keepAwake Basic/images/night-38.png b/api-samples/power/keepAwake Basic/images/night-38.png new file mode 100644 index 0000000000000000000000000000000000000000..581b5886314856d5b4589fb072cda0dacb34c86c GIT binary patch literal 2433 zcmV-{34Zp8P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*e^ z7YZ?dDY8BQ000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000QwNklEgpkf;eUfH6pH=_qj1jSUyN@2y+U^Vr+L-sjXgRktfusj73%-fQh| zt@W*k0GEO15kc@T7k(B0k@8pNCB+*DtX3cOyi_+sZhNnk`htikS8h})Ct>qqksuD7 zDz=r1e5oiS)y@4isjsQa2+z21Kv7_G@@#=Y1W{(=q*5vu7AZj?I0V6gQUz4u>AEs2 zWUAMtTq7cN#Dc=2G&WB#h(bj98zMMXf?TntJgL-CQc^chK&>=y*iTCIk9xjtP7y&w zVMGMcrh-!CtQGTAl`9bua86c0R0Ye*+~TBeYFXdkm1|tMZ$uQLbZz5cEdReiD8T0z zS4>+N=munqo5Sj>vnnU!o!@t^CZWP z4H@)%L~+cmTl?&N`Wd^|KjzPGyv1ZPt4Fgc%q}ZG6$hda)95%IcUX^17kol0$P`qv z@-yL^Pd~x=2kxWQY+|hiAdX|A$S|GG$g(UTFT8u@KfLtvYZZ5^`&eP}Wb&zZezabp z<2I42MOyH|?X5mfKK3O(cl;Q6mSK!Wh(N#?&1^OYAXz4u$l#pg{`>CX)Xs=My!ux@ zymlj)zv3>cP*S0F)V!?XHiD1N!snDIP#RIne+b{AIDUM{Q;$7DBQ_YL84kDTbUH+F z1VEPMjK^aR4h~4tgd|OIc}^5X^tv6s{?&`T_x^SE4ko~g(#G0L~BHa zZ_#z-L?g=h`%3fl6BlVVVnl>arwhRD?k-oaUS)T8m;L>HthGc@#B4U>;~O_f(u_Ec zX*QcUoAa}u{0Py0e--xiAZSDsTF0%+I_`Kb3reVPib82r!E=gdo_ds%qhn~T>GgVa zyIp3}DSLZ++`M@cYb{!9oO4)biHujSKj@=Gh~pUN9FIT#D3LMz?d^A0mJ+X2ww#~} z(~0&96{iqo5N!~pP)eh;q1|b7_o)%f+Y&2E#t0bdXtCOrZoXSj2c`#H>uI{58ZQ`Ni{}XEvKM znM|0^=PZ{?X7d?Q9I?H+yTFZDmCd)Dwiv?+#((m`_bUIjTNt2W)iafGP@$5Ig z7OZ4~#f-NUA`mo2ReVipkm8z0?w-4LN>r@1?C&42zrW99GNI9EV69~~n{jw}NSdbP zd0zW(tz|l$VvHfrGaQaDKYTvOR2~Se5fKbZAzB9(4m_pmRYJjGy-pKx77+=j&iUx0 zJ=*OyS(cIKIZ2Y>oFhq6vMgghpOfb~qtOTvL2J!mI3%||E1$dfp0bE=m9oi*2ucjD zq^p8gR4s=A*eoN@z26k4Un~|RNrEv3rBtb$)|x1aP)d;`2|#JiyHB5{8GD4c2E7ny z;%SUBB`Hv9t#YW7Z1F40ygAY|rO{{*MG?(rlPt?>c{7e!m#8RNFOgT3!Fal4yWjF8 z=^R?G6aq+7?^o7Z7K;U1Ypk`H$dG3_cieFYQ5>_F&uKQB2ypfN>kxn`5Jwb7kP4L5 zid7WV!JEjWPG$b*zlKf*ztMCW;b9qY+9ey4^0@+uIBV1J0d0$JW*s^Vy8g zZr)@x8exp#m%n?J$sz%*Q9-6F3eV2ytm}-<)=cSSy(X`h!$0`;MLMyr`BW4|Xst_X z5D`wCIKib$mlzC(WNFHBvGg1%@b14q>gTC+nXDhWyZ zw!NRMV!?S+U*9|Aw}1K<&p!SIy3Hn9YdW0{qtS>{cb;N682W-hz!+bnX(gD*@XsqB za{0yIaP7t|Us0^dPz6o(cFwu(&biX3Ttz8YYic*66yS%?J;QU~x@yJ7GxcBZIc1Fi&wOV}g=|1m$aDzAgag|qIe~a;SQEQOvE#KiqAJ&A?ZBJeSctq;uGFEcu0Shll{1gfCfBvs2ORRC)K7jmUXEG%-B zSyou4ZnIg5P<5bJ4olX?OQF43?Z3%i)nGDZGg63BM^6arKDRmFT9J1!v0Fcklrgmm zslx2kCXZ#Im@lV9twDXNSfhYCeH3}KG@GoEPx#24YJ`L;wH)0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*e^ z6%{Pf@dOtD000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000AuNklmhf&G?&Vz*GLeJeTeGE4?1xqiuSCfG8*kpnwRd%Boy- z<#U<8Gzy=wSHHlHt_V+V3W8#o>kf85sT$U5xxk_|v05>~CTYbWD2bUGtjW$sv*x>apX?zGk3P{>Uiwe_8 zA@c+qBebefTVh>?$sDql5;RNn^*R3KJ}wF|d5(?lbm@Nm5flaLJd_1A4lPbFn~HQ# zz=19Wntl8s=$_zo^>0C|h1NB?Q)A7VB^*1vYtY#dGA@t~$U4YGp#@Z{=fvp>d@krz z;mn{-j`{$TS*-YDBsj(nKt@3KvWqQ{YY9k#Bqit)T?RmbTzGT_RL;`@hoajSyo&3_;W; zpY4-QZsVJpGCM|HO{+I1>fF<&oYU$KK@12O6XIP(H9tkY2SCIS pCRb43;9U*g;a!D#k8LH-{{{NP$d<5`72E&-002ovPDHLkV1h~L)mQ)k literal 0 HcmV?d00001 diff --git a/api-samples/power/keepAwake Basic/images/sunset-38.png b/api-samples/power/keepAwake Basic/images/sunset-38.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb716a210c66927c9eb735613eeafe906cee7a5 GIT binary patch literal 3010 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 Ef^4;eUH||9 literal 0 HcmV?d00001 diff --git a/api-samples/power/keepAwake Basic/manifest.json b/api-samples/power/keepAwake Basic/manifest.json new file mode 100644 index 0000000000..00b569af95 --- /dev/null +++ b/api-samples/power/keepAwake Basic/manifest.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 3, + + "name": "__MSG_extensionName__", + "description": "__MSG_extensionDescription__", + "version": "1.9", + "icons": { + "16": "images/icon-16.png", + "48": "images/icon-48.png", + "128": "images/icon-128.png" + }, + + "permissions": ["power", "storage"], + "action": { + "default_title": "__MSG_disabledTitle__", + "default_icon": { + "19": "images/night-19.png", + "38": "images/night-38.png" + } + }, + "background": { + "service_worker": "background.js" + }, + + "default_locale": "en" +} From 9d827733c5753069f7e1d364ff3fc1bad74323ed Mon Sep 17 00:00:00 2001 From: nielm Date: Thu, 7 Mar 2024 17:13:12 +0100 Subject: [PATCH 3/5] Add popup to select which mode, and configurable timeout Keep backward compatible behavior, and add a context menu option to show the popup. --- .../power/keepAwake Advanced/README.md | 14 +- .../_locales/en/messages.json | 32 ++ .../power/keepAwake Advanced/background.js | 298 ++++++++++++++---- .../power/keepAwake Advanced/common.js | 75 +++++ .../power/keepAwake Advanced/manifest.json | 13 +- .../power/keepAwake Advanced/popup.css | 83 +++++ .../power/keepAwake Advanced/popup.html | 44 +++ api-samples/power/keepAwake Advanced/popup.js | 145 +++++++++ 8 files changed, 639 insertions(+), 65 deletions(-) create mode 100644 api-samples/power/keepAwake Advanced/common.js create mode 100644 api-samples/power/keepAwake Advanced/popup.css create mode 100644 api-samples/power/keepAwake Advanced/popup.html create mode 100644 api-samples/power/keepAwake Advanced/popup.js diff --git a/api-samples/power/keepAwake Advanced/README.md b/api-samples/power/keepAwake Advanced/README.md index 0ef26affe2..6448511b2f 100644 --- a/api-samples/power/keepAwake Advanced/README.md +++ b/api-samples/power/keepAwake Advanced/README.md @@ -4,10 +4,22 @@ This extension demonstrates the `chrome.power` API by allowing users to override ## Overview -The extension adds a popup that cycles different states when clicked. It will go though a mode that prevents the display from dimming or going to sleep, a mode that keeps the system awake but allows the screen to dim/go to sleep, and a mode that uses the system's default. +The extension adds an icon that allows the user to choose different power management states when clicked: + +- System Default +- Screen stays awake +- System stays awake, but screen can sleep + +There is also a context menu popup where the user can also optionally specify an automatic timeout for the chosen state. ## Running this extension +Either install it from the Chrome Web Store: + +- [Keep Awake Extension](https://chrome.google.com/webstore/detail/keep-awake/bijihlabcfdnabacffofojgmehjdielb) + +Or load it as an upacked extension: + 1. Clone this repository. 2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked). 3. Pin the extension and click the action button. diff --git a/api-samples/power/keepAwake Advanced/_locales/en/messages.json b/api-samples/power/keepAwake Advanced/_locales/en/messages.json index ef50944de1..af8dabb29e 100644 --- a/api-samples/power/keepAwake Advanced/_locales/en/messages.json +++ b/api-samples/power/keepAwake Advanced/_locales/en/messages.json @@ -18,5 +18,37 @@ "systemTitle": { "message": "System will stay awake", "description": "Browser action title when preventing system sleep." + }, + "untilText": { + "message": " until: ", + "description": "Suffix to append to above Titles to append an end time" + }, + "autoDisableText": { + "message": "Automatically disable after:", + "description": "Text labelling a slider allowing setting a timeout for disabling the power saving state." + }, + "autoDisableHoursSuffix": { + "message": "h", + "description": "Text to append after a number indicating a quantity of hours" + }, + "disabledLabel": { + "message": "Disabled", + "description": "Button label to indicated keep awake is disabled." + }, + "displayLabel": { + "message": "Screen on", + "description": "Button label to indicated keep awake is preventing screen-off." + }, + "systemLabel": { + "message": "System on", + "description": "Button label to indicated keep awake is preventing system sleep." + }, + "usePopupMenuTitle": { + "message": "Always show State Popup", + "description": "Checkbox item indicating that the popup menu should always be shown." + }, + "openStateWindowMenuTitle": { + "message": "Change State...", + "description": "Menu item opening a popup window to change the state." } } diff --git a/api-samples/power/keepAwake Advanced/background.js b/api-samples/power/keepAwake Advanced/background.js index 59dd9b62a5..75e244d074 100644 --- a/api-samples/power/keepAwake Advanced/background.js +++ b/api-samples/power/keepAwake Advanced/background.js @@ -1,47 +1,45 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +// @ts-check -/** - * States that the extension can be in. - */ -let StateEnum = { - DISABLED: 'disabled', - DISPLAY: 'display', - SYSTEM: 'system' -}; +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -/** - * Key used for storing the current state in {localStorage}. - */ -let STATE_KEY = 'state'; +import { StateEnum, getSavedMode, verifyMode } from './common.js'; +/** @typedef {import('./common.js').KeepAwakeMode} KeepAwakeMode */ + +const ALARM_NAME = 'keepAwakeTimeout'; +const HOUR_TO_MILLIS = 60 * 60 * 1000; +const USE_POPUP_DEFAULT = { usePopup: true }; /** - * Loads the locally-saved state asynchronously. - * @param {function} callback Callback invoked with the loaded {StateEnum}. + * Simple timestamped log function + * @param {string} msg + * @param {...*} args */ -function loadSavedState(callback) { - chrome.storage.local.get(STATE_KEY, function (items) { - let savedState = items[STATE_KEY]; - for (let key in StateEnum) { - if (savedState == StateEnum[key]) { - callback(savedState); - return; - } - } - callback(StateEnum.DISABLED); - }); +function log(msg, ...args) { + console.log(new Date().toLocaleTimeString('short') + ' ' + msg, ...args); } /** - * Switches to a new state. - * @param {string} newState New {StateEnum} to use. + * Set keep awake mode, and update icon. + * + * @param {KeepAwakeMode} mode */ -function setState(newState) { - let imagePrefix = 'night'; - let title = ''; +function updateState(mode) { + let imagePrefix; + let title; - switch (newState) { + switch (mode.state) { case StateEnum.DISABLED: chrome.power.releaseKeepAwake(); imagePrefix = 'night'; @@ -58,42 +56,224 @@ function setState(newState) { title = chrome.i18n.getMessage('systemTitle'); break; default: - throw 'Invalid state "' + newState + '"'; + throw 'Invalid state "' + mode.state + '"'; } - let items = {}; - items[STATE_KEY] = newState; - chrome.storage.local.set(items); - chrome.action.setIcon({ path: { 19: 'images/' + imagePrefix + '-19.png', 38: 'images/' + imagePrefix + '-38.png' } }); - chrome.action.setTitle({ title: title }); + + if (mode.endMillis && mode.state != StateEnum.DISABLED) { + // a timeout is specified, update the badge and the title text + let hoursLeft = Math.ceil((mode.endMillis - Date.now()) / HOUR_TO_MILLIS); + chrome.action.setBadgeText({ text: `${hoursLeft}h` }); + const endDate = new Date(mode.endMillis); + chrome.action.setTitle({ + title: `${title}${chrome.i18n.getMessage('untilText')} ${endDate.toLocaleTimeString('short')}` + }); + log( + `mode = ${mode.state} for the next ${hoursLeft}hrs until ${endDate.toLocaleTimeString('short')}` + ); + } else { + // No timeout. + chrome.action.setBadgeText({ text: '' }); + chrome.action.setTitle({ title: title }); + log(`mode = ${mode.state}`); + } } -chrome.action.onClicked.addListener(function () { - loadSavedState(function (state) { - switch (state) { - case StateEnum.DISABLED: - setState(StateEnum.DISPLAY); - break; - case StateEnum.DISPLAY: - setState(StateEnum.SYSTEM); - break; - case StateEnum.SYSTEM: - setState(StateEnum.DISABLED); - break; - default: - throw 'Invalid state "' + state + '"'; - } +/** + * Apply a new KeepAwake mode. + * + * @param {KeepAwakeMode} newMode + */ +async function setNewMode(newMode) { + // Clear any old alarms + await chrome.alarms.clearAll(); + + // is a timeout required? + if (newMode.defaultDurationHrs && newMode.state !== StateEnum.DISABLED) { + // Set an alarm every 60 mins. + chrome.alarms.create(ALARM_NAME, { + delayInMinutes: 60, + periodInMinutes: 60 + }); + newMode.endMillis = + Date.now() + newMode.defaultDurationHrs * HOUR_TO_MILLIS; + } else { + newMode.endMillis = null; + } + + // Store the new mode. + chrome.storage.local.set(newMode); + updateState(newMode); +} + +/** + * Check to see if any set timeout has expired, and if so, reset the mode. + */ +async function checkTimeoutAndUpdateDisplay() { + const mode = await getSavedMode(); + if (mode.endMillis && mode.endMillis < Date.now()) { + log(`timer expired`); + // reset state to disabled + mode.state = StateEnum.DISABLED; + mode.endMillis = null; + setNewMode(mode); + } else { + updateState(mode); + } +} + +async function recreateAlarms() { + const mode = await getSavedMode(); + await chrome.alarms.clearAll(); + if ( + mode.state !== StateEnum.DISABLED && + mode.endMillis && + mode.endMillis > Date.now() + ) { + // previous timeout has not yet expired... + // restart alarm to be triggered at the next 1hr of the timeout + const remainingMillis = mode.endMillis - Date.now(); + const millisToNextHour = remainingMillis % HOUR_TO_MILLIS; + + log( + `recreating alarm, next = ${new Date(Date.now() + millisToNextHour).toLocaleTimeString()}` + ); + chrome.alarms.create(ALARM_NAME, { + delayInMinutes: millisToNextHour / 60_000, + periodInMinutes: 60 + }); + } +} + +/** + * Creates the context menu buttons on the action icon. + */ +async function reCreateContextMenus() { + chrome.contextMenus.removeAll(); + + chrome.contextMenus.create({ + type: 'normal', + id: 'openStateMenu', + title: chrome.i18n.getMessage('openStateWindowMenuTitle'), + contexts: ['action'] + }); + chrome.contextMenus.create({ + type: 'checkbox', + checked: USE_POPUP_DEFAULT.usePopup, + id: 'usePopupMenu', + title: chrome.i18n.getMessage('usePopupMenuTitle'), + contexts: ['action'] }); + + updateUsePopupMenu( + (await chrome.storage.sync.get(USE_POPUP_DEFAULT)).usePopup + ); +} + +/** + * Sets whether or not to use the popup menu when clicking on the action icon. + * + * @param {boolean} usePopup + */ +function updateUsePopupMenu(usePopup) { + chrome.contextMenus.update('usePopupMenu', { checked: usePopup }); + if (usePopup) { + chrome.action.setPopup({ popup: 'popup.html' }); + } else { + chrome.action.setPopup({ popup: '' }); + } +} + +// Handle messages received from the popup. +chrome.runtime.onMessage.addListener(function (request, _, sendResponse) { + log( + `Got message from popup: state: %s, duration: %d`, + request.state, + request.duration + ); + sendResponse({}); + + setNewMode( + verifyMode({ + state: request.state, + defaultDurationHrs: request.duration, + endMillis: null + }) + ); }); -chrome.runtime.onStartup.addListener(function () { - loadSavedState(function (state) { - setState(state); - }); +// Handle action clicks - rotates the mode to the next mode. +chrome.action.onClicked.addListener(async () => { + log(`Action clicked`); + + const mode = await getSavedMode(); + switch (mode.state) { + case StateEnum.DISABLED: + mode.state = StateEnum.DISPLAY; + break; + case StateEnum.DISPLAY: + mode.state = StateEnum.SYSTEM; + break; + case StateEnum.SYSTEM: + mode.state = StateEnum.DISABLED; + break; + default: + throw 'Invalid state "' + mode.state + '"'; + } + setNewMode(mode); }); + +// Handle context menu clicks +chrome.contextMenus.onClicked.addListener(async (e) => { + switch (e.menuItemId) { + case 'openStateMenu': + chrome.windows.create({ + focused: true, + height: 220, + width: 240, + type: 'popup', + url: './popup.html' + }); + break; + + case 'usePopupMenu': + // e.checked is new state, after being clicked. + chrome.storage.sync.set({ usePopup: !!e.checked }); + updateUsePopupMenu(!!e.checked); + break; + } +}); + +// Whenever the alarm is triggered check the timeout and update the icon. +chrome.alarms.onAlarm.addListener(() => { + log('alarm!'); + checkTimeoutAndUpdateDisplay(); +}); + +chrome.runtime.onStartup.addListener(async () => { + log('onStartup'); + recreateAlarms(); + reCreateContextMenus(); +}); + +chrome.runtime.onInstalled.addListener(async () => { + log('onInstalled'); + recreateAlarms(); + reCreateContextMenus(); +}); + +chrome.storage.sync.onChanged.addListener((changes) => { + if (changes.usePopup != null) { + log('usePopup changed to %s', changes.usePopup.newValue); + updateUsePopupMenu(!!changes.usePopup.newValue); + } +}); + +// Whenever the service worker starts up, check the timeout and update the state +checkTimeoutAndUpdateDisplay(); diff --git a/api-samples/power/keepAwake Advanced/common.js b/api-samples/power/keepAwake Advanced/common.js new file mode 100644 index 0000000000..4a82904c78 --- /dev/null +++ b/api-samples/power/keepAwake Advanced/common.js @@ -0,0 +1,75 @@ +// @ts-check + +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export const DURATION_FOR_EVER = 13; + +/** + * States that the extension can be in. + * @readonly + * @enum {string} + */ +export const StateEnum = { + DISABLED: 'disabled', + DISPLAY: 'display', + SYSTEM: 'system' +}; + +/** + * @typedef {{ + * state: StateEnum; + * defaultDurationHrs: number?; + * endMillis: number | null | undefined; + * }} KeepAwakeMode + */ + +/** + * Key used for storing the current state in {localStorage}. + * @type {KeepAwakeMode} + */ +const DEFAULT_MODE = { + state: StateEnum.DISABLED, + defaultDurationHrs: null, // no timeout. + endMillis: null +}; + +/** + * Gets the saved Keep Awake mode from local storage + * @return {Promise} + */ +export async function getSavedMode() { + let mode = await chrome.storage.local.get(DEFAULT_MODE); + return verifyMode(mode); +} + +/** + * Validates the values of the keepawake mode + * + * @param {*} mode + * @return {KeepAwakeMode} + */ +export function verifyMode(mode) { + if (!Object.values(StateEnum).includes(mode.state)) { + mode.state = DEFAULT_MODE.state; + } + mode.defaultDurationHrs = Number(mode.defaultDurationHrs); + if ( + mode.defaultDurationHrs < 1 || + mode.defaultDurationHrs >= DURATION_FOR_EVER + ) { + mode.defaultDurationHrs = null; // no timeout. + } + return mode; +} diff --git a/api-samples/power/keepAwake Advanced/manifest.json b/api-samples/power/keepAwake Advanced/manifest.json index 00b569af95..7cd7010958 100644 --- a/api-samples/power/keepAwake Advanced/manifest.json +++ b/api-samples/power/keepAwake Advanced/manifest.json @@ -1,26 +1,29 @@ { + "update_url": "https://clients2.google.com/service/update2/crx", + "manifest_version": 3, "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", - "version": "1.9", + "version": "2.0", "icons": { "16": "images/icon-16.png", "48": "images/icon-48.png", "128": "images/icon-128.png" }, - "permissions": ["power", "storage"], + "permissions": ["power", "storage", "alarms", "contextMenus"], "action": { "default_title": "__MSG_disabledTitle__", "default_icon": { "19": "images/night-19.png", "38": "images/night-38.png" - } + }, + "default_popup": "popup.html" }, "background": { - "service_worker": "background.js" + "service_worker": "background.js", + "type": "module" }, - "default_locale": "en" } diff --git a/api-samples/power/keepAwake Advanced/popup.css b/api-samples/power/keepAwake Advanced/popup.css new file mode 100644 index 0000000000..cbccdd566d --- /dev/null +++ b/api-samples/power/keepAwake Advanced/popup.css @@ -0,0 +1,83 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:root { + --background-color: #e8eef7; + --border-color: #00b4fe; + --main-color: #000000; + --button-background-color: #d5deec; + --button-active-background-color: #c0ccde; +} + +@media (prefers-color-scheme: dark) { + body { + --background-color: #333333; + --main-color: #dddddd; + --button-background-color: #444444; + --button-active-background-color: #555555; + --border-color: #005b80; + } +} + +body { + font: 13px arial, sans-serif; + margin: 0px; + background-color: var(--background-color); + color: var(--main-color); +} + +#popup { + padding: 12px; + border: 3px solid var(--border-color); + margin: auto; + width: fit-content; + text-align: left; + text-wrap: nowrap; +} + +h3 { + margin: 0px; +} + +div { + margin-top:5px; +} + +div#buttons { + display:flex; + gap: 6px; +} + +div#buttons button { + flex: 1 0 0; + min-width: 0; + background-color: var(--button-background-color); + color: var(--main-color); + border: 0; + border-radius: 0.5em; + box-shadow: 3px 3px 2px rgba(0,0,0,0.5); + padding: 5px; +} +div#buttons button:active, div#buttons button.active { + top:2px; + left:1px; + box-shadow: inset 3px 3px 2px rgba(0,0,0,0.5); + background-color: var(--button-active-background-color); +} + +.buttonLabel { + font-size: 11px; +} diff --git a/api-samples/power/keepAwake Advanced/popup.html b/api-samples/power/keepAwake Advanced/popup.html new file mode 100644 index 0000000000..8c1aef91d1 --- /dev/null +++ b/api-samples/power/keepAwake Advanced/popup.html @@ -0,0 +1,44 @@ + + + + Keep Awake + + + + + +

+ + diff --git a/api-samples/power/keepAwake Advanced/popup.js b/api-samples/power/keepAwake Advanced/popup.js new file mode 100644 index 0000000000..dfb87501a3 --- /dev/null +++ b/api-samples/power/keepAwake Advanced/popup.js @@ -0,0 +1,145 @@ +// @ts-check + +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { DURATION_FOR_EVER, getSavedMode, StateEnum } from './common.js'; + +/** @type {HTMLLabelElement} */ +let durationLabel; +/** @type {HTMLInputElement} */ +let durationSlider; + +/** + * Get an HTML element by selector and asserts not null. + * @param {string} query + * @return {HTMLElement} + */ +function querySelectorAndAssert(query) { + const e = document.querySelector(query); + if (!e) { + throw new Error('unable to get element with query: ' + query); + } + return /** @type {HTMLElement} */ (e); +} + +/** + * Updates the label for the duration slider. + */ +function updateDurationLabel() { + durationLabel.textContent = + (durationSlider.value === DURATION_FOR_EVER.toString() + ? '∞' + : durationSlider.value) + + ' ' + + chrome.i18n.getMessage('autoDisableHoursSuffix'); +} + +/** + * Called when the duration slider is changed + * + * Sends a message to the background service worker to set the new state + * and duration. + */ +async function durationSliderChanged() { + updateDurationLabel(); + + await sendMessageToBackground( + (await getSavedMode()).state, + Number(durationSlider.value) + ); +} + +/** + * Sends a message to the background service worker to set the new state and + * duration. + * @param {string} state + * @param {number} duration + */ +async function sendMessageToBackground(state, duration) { + if (!Object.values(StateEnum).includes(state)) { + throw new Error('invalid State: ' + state); + } + + const message = { state: state }; + if (duration < 1 || duration >= DURATION_FOR_EVER) { + // no timeout + message.duration = null; + } else { + message.duration = duration; + } + + chrome.runtime.sendMessage(message); +} + +/** + * Called when one of the keepalive buttons is clicked. + * + * Sends a message to the background service worker to set the new state + * and duration. + * + * @param {MouseEvent} e + */ +async function buttonClicked(e) { + const button = /** @type {HTMLElement} */ (e.currentTarget); + await sendMessageToBackground( + // Button id is named after state. + button.id, + Number(durationSlider.value) + ); + // Re-set active button state. + document.querySelector(`#buttons .active`)?.classList?.remove('active'); + button.classList.add('active'); +} + +/** + * Run when document is loaded. + * + * Initializes the popup, and sets I18n labels. + */ +async function onload() { + durationSlider = /** @type {HTMLInputElement} */ ( + querySelectorAndAssert('#durationSlider') + ); + durationLabel = /** @type {HTMLLabelElement} */ ( + querySelectorAndAssert('#durationLabel') + ); + + querySelectorAndAssert('#title').textContent = + chrome.i18n.getMessage('extensionName'); + querySelectorAndAssert('#autodisable-text').title = + chrome.i18n.getMessage('autoDisableText'); + + // set button titles and listeners + for (const id of Object.values(StateEnum)) { + const button = querySelectorAndAssert(`#buttons #${id}`); + button.addEventListener('click', buttonClicked); + button.title = chrome.i18n.getMessage(button.id + 'Title'); + querySelectorAndAssert(`#buttons #${id} .buttonLabel`).textContent = + chrome.i18n.getMessage(button.id + 'Label'); + } + + // set active button state. Assumes buttons have same IDs as state names. + const mode = await getSavedMode(); + querySelectorAndAssert(`#buttons #${mode.state}`).classList?.add('active'); + + durationSlider.max = DURATION_FOR_EVER.toString(); + durationSlider.value = ( + mode.defaultDurationHrs ? mode.defaultDurationHrs : DURATION_FOR_EVER + ).toString(); + updateDurationLabel(); + durationSlider.addEventListener('input', durationSliderChanged); +} + +document.addEventListener('DOMContentLoaded', onload); From 445e8ffcbfc9b41ea95e33848c412d993c713b7b Mon Sep 17 00:00:00 2001 From: nielm Date: Thu, 7 Mar 2024 17:19:11 +0100 Subject: [PATCH 4/5] Review comments --- api-samples/power/keepAwake Advanced/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api-samples/power/keepAwake Advanced/README.md b/api-samples/power/keepAwake Advanced/README.md index 6448511b2f..76ae325dcc 100644 --- a/api-samples/power/keepAwake Advanced/README.md +++ b/api-samples/power/keepAwake Advanced/README.md @@ -10,7 +10,9 @@ The extension adds an icon that allows the user to choose different power manage - Screen stays awake - System stays awake, but screen can sleep -There is also a context menu popup where the user can also optionally specify an automatic timeout for the chosen state. +There is also a popup where the user can also optionally specify an automatic +timeout for the chosen state. This popup can be triggered by clicking the icon +or by selecting it from the icon's context menu. ## Running this extension From 3eaf5905cc597f20727b1afdb1aaf7c67c9d613c Mon Sep 17 00:00:00 2001 From: nielm Date: Thu, 30 May 2024 13:37:18 +0200 Subject: [PATCH 5/5] Add autodisable time indicator to popup menu When a keepawake with autodisable timeeout is active, show the message "Automatically disable at HH:MM" in the popup --- .../_locales/en/messages.json | 6 ++- .../power/keepAwake Advanced/background.js | 16 +++++-- .../power/keepAwake Advanced/popup.html | 18 +++---- api-samples/power/keepAwake Advanced/popup.js | 47 ++++++++++++++----- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/api-samples/power/keepAwake Advanced/_locales/en/messages.json b/api-samples/power/keepAwake Advanced/_locales/en/messages.json index af8dabb29e..a37e542a31 100644 --- a/api-samples/power/keepAwake Advanced/_locales/en/messages.json +++ b/api-samples/power/keepAwake Advanced/_locales/en/messages.json @@ -23,10 +23,14 @@ "message": " until: ", "description": "Suffix to append to above Titles to append an end time" }, - "autoDisableText": { + "autoDisableAfterText": { "message": "Automatically disable after:", "description": "Text labelling a slider allowing setting a timeout for disabling the power saving state." }, + "autoDisableAtText": { + "message": "Automatically disable at: ", + "description": "Prefix Text indicating the time when the state will automatically switch to disabled." + }, "autoDisableHoursSuffix": { "message": "h", "description": "Text to append after a number indicating a quantity of hours" diff --git a/api-samples/power/keepAwake Advanced/background.js b/api-samples/power/keepAwake Advanced/background.js index 75e244d074..79f66ace1d 100644 --- a/api-samples/power/keepAwake Advanced/background.js +++ b/api-samples/power/keepAwake Advanced/background.js @@ -72,10 +72,10 @@ function updateState(mode) { chrome.action.setBadgeText({ text: `${hoursLeft}h` }); const endDate = new Date(mode.endMillis); chrome.action.setTitle({ - title: `${title}${chrome.i18n.getMessage('untilText')} ${endDate.toLocaleTimeString('short')}` + title: `${title}${chrome.i18n.getMessage('untilText')} ${endDate.toLocaleTimeString(undefined, { timeStyle: 'short' })}` }); log( - `mode = ${mode.state} for the next ${hoursLeft}hrs until ${endDate.toLocaleTimeString('short')}` + `mode = ${mode.state} for the next ${hoursLeft}hrs until ${endDate.toLocaleTimeString()}` ); } else { // No timeout. @@ -86,9 +86,11 @@ function updateState(mode) { } /** + * * Apply a new KeepAwake mode. * * @param {KeepAwakeMode} newMode + * @return {Promise} */ async function setNewMode(newMode) { // Clear any old alarms @@ -110,6 +112,7 @@ async function setNewMode(newMode) { // Store the new mode. chrome.storage.local.set(newMode); updateState(newMode); + return newMode; } /** @@ -197,7 +200,6 @@ chrome.runtime.onMessage.addListener(function (request, _, sendResponse) { request.state, request.duration ); - sendResponse({}); setNewMode( verifyMode({ @@ -205,7 +207,13 @@ chrome.runtime.onMessage.addListener(function (request, _, sendResponse) { defaultDurationHrs: request.duration, endMillis: null }) - ); + ) + .then((newMode) => sendResponse(newMode)) + .catch((e) => { + log(`failed to set new mode: ${e}`, e); + sendResponse(null); + }); + return true; // sendResponse() called asynchronously }); // Handle action clicks - rotates the mode to the next mode. diff --git a/api-samples/power/keepAwake Advanced/popup.html b/api-samples/power/keepAwake Advanced/popup.html index 8c1aef91d1..f62c7a2f79 100644 --- a/api-samples/power/keepAwake Advanced/popup.html +++ b/api-samples/power/keepAwake Advanced/popup.html @@ -1,32 +1,32 @@ - Keep Awake + #extensionName