From 4e3daa616895e5220d7edd919a6b8e1771d6be42 Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 09:38:00 -0500 Subject: [PATCH 1/8] Disallow the use of performance --- .eslintrc.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintrc.json b/.eslintrc.json index bc8533d4f..debd69904 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -79,6 +79,10 @@ { "message": "Avoid using Response.json(), prefer readResponseJson.", "selector": "MemberExpression[property.name=json]" + }, + { + "message": "Avoid using performance, prefer safePerformance.", + "selector": "MemberExpression[object.name=performance]" } ], "no-self-compare": "error", From 35c130f63544f78587aa74bc043c674e31a456d4 Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 10:24:39 -0500 Subject: [PATCH 2/8] Add safePerformance --- ext/js/core/safe-performance.js | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 ext/js/core/safe-performance.js diff --git a/ext/js/core/safe-performance.js b/ext/js/core/safe-performance.js new file mode 100644 index 000000000..af8cbb6a3 --- /dev/null +++ b/ext/js/core/safe-performance.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 Yomitan 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 . + */ + +import {log} from './log.js'; + +/** + * This class safely handles performance methods. + */ +class SafePerformance extends Performance { + constructor() { + super(); + } + + /** + * @param {string} markName + * @param {PerformanceMarkOptions} [markOptions] + * @returns {PerformanceMark | undefined} + */ + // @ts-expect-error - ts does not allow return types of overridden methods to be broader and this function must not error + mark(markName, markOptions) { + try { + // eslint-disable-next-line no-restricted-syntax + return performance.mark(markName, markOptions); + } catch (e) { + log.error(e); + } + } + + /** + * + * @param {string} measureName + * @param {string | PerformanceMeasureOptions} [startOrMeasureOptions] + * @param {string} [endMark] + * @returns {PerformanceMeasure | undefined} + */ + // @ts-expect-error - ts does not allow return types of overridden methods to be broader and this function must not error + measure(measureName, startOrMeasureOptions, endMark) { + try { + // eslint-disable-next-line no-restricted-syntax + return performance.measure(measureName, startOrMeasureOptions, endMark); + } catch (e) { + log.error(e); + } + } +} + +/** + * This object is the default performance measurer used by the runtime. + */ +export const safePerformance = new SafePerformance(); From 4cbb2b21bb26d047a986b8617ba415b8a0cffac2 Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 10:28:46 -0500 Subject: [PATCH 3/8] Replace all performance with safePerformance --- ext/js/app/frontend.js | 7 +-- ext/js/app/popup.js | 2 +- ext/js/comm/cross-frame-api.js | 2 +- ext/js/core/promise-animation-frame.js | 2 +- ext/js/dictionary/dictionary-database.js | 7 +-- ext/js/display/display-generator.js | 13 +++--- ext/js/display/display.js | 57 ++++++++++++------------ ext/js/language/text-scanner.js | 26 +++++------ ext/js/language/translator.js | 19 ++++---- 9 files changed, 70 insertions(+), 65 deletions(-) diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index d532cc9b6..08d62a081 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -20,6 +20,7 @@ import {createApiMap, invokeApiMapHandler} from '../core/api-map.js'; import {EventListenerCollection} from '../core/event-listener-collection.js'; import {log} from '../core/log.js'; import {promiseAnimationFrame} from '../core/promise-animation-frame.js'; +import {safePerformance} from '../core/safe-performance.js'; import {setProfile} from '../data/profiles-util.js'; import {addFullscreenChangeEventListener, getFullscreenElement} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; @@ -952,13 +953,13 @@ export class Frontend { * @returns {Promise} */ async _scanSelectedText(allowEmptyRange, disallowExpandSelection, showEmpty = false) { - performance.mark('frontend:scanSelectedText:start'); + safePerformance.mark('frontend:scanSelectedText:start'); const range = this._getFirstSelectionRange(allowEmptyRange); if (range === null) { return false; } const source = disallowExpandSelection ? TextSourceRange.createLazy(range) : TextSourceRange.create(range); await this._textScanner.search(source, {focus: true, restoreSelection: true}, showEmpty); - performance.mark('frontend:scanSelectedText:end'); - performance.measure('frontend:scanSelectedText', 'frontend:scanSelectedText:start', 'frontend:scanSelectedText:end'); + safePerformance.mark('frontend:scanSelectedText:end'); + safePerformance.measure('frontend:scanSelectedText', 'frontend:scanSelectedText:start', 'frontend:scanSelectedText:end'); return true; } diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index cc7abb9e5..eb5e85a58 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -302,7 +302,7 @@ export class Popup extends EventDispatcher { await this._show(sourceRects, writingMode); if (displayDetails !== null) { - performance.mark('invokeDisplaySetContent:start'); + safePerformance.mark('invokeDisplaySetContent:start'); void this._invokeSafe('displaySetContent', {details: displayDetails}); } } diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index a55bbfdd4..e1413687a 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -106,7 +106,7 @@ export class CrossFrameAPIPort extends EventDispatcher { return; } } - performance.mark(`cross-frame-api:invoke:${action}`); + safePerformance.mark(`cross-frame-api:invoke:${action}`); try { this._port.postMessage(/** @type {import('cross-frame-api').InvokeMessage} */ ({type: 'invoke', id, data: {action, params}})); } catch (e) { diff --git a/ext/js/core/promise-animation-frame.js b/ext/js/core/promise-animation-frame.js index 0bcd6970d..aefab27bb 100644 --- a/ext/js/core/promise-animation-frame.js +++ b/ext/js/core/promise-animation-frame.js @@ -51,7 +51,7 @@ export function promiseAnimationFrame(timeout) { cancelAnimationFrame(frameRequest); frameRequest = null; } - resolve({time: performance.now(), timeout: true}); + resolve({time: safePerformance.now(), timeout: true}); }; frameRequest = requestAnimationFrame(onFrame); diff --git a/ext/js/dictionary/dictionary-database.js b/ext/js/dictionary/dictionary-database.js index 5c14bcb0b..4eb601e8a 100644 --- a/ext/js/dictionary/dictionary-database.js +++ b/ext/js/dictionary/dictionary-database.js @@ -17,6 +17,7 @@ */ import {log} from '../core/log.js'; +import {safePerformance} from '../core/safe-performance.js'; import {stringReverse} from '../core/utilities.js'; import {Database} from '../data/database.js'; @@ -455,7 +456,7 @@ export class DictionaryDatabase { * @returns {Promise} */ _findMultiBulk(objectStoreName, indexNames, items, createQuery, predicate, createResult) { - performance.mark('findMultiBulk:start'); + safePerformance.mark('findMultiBulk:start'); return new Promise((resolve, reject) => { const itemCount = items.length; const indexCount = indexNames.length; @@ -463,8 +464,8 @@ export class DictionaryDatabase { const results = []; if (itemCount === 0 || indexCount === 0) { resolve(results); - performance.mark('findMultiBulk:end'); - performance.measure('findMultiBulk', 'findMultiBulk:start', 'findMultiBulk:end'); + safePerformance.mark('findMultiBulk:end'); + safePerformance.measure('findMultiBulk', 'findMultiBulk:start', 'findMultiBulk:end'); return; } diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index e181eff5f..48d0c797c 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -17,6 +17,7 @@ */ import {ExtensionError} from '../core/extension-error.js'; +import {safePerformance} from '../core/safe-performance.js'; import {getDisambiguations, getGroupedPronunciations, getTermFrequency, groupKanjiFrequencies, groupTermFrequencies, groupTermTags, isNonNounVerbOrAdjective} from '../dictionary/dictionary-data-util.js'; import {HtmlTemplateCollection} from '../dom/html-template-collection.js'; import {distributeFurigana, getKanaMorae, getPitchCategory, isCodePointKanji} from '../language/ja/japanese.js'; @@ -111,23 +112,23 @@ export class DisplayGenerator { node.dataset.groupedFrequencyCount = `${groupedFrequencies.length}`; node.dataset.primaryMatchTypes = [...primaryMatchTypes].join(' '); - performance.mark('displayGenerator:createTermEntry:createTermHeadword:start'); + safePerformance.mark('displayGenerator:createTermEntry:createTermHeadword:start'); for (let i = 0, ii = headwords.length; i < ii; ++i) { const node2 = this._createTermHeadword(headwords[i], i, pronunciations); node2.dataset.index = `${i}`; headwordsContainer.appendChild(node2); } headwordsContainer.dataset.count = `${headwords.length}`; - performance.mark('displayGenerator:createTermEntry:createTermHeadword:end'); - performance.measure('displayGenerator:createTermEntry:createTermHeadword', 'displayGenerator:createTermEntry:createTermHeadword:start', 'displayGenerator:createTermEntry:createTermHeadword:end'); + safePerformance.mark('displayGenerator:createTermEntry:createTermHeadword:end'); + safePerformance.measure('displayGenerator:createTermEntry:createTermHeadword', 'displayGenerator:createTermEntry:createTermHeadword:start', 'displayGenerator:createTermEntry:createTermHeadword:end'); - performance.mark('displayGenerator:createTermEntry:promises:start'); + safePerformance.mark('displayGenerator:createTermEntry:promises:start'); this._appendMultiple(inflectionRuleChainsContainer, this._createInflectionRuleChain.bind(this), inflectionRuleChainCandidates); this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, false); this._appendMultiple(groupedPronunciationsContainer, this._createGroupedPronunciation.bind(this), groupedPronunciations); this._appendMultiple(headwordTagsContainer, this._createTermTag.bind(this), termTags, headwords.length); - performance.mark('displayGenerator:createTermEntry:promises:end'); - performance.measure('displayGenerator:createTermEntry:promises', 'displayGenerator:createTermEntry:promises:start', 'displayGenerator:createTermEntry:promises:end'); + safePerformance.mark('displayGenerator:createTermEntry:promises:end'); + safePerformance.measure('displayGenerator:createTermEntry:promises', 'displayGenerator:createTermEntry:promises:start', 'displayGenerator:createTermEntry:promises:end'); for (const term of uniqueTerms) { headwordTagsContainer.appendChild(this._createSearchTag(term)); diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 49404cc28..34a8c099b 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -24,6 +24,7 @@ import {EventDispatcher} from '../core/event-dispatcher.js'; import {EventListenerCollection} from '../core/event-listener-collection.js'; import {ExtensionError} from '../core/extension-error.js'; import {log} from '../core/log.js'; +import {safePerformance} from '../core/safe-performance.js'; import {toError} from '../core/to-error.js'; import {clone, deepEqual, promiseTimeout} from '../core/utilities.js'; import {setProfile} from '../data/profiles-util.js'; @@ -733,7 +734,7 @@ export class Display extends EventDispatcher { /** @type {import('display').DirectApiHandler<'displaySetContent'>} */ _onMessageSetContent({details}) { - performance.mark('invokeDisplaySetContent:end'); + safePerformance.mark('invokeDisplaySetContent:end'); this.setContent(details); } @@ -787,14 +788,14 @@ export class Display extends EventDispatcher { async _onStateChanged() { if (this._historyChangeIgnore) { return; } - performance.mark('display:onStateChanged:start'); + safePerformance.mark('display:onStateChanged:start'); /** @type {?import('core').TokenObject} */ const token = {}; // Unique identifier token this._setContentToken = token; try { // Clear - performance.mark('display:clear:start'); + safePerformance.mark('display:clear:start'); this._closePopups(); this._closeAllPopupMenus(); this._eventListeners.removeAllEventListeners(); @@ -805,11 +806,11 @@ export class Display extends EventDispatcher { this._dictionaryEntries = []; this._dictionaryEntryNodes = []; this._elementOverflowController.clearElements(); - performance.mark('display:clear:end'); - performance.measure('display:clear', 'display:clear:start', 'display:clear:end'); + safePerformance.mark('display:clear:end'); + safePerformance.measure('display:clear', 'display:clear:start', 'display:clear:end'); // Prepare - performance.mark('display:prepare:start'); + safePerformance.mark('display:prepare:start'); const urlSearchParams = new URLSearchParams(location.search); let type = urlSearchParams.get('type'); if (type === null && urlSearchParams.get('query') !== null) { type = 'terms'; } @@ -818,10 +819,10 @@ export class Display extends EventDispatcher { this._queryParserVisibleOverride = (fullVisible === null ? null : (fullVisible !== 'false')); this._historyHasChanged = true; - performance.mark('display:_onStateChanged:prepare:end'); - performance.measure('display:_onStateChanged:prepare', 'display:_onStateChanged:prepare:start', 'display:_onStateChanged:prepare:end'); + safePerformance.mark('display:_onStateChanged:prepare:end'); + safePerformance.measure('display:_onStateChanged:prepare', 'display:_onStateChanged:prepare:start', 'display:_onStateChanged:prepare:end'); - performance.mark('display:_onStateChanged:setContent:start'); + safePerformance.mark('display:_onStateChanged:setContent:start'); // Set content switch (type) { case 'terms': @@ -838,13 +839,13 @@ export class Display extends EventDispatcher { this._clearContent(); break; } - performance.mark('display:_onStateChanged:setContent:end'); - performance.measure('display:_onStateChanged:setContent', 'display:_onStateChanged:setContent:start', 'display:_onStateChanged:setContent:end'); + safePerformance.mark('display:_onStateChanged:setContent:end'); + safePerformance.measure('display:_onStateChanged:setContent', 'display:_onStateChanged:setContent:start', 'display:_onStateChanged:setContent:end'); } catch (e) { this.onError(toError(e)); } - performance.mark('display:_onStateChanged:end'); - performance.measure('display:_onStateChanged', 'display:_onStateChanged:start', 'display:_onStateChanged:end'); + safePerformance.mark('display:_onStateChanged:end'); + safePerformance.measure('display:_onStateChanged', 'display:_onStateChanged:start', 'display:_onStateChanged:end'); } /** @@ -1327,7 +1328,7 @@ export class Display extends EventDispatcher { const hasEnabledDictionaries = this._options ? this._options.dictionaries.some(({enabled}) => enabled) : false; // Set query - performance.mark('display:setQuery:start'); + safePerformance.mark('display:setQuery:start'); let query = urlSearchParams.get('query'); if (query === null) { query = ''; } let queryFull = urlSearchParams.get('full'); @@ -1340,8 +1341,8 @@ export class Display extends EventDispatcher { queryOffset = Number.isFinite(queryOffset) ? Math.max(0, Math.min(queryFull.length - query.length, queryOffset)) : 0; } this._setQuery(query, queryFull, queryOffset); - performance.mark('display:setQuery:end'); - performance.measure('display:setQuery', 'display:setQuery:start', 'display:setQuery:end'); + safePerformance.mark('display:setQuery:end'); + safePerformance.measure('display:setQuery', 'display:setQuery:start', 'display:setQuery:end'); let {state, content} = this._history; let changeHistory = false; @@ -1364,10 +1365,10 @@ export class Display extends EventDispatcher { let {dictionaryEntries} = content; if (!Array.isArray(dictionaryEntries)) { - performance.mark('display:findDictionaryEntries:start'); + safePerformance.mark('display:findDictionaryEntries:start'); dictionaryEntries = hasEnabledDictionaries && lookup && query.length > 0 ? await this._findDictionaryEntries(type === 'kanji', query, primaryReading, wildcardsEnabled, optionsContext) : []; - performance.mark('display:findDictionaryEntries:end'); - performance.measure('display:findDictionaryEntries', 'display:findDictionaryEntries:start', 'display:findDictionaryEntries:end'); + safePerformance.mark('display:findDictionaryEntries:end'); + safePerformance.measure('display:findDictionaryEntries', 'display:findDictionaryEntries:start', 'display:findDictionaryEntries:end'); if (this._setContentToken !== token) { return; } content.dictionaryEntries = dictionaryEntries; changeHistory = true; @@ -1402,10 +1403,10 @@ export class Display extends EventDispatcher { this._dictionaryEntries = dictionaryEntries; - performance.mark('display:updateNavigationAuto:start'); + safePerformance.mark('display:updateNavigationAuto:start'); this._updateNavigationAuto(); - performance.mark('display:updateNavigationAuto:end'); - performance.measure('display:updateNavigationAuto', 'display:updateNavigationAuto:start', 'display:updateNavigationAuto:end'); + safePerformance.mark('display:updateNavigationAuto:end'); + safePerformance.measure('display:updateNavigationAuto', 'display:updateNavigationAuto:start', 'display:updateNavigationAuto:end'); this._setNoContentVisible(hasEnabledDictionaries && dictionaryEntries.length === 0 && lookup); this._setNoDictionariesVisible(!hasEnabledDictionaries); @@ -1413,11 +1414,11 @@ export class Display extends EventDispatcher { const container = this._container; container.textContent = ''; - performance.mark('display:contentUpdate:start'); + safePerformance.mark('display:contentUpdate:start'); this._triggerContentUpdateStart(); for (let i = 0, ii = dictionaryEntries.length; i < ii; ++i) { - performance.mark('display:createEntry:start'); + safePerformance.mark('display:createEntry:start'); if (i > 0) { await promiseTimeout(1); @@ -1441,8 +1442,8 @@ export class Display extends EventDispatcher { this._elementOverflowController.addElements(entry); - performance.mark('display:createEntry:end'); - performance.measure('display:createEntry', 'display:createEntry:start', 'display:createEntry:end'); + safePerformance.mark('display:createEntry:end'); + safePerformance.measure('display:createEntry', 'display:createEntry:start', 'display:createEntry:end'); } if (typeof scrollX === 'number' || typeof scrollY === 'number') { @@ -1454,8 +1455,8 @@ export class Display extends EventDispatcher { } this._triggerContentUpdateComplete(); - performance.mark('display:contentUpdate:end'); - performance.measure('display:contentUpdate', 'display:contentUpdate:start', 'display:contentUpdate:end'); + safePerformance.mark('display:contentUpdate:end'); + safePerformance.measure('display:contentUpdate', 'display:contentUpdate:start', 'display:contentUpdate:end'); } /** */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 886491f41..ef1451f76 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -16,13 +16,13 @@ * along with this program. If not, see . */ -import {ThemeController} from '../app/theme-controller.js'; -import {EventDispatcher} from '../core/event-dispatcher.js'; -import {EventListenerCollection} from '../core/event-listener-collection.js'; -import {log} from '../core/log.js'; -import {clone} from '../core/utilities.js'; -import {anyNodeMatchesSelector, everyNodeMatchesSelector, getActiveModifiers, getActiveModifiersAndButtons, isPointInSelection} from '../dom/document-util.js'; -import {TextSourceElement} from '../dom/text-source-element.js'; +import { ThemeController } from '../app/theme-controller.js'; +import { EventDispatcher } from '../core/event-dispatcher.js'; +import { EventListenerCollection } from '../core/event-listener-collection.js'; +import { log } from '../core/log.js'; +import { clone } from '../core/utilities.js'; +import { anyNodeMatchesSelector, everyNodeMatchesSelector, getActiveModifiers, getActiveModifiersAndButtons, isPointInSelection } from '../dom/document-util.js'; +import { TextSourceElement } from '../dom/text-source-element.js'; const SCAN_RESOLUTION_EXCLUDED_LANGUAGES = new Set(['ja', 'zh', 'yue', 'ko']); @@ -456,7 +456,7 @@ export class TextScanner extends EventDispatcher { */ async _search(textSource, searchTerms, searchKanji, inputInfo, showEmpty = false, disallowExpandStartOffset = false) { try { - performance.mark('scanner:_search:start'); + safePerformance.mark('scanner:_search:start'); const isAltText = textSource instanceof TextSourceElement; if (inputInfo.pointerType === 'touch') { if (isAltText) { @@ -530,8 +530,8 @@ export class TextScanner extends EventDispatcher { } else { this._triggerSearchEmpty(inputInfo); } - performance.mark('scanner:_search:end'); - performance.measure('scanner:_search', 'scanner:_search:start', 'scanner:_search:end'); + safePerformance.mark('scanner:_search:end'); + safePerformance.measure('scanner:_search', 'scanner:_search:start', 'scanner:_search:end'); } catch (error) { this.trigger('searchError', { error: error instanceof Error ? error : new Error(`A search error occurred: ${error}`), @@ -1260,7 +1260,7 @@ export class TextScanner extends EventDispatcher { if (this._pendingLookup) { return; } try { - performance.mark('scanner:_searchAt:start'); + safePerformance.mark('scanner:_searchAt:start'); const sourceInput = inputInfo.input; let searchTerms = this._searchTerms; let searchKanji = this._searchKanji; @@ -1290,8 +1290,8 @@ export class TextScanner extends EventDispatcher { } else { this._triggerSearchEmpty(inputInfo); } - performance.mark('scanner:_searchAt:end'); - performance.measure('scanner:_searchAt', 'scanner:_searchAt:start', 'scanner:_searchAt:end'); + safePerformance.mark('scanner:_searchAt:end'); + safePerformance.measure('scanner:_searchAt', 'scanner:_searchAt:start', 'scanner:_searchAt:end'); } catch (e) { log.error(e); } finally { diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 0020db3c7..584a39489 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import {safePerformance} from '../core/safe-performance.js'; import {applyTextReplacement} from '../general/regex-util.js'; import {isCodePointJapanese} from './ja/japanese.js'; import {LanguageTransformer} from './language-transformer.js'; @@ -76,7 +77,7 @@ export class Translator { * @returns {Promise<{dictionaryEntries: import('dictionary').TermDictionaryEntry[], originalTextLength: number}>} An object containing dictionary entries and the length of the original source text. */ async findTerms(mode, text, options) { - performance.mark('translator:findTerms:start'); + safePerformance.mark('translator:findTerms:start'); const {enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language, primaryReading} = options; const tagAggregator = new TranslatorTagAggregator(); let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, options, tagAggregator, primaryReading); @@ -122,8 +123,8 @@ export class Translator { if (pronunciations.length > 1) { this._sortTermDictionaryEntrySimpleData(pronunciations); } } const withUserFacingInflections = this._addUserFacingInflections(language, dictionaryEntries); - performance.mark('translator:findTerms:end'); - performance.measure('translator:findTerms', 'translator:findTerms:start', 'translator:findTerms:end'); + safePerformance.mark('translator:findTerms:end'); + safePerformance.measure('translator:findTerms', 'translator:findTerms:start', 'translator:findTerms:end'); return {dictionaryEntries: withUserFacingInflections, originalTextLength}; } @@ -373,7 +374,7 @@ export class Translator { * @returns {Promise} */ async _getDeinflections(text, options) { - performance.mark('translator:getDeinflections:start'); + safePerformance.mark('translator:getDeinflections:start'); let deinflections = ( options.deinflect ? this._getAlgorithmDeinflections(text, options) : @@ -396,8 +397,8 @@ export class Translator { } deinflections = deinflections.filter((deinflection) => deinflection.databaseEntries.length); - performance.mark('translator:getDeinflections:end'); - performance.measure('translator:getDeinflections', 'translator:getDeinflections:start', 'translator:getDeinflections:end'); + safePerformance.mark('translator:getDeinflections:end'); + safePerformance.measure('translator:getDeinflections', 'translator:getDeinflections:start', 'translator:getDeinflections:end'); return deinflections; } @@ -409,7 +410,7 @@ export class Translator { * @returns {Promise} */ async _getDictionaryDeinflections(language, deinflections, enabledDictionaryMap, matchType) { - performance.mark('translator:getDictionaryDeinflections:start'); + safePerformance.mark('translator:getDictionaryDeinflections:start'); /** @type {import('translation-internal').DatabaseDeinflection[]} */ const dictionaryDeinflections = []; for (const deinflection of deinflections) { @@ -440,8 +441,8 @@ export class Translator { await this._addEntriesToDeinflections(language, dictionaryDeinflections, enabledDictionaryMap, matchType); - performance.mark('translator:getDictionaryDeinflections:end'); - performance.measure('translator:getDictionaryDeinflections', 'translator:getDictionaryDeinflections:start', 'translator:getDictionaryDeinflections:end'); + safePerformance.mark('translator:getDictionaryDeinflections:end'); + safePerformance.measure('translator:getDictionaryDeinflections', 'translator:getDictionaryDeinflections:start', 'translator:getDictionaryDeinflections:end'); return dictionaryDeinflections; } From 36aae7fbe5a389950ecee21ac81d8b78323cbd3e Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 11:04:22 -0500 Subject: [PATCH 4/8] Disconnect safePerformance from Performance due to constructor issues --- ext/js/core/safe-performance.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ext/js/core/safe-performance.js b/ext/js/core/safe-performance.js index af8cbb6a3..0bf5534ac 100644 --- a/ext/js/core/safe-performance.js +++ b/ext/js/core/safe-performance.js @@ -20,17 +20,14 @@ import {log} from './log.js'; /** * This class safely handles performance methods. */ -class SafePerformance extends Performance { - constructor() { - super(); - } +class SafePerformance { + constructor() {} /** * @param {string} markName * @param {PerformanceMarkOptions} [markOptions] * @returns {PerformanceMark | undefined} */ - // @ts-expect-error - ts does not allow return types of overridden methods to be broader and this function must not error mark(markName, markOptions) { try { // eslint-disable-next-line no-restricted-syntax @@ -47,7 +44,6 @@ class SafePerformance extends Performance { * @param {string} [endMark] * @returns {PerformanceMeasure | undefined} */ - // @ts-expect-error - ts does not allow return types of overridden methods to be broader and this function must not error measure(measureName, startOrMeasureOptions, endMark) { try { // eslint-disable-next-line no-restricted-syntax @@ -56,6 +52,14 @@ class SafePerformance extends Performance { log.error(e); } } + + /** + * @returns {DOMHighResTimeStamp} + */ + now() { + // eslint-disable-next-line no-restricted-syntax + return performance.now(); + } } /** From 2eff89d6f55a58aa0343d7501920e8af7f0d0f0d Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 11:04:59 -0500 Subject: [PATCH 5/8] Fix missing import --- ext/js/core/promise-animation-frame.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/js/core/promise-animation-frame.js b/ext/js/core/promise-animation-frame.js index aefab27bb..23f643ace 100644 --- a/ext/js/core/promise-animation-frame.js +++ b/ext/js/core/promise-animation-frame.js @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import {safePerformance} from './safe-performance.js'; + /** * Creates a promise that will resolve after the next animation frame, using `requestAnimationFrame`. * @param {number} [timeout] A maximum duration (in milliseconds) to wait until the promise resolves. If null or omitted, no timeout is used. From 6fc2914ad2a50586e38edc8d86adc974f38499c0 Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 11:12:15 -0500 Subject: [PATCH 6/8] Add safe-performance to eslintrc --- .eslintrc.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.eslintrc.json b/.eslintrc.json index debd69904..16517610a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -586,6 +586,7 @@ "ext/js/core/extension-error.js", "ext/js/core/json.js", "ext/js/core/log.js", + "ext/js/core/safe-performance.js", "ext/js/core/to-error.js", "ext/js/core/utilities.js", "ext/js/data/database.js", @@ -623,6 +624,7 @@ "ext/js/core/log-utilities.js", "ext/js/core/log.js", "ext/js/core/object-utilities.js", + "ext/js/core/safe-performance.js", "ext/js/core/to-error.js", "ext/js/core/utilities.js", "ext/js/data/anki-util.js", From 7416f12eccf3ce8e29c9fec909e970ad864d679b Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 11:15:28 -0500 Subject: [PATCH 7/8] Ignore errors for using performance in dev directory --- dev/bin/schema-validate.js | 3 +++ dev/dictionary-validate.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/dev/bin/schema-validate.js b/dev/bin/schema-validate.js index 4baeb8aba..dc40e55f4 100644 --- a/dev/bin/schema-validate.js +++ b/dev/bin/schema-validate.js @@ -43,15 +43,18 @@ function main() { const schema = parseJson(schemaSource); for (const dataFileName of args.slice(1)) { + // eslint-disable-next-line no-restricted-syntax const start = performance.now(); try { console.log(`Validating ${dataFileName}...`); const dataSource = fs.readFileSync(dataFileName, {encoding: 'utf8'}); const data = parseJson(dataSource); createJsonSchema(mode, schema).validate(data); + // eslint-disable-next-line no-restricted-syntax const end = performance.now(); console.log(`No issues detected (${((end - start) / 1000).toFixed(2)}s)`); } catch (e) { + // eslint-disable-next-line no-restricted-syntax const end = performance.now(); console.log(`Encountered an error (${((end - start) / 1000).toFixed(2)}s)`); console.warn(e); diff --git a/dev/dictionary-validate.js b/dev/dictionary-validate.js index 18532ad69..77fa0fca3 100644 --- a/dev/dictionary-validate.js +++ b/dev/dictionary-validate.js @@ -131,14 +131,17 @@ export async function testDictionaryFiles(mode, dictionaryFileNames) { const schemas = getSchemas(); for (const dictionaryFileName of dictionaryFileNames) { + // eslint-disable-next-line no-restricted-syntax const start = performance.now(); try { console.log(`Validating ${dictionaryFileName}...`); const source = fs.readFileSync(dictionaryFileName); await validateDictionary(mode, source.buffer, schemas); + // eslint-disable-next-line no-restricted-syntax const end = performance.now(); console.log(`No issues detected (${((end - start) / 1000).toFixed(2)}s)`); } catch (e) { + // eslint-disable-next-line no-restricted-syntax const end = performance.now(); console.log(`Encountered an error (${((end - start) / 1000).toFixed(2)}s)`); console.warn(e); From 7b6dd229285519b6f87841b2a38305e855359e22 Mon Sep 17 00:00:00 2001 From: kuuuube Date: Tue, 17 Dec 2024 11:22:45 -0500 Subject: [PATCH 8/8] Add missing imports --- ext/js/app/popup.js | 1 + ext/js/comm/cross-frame-api.js | 1 + ext/js/language/text-scanner.js | 15 ++++++++------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index eb5e85a58..c8ecdf687 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -21,6 +21,7 @@ import {DynamicProperty} from '../core/dynamic-property.js'; import {EventDispatcher} from '../core/event-dispatcher.js'; import {EventListenerCollection} from '../core/event-listener-collection.js'; import {ExtensionError} from '../core/extension-error.js'; +import {safePerformance} from '../core/safe-performance.js'; import {deepEqual} from '../core/utilities.js'; import {addFullscreenChangeEventListener, computeZoomScale, convertRectZoomCoordinates, getFullscreenElement} from '../dom/document-util.js'; import {loadStyle} from '../dom/style-util.js'; diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index e1413687a..1b7d3f69e 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -22,6 +22,7 @@ import {EventListenerCollection} from '../core/event-listener-collection.js'; import {ExtensionError} from '../core/extension-error.js'; import {parseJson} from '../core/json.js'; import {log} from '../core/log.js'; +import {safePerformance} from '../core/safe-performance.js'; /** * @augments EventDispatcher diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index ef1451f76..c99d7c063 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -16,13 +16,14 @@ * along with this program. If not, see . */ -import { ThemeController } from '../app/theme-controller.js'; -import { EventDispatcher } from '../core/event-dispatcher.js'; -import { EventListenerCollection } from '../core/event-listener-collection.js'; -import { log } from '../core/log.js'; -import { clone } from '../core/utilities.js'; -import { anyNodeMatchesSelector, everyNodeMatchesSelector, getActiveModifiers, getActiveModifiersAndButtons, isPointInSelection } from '../dom/document-util.js'; -import { TextSourceElement } from '../dom/text-source-element.js'; +import {ThemeController} from '../app/theme-controller.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; +import {log} from '../core/log.js'; +import {safePerformance} from '../core/safe-performance.js'; +import {clone} from '../core/utilities.js'; +import {anyNodeMatchesSelector, everyNodeMatchesSelector, getActiveModifiers, getActiveModifiersAndButtons, isPointInSelection} from '../dom/document-util.js'; +import {TextSourceElement} from '../dom/text-source-element.js'; const SCAN_RESOLUTION_EXCLUDED_LANGUAGES = new Set(['ja', 'zh', 'yue', 'ko']);