Skip to content

Commit

Permalink
safePerformance for performance-canvas (#1487) (#1678)
Browse files Browse the repository at this point in the history
# This targets the branch of #1487

Fixes merge conflicts from #1677

History here looks a little funky but when it's actually merged into the
PR only 65ec09d and
e80b3c3 should show since master
already has the others.
  • Loading branch information
djahandarie authored Dec 18, 2024
2 parents 2c5f42d + e80b3c3 commit 3622197
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 90 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -585,6 +589,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",
Expand Down Expand Up @@ -622,6 +627,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",
Expand Down
3 changes: 3 additions & 0 deletions dev/bin/schema-validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions dev/dictionary-validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions ext/js/app/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -952,13 +953,13 @@ export class Frontend {
* @returns {Promise<boolean>}
*/
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;
}

Expand Down
3 changes: 2 additions & 1 deletion ext/js/app/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -302,7 +303,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});
}
}
Expand Down
17 changes: 13 additions & 4 deletions ext/js/background/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,20 @@ export class Backend {
this._clipboardMonitor.stop();
}

this._setupContextMenu(options);

void this._accessibilityController.update(this._getOptionsFull(false));

this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}});
}

/**
* @param {import('settings').ProfileOptions} options
*/
_setupContextMenu(options) {
try {
if (!chrome.contextMenus) { return; }

if (options.general.enableContextMenuScanSelected) {
chrome.contextMenus.create({
id: 'yomitan_lookup',
Expand All @@ -1390,10 +1403,6 @@ export class Backend {
} catch (e) {
log.error(e);
}

void this._accessibilityController.update(this._getOptionsFull(false));

this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}});
}

/**
Expand Down
3 changes: 2 additions & 1 deletion ext/js/comm/cross-frame-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<import('cross-frame-api').CrossFrameAPIPortEvents>
Expand Down Expand Up @@ -106,7 +107,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) {
Expand Down
4 changes: 3 additions & 1 deletion ext/js/core/promise-animation-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

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.
Expand Down Expand Up @@ -51,7 +53,7 @@ export function promiseAnimationFrame(timeout) {
cancelAnimationFrame(frameRequest);
frameRequest = null;
}
resolve({time: performance.now(), timeout: true});
resolve({time: safePerformance.now(), timeout: true});
};

frameRequest = requestAnimationFrame(onFrame);
Expand Down
68 changes: 68 additions & 0 deletions ext/js/core/safe-performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

import {log} from './log.js';

/**
* This class safely handles performance methods.
*/
class SafePerformance {
constructor() {}

/**
* @param {string} markName
* @param {PerformanceMarkOptions} [markOptions]
* @returns {PerformanceMark | undefined}
*/
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}
*/
measure(measureName, startOrMeasureOptions, endMark) {
try {
// eslint-disable-next-line no-restricted-syntax
return performance.measure(measureName, startOrMeasureOptions, endMark);
} catch (e) {
log.error(e);
}
}

/**
* @returns {DOMHighResTimeStamp}
*/
now() {
// eslint-disable-next-line no-restricted-syntax
return performance.now();
}
}

/**
* This object is the default performance measurer used by the runtime.
*/
export const safePerformance = new SafePerformance();
47 changes: 24 additions & 23 deletions ext/js/dictionary/dictionary-database.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import {initWasm, Resvg} from '../../lib/resvg-wasm.js';
import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';
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';

Expand Down Expand Up @@ -401,7 +402,7 @@ export class DictionaryDatabase {
return;
}
// otherwise, you are the worker, so do the work
performance.mark('drawMedia:start');
safePerformance.mark('drawMedia:start');

