diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-02-08 17:52:41 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-08 17:52:41 -0500 |
commit | 849e4fabe1dffc2851fcb338dae8400d6c8e46ca (patch) | |
tree | e49dbd86ec20a2a11fc8938b8f6a4e1a9c57501e | |
parent | 008809e0e73665249b7b3dd7c14a1761aa06bd39 (diff) |
Native messaging optional permission (#1348)
* Refactor PermissionsToggleController to not require a setting
* Update nativeMessaging to be optional on Chrome
* Update parsing.enableMecabParser setting to request permissions
* Update permissions page to use PermissionsToggleController
* Update permissions documentation
* Disable toggle for permissions which are not optional
-rw-r--r-- | dev/data/manifest-variants.json | 14 | ||||
-rw-r--r-- | docs/permissions.md | 13 | ||||
-rw-r--r-- | ext/bg/js/permissions-main.js | 65 | ||||
-rw-r--r-- | ext/bg/js/settings/permissions-toggle-controller.js | 42 | ||||
-rw-r--r-- | ext/bg/permissions.html | 31 | ||||
-rw-r--r-- | ext/bg/settings.html | 2 | ||||
-rw-r--r-- | ext/bg/settings2.html | 2 | ||||
-rw-r--r-- | ext/manifest.json | 4 |
8 files changed, 99 insertions, 74 deletions
diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 43ea50c0..d221d994 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -79,12 +79,12 @@ "storage", "clipboardWrite", "unlimitedStorage", - "nativeMessaging", "webRequest", "webRequestBlocking" ], "optional_permissions": [ - "clipboardRead" + "clipboardRead", + "nativeMessaging" ], "commands": { "toggleTextScanning": { @@ -204,6 +204,16 @@ "strict_min_version": "57.0" } } + }, + { + "action": "remove", + "path": ["optional_permissions"], + "item": "nativeMessaging" + }, + { + "action": "add", + "path": ["permissions"], + "items": ["nativeMessaging"] } ], "excludeFiles": [ diff --git a/docs/permissions.md b/docs/permissions.md index 49e201a6..b337bb31 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -23,12 +23,6 @@ Yomichan will sometimes need to inject stylesheets into webpages in order to properly display the search popup. -* `nativeMessaging` <br> - Yomichan has the ability to communicate with an optional native messaging component in order to support - parsing large blocks of Japanese text using - [MeCab](https://en.wikipedia.org/wiki/MeCab). - The installation of this component is optional and is not included by default. - * `clipboardWrite` <br> Yomichan supports simulating the `Ctrl+C` (copy to clipboard) keyboard shortcut when a definitions popup is open and focused. @@ -38,3 +32,10 @@ while the browser is running, depending on how certain settings are configured. This allows Yomichan to support scanning text from external applications, provided there is a way to copy text from those applications to the clipboard. + +* `nativeMessaging` (optional on Chrome) <br> + Yomichan has the ability to communicate with an optional native messaging component in order to support + parsing large blocks of Japanese text using + [MeCab](https://en.wikipedia.org/wiki/MeCab). + The installation of this component is optional and is not included by default. + This permission is optional on Chrome, but required on Firefox, because Firefox does not permit it to be optional. diff --git a/ext/bg/js/permissions-main.js b/ext/bg/js/permissions-main.js index 366a057b..5b17a5dd 100644 --- a/ext/bg/js/permissions-main.js +++ b/ext/bg/js/permissions-main.js @@ -17,6 +17,8 @@ /* global * DocumentFocusController + * PermissionsToggleController + * SettingsController * api */ @@ -36,45 +38,24 @@ async function isAllowedFileSchemeAccess() { return await new Promise((resolve) => chrome.extension.isAllowedFileSchemeAccess(resolve)); } -function hasPermissions(permissions) { - return new Promise((resolve) => chrome.permissions.contains({permissions}, (result) => { - const e = chrome.runtime.lastError; - resolve(!e && result); - })); -} - -function setPermissionsGranted(permissions, shouldHave) { - return ( - shouldHave ? - new Promise((resolve, reject) => chrome.permissions.request({permissions}, (result) => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(result); - } - })) : - new Promise((resolve, reject) => chrome.permissions.remove({permissions}, (result) => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(!result); - } - })) - ); -} +function setupPermissionsToggles() { + const manifest = chrome.runtime.getManifest(); + let optionalPermissions = manifest.optional_permissions; + if (!Array.isArray(optionalPermissions)) { optionalPermissions = []; } + optionalPermissions = new Set(optionalPermissions); -function setupPermissionCheckbox(checkbox, permissions) { - checkbox.addEventListener('change', (e) => { - updatePermissionCheckbox(checkbox, permissions, e.currentTarget.checked); - }, false); -} + const hasAllPermisions = (set, values) => { + for (const value of values) { + if (!set.has(value)) { return false; } + } + return true; + }; -async function updatePermissionCheckbox(checkbox, permissions, value) { - checkbox.checked = !value; - const hasPermission = await setPermissionsGranted(permissions, value); - checkbox.checked = hasPermission; + for (const toggle of document.querySelectorAll('.permissions-toggle')) { + let permissions = toggle.dataset.requiredPermissions; + permissions = (typeof permissions === 'string' && permissions.length > 0 ? permissions.split(' ') : []); + toggle.disabled = !hasAllPermisions(optionalPermissions, permissions); + } } (async () => { @@ -82,6 +63,8 @@ async function updatePermissionCheckbox(checkbox, permissions, value) { const documentFocusController = new DocumentFocusController(); documentFocusController.prepare(); + setupPermissionsToggles(); + for (const node of document.querySelectorAll('.extension-id-example')) { node.textContent = chrome.runtime.getURL('/'); } @@ -92,13 +75,11 @@ async function updatePermissionCheckbox(checkbox, permissions, value) { setupEnvironmentInfo(); const permissionsCheckboxes = [ - document.querySelector('#permission-checkbox-clipboard-read'), document.querySelector('#permission-checkbox-allow-in-private-windows'), document.querySelector('#permission-checkbox-allow-file-url-access') ]; const permissions = await Promise.all([ - hasPermissions(['clipboardRead']), isAllowedIncognitoAccess(), isAllowedFileSchemeAccess() ]); @@ -107,7 +88,11 @@ async function updatePermissionCheckbox(checkbox, permissions, value) { permissionsCheckboxes[i].checked = permissions[i]; } - setupPermissionCheckbox(permissionsCheckboxes[0], ['clipboardRead']); + const settingsController = new SettingsController(0); + settingsController.prepare(); + + const permissionsToggleController = new PermissionsToggleController(settingsController); + permissionsToggleController.prepare(); await promiseTimeout(100); diff --git a/ext/bg/js/settings/permissions-toggle-controller.js b/ext/bg/js/settings/permissions-toggle-controller.js index 07db1cf8..2e58ec67 100644 --- a/ext/bg/js/settings/permissions-toggle-controller.js +++ b/ext/bg/js/settings/permissions-toggle-controller.js @@ -41,9 +41,16 @@ class PermissionsToggleController { // Private _onOptionsChanged({options}) { - const accessor = new ObjectPropertyAccessor(options); + let accessor = null; for (const toggle of this._toggles) { - const path = ObjectPropertyAccessor.getPathArray(toggle.dataset.permissionsSetting); + const {permissionsSetting} = toggle.dataset; + if (typeof permissionsSetting !== 'string') { continue; } + + if (accessor === null) { + accessor = new ObjectPropertyAccessor(options); + } + + const path = ObjectPropertyAccessor.getPathArray(permissionsSetting); let value; try { value = accessor.get(path, path.length); @@ -58,23 +65,38 @@ class PermissionsToggleController { async _onPermissionsToggleChange(e) { const toggle = e.currentTarget; let value = toggle.checked; + const valuePre = !value; + const {permissionsSetting} = toggle.dataset; + const hasPermissionsSetting = typeof permissionsSetting === 'string'; - if (value) { - toggle.checked = false; - value = await this._settingsController.setPermissionsGranted(this._getRequiredPermissions(toggle), true); + if (value || !hasPermissionsSetting) { + toggle.checked = valuePre; + try { + value = await this._settingsController.setPermissionsGranted(this._getRequiredPermissions(toggle), value); + } catch (error) { + value = valuePre; + } toggle.checked = value; } - this._setToggleValid(toggle, true); - - await this._settingsController.setProfileSetting(toggle.dataset.permissionsSetting, value); + if (hasPermissionsSetting) { + this._setToggleValid(toggle, true); + await this._settingsController.setProfileSetting(permissionsSetting, value); + } } _onPermissionsChanged({permissions: {permissions}}) { const permissionsSet = new Set(permissions); for (const toggle of this._toggles) { - const valid = !toggle.checked || this._hasAll(permissionsSet, this._getRequiredPermissions(toggle)); - this._setToggleValid(toggle, valid); + const {permissionsSetting} = toggle.dataset; + const hasPermissions = this._hasAll(permissionsSet, this._getRequiredPermissions(toggle)); + + if (typeof permissionsSetting === 'string') { + const valid = !toggle.checked || hasPermissions; + this._setToggleValid(toggle, valid); + } else { + toggle.checked = hasPermissions; + } } } diff --git a/ext/bg/permissions.html b/ext/bg/permissions.html index 57b4b215..45c65a65 100644 --- a/ext/bg/permissions.html +++ b/ext/bg/permissions.html @@ -86,17 +86,6 @@ </div></div> <div class="settings-item"><div class="settings-item-inner"> <div class="settings-item-left"> - <div class="settings-item-label"><code>nativeMessaging</code></div> - <div class="settings-item-description"> - Yomichan has the ability to communicate with an optional native messaging component in order to support - parsing large blocks of Japanese text using - <a href="https://en.wikipedia.org/wiki/MeCab" target="_blank" rel="noopener noreferrer">MeCab</a>. - The installation of this component is optional and is not included by default. - </div> - </div> - </div></div> - <div class="settings-item"><div class="settings-item-inner"> - <div class="settings-item-left"> <div class="settings-item-label"><code>clipboardWrite</code></div> <div class="settings-item-description"> Yomichan supports simulating the <code>Ctrl+C</code> (copy to clipboard) keyboard shortcut @@ -115,7 +104,21 @@ </div> </div> <div class="settings-item-right"> - <label class="toggle"><input type="checkbox" id="permission-checkbox-clipboard-read"><span class="toggle-body"><span class="toggle-track"></span><span class="toggle-knob"></span></span></label> + <label class="toggle"><input type="checkbox" class="permissions-toggle" data-required-permissions="clipboardRead"><span class="toggle-body"><span class="toggle-track"></span><span class="toggle-knob"></span></span></label> + </div> + </div></div> + <div class="settings-item"><div class="settings-item-inner"> + <div class="settings-item-left"> + <div class="settings-item-label"><code>nativeMessaging</code> <span class="light" data-show-for-browser="chrome edge">(optional)</span></div> + <div class="settings-item-description"> + Yomichan has the ability to communicate with an optional native messaging component in order to support + parsing large blocks of Japanese text using + <a href="https://en.wikipedia.org/wiki/MeCab" target="_blank" rel="noopener noreferrer">MeCab</a>. + The installation of this component is optional and is not included by default. + </div> + </div> + <div class="settings-item-right"> + <label class="toggle"><input type="checkbox" class="permissions-toggle" data-required-permissions="nativeMessaging"><span class="toggle-body"><span class="toggle-track"></span><span class="toggle-knob"></span></span></label> </div> </div></div> <div class="settings-item"><div class="settings-item-inner"> @@ -164,6 +167,10 @@ <script src="/mixed/js/api.js"></script> <script src="/mixed/js/document-focus-controller.js"></script> +<script src="/mixed/js/html-template-collection.js"></script> + +<script src="/bg/js/settings/permissions-toggle-controller.js"></script> +<script src="/bg/js/settings/settings-controller.js"></script> <script src="/bg/js/permissions-main.js"></script> diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3064e97a..a087ba95 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -682,7 +682,7 @@ </div> <div class="checkbox"> - <label><input type="checkbox" id="parsing-mecab-enable" data-setting="parsing.enableMecabParser"> Enable text parsing using MeCab</label> + <label><input type="checkbox" id="parsing-mecab-enable" class="permissions-toggle" data-permissions-setting="parsing.enableMecabParser" data-required-permissions="nativeMessaging"> Enable text parsing using MeCab</label> </div> <div class="checkbox"> diff --git a/ext/bg/settings2.html b/ext/bg/settings2.html index edaa90af..5dd85dd2 100644 --- a/ext/bg/settings2.html +++ b/ext/bg/settings2.html @@ -1151,7 +1151,7 @@ </div> </div> <div class="settings-item-right"> - <label class="toggle"><input type="checkbox" data-setting="parsing.enableMecabParser"><span class="toggle-body"><span class="toggle-track"></span><span class="toggle-knob"></span></span></label> + <label class="toggle"><input type="checkbox" class="permissions-toggle" data-permissions-setting="parsing.enableMecabParser" data-required-permissions="nativeMessaging"><span class="toggle-body"><span class="toggle-track"></span><span class="toggle-knob"></span></span></label> </div> </div> <div class="settings-item-children more" hidden> diff --git a/ext/manifest.json b/ext/manifest.json index 5f9a29fd..54df2a89 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -78,12 +78,12 @@ "storage", "clipboardWrite", "unlimitedStorage", - "nativeMessaging", "webRequest", "webRequestBlocking" ], "optional_permissions": [ - "clipboardRead" + "clipboardRead", + "nativeMessaging" ], "commands": { "toggleTextScanning": { |