summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/bg/js/backend.js50
-rw-r--r--ext/bg/js/mecab.js33
-rw-r--r--ext/bg/js/settings2/mecab-controller.js67
-rw-r--r--ext/bg/js/settings2/settings-main.js4
-rw-r--r--ext/bg/settings2.html7
-rw-r--r--ext/mixed/js/api.js4
6 files changed, 163 insertions, 2 deletions
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 67b17cc9..f1983cb3 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -122,7 +122,8 @@ class Backend {
['setAllSettings', {async: true, contentScript: false, handler: this._onApiSetAllSettings.bind(this)}],
['getOrCreateSearchPopup', {async: true, contentScript: true, handler: this._onApiGetOrCreateSearchPopup.bind(this)}],
['isTabSearchPopup', {async: true, contentScript: true, handler: this._onApiIsTabSearchPopup.bind(this)}],
- ['triggerDatabaseUpdated', {async: false, contentScript: true, handler: this._onApiTriggerDatabaseUpdated.bind(this)}]
+ ['triggerDatabaseUpdated', {async: false, contentScript: true, handler: this._onApiTriggerDatabaseUpdated.bind(this)}],
+ ['testMecab', {async: true, contentScript: true, handler: this._onApiTestMecab.bind(this)}]
]);
this._messageHandlersWithProgress = new Map([
]);
@@ -676,6 +677,42 @@ class Backend {
this._triggerDatabaseUpdated(type, cause);
}
+ async _onApiTestMecab() {
+ if (!this._mecab.isEnabled()) {
+ throw new Error('MeCab not enabled');
+ }
+
+ let permissionsOkay = false;
+ try {
+ permissionsOkay = await this._hasPermissions({permissions: ['nativeMessaging']});
+ } catch (e) {
+ // NOP
+ }
+ if (!permissionsOkay) {
+ throw new Error('Insufficient permissions');
+ }
+
+ const disconnect = !this._mecab.isConnected();
+ try {
+ const version = await this._mecab.getVersion();
+ if (version === null) {
+ throw new Error('Could not connect to native MeCab component');
+ }
+
+ const localVersion = this._mecab.getLocalVersion();
+ if (version !== localVersion) {
+ throw new Error(`MeCab component version not supported: ${version}`);
+ }
+ } finally {
+ // Disconnect if the connection was previously disconnected
+ if (disconnect && this._mecab.isEnabled() && this._mecab.isActive()) {
+ this._mecab.disconnect();
+ }
+ }
+
+ return true;
+ }
+
// Command handlers
async _onCommandOpenSearchPage(params) {
@@ -1904,4 +1941,15 @@ class Backend {
});
});
}
+
+ _hasPermissions(permissions) {
+ return new Promise((resolve, reject) => chrome.permissions.contains(permissions, (result) => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve(result);
+ }
+ }));
+ }
}
diff --git a/ext/bg/js/mecab.js b/ext/bg/js/mecab.js
index ef5d6821..4eff2927 100644
--- a/ext/bg/js/mecab.js
+++ b/ext/bg/js/mecab.js
@@ -54,6 +54,39 @@ class Mecab {
}
/**
+ * Disconnects the current port, but does not disable future connections.
+ */
+ disconnect() {
+ if (this._port !== null) {
+ this._clearPort();
+ }
+ }
+
+ /**
+ * Returns whether or not the connection to the native application is active.
+ * @returns `true` if the connection is active, `false` otherwise.
+ */
+ isConnected() {
+ return (this._port !== null);
+ }
+
+ /**
+ * Returns whether or not any invocation is currently active.
+ * @returns `true` if an invocation is active, `false` otherwise.
+ */
+ isActive() {
+ return (this._invocations.size > 0);
+ }
+
+ /**
+ * Gets the local API version being used.
+ * @returns An integer representing the API version that Yomichan uses.
+ */
+ getLocalVersion() {
+ return this._version;
+ }
+
+ /**
* Gets the version of the MeCab component.
* @returns The version of the MeCab component, or `null` if the component was not found.
*/
diff --git a/ext/bg/js/settings2/mecab-controller.js b/ext/bg/js/settings2/mecab-controller.js
new file mode 100644
index 00000000..ff2a4a66
--- /dev/null
+++ b/ext/bg/js/settings2/mecab-controller.js
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* global
+ * api
+ */
+
+class MecabController {
+ constructor(settingsController) {
+ this._settingsController = settingsController;
+ this._testButton = null;
+ this._resultsContainer = null;
+ this._testActive = false;
+ }
+
+ prepare() {
+ this._testButton = document.querySelector('#test-mecab-button');
+ this._resultsContainer = document.querySelector('#test-mecab-results');
+
+ this._testButton.addEventListener('click', this._onTestButtonClick.bind(this), false);
+ }
+
+ // Private
+
+ _onTestButtonClick(e) {
+ e.preventDefault();
+ this._testMecab();
+ }
+
+ async _testMecab() {
+ if (this._testActive) { return; }
+
+ try {
+ this._testActive = true;
+ this._testButton.disabled = true;
+ this._resultsContainer.textContent = '';
+ this._resultsContainer.hidden = true;
+ await api.testMecab();
+ this._setStatus('Connection was successful', false);
+ } catch (e) {
+ this._setStatus(e.message, true);
+ } finally {
+ this._testActive = false;
+ this._testButton.disabled = false;
+ }
+ }
+
+ _setStatus(message, isError) {
+ this._resultsContainer.textContent = message;
+ this._resultsContainer.hidden = false;
+ this._resultsContainer.classList.toggle('danger-text', isError);
+ }
+}
diff --git a/ext/bg/js/settings2/settings-main.js b/ext/bg/js/settings2/settings-main.js
index f4a38d85..24248110 100644
--- a/ext/bg/js/settings2/settings-main.js
+++ b/ext/bg/js/settings2/settings-main.js
@@ -26,6 +26,7 @@
* ExtensionKeyboardShortcutController
* GenericSettingController
* KeyboardShortcutController
+ * MecabController
* ModalController
* NestedPopupsController
* PermissionsToggleController
@@ -140,6 +141,9 @@ async function setupGenericSettingsController(genericSettingController) {
const popupWindowController = new PopupWindowController();
popupWindowController.prepare();
+ const mecabController = new MecabController();
+ mecabController.prepare();
+
await Promise.all(preparePromises);
document.documentElement.dataset.loaded = 'true';
diff --git a/ext/bg/settings2.html b/ext/bg/settings2.html
index 41293a6e..b772ece1 100644
--- a/ext/bg/settings2.html
+++ b/ext/bg/settings2.html
@@ -1166,7 +1166,11 @@
In order for Yomichan to use it, both MeCab and a native messaging component must be installed.
A setup guide can be found <a href="https://github.com/siikamiika/yomichan-mecab-installer/blob/master/README.md" target="_blank" rel="noopener noreferrer">here</a>.
</p>
- <p>
+ <div class="margin-above flex-row-nowrap">
+ <button id="test-mecab-button">Test</button>
+ <div id="test-mecab-results" class="flex-margin-left" hidden></div>
+ </div>
+ <p class="margin-above">
<a class="more-toggle" data-parent-distance="3">Less&hellip;</a>
</p>
</div>
@@ -3241,6 +3245,7 @@
<script src="/bg/js/settings2/extension-keyboard-shortcuts-controller.js"></script>
<script src="/bg/js/settings2/keyboard-shortcuts-controller.js"></script>
+<script src="/bg/js/settings2/mecab-controller.js"></script>
<script src="/bg/js/settings2/nested-popups-controller.js"></script>
<script src="/bg/js/settings2/popup-window-controller.js"></script>
<script src="/bg/js/settings2/secondary-search-dictionary-controller.js"></script>
diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js
index fc765063..d37b091a 100644
--- a/ext/mixed/js/api.js
+++ b/ext/mixed/js/api.js
@@ -193,6 +193,10 @@ const api = (() => {
return this._invoke('triggerDatabaseUpdated', {type, cause});
}
+ testMecab() {
+ return this._invoke('testMecab', {});
+ }
+
// Utilities
_createActionPort(timeout=5000) {