// merge items with the same path to reduce the number of database queries. collects the canvases into a single array for each path.
/** @type {Map<string, import('dictionary-database').DrawMediaGroupedRequest>} */
Expand All @@ -423,10 +424,10 @@ export class DictionaryDatabase {
// move all svgs to front to have a hotter loop
results.sort((a, _b) => (a.mediaType === 'image/svg+xml' ? -1 : 1));

performance.mark('drawMedia:draw:start');
safePerformance.mark('drawMedia:draw:start');
for (const m of results) {
if (m.mediaType === 'image/svg+xml') {
performance.mark('drawMedia:draw:svg:start');
safePerformance.mark('drawMedia:draw:svg:start');
/** @type {import('@resvg/resvg-wasm').ResvgRenderOptions} */
const opts = {
fitTo: {
Expand All @@ -440,10 +441,10 @@ export class DictionaryDatabase {
const resvgJS = new Resvg(new Uint8Array(m.content), opts);
const render = resvgJS.render();
source.postMessage({action: 'drawBufferToCanvases', params: {buffer: render.pixels.buffer, width: render.width, height: render.height, canvasIndexes: m.canvasIndexes, generation: m.generation}}, [render.pixels.buffer]);
performance.mark('drawMedia:draw:svg:end');
performance.measure('drawMedia:draw:svg', 'drawMedia:draw:svg:start', 'drawMedia:draw:svg:end');
safePerformance.mark('drawMedia:draw:svg:end');
safePerformance.measure('drawMedia:draw:svg', 'drawMedia:draw:svg:start', 'drawMedia:draw:svg:end');
} else {
performance.mark('drawMedia:draw:raster:start');
safePerformance.mark('drawMedia:draw:raster:start');

// ImageDecoder is slightly faster than Blob/createImageBitmap, but
// 1) it is not available in Firefox <133
Expand Down Expand Up @@ -472,15 +473,15 @@ export class DictionaryDatabase {
}
});
}
performance.mark('drawMedia:draw:raster:end');
performance.measure('drawMedia:draw:raster', 'drawMedia:draw:raster:start', 'drawMedia:draw:raster:end');
safePerformance.mark('drawMedia:draw:raster:end');
safePerformance.measure('drawMedia:draw:raster', 'drawMedia:draw:raster:start', 'drawMedia:draw:raster:end');
}
}
performance.mark('drawMedia:draw:end');
performance.measure('drawMedia:draw', 'drawMedia:draw:start', 'drawMedia:draw:end');
safePerformance.mark('drawMedia:draw:end');
safePerformance.measure('drawMedia:draw', 'drawMedia:draw:start', 'drawMedia:draw:end');

performance.mark('drawMedia:end');
performance.measure('drawMedia', 'drawMedia:start', 'drawMedia:end');
safePerformance.mark('drawMedia:end');
safePerformance.measure('drawMedia', 'drawMedia:start', 'drawMedia:end');
}

/**
Expand Down Expand Up @@ -592,16 +593,16 @@ export class DictionaryDatabase {
* @returns {Promise<TResult[]>}
*/
_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;
/** @type {TResult[]} */
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;
}

Expand All @@ -619,8 +620,8 @@ export class DictionaryDatabase {
*/
const onGetAll = (item) => (rows, data) => {
if (typeof item === 'object' && item !== null && 'path' in item) {
performance.mark(`findMultiBulk:onGetAll:${item.path}:end`);
performance.measure(`findMultiBulk:onGetAll:${item.path}`, `findMultiBulk:onGetAll:${item.path}:start`, `findMultiBulk:onGetAll:${item.path}:end`);
safePerformance.mark(`findMultiBulk:onGetAll:${item.path}:end`);
safePerformance.measure(`findMultiBulk:onGetAll:${item.path}`, `findMultiBulk:onGetAll:${item.path}:start`, `findMultiBulk:onGetAll:${item.path}:end`);
}
for (const row of rows) {
if (predicate(row, data.item)) {
Expand All @@ -629,25 +630,25 @@ export class DictionaryDatabase {
}
if (++completeCount >= requiredCompleteCount) {
resolve(results);
performance.mark('findMultiBulk:end');
performance.measure('findMultiBulk', 'findMultiBulk:start', 'findMultiBulk:end');
safePerformance.mark('findMultiBulk:end');
safePerformance.measure('findMultiBulk', 'findMultiBulk:start', 'findMultiBulk:end');
}
};
performance.mark('findMultiBulk:getAll:start');
safePerformance.mark('findMultiBulk:getAll:start');
for (let i = 0; i < itemCount; ++i) {
const item = items[i];
const query = createQuery(item);
for (let j = 0; j < indexCount; ++j) {
/** @type {import('dictionary-database').FindMultiBulkData<TItem>} */
const data = {item, itemIndex: i, indexIndex: j};
if (typeof item === 'object' && item !== null && 'path' in item) {
performance.mark(`findMultiBulk:onGetAll:${item.path}:start`);
safePerformance.mark(`findMultiBulk:onGetAll:${item.path}:start`);
}
this._db.getAll(indexList[j], query, onGetAll(item), reject, data);
}
}
performance.mark('findMultiBulk:getAll:end');
performance.measure('findMultiBulk:getAll', 'findMultiBulk:getAll:start', 'findMultiBulk:getAll:end');
safePerformance.mark('findMultiBulk:getAll:end');
safePerformance.measure('findMultiBulk:getAll', 'findMultiBulk:getAll:start', 'findMultiBulk:getAll:end');
});
}

Expand Down
Loading

0 comments on commit 3622197

Please sign in to comment.