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
This commit is contained in:
parent
008809e0e7
commit
849e4fabe1
@ -79,12 +79,12 @@
|
|||||||
"storage",
|
"storage",
|
||||||
"clipboardWrite",
|
"clipboardWrite",
|
||||||
"unlimitedStorage",
|
"unlimitedStorage",
|
||||||
"nativeMessaging",
|
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"webRequestBlocking"
|
"webRequestBlocking"
|
||||||
],
|
],
|
||||||
"optional_permissions": [
|
"optional_permissions": [
|
||||||
"clipboardRead"
|
"clipboardRead",
|
||||||
|
"nativeMessaging"
|
||||||
],
|
],
|
||||||
"commands": {
|
"commands": {
|
||||||
"toggleTextScanning": {
|
"toggleTextScanning": {
|
||||||
@ -204,6 +204,16 @@
|
|||||||
"strict_min_version": "57.0"
|
"strict_min_version": "57.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "remove",
|
||||||
|
"path": ["optional_permissions"],
|
||||||
|
"item": "nativeMessaging"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "add",
|
||||||
|
"path": ["permissions"],
|
||||||
|
"items": ["nativeMessaging"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"excludeFiles": [
|
"excludeFiles": [
|
||||||
|
@ -23,12 +23,6 @@
|
|||||||
Yomichan will sometimes need to inject stylesheets into webpages in order to
|
Yomichan will sometimes need to inject stylesheets into webpages in order to
|
||||||
properly display the search popup.
|
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>
|
* `clipboardWrite` <br>
|
||||||
Yomichan supports simulating the `Ctrl+C` (copy to clipboard) keyboard shortcut
|
Yomichan supports simulating the `Ctrl+C` (copy to clipboard) keyboard shortcut
|
||||||
when a definitions popup is open and focused.
|
when a definitions popup is open and focused.
|
||||||
@ -38,3 +32,10 @@
|
|||||||
while the browser is running, depending on how certain settings are configured.
|
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
|
This allows Yomichan to support scanning text from external applications, provided there is a way
|
||||||
to copy text from those applications to the clipboard.
|
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.
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
/* global
|
/* global
|
||||||
* DocumentFocusController
|
* DocumentFocusController
|
||||||
|
* PermissionsToggleController
|
||||||
|
* SettingsController
|
||||||
* api
|
* api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -36,45 +38,24 @@ async function isAllowedFileSchemeAccess() {
|
|||||||
return await new Promise((resolve) => chrome.extension.isAllowedFileSchemeAccess(resolve));
|
return await new Promise((resolve) => chrome.extension.isAllowedFileSchemeAccess(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasPermissions(permissions) {
|
function setupPermissionsToggles() {
|
||||||
return new Promise((resolve) => chrome.permissions.contains({permissions}, (result) => {
|
const manifest = chrome.runtime.getManifest();
|
||||||
const e = chrome.runtime.lastError;
|
let optionalPermissions = manifest.optional_permissions;
|
||||||
resolve(!e && result);
|
if (!Array.isArray(optionalPermissions)) { optionalPermissions = []; }
|
||||||
}));
|
optionalPermissions = new Set(optionalPermissions);
|
||||||
}
|
|
||||||
|
|
||||||
function setPermissionsGranted(permissions, shouldHave) {
|
const hasAllPermisions = (set, values) => {
|
||||||
return (
|
for (const value of values) {
|
||||||
shouldHave ?
|
if (!set.has(value)) { return false; }
|
||||||
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);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
function setupPermissionCheckbox(checkbox, permissions) {
|
for (const toggle of document.querySelectorAll('.permissions-toggle')) {
|
||||||
checkbox.addEventListener('change', (e) => {
|
let permissions = toggle.dataset.requiredPermissions;
|
||||||
updatePermissionCheckbox(checkbox, permissions, e.currentTarget.checked);
|
permissions = (typeof permissions === 'string' && permissions.length > 0 ? permissions.split(' ') : []);
|
||||||
}, false);
|
toggle.disabled = !hasAllPermisions(optionalPermissions, permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updatePermissionCheckbox(checkbox, permissions, value) {
|
|
||||||
checkbox.checked = !value;
|
|
||||||
const hasPermission = await setPermissionsGranted(permissions, value);
|
|
||||||
checkbox.checked = hasPermission;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -82,6 +63,8 @@ async function updatePermissionCheckbox(checkbox, permissions, value) {
|
|||||||
const documentFocusController = new DocumentFocusController();
|
const documentFocusController = new DocumentFocusController();
|
||||||
documentFocusController.prepare();
|
documentFocusController.prepare();
|
||||||
|
|
||||||
|
setupPermissionsToggles();
|
||||||
|
|
||||||
for (const node of document.querySelectorAll('.extension-id-example')) {
|
for (const node of document.querySelectorAll('.extension-id-example')) {
|
||||||
node.textContent = chrome.runtime.getURL('/');
|
node.textContent = chrome.runtime.getURL('/');
|
||||||
}
|
}
|
||||||
@ -92,13 +75,11 @@ async function updatePermissionCheckbox(checkbox, permissions, value) {
|
|||||||
setupEnvironmentInfo();
|
setupEnvironmentInfo();
|
||||||
|
|
||||||
const permissionsCheckboxes = [
|
const permissionsCheckboxes = [
|
||||||
document.querySelector('#permission-checkbox-clipboard-read'),
|
|
||||||
document.querySelector('#permission-checkbox-allow-in-private-windows'),
|
document.querySelector('#permission-checkbox-allow-in-private-windows'),
|
||||||
document.querySelector('#permission-checkbox-allow-file-url-access')
|
document.querySelector('#permission-checkbox-allow-file-url-access')
|
||||||
];
|
];
|
||||||
|
|
||||||
const permissions = await Promise.all([
|
const permissions = await Promise.all([
|
||||||
hasPermissions(['clipboardRead']),
|
|
||||||
isAllowedIncognitoAccess(),
|
isAllowedIncognitoAccess(),
|
||||||
isAllowedFileSchemeAccess()
|
isAllowedFileSchemeAccess()
|
||||||
]);
|
]);
|
||||||
@ -107,7 +88,11 @@ async function updatePermissionCheckbox(checkbox, permissions, value) {
|
|||||||
permissionsCheckboxes[i].checked = permissions[i];
|
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);
|
await promiseTimeout(100);
|
||||||
|
|
||||||
|
@ -41,9 +41,16 @@ class PermissionsToggleController {
|
|||||||
// Private
|
// Private
|
||||||
|
|
||||||
_onOptionsChanged({options}) {
|
_onOptionsChanged({options}) {
|
||||||
const accessor = new ObjectPropertyAccessor(options);
|
let accessor = null;
|
||||||
for (const toggle of this._toggles) {
|
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;
|
let value;
|
||||||
try {
|
try {
|
||||||
value = accessor.get(path, path.length);
|
value = accessor.get(path, path.length);
|
||||||
@ -58,23 +65,38 @@ class PermissionsToggleController {
|
|||||||
async _onPermissionsToggleChange(e) {
|
async _onPermissionsToggleChange(e) {
|
||||||
const toggle = e.currentTarget;
|
const toggle = e.currentTarget;
|
||||||
let value = toggle.checked;
|
let value = toggle.checked;
|
||||||
|
const valuePre = !value;
|
||||||
|
const {permissionsSetting} = toggle.dataset;
|
||||||
|
const hasPermissionsSetting = typeof permissionsSetting === 'string';
|
||||||
|
|
||||||
if (value) {
|
if (value || !hasPermissionsSetting) {
|
||||||
toggle.checked = false;
|
toggle.checked = valuePre;
|
||||||
value = await this._settingsController.setPermissionsGranted(this._getRequiredPermissions(toggle), true);
|
try {
|
||||||
|
value = await this._settingsController.setPermissionsGranted(this._getRequiredPermissions(toggle), value);
|
||||||
|
} catch (error) {
|
||||||
|
value = valuePre;
|
||||||
|
}
|
||||||
toggle.checked = value;
|
toggle.checked = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasPermissionsSetting) {
|
||||||
this._setToggleValid(toggle, true);
|
this._setToggleValid(toggle, true);
|
||||||
|
await this._settingsController.setProfileSetting(permissionsSetting, value);
|
||||||
await this._settingsController.setProfileSetting(toggle.dataset.permissionsSetting, value);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPermissionsChanged({permissions: {permissions}}) {
|
_onPermissionsChanged({permissions: {permissions}}) {
|
||||||
const permissionsSet = new Set(permissions);
|
const permissionsSet = new Set(permissions);
|
||||||
for (const toggle of this._toggles) {
|
for (const toggle of this._toggles) {
|
||||||
const valid = !toggle.checked || this._hasAll(permissionsSet, this._getRequiredPermissions(toggle));
|
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);
|
this._setToggleValid(toggle, valid);
|
||||||
|
} else {
|
||||||
|
toggle.checked = hasPermissions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,17 +84,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div></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></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"><div class="settings-item-inner">
|
||||||
<div class="settings-item-left">
|
<div class="settings-item-left">
|
||||||
<div class="settings-item-label"><code>clipboardWrite</code></div>
|
<div class="settings-item-label"><code>clipboardWrite</code></div>
|
||||||
@ -115,7 +104,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-item-right">
|
<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>
|
</div></div>
|
||||||
<div class="settings-item"><div class="settings-item-inner">
|
<div class="settings-item"><div class="settings-item-inner">
|
||||||
@ -164,6 +167,10 @@
|
|||||||
<script src="/mixed/js/api.js"></script>
|
<script src="/mixed/js/api.js"></script>
|
||||||
|
|
||||||
<script src="/mixed/js/document-focus-controller.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>
|
<script src="/bg/js/permissions-main.js"></script>
|
||||||
|
|
||||||
|
@ -682,7 +682,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox">
|
<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>
|
||||||
|
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
@ -1151,7 +1151,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-item-right">
|
<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>
|
</div>
|
||||||
<div class="settings-item-children more" hidden>
|
<div class="settings-item-children more" hidden>
|
||||||
|
@ -78,12 +78,12 @@
|
|||||||
"storage",
|
"storage",
|
||||||
"clipboardWrite",
|
"clipboardWrite",
|
||||||
"unlimitedStorage",
|
"unlimitedStorage",
|
||||||
"nativeMessaging",
|
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"webRequestBlocking"
|
"webRequestBlocking"
|
||||||
],
|
],
|
||||||
"optional_permissions": [
|
"optional_permissions": [
|
||||||
"clipboardRead"
|
"clipboardRead",
|
||||||
|
"nativeMessaging"
|
||||||
],
|
],
|
||||||
"commands": {
|
"commands": {
|
||||||
"toggleTextScanning": {
|
"toggleTextScanning": {
|
||||||
|
Loading…
Reference in New Issue
Block a user