Improve performance by limiting renders

This commit is contained in:
Kuba Orlik 2022-02-07 15:28:01 +01:00
parent cf47b79052
commit 7330ebf9f2
6 changed files with 79 additions and 109 deletions

View File

@ -5,7 +5,7 @@ import { RequestCluster } from './request-cluster';
export default class Memory extends EventEmitter { export default class Memory extends EventEmitter {
origin_to_history = {} as Record<string, Record<string, RequestCluster>>; origin_to_history = {} as Record<string, Record<string, RequestCluster>>;
private throttle = makeThrottle(200); private throttle = makeThrottle(100);
async register(request: ExtendedRequest) { async register(request: ExtendedRequest) {
await request.init(); await request.init();
if (!request.isThirdParty()) { if (!request.isThirdParty()) {
@ -20,7 +20,7 @@ export default class Memory extends EventEmitter {
this.origin_to_history[request.origin][shorthost] = cluster; this.origin_to_history[request.origin][shorthost] = cluster;
} }
this.origin_to_history[request.origin][shorthost].add(request); this.origin_to_history[request.origin][shorthost].add(request);
this.emit('change'); this.emit('change', false, shorthost, 'registered request(shorthost emit)');
} }
constructor() { constructor() {
@ -35,9 +35,9 @@ export default class Memory extends EventEmitter {
); );
browser.webRequest.onBeforeSendHeaders.addListener( browser.webRequest.onBeforeSendHeaders.addListener(
async (request) => { async (request) => {
const extendedRequest = ExtendedRequest.by_id[ const extendedRequest = ExtendedRequest.by_id[request.requestId].addHeaders(
request.requestId request.requestHeaders || []
].addHeaders(request.requestHeaders || []); );
this.register(extendedRequest); this.register(extendedRequest);
}, },
{ urls: ['<all_urls>'] }, { urls: ['<all_urls>'] },
@ -45,13 +45,16 @@ export default class Memory extends EventEmitter {
); );
} }
emit(eventName: string, immediate = false) { emit(eventName: string, immediate = false, data = 'any', reason: string) {
console.log('emitting!', eventName, data, reason);
setTimeout(() => super.emit(eventName, data), 0);
return;
try { try {
if (immediate) { if (immediate) {
super.emit(eventName); super.emit(eventName, data);
return; return;
} else { } else {
this.throttle(() => super.emit(eventName)); this.throttle(() => super.emit(eventName, data));
} }
return true; return true;
} catch (e) { } catch (e) {
@ -68,12 +71,7 @@ export default class Memory extends EventEmitter {
if (shorthost) { if (shorthost) {
const cookies = await browser.cookies.getAll({ domain: shorthost }); const cookies = await browser.cookies.getAll({ domain: shorthost });
for (const cookie of cookies) { for (const cookie of cookies) {
console.log( console.log('removing cookie', cookie.name, 'from', cookie.domain);
'removing cookie',
cookie.name,
'from',
cookie.domain
);
await browser.cookies.remove({ await browser.cookies.remove({
name: cookie.name, name: cookie.name,
url: `https://${cookie.domain}`, url: `https://${cookie.domain}`,
@ -102,6 +100,5 @@ export function init() {
} }
export function getMemory(): Memory { export function getMemory(): Memory {
return (browser.extension.getBackgroundPage().window as any) return (browser.extension.getBackgroundPage().window as any).memory as Memory;
.memory as Memory;
} }

View File

@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import Options from '../options'; import Options from '../options';
import { StolenData } from './stolen-data'; import { StolenData } from './stolen-data';
import { useEmitter } from '../util'; import { getshorthost, useEmitter } from '../util';
import { getMemory } from '../memory'; import { getMemory } from '../memory';
async function getCurrentTab() { async function getCurrentTab() {
@ -26,7 +26,7 @@ const Sidebar = () => {
const [cookiesOnly, setCookiesOnly] = React.useState<boolean>(false); const [cookiesOnly, setCookiesOnly] = React.useState<boolean>(false);
const [stolenDataView, setStolenDataView] = React.useState<boolean>(true); const [stolenDataView, setStolenDataView] = React.useState<boolean>(true);
const [cookiesOrOriginOnly, setCookiesOrOriginOnly] = React.useState<boolean>(false); const [cookiesOrOriginOnly, setCookiesOrOriginOnly] = React.useState<boolean>(false);
const [counter, setCounter] = useEmitter(getMemory()); const [eventCounts, setEventCounts] = useEmitter(getMemory());
const [marksOccurrence, setMarksOccurrence] = React.useState<boolean>(false); const [marksOccurrence, setMarksOccurrence] = React.useState<boolean>(false);
const [warningDataDialogAck, setWarningDataDialogAck] = React.useState<boolean>( const [warningDataDialogAck, setWarningDataDialogAck] = React.useState<boolean>(
localStorage.getItem('warningDataDialogAck') === null localStorage.getItem('warningDataDialogAck') === null
@ -44,7 +44,7 @@ const Sidebar = () => {
); );
React.useEffect(() => { React.useEffect(() => {
const listener = async (data: any) => { const listener = async () => {
console.log('tab change!'); console.log('tab change!');
const tab = await getCurrentTab(); const tab = await getCurrentTab();
const url = new URL(tab.url); const url = new URL(tab.url);
@ -66,7 +66,7 @@ const Sidebar = () => {
} }
} }
return setMarksOccurrence(false); return setMarksOccurrence(false);
}, [counter, origin]); }, [eventCounts['*'], origin]);
return ( return (
<div className="sidebar"> <div className="sidebar">
@ -112,7 +112,7 @@ const Sidebar = () => {
<button <button
onClick={() => { onClick={() => {
getMemory().removeRequestsFor(origin); getMemory().removeRequestsFor(origin);
setCounter((c) => c + 1); getMemory().emit('change', false, origin, 'clicked trash');
setMarksOccurrence(false); setMarksOccurrence(false);
}} }}
> >
@ -122,7 +122,7 @@ const Sidebar = () => {
<button <button
onClick={() => { onClick={() => {
getMemory().removeCookiesFor(origin); getMemory().removeCookiesFor(origin);
setCounter((c) => c + 1); getMemory().emit('change', false, origin, 'clicked clear cookies');
setMarksOccurrence(false); setMarksOccurrence(false);
}} }}
> >
@ -193,8 +193,7 @@ const Sidebar = () => {
) : null} ) : null}
<StolenData <StolenData
origin={origin} origin={origin}
refreshToken={counter} eventCounts={eventCounts}
refresh={() => setCounter((c) => c + 1)}
minValueLength={minValueLength} minValueLength={minValueLength}
cookiesOnly={cookiesOnly} cookiesOnly={cookiesOnly}
cookiesOrOriginOnly={cookiesOrOriginOnly} cookiesOrOriginOnly={cookiesOrOriginOnly}

View File

@ -8,23 +8,14 @@ import './stolen-data-cluster.scss';
const MAX_STRING_VALUE_LENGTH = 100; const MAX_STRING_VALUE_LENGTH = 100;
function StolenDataValue({ function StolenDataValue({ entry }: { entry: StolenDataEntry; prefixKey?: string }) {
entry,
refresh,
}: {
entry: StolenDataEntry;
refresh: Function;
prefixKey?: string;
}) {
const [version] = useEmitter(entry); const [version] = useEmitter(entry);
let body = null; let body = null;
if (!entry.value) { if (!entry.value) {
body = <></>; body = <></>;
} else { } else {
body = ( body = (
<div data-version={version}> <div data-version={version}>{maskString(entry.value, 1, MAX_STRING_VALUE_LENGTH)}</div>
{maskString(entry.value, 1, MAX_STRING_VALUE_LENGTH)}
</div>
); );
} }
return ( return (
@ -32,7 +23,7 @@ function StolenDataValue({
className="value" className="value"
onClick={(e) => { onClick={(e) => {
entry.toggleMark(); entry.toggleMark();
refresh(); getMemory().emit('change', false, entry.request.shorthost, 'clicked value');
e.stopPropagation(); e.stopPropagation();
}} }}
title={maskString(entry.value, 1, MAX_STRING_VALUE_LENGTH)} title={maskString(entry.value, 1, MAX_STRING_VALUE_LENGTH)}
@ -42,13 +33,7 @@ function StolenDataValue({
); );
} }
function StolenDataRow({ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
entry,
refresh,
}: {
entry: StolenDataEntry;
refresh: Function;
}) {
const [version] = useEmitter(entry); const [version] = useEmitter(entry);
return ( return (
<tr <tr
@ -62,7 +47,12 @@ function StolenDataRow({
checked={entry.isMarked} checked={entry.isMarked}
onChange={() => { onChange={() => {
entry.toggleMark(); entry.toggleMark();
refresh(); getMemory().emit(
'change',
false,
entry.request.shorthost,
'clicked checkbox'
);
}} }}
/> />
</td> </td>
@ -70,7 +60,12 @@ function StolenDataRow({
title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`} title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}
onClick={() => { onClick={() => {
entry.toggleMark(); entry.toggleMark();
refresh(); getMemory().emit(
'change',
false,
entry.request.shorthost,
'Clicked entry name'
);
}} }}
> >
{entry.name} {entry.name}
@ -86,10 +81,7 @@ function StolenDataRow({
/> />
</span> </span>
) : entry.request.hasCookie() ? ( ) : entry.request.hasCookie() ? (
<span <span title="Wysłane w zapytaniu opatrzonym Cookies" style={{ opacity: 0.25 }}>
title="Wysłane w zapytaniu opatrzonym Cookies"
style={{ opacity: 0.25 }}
>
<img <img
src="/assets/icons/cookie.svg" src="/assets/icons/cookie.svg"
height={16} height={16}
@ -121,7 +113,7 @@ function StolenDataRow({
</span> </span>
) : null} ) : null}
</td> </td>
<StolenDataValue refresh={refresh} entry={entry} /> <StolenDataValue entry={entry} />
</tr> </tr>
); );
} }
@ -130,21 +122,22 @@ export default function StolenDataCluster({
origin, origin,
shorthost, shorthost,
minValueLength, minValueLength,
refresh,
cookiesOnly, cookiesOnly,
refreshToken,
cookiesOrOriginOnly, cookiesOrOriginOnly,
}: { }: {
origin: string; origin: string;
shorthost: string; shorthost: string;
refreshToken: number; refreshToken: number;
minValueLength: number; minValueLength: number;
refresh: Function;
cookiesOnly: boolean; cookiesOnly: boolean;
cookiesOrOriginOnly: boolean; cookiesOrOriginOnly: boolean;
}) { }) {
const cluster = getMemory().getClustersForOrigin(origin)[shorthost]; const cluster = getMemory().getClustersForOrigin(origin)[shorthost];
const fullHosts = cluster.getFullHosts(); const fullHosts = cluster.getFullHosts();
/* console.log('Stolen data cluster!', shorthost, refreshToken); */
return ( return (
<div className="stolen-data-cluster-container"> <div className="stolen-data-cluster-container">
<header className="domains-container"> <header className="domains-container">
@ -153,13 +146,8 @@ export default function StolenDataCluster({
</a> </a>
<div className="subdomains-container"> <div className="subdomains-container">
{fullHosts.map((host, index) => ( {fullHosts.map((host, index) => (
<a <a className="subdomain" key={host} href={`https://${host}`}>
className="subdomain" {host} {`${fullHosts.length - 1 !== index ? '· ' : ''}`}
key={host}
href={`https://${host}`}
>
{host}{' '}
{`${fullHosts.length - 1 !== index ? '· ' : ''}`}
</a> </a>
))} ))}
</div> </div>
@ -182,7 +170,6 @@ export default function StolenDataCluster({
}) })
.map((entry) => ( .map((entry) => (
<StolenDataRow <StolenDataRow
refresh={refresh}
{...{ {...{
entry, entry,
key: entry.id, key: entry.id,

View File

@ -1,23 +1,19 @@
import React from 'react'; import { getMemory } from '../memory';
import { RequestCluster } from '../request-cluster'; import { RequestCluster } from '../request-cluster';
import StolenDataCluster from './stolen-data-cluster'; import StolenDataCluster from './stolen-data-cluster';
import { getshorthost } from '../util';
import { getMemory } from '../memory';
import './stolen-data.scss'; import './stolen-data.scss';
export function StolenData({ export function StolenData({
origin, origin,
minValueLength, minValueLength,
refreshToken, eventCounts,
refresh,
cookiesOnly, cookiesOnly,
cookiesOrOriginOnly, cookiesOrOriginOnly,
}: { }: {
origin: string; origin: string;
refreshToken: number; eventCounts: Record<string, number>;
refresh: () => void;
minValueLength: number; minValueLength: number;
cookiesOnly: boolean; cookiesOnly: boolean;
cookiesOrOriginOnly: boolean; cookiesOrOriginOnly: boolean;
@ -33,10 +29,7 @@ export function StolenData({
.sort(RequestCluster.sortCompare) .sort(RequestCluster.sortCompare)
.filter((cluster) => !cookiesOnly || cluster.hasCookies()) .filter((cluster) => !cookiesOnly || cluster.hasCookies())
.filter( .filter(
(cluster) => (cluster) => !cookiesOrOriginOnly || cluster.hasCookies() || cluster.exposesOrigin()
!cookiesOrOriginOnly ||
cluster.hasCookies() ||
cluster.exposesOrigin()
); );
return ( return (
<div className="stolen-data-container"> <div className="stolen-data-container">
@ -48,8 +41,7 @@ export function StolenData({
origin={origin} origin={origin}
shorthost={cluster.id} shorthost={cluster.id}
key={cluster.id + origin} key={cluster.id + origin}
refresh={refresh} refreshToken={eventCounts[cluster.id]}
refreshToken={refreshToken}
minValueLength={minValueLength} minValueLength={minValueLength}
cookiesOnly={cookiesOnly} cookiesOnly={cookiesOnly}
cookiesOrOriginOnly={cookiesOrOriginOnly} cookiesOrOriginOnly={cookiesOrOriginOnly}

View File

@ -12,12 +12,7 @@ import {
safeDecodeURIComponent, safeDecodeURIComponent,
} from './util'; } from './util';
export type Sources = export type Sources = 'cookie' | 'pathname' | 'queryparams' | 'header' | 'request_body';
| 'cookie'
| 'pathname'
| 'queryparams'
| 'header'
| 'request_body';
export const Classifications = <const>{ export const Classifications = <const>{
id: 'Identyfikator internetowy', id: 'Identyfikator internetowy',
@ -123,10 +118,7 @@ export class StolenDataEntry extends EventEmitter {
} }
).entries() ).entries()
); );
if ( if (typeof hash !== 'object' && Object.keys(searchParams).length === 0) {
typeof hash !== 'object' &&
Object.keys(searchParams).length === 0
) {
return value; // just a string; return value; // just a string;
} }
const object = { const object = {
@ -134,11 +126,7 @@ export class StolenDataEntry extends EventEmitter {
host: url.host, host: url.host,
path: url.pathname, path: url.pathname,
searchParams, searchParams,
...(hash === '' ...(hash === '' ? {} : typeof hash === 'string' ? { hash } : hash),
? {}
: typeof hash === 'string'
? { hash }
: hash),
}; };
return object; return object;
} else if (value === null) { } else if (value === null) {
@ -148,16 +136,12 @@ export class StolenDataEntry extends EventEmitter {
} }
} }
getParsedValue( getParsedValue(key_path: string): string | Record<string | symbol, unknown> {
key_path: string
): string | Record<string | symbol, unknown> {
let object = StolenDataEntry.parseValue(this.value); let object = StolenDataEntry.parseValue(this.value);
for (const key of key_path.split('.')) { for (const key of key_path.split('.')) {
if (key === '') continue; if (key === '') continue;
if (typeof key === 'string') { if (typeof key === 'string') {
throw new Error( throw new Error('something went wrong when parsing ' + key_path);
'something went wrong when parsing ' + key_path
);
} }
object = StolenDataEntry.parseValue(object[key]); object = StolenDataEntry.parseValue(object[key]);
} }
@ -176,7 +160,7 @@ export class StolenDataEntry extends EventEmitter {
const had_been_marked_before = this.marked; const had_been_marked_before = this.marked;
this.marked = false; this.marked = false;
if (had_been_marked_before) { if (had_been_marked_before) {
this.emit('change'); this.emit('change', this.request.origin);
} }
} }
@ -199,9 +183,7 @@ export class StolenDataEntry extends EventEmitter {
} }
isRelatedToID() { isRelatedToID() {
return this.request.stolenData.some( return this.request.stolenData.some((entry) => entry.classification == 'id');
(entry) => entry.classification == 'id'
);
} }
matchesHAREntry(har: HAREntry): boolean { matchesHAREntry(har: HAREntry): boolean {
@ -216,10 +198,7 @@ export class StolenDataEntry extends EventEmitter {
: value.toString(); : value.toString();
if (typeof value !== 'object' && this.classification == 'id') { if (typeof value !== 'object' && this.classification == 'id') {
return maskString(value, 1 / 3, ID_PREVIEW_MAX_LENGTH); return maskString(value, 1 / 3, ID_PREVIEW_MAX_LENGTH);
} else if ( } else if (typeof value === 'object' && value[Symbol.for('originalString')]) {
typeof value === 'object' &&
value[Symbol.for('originalString')]
) {
return value[Symbol.for('originalString')] as string; return value[Symbol.for('originalString')] as string;
} else { } else {
return str; return str;
@ -267,8 +246,8 @@ export class StolenDataEntry extends EventEmitter {
} }
exposesHost() { exposesHost() {
return [this.value, safeDecodeURIComponent(this.value)].some( return [this.value, safeDecodeURIComponent(this.value)].some((haystack) =>
(haystack) => haystack.includes(getshorthost(this.request.origin)) haystack.includes(getshorthost(this.request.origin))
); );
} }
} }

26
util.ts
View File

@ -47,18 +47,34 @@ export function getshorthost(host: string) {
export function useEmitter( export function useEmitter(
e: EventEmitter e: EventEmitter
): [number, React.Dispatch<React.SetStateAction<number>>] { ): [
const [counter, setCounter] = React.useState<number>(0); Record<string, number | undefined>,
React.Dispatch<React.SetStateAction<Record<string, number | undefined>>>
] {
const [eventCounts, setEventCounts] = React.useState<Record<string, number | undefined>>({
'*': 0,
});
React.useEffect(() => { React.useEffect(() => {
const callback = () => { const callback = (eventSubtype: string) => {
setCounter((counter) => counter + 1); setEventCounts((eventCounts) => {
console.log({
...eventCounts,
...{ [eventSubtype]: (eventCounts[eventSubtype] || 0) + 1 },
...{ '*': (eventCounts['*'] === undefined ? 0 : eventCounts['*']) + 1 },
});
return {
...eventCounts,
...{ [eventSubtype]: (eventCounts[eventSubtype] || 0) + 1 },
...{ '*': (eventCounts['*'] === undefined ? 0 : eventCounts['*']) + 1 },
};
});
}; };
e.on('change', callback); e.on('change', callback);
return () => { return () => {
e.removeListener('change', callback); e.removeListener('change', callback);
}; };
}, []); }, []);
return [counter, setCounter]; return [eventCounts, setEventCounts];
} }
export function parseCookie(cookie: string): Record<string, string> { export function parseCookie(cookie: string): Record<string, string> {