diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-06-28 11:26:43 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-28 11:26:43 -0400 | 
| commit | 5bf805755a33f6f10fd9621f8a2bff7ba1cb7440 (patch) | |
| tree | 836df16ec300e46af16c178f0aebca20802bde7c | |
| parent | 51981f0c4e11548e138f8b4e57ffd8b96bdee4fe (diff) | |
Yomichan object separation (#627)
* Move "yomichan" object setup to a separate file
* Update script imports
* Align message handlers
* Rename Yomichan.prepare to Yomichan.ready
* Add new prepare function
* Improve isExtensionUrl
| -rw-r--r-- | .eslintrc.json | 11 | ||||
| -rw-r--r-- | ext/bg/background.html | 1 | ||||
| -rw-r--r-- | ext/bg/context.html | 1 | ||||
| -rw-r--r-- | ext/bg/js/context-main.js | 2 | ||||
| -rw-r--r-- | ext/bg/js/search-main.js | 2 | ||||
| -rw-r--r-- | ext/bg/js/settings/main.js | 2 | ||||
| -rw-r--r-- | ext/bg/search.html | 1 | ||||
| -rw-r--r-- | ext/bg/settings-popup-preview.html | 1 | ||||
| -rw-r--r-- | ext/bg/settings.html | 1 | ||||
| -rw-r--r-- | ext/fg/float.html | 1 | ||||
| -rw-r--r-- | ext/fg/js/content-script-main.js | 2 | ||||
| -rw-r--r-- | ext/manifest.json | 1 | ||||
| -rw-r--r-- | ext/mixed/js/core.js | 192 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 2 | ||||
| -rw-r--r-- | ext/mixed/js/yomichan.js | 208 | ||||
| -rw-r--r-- | test/test-database.js | 2 | 
16 files changed, 231 insertions, 199 deletions
| diff --git a/.eslintrc.json b/.eslintrc.json index 68b840f8..b8b7bee8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -86,7 +86,6 @@              "files": ["ext/**/*.js"],              "excludedFiles": ["ext/mixed/js/core.js"],              "globals": { -                "yomichan": "readonly",                  "errorToJson": "readonly",                  "jsonToError": "readonly",                  "isObject": "readonly", @@ -106,6 +105,16 @@              }          },          { +            "files": ["ext/**/*.js"], +            "excludedFiles": [ +                "ext/mixed/js/core.js", +                "ext/mixed/js/yomichan.js" +            ], +            "globals": { +                "yomichan": "readonly" +            } +        }, +        {              "files": ["ext/mixed/js/core.js"],              "globals": {                  "chrome": "writable" diff --git a/ext/bg/background.html b/ext/bg/background.html index 55380ae7..11ea002f 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -20,6 +20,7 @@          <script src="/mixed/lib/wanakana.min.js"></script>          <script src="/mixed/js/core.js"></script> +        <script src="/mixed/js/yomichan.js"></script>          <script src="/mixed/js/environment.js"></script>          <script src="/mixed/js/japanese.js"></script> diff --git a/ext/bg/context.html b/ext/bg/context.html index 1e7e6155..9ed0544f 100644 --- a/ext/bg/context.html +++ b/ext/bg/context.html @@ -47,6 +47,7 @@  </div>  <script src="/mixed/js/core.js"></script> +<script src="/mixed/js/yomichan.js"></script>  <script src="/mixed/js/comm.js"></script>  <script src="/mixed/js/dom.js"></script>  <script src="/mixed/js/api.js"></script> diff --git a/ext/bg/js/context-main.js b/ext/bg/js/context-main.js index 4a2ea168..8718f583 100644 --- a/ext/bg/js/context-main.js +++ b/ext/bg/js/context-main.js @@ -51,7 +51,7 @@ function setupButtonEvents(selector, command, url) {  async function mainInner() {      api.forwardLogsToBackend(); -    await yomichan.prepare(); +    await yomichan.ready();      await api.logIndicatorClear(); diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js index 13bd8767..bbe6c343 100644 --- a/ext/bg/js/search-main.js +++ b/ext/bg/js/search-main.js @@ -23,7 +23,7 @@  (async () => {      try {          api.forwardLogsToBackend(); -        await yomichan.prepare(); +        await yomichan.ready();          const displaySearch = new DisplaySearch();          await displaySearch.prepare(); diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index e22c5e53..48b72735 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -64,7 +64,7 @@ async function setupEnvironmentInfo() {  (async () => {      api.forwardLogsToBackend(); -    await yomichan.prepare(); +    await yomichan.ready();      setupEnvironmentInfo();      showExtensionInformation(); diff --git a/ext/bg/search.html b/ext/bg/search.html index 4a28dd88..cfcf1f96 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -71,6 +71,7 @@          <script src="/mixed/lib/wanakana.min.js"></script>          <script src="/mixed/js/core.js"></script> +        <script src="/mixed/js/yomichan.js"></script>          <script src="/mixed/js/comm.js"></script>          <script src="/mixed/js/dom.js"></script>          <script src="/mixed/js/api.js"></script> diff --git a/ext/bg/settings-popup-preview.html b/ext/bg/settings-popup-preview.html index 75bf06c8..c0c8e3b9 100644 --- a/ext/bg/settings-popup-preview.html +++ b/ext/bg/settings-popup-preview.html @@ -119,6 +119,7 @@          </div></div></div>          <script src="/mixed/js/core.js"></script> +        <script src="/mixed/js/yomichan.js"></script>          <script src="/mixed/js/comm.js"></script>          <script src="/mixed/js/dom.js"></script>          <script src="/mixed/js/api.js"></script> diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 51cb14e7..6fa54e23 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1133,6 +1133,7 @@          <script src="/mixed/lib/wanakana.min.js"></script>          <script src="/mixed/js/core.js"></script> +        <script src="/mixed/js/yomichan.js"></script>          <script src="/mixed/js/comm.js"></script>          <script src="/mixed/js/dom.js"></script>          <script src="/mixed/js/environment.js"></script> diff --git a/ext/fg/float.html b/ext/fg/float.html index 3e41cde5..735a880a 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -40,6 +40,7 @@          </div>          <script src="/mixed/js/core.js"></script> +        <script src="/mixed/js/yomichan.js"></script>          <script src="/mixed/js/comm.js"></script>          <script src="/mixed/js/dom.js"></script>          <script src="/mixed/js/api.js"></script> diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js index 1f3a69e5..6b0706fa 100644 --- a/ext/fg/js/content-script-main.js +++ b/ext/fg/js/content-script-main.js @@ -24,7 +24,7 @@  (async () => {      try {          api.forwardLogsToBackend(); -        await yomichan.prepare(); +        await yomichan.ready();          const {frameId} = await api.frameInformationGet();          if (typeof frameId !== 'number') { diff --git a/ext/manifest.json b/ext/manifest.json index c356f3d3..38069aea 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -36,6 +36,7 @@          "matches": ["http://*/*", "https://*/*", "file://*/*"],          "js": [              "mixed/js/core.js", +            "mixed/js/yomichan.js",              "mixed/js/comm.js",              "mixed/js/dom.js",              "mixed/js/api.js", diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 0fe5ea20..21b7bf5e 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -303,195 +303,3 @@ class EventListenerCollection {          this._eventListeners = [];      }  } - - -/* - * Default message handlers - */ - -const yomichan = (() => { -    class Yomichan extends EventDispatcher { -        constructor() { -            super(); - -            this._isBackendPreparedPromise = this.getTemporaryListenerResult( -                chrome.runtime.onMessage, -                ({action}, {resolve}) => { -                    if (action === 'backendPrepared') { -                        resolve(); -                    } -                } -            ); - -            this._messageHandlers = new Map([ -                ['getUrl', this._onMessageGetUrl.bind(this)], -                ['optionsUpdated', this._onMessageOptionsUpdated.bind(this)], -                ['zoomChanged', this._onMessageZoomChanged.bind(this)] -            ]); - -            chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); -        } - -        // Public - -        prepare() { -            chrome.runtime.sendMessage({action: 'yomichanCoreReady'}); -            return this._isBackendPreparedPromise; -        } - -        generateId(length) { -            const array = new Uint8Array(length); -            crypto.getRandomValues(array); -            let id = ''; -            for (const value of array) { -                id += value.toString(16).padStart(2, '0'); -            } -            return id; -        } - -        triggerOrphaned(error) { -            this.trigger('orphaned', {error}); -        } - -        isExtensionUrl(url) { -            try { -                const urlBase = chrome.runtime.getURL('/'); -                return url.substring(0, urlBase.length) === urlBase; -            } catch (e) { -                return false; -            } -        } - -        getTemporaryListenerResult(eventHandler, userCallback, timeout=null) { -            if (!( -                typeof eventHandler.addListener === 'function' && -                typeof eventHandler.removeListener === 'function' -            )) { -                throw new Error('Event handler type not supported'); -            } - -            return new Promise((resolve, reject) => { -                const runtimeMessageCallback = ({action, params}, sender, sendResponse) => { -                    let timeoutId = null; -                    if (timeout !== null) { -                        timeoutId = setTimeout(() => { -                            timeoutId = null; -                            eventHandler.removeListener(runtimeMessageCallback); -                            reject(new Error(`Listener timed out in ${timeout} ms`)); -                        }, timeout); -                    } - -                    const cleanupResolve = (value) => { -                        if (timeoutId !== null) { -                            clearTimeout(timeoutId); -                            timeoutId = null; -                        } -                        eventHandler.removeListener(runtimeMessageCallback); -                        sendResponse(); -                        resolve(value); -                    }; - -                    userCallback({action, params}, {resolve: cleanupResolve, sender}); -                }; - -                eventHandler.addListener(runtimeMessageCallback); -            }); -        } - -        logWarning(error) { -            this.log(error, 'warn'); -        } - -        logError(error) { -            this.log(error, 'error'); -        } - -        log(error, level, context=null) { -            if (!isObject(context)) { -                context = this._getLogContext(); -            } - -            let errorString; -            try { -                errorString = error.toString(); -                if (/^\[object \w+\]$/.test(errorString)) { -                    errorString = JSON.stringify(error); -                } -            } catch (e) { -                errorString = `${error}`; -            } - -            let errorStack; -            try { -                errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : ''); -            } catch (e) { -                errorStack = ''; -            } - -            let errorData; -            try { -                errorData = error.data; -            } catch (e) { -                // NOP -            } - -            if (errorStack.startsWith(errorString)) { -                errorString = errorStack; -            } else if (errorStack.length > 0) { -                errorString += `\n${errorStack}`; -            } - -            const manifest = chrome.runtime.getManifest(); -            let message = `${manifest.name} v${manifest.version} has encountered a problem.`; -            message += `\nOriginating URL: ${context.url}\n`; -            message += errorString; -            if (typeof errorData !== 'undefined') { -                message += `\nData: ${JSON.stringify(errorData, null, 4)}`; -            } -            message += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues'; - -            switch (level) { -                case 'info': console.info(message); break; -                case 'debug': console.debug(message); break; -                case 'warn': console.warn(message); break; -                case 'error': console.error(message); break; -                default: console.log(message); break; -            } - -            this.trigger('log', {error, level, context}); -        } - -        // Private - -        _getUrl() { -            return (typeof window === 'object' && window !== null ? window.location.href : ''); -        } - -        _getLogContext() { -            return {url: this._getUrl()}; -        } - -        _onMessage({action, params}, sender, callback) { -            const handler = this._messageHandlers.get(action); -            if (typeof handler !== 'function') { return false; } - -            const result = handler(params, sender); -            callback(result); -            return false; -        } - -        _onMessageGetUrl() { -            return {url: this._getUrl()}; -        } - -        _onMessageOptionsUpdated({source}) { -            this.trigger('optionsUpdated', {source}); -        } - -        _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) { -            this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor}); -        } -    } - -    return new Yomichan(); -})(); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 1d699706..e7439796 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -167,7 +167,7 @@ class Display {      }      async prepare() { -        await yomichan.prepare(); +        await yomichan.ready();          await this.displayGenerator.prepare();      } diff --git a/ext/mixed/js/yomichan.js b/ext/mixed/js/yomichan.js new file mode 100644 index 00000000..5fa504ef --- /dev/null +++ b/ext/mixed/js/yomichan.js @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2020  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/>. + */ + +const yomichan = (() => { +    class Yomichan extends EventDispatcher { +        constructor() { +            super(); + +            const {promise, resolve} = deferPromise(); +            this._isBackendPreparedPromise = promise; +            this._isBackendPreparedPromiseResolve = resolve; + +            this._messageHandlers = new Map([ +                ['backendPrepared', this._onMessageBackendPrepared.bind(this)], +                ['getUrl',          this._onMessageGetUrl.bind(this)], +                ['optionsUpdated',  this._onMessageOptionsUpdated.bind(this)], +                ['zoomChanged',     this._onMessageZoomChanged.bind(this)] +            ]); +        } + +        // Public + +        prepare() { +            chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); +        } + +        ready() { +            chrome.runtime.sendMessage({action: 'yomichanCoreReady'}); +            return this._isBackendPreparedPromise; +        } + +        generateId(length) { +            const array = new Uint8Array(length); +            crypto.getRandomValues(array); +            let id = ''; +            for (const value of array) { +                id += value.toString(16).padStart(2, '0'); +            } +            return id; +        } + +        triggerOrphaned(error) { +            this.trigger('orphaned', {error}); +        } + +        isExtensionUrl(url) { +            try { +                return url.startsWith(chrome.runtime.getURL('/')); +            } catch (e) { +                return false; +            } +        } + +        getTemporaryListenerResult(eventHandler, userCallback, timeout=null) { +            if (!( +                typeof eventHandler.addListener === 'function' && +                typeof eventHandler.removeListener === 'function' +            )) { +                throw new Error('Event handler type not supported'); +            } + +            return new Promise((resolve, reject) => { +                const runtimeMessageCallback = ({action, params}, sender, sendResponse) => { +                    let timeoutId = null; +                    if (timeout !== null) { +                        timeoutId = setTimeout(() => { +                            timeoutId = null; +                            eventHandler.removeListener(runtimeMessageCallback); +                            reject(new Error(`Listener timed out in ${timeout} ms`)); +                        }, timeout); +                    } + +                    const cleanupResolve = (value) => { +                        if (timeoutId !== null) { +                            clearTimeout(timeoutId); +                            timeoutId = null; +                        } +                        eventHandler.removeListener(runtimeMessageCallback); +                        sendResponse(); +                        resolve(value); +                    }; + +                    userCallback({action, params}, {resolve: cleanupResolve, sender}); +                }; + +                eventHandler.addListener(runtimeMessageCallback); +            }); +        } + +        logWarning(error) { +            this.log(error, 'warn'); +        } + +        logError(error) { +            this.log(error, 'error'); +        } + +        log(error, level, context=null) { +            if (!isObject(context)) { +                context = this._getLogContext(); +            } + +            let errorString; +            try { +                errorString = error.toString(); +                if (/^\[object \w+\]$/.test(errorString)) { +                    errorString = JSON.stringify(error); +                } +            } catch (e) { +                errorString = `${error}`; +            } + +            let errorStack; +            try { +                errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : ''); +            } catch (e) { +                errorStack = ''; +            } + +            let errorData; +            try { +                errorData = error.data; +            } catch (e) { +                // NOP +            } + +            if (errorStack.startsWith(errorString)) { +                errorString = errorStack; +            } else if (errorStack.length > 0) { +                errorString += `\n${errorStack}`; +            } + +            const manifest = chrome.runtime.getManifest(); +            let message = `${manifest.name} v${manifest.version} has encountered a problem.`; +            message += `\nOriginating URL: ${context.url}\n`; +            message += errorString; +            if (typeof errorData !== 'undefined') { +                message += `\nData: ${JSON.stringify(errorData, null, 4)}`; +            } +            message += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues'; + +            switch (level) { +                case 'info': console.info(message); break; +                case 'debug': console.debug(message); break; +                case 'warn': console.warn(message); break; +                case 'error': console.error(message); break; +                default: console.log(message); break; +            } + +            this.trigger('log', {error, level, context}); +        } + +        // Private + +        _getUrl() { +            return (typeof window === 'object' && window !== null ? window.location.href : ''); +        } + +        _getLogContext() { +            return {url: this._getUrl()}; +        } + +        _onMessage({action, params}, sender, callback) { +            const handler = this._messageHandlers.get(action); +            if (typeof handler !== 'function') { return false; } + +            const result = handler(params, sender); +            callback(result); +            return false; +        } + +        _onMessageBackendPrepared() { +            if (this._isBackendPreparedPromiseResolve === null) { return; } +            this._isBackendPreparedPromiseResolve(); +            this._isBackendPreparedPromiseResolve = null; +        } + +        _onMessageGetUrl() { +            return {url: this._getUrl()}; +        } + +        _onMessageOptionsUpdated({source}) { +            this.trigger('optionsUpdated', {source}); +        } + +        _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) { +            this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor}); +        } +    } + +    return new Yomichan(); +})(); + +yomichan.prepare(); diff --git a/test/test-database.js b/test/test-database.js index 03b2bd3b..5291c138 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -109,9 +109,9 @@ const vm = new VM({  vm.context.window = vm.context;  vm.execute([ +    'mixed/js/core.js',      'bg/js/json-schema.js',      'bg/js/dictionary.js', -    'mixed/js/core.js',      'bg/js/media-utility.js',      'bg/js/request.js',      'bg/js/dictionary-importer.js', |