commit
7bea9848ab
@ -0,0 +1,70 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -e |
||||
set -u |
||||
set -o pipefail |
||||
|
||||
if [[ "${CI:-}" != 'true' ]] |
||||
then |
||||
printf '%s\n' 'CI environment variable must be set to true' |
||||
exit 1 |
||||
fi |
||||
|
||||
if [[ "${CIRCLECI:-}" != 'true' ]] |
||||
then |
||||
printf '%s\n' 'CIRCLECI environment variable must be set to true' |
||||
exit 1 |
||||
fi |
||||
|
||||
if [[ -z "${GITHUB_TOKEN:-}" ]] |
||||
then |
||||
printf '%s\n' 'GITHUB_TOKEN environment variable must be set' |
||||
exit 1 |
||||
elif [[ -z "${GITHUB_TOKEN_USER:-}" ]] |
||||
then |
||||
printf '%s\n' 'GITHUB_TOKEN_USER environment variable must be set' |
||||
exit 1 |
||||
fi |
||||
|
||||
printf '%s\n' 'Commit the manifest version and changelog if the manifest has changed' |
||||
|
||||
if [[ "${CIRCLE_BRANCH}" != "develop" ]] |
||||
then |
||||
printf 'This is not develop branch' |
||||
exit 0 |
||||
fi |
||||
|
||||
mkdir temp |
||||
|
||||
git config --global user.email "metamaskbot@users.noreply.github.com" |
||||
|
||||
git config --global user.name "MetaMask Bot" |
||||
|
||||
git clone git@github.com:MetaMask/extension_bundlesize_stats.git temp |
||||
|
||||
if [[ -f "temp/stats/bundle_size_stats-${CIRCLE_SHA1}.json" ]] |
||||
then |
||||
printf 'Bundle size of the commit is already recorded' |
||||
cd .. |
||||
rm -rf temp |
||||
exit 0 |
||||
fi |
||||
|
||||
cp -R test-artifacts/chrome/mv3/bundle_size_stats.json temp/stats |
||||
|
||||
echo " bundle_size_stats-${CIRCLE_SHA1}.json" >> temp/stats/fileList.txt |
||||
|
||||
mv temp/stats/bundle_size_stats.json "temp/stats/bundle_size_stats-${CIRCLE_SHA1}.json" |
||||
|
||||
cd temp |
||||
|
||||
git add . |
||||
|
||||
git commit --message "Bundle size at commit: ${CIRCLE_SHA1}" |
||||
|
||||
repo_slug="$CIRCLE_PROJECT_USERNAME/extension_bundlesize_stats" |
||||
git push "https://$GITHUB_TOKEN_USER:$GITHUB_TOKEN@github.com/$repo_slug" main |
||||
|
||||
cd .. |
||||
|
||||
rm -rf temp |
@ -0,0 +1,125 @@ |
||||
import { Meta } from '@storybook/addon-docs'; |
||||
|
||||
<Meta title="Foundations / Breakpoints" /> |
||||
|
||||
# Breakpoints |
||||
|
||||
Breakpoints are used for responsive layout |
||||
|
||||
## Screen Sizes |
||||
|
||||
There are 4 screen sizes that make up the breakpoints for the MetaMask extension |
||||
|
||||
- base: `0px` |
||||
- sm: `576px` |
||||
- md: `768px` |
||||
- lg: `1280px` |
||||
|
||||
### SCSS |
||||
|
||||
There are Sass variables and mixins available for use for both min and max screens sizes |
||||
|
||||
### Variables |
||||
|
||||
```css |
||||
$screen-sm-max /* 575px */ |
||||
$screen-md-max /* 767px */ |
||||
$screen-lg-max /* 1279px */ |
||||
|
||||
$screen-sm-min /* 576px */ |
||||
$screen-md-min /* 768px */ |
||||
$screen-lg-min /* 1280px */ |
||||
``` |
||||
|
||||
### Mixins |
||||
|
||||
```css |
||||
/* Max screen size */ |
||||
@include screen-sm-max { |
||||
/* equivalent css @media screen and (max-width: 575px) */ |
||||
} |
||||
@include screen-md-max { |
||||
/* equivalent css @media screen and (max-width: 767px) */ |
||||
} |
||||
@include screen-lg-max { |
||||
/* equivalent css @media screen and (max-width: 1279px) */ |
||||
} |
||||
|
||||
/* Min screen size */ |
||||
@include screen-sm-min { |
||||
/* equivalent css @media screen and (min-width: 576px) */ |
||||
} |
||||
@include screen-md-min { |
||||
/* equivalent css @media screen and (min-width: 768px) */ |
||||
} |
||||
@include screen-lg-min { |
||||
/* equivalent css @media screen and (min-width: 1280px) */ |
||||
} |
||||
``` |
||||
|
||||
Migrating from the old sass variables to the new mixins looks like this |
||||
|
||||
```css |
||||
/* Max width */ |
||||
/* Instead of the media query and sass variable */ |
||||
@media screen and (max-width: $break-small) { |
||||
right: 16px; |
||||
} |
||||
|
||||
/* Use the sass mixin */ |
||||
@include screen-sm-max { |
||||
right: 16px; |
||||
} |
||||
|
||||
/* Min width */ |
||||
/* Instead of the media query and sass variable */ |
||||
@media screen and (min-width: $break-large) { |
||||
left: 16px; |
||||
} |
||||
|
||||
/* Use the sass mixin */ |
||||
@include screen-sm-min { |
||||
left: 16px; |
||||
} |
||||
``` |
||||
|
||||
## Takeaways |
||||
|
||||
- Try to avoid using static media queries in your code. |
||||
- Try to use the provided SCSS mixins |
||||
|
||||
### ❌ Don't do this |
||||
|
||||
Don't use static media queries in your code. |
||||
|
||||
```css |
||||
/** |
||||
* Don't do this |
||||
* Static media queries create inconsistency and could break the UI if we want to update them in future |
||||
**/ |
||||
.account-menu { |
||||
@media screen and (min-width: 769px) { |
||||
right: calc((100vw - 80vw) / 2); |
||||
} |
||||
|
||||
@media screen and (min-width: 1281px) { |
||||
right: calc((100vw - 65vw) / 2); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
### ✅ Do this |
||||
|
||||
Do use the provided Sass mixins |
||||
|
||||
```css |
||||
.account-menu { |
||||
@include screen-md-min { |
||||
right: calc((100vw - 80vw) / 2); |
||||
} |
||||
|
||||
@include screen-lg-min { |
||||
right: calc((100vw - 65vw) / 2); |
||||
} |
||||
} |
||||
``` |
@ -0,0 +1,217 @@ |
||||
import { MESSAGE_TYPE } from '../../../shared/constants/app'; |
||||
import { EVENT_NAMES } from '../../../shared/constants/metametrics'; |
||||
import { SECOND } from '../../../shared/constants/time'; |
||||
import createRPCMethodTrackingMiddleware from './createRPCMethodTrackingMiddleware'; |
||||
|
||||
const trackEvent = jest.fn(); |
||||
const metricsState = { participateInMetaMetrics: null }; |
||||
const getMetricsState = () => metricsState; |
||||
|
||||
const handler = createRPCMethodTrackingMiddleware({ |
||||
trackEvent, |
||||
getMetricsState, |
||||
rateLimitSeconds: 1, |
||||
}); |
||||
|
||||
function getNext(timeout = 500) { |
||||
let deferred; |
||||
const promise = new Promise((resolve) => { |
||||
deferred = { |
||||
resolve, |
||||
}; |
||||
}); |
||||
const cb = () => deferred.resolve(); |
||||
let triggerNext; |
||||
setTimeout(() => { |
||||
deferred.resolve(); |
||||
}, timeout); |
||||
return { |
||||
executeMiddlewareStack: async () => { |
||||
if (triggerNext) { |
||||
triggerNext(() => cb()); |
||||
} |
||||
return await deferred.resolve(); |
||||
}, |
||||
promise, |
||||
next: (postReqHandler) => { |
||||
triggerNext = postReqHandler; |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
const waitForSeconds = async (seconds) => |
||||
await new Promise((resolve) => setTimeout(resolve, SECOND * seconds)); |
||||
|
||||
describe('createRPCMethodTrackingMiddleware', () => { |
||||
afterEach(() => { |
||||
jest.resetAllMocks(); |
||||
metricsState.participateInMetaMetrics = null; |
||||
}); |
||||
|
||||
describe('before participateInMetaMetrics is set', () => { |
||||
it('should not track an event for a signature request', async () => { |
||||
const req = { |
||||
method: MESSAGE_TYPE.ETH_SIGN, |
||||
origin: 'some.dapp', |
||||
}; |
||||
|
||||
const res = { |
||||
error: null, |
||||
}; |
||||
const { executeMiddlewareStack, next } = getNext(); |
||||
handler(req, res, next); |
||||
await executeMiddlewareStack(); |
||||
expect(trackEvent).not.toHaveBeenCalled(); |
||||
}); |
||||
}); |
||||
|
||||
describe('participateInMetaMetrics is set to false', () => { |
||||
beforeEach(() => { |
||||
metricsState.participateInMetaMetrics = false; |
||||
}); |
||||
|
||||
it('should not track an event for a signature request', async () => { |
||||
const req = { |
||||
method: MESSAGE_TYPE.ETH_SIGN, |
||||
origin: 'some.dapp', |
||||
}; |
||||
|
||||
const res = { |
||||
error: null, |
||||
}; |
||||
const { executeMiddlewareStack, next } = getNext(); |
||||
handler(req, res, next); |
||||
await executeMiddlewareStack(); |
||||
expect(trackEvent).not.toHaveBeenCalled(); |
||||
}); |
||||
}); |
||||
|
||||
describe('participateInMetaMetrics is set to true', () => { |
||||
beforeEach(() => { |
||||
metricsState.participateInMetaMetrics = true; |
||||
}); |
||||
|
||||
it(`should immediately track a ${EVENT_NAMES.SIGNATURE_REQUESTED} event`, () => { |
||||
const req = { |
||||
method: MESSAGE_TYPE.ETH_SIGN, |
||||
origin: 'some.dapp', |
||||
}; |
||||
|
||||
const res = { |
||||
error: null, |
||||
}; |
||||
const { next } = getNext(); |
||||
handler(req, res, next); |
||||
expect(trackEvent).toHaveBeenCalledTimes(1); |
||||
expect(trackEvent.mock.calls[0][0]).toMatchObject({ |
||||
category: 'inpage_provider', |
||||
event: EVENT_NAMES.SIGNATURE_REQUESTED, |
||||
properties: { signature_type: MESSAGE_TYPE.ETH_SIGN }, |
||||
referrer: { url: 'some.dapp' }, |
||||
}); |
||||
}); |
||||
|
||||
it(`should track a ${EVENT_NAMES.SIGNATURE_APPROVED} event if the user approves`, async () => { |
||||
const req = { |
||||
method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, |
||||
origin: 'some.dapp', |
||||
}; |
||||
|
||||
const res = { |
||||
error: null, |
||||
}; |
||||
const { next, executeMiddlewareStack } = getNext(); |
||||
handler(req, res, next); |
||||
await executeMiddlewareStack(); |
||||
expect(trackEvent).toHaveBeenCalledTimes(2); |
||||
expect(trackEvent.mock.calls[1][0]).toMatchObject({ |
||||
category: 'inpage_provider', |
||||
event: EVENT_NAMES.SIGNATURE_APPROVED, |
||||
properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4 }, |
||||
referrer: { url: 'some.dapp' }, |
||||
}); |
||||
}); |
||||
|
||||
it(`should track a ${EVENT_NAMES.SIGNATURE_REJECTED} event if the user approves`, async () => { |
||||
const req = { |
||||
method: MESSAGE_TYPE.PERSONAL_SIGN, |
||||
origin: 'some.dapp', |
||||
}; |
||||
|
||||
const res = { |
||||
error: { code: 4001 }, |
||||
}; |
||||
const { next, executeMiddlewareStack } = getNext(); |
||||
handler(req, res, next); |
||||
await executeMiddlewareStack(); |
||||
expect(trackEvent).toHaveBeenCalledTimes(2); |
||||
expect(trackEvent.mock.calls[1][0]).toMatchObject({ |
||||
category: 'inpage_provider', |
||||
event: EVENT_NAMES.SIGNATURE_REJECTED, |
||||
properties: { signature_type: MESSAGE_TYPE.PERSONAL_SIGN }, |
||||
referrer: { url: 'some.dapp' }, |
||||
}); |
||||
}); |
||||
|
||||
it(`should track a ${EVENT_NAMES.PERMISSIONS_APPROVED} event if the user approves`, async () => { |
||||
const req = { |
||||
method: MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS, |
||||
origin: 'some.dapp', |
||||
}; |
||||
|
||||
const res = {}; |
||||
const { next, executeMiddlewareStack } = getNext(); |
||||
handler(req, res, next); |
||||
await executeMiddlewareStack(); |
||||
expect(trackEvent).toHaveBeenCalledTimes(2); |
||||
expect(trackEvent.mock.calls[1][0]).toMatchObject({ |
||||
category: 'inpage_provider', |
||||
event: EVENT_NAMES.PERMISSIONS_APPROVED, |
||||
properties: { method: MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS }, |
||||
referrer: { url: 'some.dapp' }, |
||||
}); |
||||
}); |
||||
|
||||
it(`should never track blocked methods such as ${MESSAGE_TYPE.GET_PROVIDER_STATE}`, () => { |
||||
const req = { |
||||
method: MESSAGE_TYPE.GET_PROVIDER_STATE, |
||||
origin: 'www.notadapp.com', |
||||
}; |
||||
|
||||
const res = { |
||||
error: null, |
||||
}; |
||||
const { next, executeMiddlewareStack } = getNext(); |
||||
handler(req, res, next); |
||||
expect(trackEvent).not.toHaveBeenCalled(); |
||||
executeMiddlewareStack(); |
||||
}); |
||||
|
||||
it(`should only track events when not rate limited`, async () => { |
||||
const req = { |
||||
method: 'eth_chainId', |
||||
origin: 'some.dapp', |
||||
}; |
||||
|
||||
const res = { |
||||
error: null, |
||||
}; |
||||
|
||||
let callCount = 0; |
||||
|
||||
while (callCount < 3) { |
||||
callCount += 1; |
||||
const { next, executeMiddlewareStack } = getNext(); |
||||
handler(req, res, next); |
||||
await executeMiddlewareStack(); |
||||
if (callCount !== 3) { |
||||
await waitForSeconds(0.6); |
||||
} |
||||
} |
||||
|
||||
expect(trackEvent).toHaveBeenCalledTimes(2); |
||||
expect(trackEvent.mock.calls[0][0].properties.method).toBe('eth_chainId'); |
||||
expect(trackEvent.mock.calls[1][0].properties.method).toBe('eth_chainId'); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,170 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
||||
|
||||
<link |
||||
rel="stylesheet" |
||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" |
||||
/> |
||||
<link rel="stylesheet" type="text/css" href="../lib/d3-flamegraph.css" /> |
||||
|
||||
<style> |
||||
/* Space out content a bit */ |
||||
body { |
||||
padding-top: 20px; |
||||
padding-bottom: 20px; |
||||
} |
||||
|
||||
/* Custom page header */ |
||||
.header { |
||||
padding-bottom: 20px; |
||||
padding-right: 15px; |
||||
padding-left: 15px; |
||||
border-bottom: 1px solid #e5e5e5; |
||||
} |
||||
|
||||
/* Make the masthead heading the same height as the navigation */ |
||||
.header h3 { |
||||
margin-top: 0; |
||||
margin-bottom: 0; |
||||
line-height: 40px; |
||||
} |
||||
|
||||
/* Customize container */ |
||||
.container { |
||||
max-width: 990px; |
||||
} |
||||
</style> |
||||
|
||||
<title>Performance Measurements</title> |
||||
|
||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> |
||||
<!--[if lt IE 9]> |
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> |
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> |
||||
<![endif]--> |
||||
</head> |
||||
<body> |
||||
<div class="container"> |
||||
<div class="header clearfix"> |
||||
<nav> |
||||
<div class="pull-right"> |
||||
<form class="form-inline" id="form"> |
||||
<a class="btn" href="javascript: resetZoom();">Reset zoom</a> |
||||
<a class="btn" href="javascript: clear();">Clear</a> |
||||
<div class="form-group"> |
||||
<input type="text" class="form-control" id="term" /> |
||||
</div> |
||||
<a class="btn btn-primary" href="javascript: search();">Search</a> |
||||
</form> |
||||
</div> |
||||
</nav> |
||||
<h3 class="text-muted">d3-flame-graph</h3> |
||||
</div> |
||||
<div id="chart"></div> |
||||
<hr /> |
||||
<div id="details"></div> |
||||
</div> |
||||
|
||||
<!-- D3.js --> |
||||
<script src="https://d3js.org/d3.v7.js" charset="utf-8"></script> |
||||
|
||||
<!-- d3-flamegraph --> |
||||
<script type="text/javascript" src="../lib/d3-flamegraph.js"></script> |
||||
<script |
||||
type="text/javascript" |
||||
src="../lib/d3-flamegraph-tooltip.js" |
||||
></script> |
||||
|
||||
<script type="text/javascript"> |
||||
var chart = flamegraph() |
||||
.width(960) |
||||
.cellHeight(18) |
||||
.transitionDuration(750) |
||||
.minFrameSize(5) |
||||
.transitionEase(d3.easeCubic) |
||||
.sort(true) |
||||
//Example to sort in reverse order |
||||
//.sort(function(a,b){ return d3.descending(a.name, b.name);}) |
||||
.title('') |
||||
.onClick(onClick) |
||||
.selfValue(false) |
||||
.setColorMapper((d, originalColor) => |
||||
d.highlight ? '#6aff8f' : originalColor, |
||||
); |
||||
|
||||
// Example on how to use custom a tooltip. |
||||
var tip = flamegraph.tooltip |
||||
.defaultFlamegraphTooltip() |
||||
.text((d) => 'name: ' + d.data.name + ', value: ' + d.data.value); |
||||
chart.tooltip(tip); |
||||
|
||||
var details = document.getElementById('details'); |
||||
chart.setDetailsElement(details); |
||||
|
||||
// Example on how to use searchById() function in flamegraph. |
||||
// To invoke this function after loading the graph itself, this function should be registered in d3 datum(data).call() |
||||
// (See d3.json invocation in this file) |
||||
function invokeFind() { |
||||
var searchId = parseInt(location.hash.substring(1), 10); |
||||
if (searchId) { |
||||
find(searchId); |
||||
} |
||||
} |
||||
|
||||
// Example on how to use custom labels |
||||
// var label = function(d) { |
||||
// return "name: " + d.name + ", value: " + d.value; |
||||
// } |
||||
// chart.label(label); |
||||
|
||||
// Example of how to set fixed chart height |
||||
// chart.height(540); |
||||
|
||||
d3.json('stacks.json') |
||||
.then((data) => { |
||||
d3.select('#chart').datum(data).call(chart).call(invokeFind); |
||||
}) |
||||
.catch((error) => { |
||||
return console.warn(error); |
||||
}); |
||||
|
||||
document |
||||
.getElementById('form') |
||||
.addEventListener('submit', function (event) { |
||||
event.preventDefault(); |
||||
search(); |
||||
}); |
||||
|
||||
function search() { |
||||
var term = document.getElementById('term').value; |
||||
chart.search(term); |
||||
} |
||||
|
||||
function find(id) { |
||||
var elem = chart.findById(id); |
||||
if (elem) { |
||||
console.log(elem); |
||||
chart.zoomTo(elem); |
||||
} |
||||
} |
||||
|
||||
function clear() { |
||||
document.getElementById('term').value = ''; |
||||
chart.clear(); |
||||
} |
||||
|
||||
function resetZoom() { |
||||
chart.resetZoom(); |
||||
} |
||||
|
||||
function onClick(d) { |
||||
console.info(`Clicked on ${d.data.name}, id: "${d.id}"`); |
||||
history.pushState({ id: d.id }, d.data.name, `#${d.id}`); |
||||
} |
||||
</script> |
||||
</body> |
||||
</html> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,46 @@ |
||||
.d3-flame-graph rect { |
||||
stroke: #EEEEEE; |
||||
fill-opacity: .8; |
||||
} |
||||
|
||||
.d3-flame-graph rect:hover { |
||||
stroke: #474747; |
||||
stroke-width: 0.5; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.d3-flame-graph-label { |
||||
pointer-events: none; |
||||
white-space: nowrap; |
||||
text-overflow: ellipsis; |
||||
overflow: hidden; |
||||
font-size: 12px; |
||||
font-family: Verdana; |
||||
margin-left: 4px; |
||||
margin-right: 4px; |
||||
line-height: 1.5; |
||||
padding: 0 0 0; |
||||
font-weight: 400; |
||||
color: black; |
||||
text-align: left; |
||||
} |
||||
|
||||
.d3-flame-graph .fade { |
||||
opacity: 0.6 !important; |
||||
} |
||||
|
||||
.d3-flame-graph .title { |
||||
font-size: 20px; |
||||
font-family: Verdana; |
||||
} |
||||
|
||||
.d3-flame-graph-tip { |
||||
background-color: black; |
||||
border: none; |
||||
border-radius: 3px; |
||||
padding: 5px 10px 5px 10px; |
||||
min-width: 250px; |
||||
text-align: left; |
||||
color: white; |
||||
z-index: 10; |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,67 @@ |
||||
<html> |
||||
<head> |
||||
<script type="text/javascript" src="./jquery.min.js"></script> |
||||
<style> |
||||
table { |
||||
border: 1px solid black; |
||||
border-collapse: collapse; |
||||
} |
||||
tr { |
||||
border: 1px solid black; |
||||
} |
||||
</style> |
||||
<script> |
||||
$(function () { |
||||
var loadTime = []; |
||||
|
||||
$.getJSON('stats.json', function (data) { |
||||
$.each(data.children, function (index, datum) { |
||||
if (datum.name) { |
||||
var tblRow = |
||||
'<tr>' + |
||||
'<td>' + |
||||
index + |
||||
'</td>' + |
||||
'<td>' + |
||||
datum.name + |
||||
'</td>' + |
||||
'<td>' + |
||||
datum.value + |
||||
'</td>' + |
||||
'</tr>'; |
||||
$(tblRow).appendTo('#loaddata tbody'); |
||||
} |
||||
}); |
||||
$( |
||||
'<tr>' + |
||||
'<td>' + |
||||
'Total' + |
||||
'</td>' + |
||||
'<td>' + |
||||
'--' + |
||||
'</td>' + |
||||
'<td>' + |
||||
data.value + |
||||
'</td>' + |
||||
'</tr>', |
||||
).appendTo('#loaddata tbody'); |
||||
}); |
||||
}); |
||||
</script> |
||||
</head> |
||||
|
||||
<body> |
||||
<div class="wrapper"> |
||||
<div class="profile"> |
||||
<table id="loaddata"> |
||||
<thead> |
||||
<th>S.No</th> |
||||
<th>Name</th> |
||||
<th>TotalTime</th> |
||||
</thead> |
||||
<tbody></tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
</body> |
||||
</html> |
File diff suppressed because one or more lines are too long
@ -0,0 +1,4 @@ |
||||
export const PHISHING_NEW_ISSUE_URLS = { |
||||
MetaMask: 'https://github.com/metamask/eth-phishing-detect/issues/new', |
||||
PhishFort: 'https://github.com/phishfort/phishfort-lists/issues/new', |
||||
}; |
@ -0,0 +1,140 @@ |
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable node/shebang */ |
||||
const path = require('path'); |
||||
const { promises: fs, constants: fsConstants } = require('fs'); |
||||
const yargs = require('yargs/yargs'); |
||||
const { hideBin } = require('yargs/helpers'); |
||||
const { exitWithError } = require('../../development/lib/exit-with-error'); |
||||
const { withFixtures, tinyDelayMs } = require('./helpers'); |
||||
|
||||
async function measurePage() { |
||||
let metrics; |
||||
try { |
||||
await withFixtures({ fixtures: 'imported-account' }, async ({ driver }) => { |
||||
await driver.delay(tinyDelayMs); |
||||
await driver.navigate(); |
||||
await driver.findElement('#password'); |
||||
await driver.delay(1000); |
||||
const logs = await driver.checkBrowserForLavamoatLogs(); |
||||
|
||||
let logString = ''; |
||||
let inObject = false; |
||||
|
||||
const parsedLogs = []; |
||||
|
||||
logs.forEach((log) => { |
||||
if (log.indexOf('"version": 1') >= 0) { |
||||
logString += log; |
||||
parsedLogs.push(`{${logString}}`); |
||||
logString = ''; |
||||
inObject = false; |
||||
} else if (inObject) { |
||||
logString += log; |
||||
} else if ( |
||||
log.search(/"name": ".*app\/scripts\/background.js",/u) >= 0 || |
||||
log.search(/"name": ".*app\/scripts\/ui.js",/u) >= 0 |
||||
) { |
||||
logString += log; |
||||
inObject = true; |
||||
} |
||||
}); |
||||
|
||||
metrics = parsedLogs.map((pl) => JSON.parse(pl)); |
||||
}); |
||||
} catch (error) { |
||||
// do nothing
|
||||
} |
||||
return metrics; |
||||
} |
||||
|
||||
async function profilePageLoad() { |
||||
const results = await measurePage(); |
||||
const metrics = {}; |
||||
|
||||
metrics['background.js'] = results[0]; |
||||
metrics['ui.js'] = results[1]; |
||||
|
||||
return metrics; |
||||
} |
||||
|
||||
async function isWritable(directory) { |
||||
try { |
||||
await fs.access(directory, fsConstants.W_OK); |
||||
return true; |
||||
} catch (error) { |
||||
if (error.code !== 'EACCES') { |
||||
throw error; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
async function getFirstParentDirectoryThatExists(directory) { |
||||
let nextDirectory = directory; |
||||
for (;;) { |
||||
try { |
||||
await fs.access(nextDirectory, fsConstants.F_OK); |
||||
return nextDirectory; |
||||
} catch (error) { |
||||
if (error.code !== 'ENOENT') { |
||||
throw error; |
||||
} else if (nextDirectory === path.dirname(nextDirectory)) { |
||||
throw new Error('Failed to find parent directory that exists'); |
||||
} |
||||
nextDirectory = path.dirname(nextDirectory); |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function main() { |
||||
const { argv } = yargs(hideBin(process.argv)).usage( |
||||
'$0 [options]', |
||||
'Run a page load benchmark', |
||||
(_yargs) => |
||||
_yargs.option('out', { |
||||
description: |
||||
'Output filename. Output printed to STDOUT of this is omitted.', |
||||
type: 'string', |
||||
normalize: true, |
||||
}), |
||||
); |
||||
|
||||
const { out } = argv; |
||||
|
||||
let outputDirectory; |
||||
let existingParentDirectory; |
||||
if (out) { |
||||
outputDirectory = path.dirname(out); |
||||
existingParentDirectory = await getFirstParentDirectoryThatExists( |
||||
outputDirectory, |
||||
); |
||||
if (!(await isWritable(existingParentDirectory))) { |
||||
throw new Error('Specified output file directory is not writable'); |
||||
} |
||||
} |
||||
|
||||
const results = await profilePageLoad(); |
||||
|
||||
if (out) { |
||||
if (outputDirectory !== existingParentDirectory) { |
||||
await fs.mkdir(outputDirectory, { recursive: true }); |
||||
} |
||||
|
||||
await fs.writeFile( |
||||
path.join(out, 'background.json'), |
||||
JSON.stringify(results['background.js'], null, 2), |
||||
); |
||||
|
||||
await fs.writeFile( |
||||
path.join(out, 'ui.json'), |
||||
JSON.stringify(results['ui.js'], null, 2), |
||||
); |
||||
} else { |
||||
console.log(JSON.stringify(results, null, 2)); |
||||
} |
||||
} |
||||
|
||||
main().catch((error) => { |
||||
exitWithError(error); |
||||
}); |
@ -0,0 +1,119 @@ |
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable node/shebang */ |
||||
const path = require('path'); |
||||
const { promises: fs } = require('fs'); |
||||
const yargs = require('yargs/yargs'); |
||||
const { hideBin } = require('yargs/helpers'); |
||||
const { |
||||
isWritable, |
||||
getFirstParentDirectoryThatExists, |
||||
} = require('../../helpers/file'); |
||||
|
||||
const { exitWithError } = require('../../../development/lib/exit-with-error'); |
||||
|
||||
/** |
||||
* The e2e test case is used to capture bundle time statistics for extension. |
||||
*/ |
||||
|
||||
const backgroundFiles = [ |
||||
'runtime-lavamoat.js', |
||||
'lockdown-more.js', |
||||
'globalthis.js', |
||||
'sentry-install.js', |
||||
'policy-load.js', |
||||
]; |
||||
|
||||
const uiFiles = [ |
||||
'globalthis.js', |
||||
'sentry-install.js', |
||||
'runtime-lavamoat.js', |
||||
'lockdown-more.js', |
||||
'policy-load.js', |
||||
]; |
||||
|
||||
const BackgroundFileRegex = /background-[0-9]*.js/u; |
||||
const CommonFileRegex = /common-[0-9]*.js/u; |
||||
const UIFileRegex = /ui-[0-9]*.js/u; |
||||
|
||||
async function main() { |
||||
const { argv } = yargs(hideBin(process.argv)).usage( |
||||
'$0 [options]', |
||||
'Run a page load benchmark', |
||||
(_yargs) => |
||||
_yargs.option('out', { |
||||
description: |
||||
'Output filename. Output printed to STDOUT of this is omitted.', |
||||
type: 'string', |
||||
normalize: true, |
||||
}), |
||||
); |
||||
const { out } = argv; |
||||
|
||||
const distFolder = 'dist/chrome'; |
||||
const backgroundFileList = []; |
||||
const uiFileList = []; |
||||
|
||||
const files = await fs.readdir(distFolder); |
||||
for (let i = 0; i < files.length; i++) { |
||||
const file = files[i]; |
||||
if (CommonFileRegex.test(file)) { |
||||
const stats = await fs.stat(`${distFolder}/${file}`); |
||||
backgroundFileList.push({ name: file, size: stats.size }); |
||||
uiFileList.push({ name: file, size: stats.size }); |
||||
} else if ( |
||||
backgroundFiles.includes(file) || |
||||
BackgroundFileRegex.test(file) |
||||
) { |
||||
const stats = await fs.stat(`${distFolder}/${file}`); |
||||
backgroundFileList.push({ name: file, size: stats.size }); |
||||
} else if (uiFiles.includes(file) || UIFileRegex.test(file)) { |
||||
const stats = await fs.stat(`${distFolder}/${file}`); |
||||
uiFileList.push({ name: file, size: stats.size }); |
||||
} |
||||
} |
||||
|
||||
const backgroundBundleSize = backgroundFileList.reduce( |
||||
(result, file) => result + file.size, |
||||
0, |
||||
); |
||||
|
||||
const uiBundleSize = uiFileList.reduce( |
||||
(result, file) => result + file.size, |
||||
0, |
||||
); |
||||
|
||||
const result = { |
||||
background: { |
||||
name: 'background', |
||||
size: backgroundBundleSize, |
||||
fileList: backgroundFileList, |
||||
}, |
||||
ui: { |
||||
name: 'ui', |
||||
size: uiBundleSize, |
||||
fileList: uiFileList, |
||||
}, |
||||
}; |
||||
|
||||
if (out) { |
||||
const outPath = `${out}/bundle_size.json`; |
||||
const outputDirectory = path.dirname(outPath); |
||||
const existingParentDirectory = await getFirstParentDirectoryThatExists( |
||||
outputDirectory, |
||||
); |
||||
if (!(await isWritable(existingParentDirectory))) { |
||||
throw new Error('Specified output file directory is not writable'); |
||||
} |
||||
if (outputDirectory !== existingParentDirectory) { |
||||
await fs.mkdir(outputDirectory, { recursive: true }); |
||||
} |
||||
await fs.writeFile(outPath, JSON.stringify(result, null, 2)); |
||||
} else { |
||||
console.log(JSON.stringify(result, null, 2)); |
||||
} |
||||
} |
||||
|
||||
main().catch((error) => { |
||||
exitWithError(error); |
||||
}); |
@ -0,0 +1,2 @@ |
||||
require('./init-load-stats'); |
||||
require('./bundle-size'); |
@ -0,0 +1,111 @@ |
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable node/shebang */ |
||||
const path = require('path'); |
||||
const { promises: fs } = require('fs'); |
||||
const yargs = require('yargs/yargs'); |
||||
const { hideBin } = require('yargs/helpers'); |
||||
|
||||
const { exitWithError } = require('../../../development/lib/exit-with-error'); |
||||
const { |
||||
isWritable, |
||||
getFirstParentDirectoryThatExists, |
||||
} = require('../../helpers/file'); |
||||
const { withFixtures, tinyDelayMs } = require('../helpers'); |
||||
|
||||
/** |
||||
* The e2e test case is used to capture load and initialisation time statistics for extension in MV3 environment. |
||||
*/ |
||||
|
||||
async function profilePageLoad() { |
||||
const parsedLogs = {}; |
||||
try { |
||||
await withFixtures({ fixtures: 'imported-account' }, async ({ driver }) => { |
||||
await driver.delay(tinyDelayMs); |
||||
await driver.navigate(); |
||||
await driver.delay(1000); |
||||
const logs = await driver.checkBrowserForLavamoatLogs(); |
||||
|
||||
let logString = ''; |
||||
let logType = ''; |
||||
|
||||
logs.forEach((log) => { |
||||
if (log.indexOf('"version": 1') >= 0) { |
||||
// log end here
|
||||
logString += log; |
||||
parsedLogs[logType] = JSON.parse(`{${logString}}`); |
||||
logString = ''; |
||||
logType = ''; |
||||
} else if (logType) { |
||||
// log string continues
|
||||
logString += log; |
||||
} else if ( |
||||
log.search(/"name": ".*app\/scripts\/background.js",/u) >= 0 |
||||
) { |
||||
// background log starts
|
||||
logString += log; |
||||
logType = 'background'; |
||||
} else if (log.search(/"name": ".*app\/scripts\/ui.js",/u) >= 0) { |
||||
// ui log starts
|
||||
logString += log; |
||||
logType = 'ui'; |
||||
} else if (log.search(/"name": "Total"/u) >= 0) { |
||||
// load time log starts
|
||||
logString += log; |
||||
logType = 'loadTime'; |
||||
} |
||||
}); |
||||
}); |
||||
} catch (error) { |
||||
console.log('Error in trying to parse logs.'); |
||||
} |
||||
return parsedLogs; |
||||
} |
||||
|
||||
async function main() { |
||||
const { argv } = yargs(hideBin(process.argv)).usage( |
||||
'$0 [options]', |
||||
'Run a page load benchmark', |
||||
(_yargs) => |
||||
_yargs.option('out', { |
||||
description: |
||||
'Output filename. Output printed to STDOUT of this is omitted.', |
||||
type: 'string', |
||||
normalize: true, |
||||
}), |
||||
); |
||||
|
||||
const results = await profilePageLoad(); |
||||
const { out } = argv; |
||||
|
||||
const logCategories = [ |
||||
{ key: 'background', dirPath: 'initialisation/background/stacks.json' }, |
||||
{ key: 'ui', dirPath: 'initialisation/ui/stacks.json' }, |
||||
{ key: 'loadTime', dirPath: 'load_time/stats.json' }, |
||||
]; |
||||
|
||||
if (out) { |
||||
logCategories.forEach(async ({ key, dirPath }) => { |
||||
if (results[key]) { |
||||
const outPath = `${out}/${dirPath}`; |
||||
const outputDirectory = path.dirname(outPath); |
||||
const existingParentDirectory = await getFirstParentDirectoryThatExists( |
||||
outputDirectory, |
||||
); |
||||
if (!(await isWritable(existingParentDirectory))) { |
||||
throw new Error('Specified output file directory is not writable'); |
||||
} |
||||
if (outputDirectory !== existingParentDirectory) { |
||||
await fs.mkdir(outputDirectory, { recursive: true }); |
||||
} |
||||
await fs.writeFile(outPath, JSON.stringify(results[key], null, 2)); |
||||
} |
||||
}); |
||||
} else { |
||||
console.log(JSON.stringify(results, null, 2)); |
||||
} |
||||
} |
||||
|
||||
main().catch((error) => { |
||||
exitWithError(error); |
||||
}); |
@ -0,0 +1,111 @@ |
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable node/shebang */ |
||||
const path = require('path'); |
||||
const { promises: fs } = require('fs'); |
||||
const yargs = require('yargs/yargs'); |
||||
const { hideBin } = require('yargs/helpers'); |
||||
|
||||
const { exitWithError } = require('../../development/lib/exit-with-error'); |
||||
const { |
||||
isWritable, |
||||
getFirstParentDirectoryThatExists, |
||||
} = require('../helpers/file'); |
||||
const { withFixtures, tinyDelayMs } = require('./helpers'); |
||||
|
||||
/** |
||||
* The e2e test case is used to capture load and initialisation time statistics for extension in MV3 environment. |
||||
*/ |
||||
|
||||
async function profilePageLoad() { |
||||
const parsedLogs = {}; |
||||
try { |
||||
await withFixtures({ fixtures: 'imported-account' }, async ({ driver }) => { |
||||
await driver.delay(tinyDelayMs); |
||||
await driver.navigate(); |
||||
await driver.delay(1000); |
||||
const logs = await driver.checkBrowserForLavamoatLogs(); |
||||
|
||||
let logString = ''; |
||||
let logType = ''; |
||||
|
||||
logs.forEach((log) => { |
||||
if (log.indexOf('"version": 1') >= 0) { |
||||
// log end here
|
||||
logString += log; |
||||
parsedLogs[logType] = JSON.parse(`{${logString}}`); |
||||
logString = ''; |
||||
logType = ''; |
||||
} else if (logType) { |
||||
// log string continues
|
||||
logString += log; |
||||
} else if ( |
||||
log.search(/"name": ".*app\/scripts\/background.js",/u) >= 0 |
||||
) { |
||||
// background log starts
|
||||
logString += log; |
||||
logType = 'background'; |
||||
} else if (log.search(/"name": ".*app\/scripts\/ui.js",/u) >= 0) { |
||||
// ui log starts
|
||||
logString += log; |
||||
logType = 'ui'; |
||||
} else if (log.search(/"name": "Total"/u) >= 0) { |
||||
// load time log starts
|
||||
logString += log; |
||||
logType = 'loadTime'; |
||||
} |
||||
}); |
||||
}); |
||||
} catch (error) { |
||||
console.log('Error in trying to parse logs.'); |
||||
} |
||||
return parsedLogs; |
||||
} |
||||
|
||||
async function main() { |
||||
const { argv } = yargs(hideBin(process.argv)).usage( |
||||
'$0 [options]', |
||||
'Run a page load benchmark', |
||||
(_yargs) => |
||||
_yargs.option('out', { |
||||
description: |
||||
'Output filename. Output printed to STDOUT of this is omitted.', |
||||
type: 'string', |
||||
normalize: true, |
||||
}), |
||||
); |
||||
|
||||
const results = await profilePageLoad(); |
||||
const { out } = argv; |
||||
|
||||
const logCategories = [ |
||||
{ key: 'background', dirPath: 'initialisation/background/stacks.json' }, |
||||
{ key: 'ui', dirPath: 'initialisation/ui/stacks.json' }, |
||||
{ key: 'loadTime', dirPath: 'load_time/stats.json' }, |
||||
]; |
||||
|
||||
if (out) { |
||||
logCategories.forEach(async ({ key, dirPath }) => { |
||||
if (results[key]) { |
||||
const outPath = `${out}/${dirPath}`; |
||||
const outputDirectory = path.dirname(outPath); |
||||
const existingParentDirectory = await getFirstParentDirectoryThatExists( |
||||
outputDirectory, |
||||
); |
||||
if (!(await isWritable(existingParentDirectory))) { |
||||
throw new Error('Specified output file directory is not writable'); |
||||
} |
||||
if (outputDirectory !== existingParentDirectory) { |
||||
await fs.mkdir(outputDirectory, { recursive: true }); |
||||
} |
||||
await fs.writeFile(outPath, JSON.stringify(results[key], null, 2)); |
||||
} |
||||
}); |
||||
} else { |
||||
console.log(JSON.stringify(results, null, 2)); |
||||
} |
||||
} |
||||
|
||||
main().catch((error) => { |
||||
exitWithError(error); |
||||
}); |
@ -0,0 +1,28 @@ |
||||
/* |
||||
* Use this class to store pre-deployed smart contract addresses of the contracts deployed to |
||||
* a local blockchain instance ran by Ganache. |
||||
*/ |
||||
class GanacheContractAddressRegistry { |
||||
#addresses = {}; |
||||
|
||||
/** |
||||
* Store new contract address in key:value pair. |
||||
* |
||||
* @param contractName |
||||
* @param contractAddress |
||||
*/ |
||||
storeNewContractAddress(contractName, contractAddress) { |
||||
this.#addresses[contractName] = contractAddress; |
||||
} |
||||
|
||||
/** |
||||
* Get deployed contract address by its name (key). |
||||
* |
||||
* @param contractName |
||||
*/ |
||||
getContractAddress(contractName) { |
||||
return this.#addresses[contractName]; |
||||
} |
||||
} |
||||
|
||||
module.exports = GanacheContractAddressRegistry; |
@ -0,0 +1,88 @@ |
||||
const { ethers } = require('ethers'); |
||||
const ganache = require('ganache'); |
||||
const { contractConfiguration } = require('./smart-contracts'); |
||||
const GanacheContractAddressRegistry = require('./ganache-contract-address-registry'); |
||||
|
||||
/* |
||||
* Ganache seeder is used to seed initial smart contract or set initial blockchain state. |
||||
*/ |
||||
class GanacheSeeder { |
||||
constructor(debug = false) { |
||||
this.debug = debug; |
||||
this.smartContractRegistry = new GanacheContractAddressRegistry(); |
||||
} |
||||
|
||||
/** |
||||
* Deploy initial smart contracts that can be used later within the e2e tests. |
||||
* |
||||
* @param contractName |
||||
*/ |
||||
|
||||
async deploySmartContract(contractName) { |
||||
if (this.debug) { |
||||
console.log('Deploying smart contracts using GanacheSeeder'); |
||||
} |
||||
|
||||
const ethersProvider = new ethers.providers.Web3Provider( |
||||
ganache.provider(), |
||||
'any', |
||||
); |
||||
const contractFactory = new ethers.ContractFactory( |
||||
contractConfiguration[contractName].abi, |
||||
contractConfiguration[contractName].bytecode, |
||||
ethersProvider.getSigner(), |
||||
); |
||||
|
||||
let contract; |
||||
|
||||
if (contractName === 'hst') { |
||||
contract = await contractFactory.deploy( |
||||
contractConfiguration.hst.initialAmount, |
||||
contractConfiguration.hst.tokenName, |
||||
contractConfiguration.hst.decimalUnits, |
||||
contractConfiguration.hst.tokenSymbol, |
||||
); |
||||
} else { |
||||
contract = await contractFactory.deploy(); |
||||
} |
||||
|
||||
await contract.deployTransaction.wait(); |
||||
|
||||
if (this.debug) { |
||||
console.log( |
||||
`Contract mined! address: ${contract.address} transactionHash: ${contract.deployTransaction.hash}`, |
||||
); |
||||
} |
||||
this.storeSmartContractAddress(contractName, contract.address); |
||||
} |
||||
|
||||
/** |
||||
* Store deployed smart contract address within the environment variables |
||||
* to make it available everywhere. |
||||
* |
||||
* @param contractName |
||||
* @param contractAddress |
||||
*/ |
||||
storeSmartContractAddress(contractName, contractAddress) { |
||||
if (this.debug) { |
||||
console.log( |
||||
`Storing smart contract address: [${contractName}] => ${contractAddress}`, |
||||
); |
||||
} |
||||
this.smartContractRegistry.storeNewContractAddress( |
||||
contractName, |
||||
contractAddress, |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Return an instance of the currently used smart contract registry. |
||||
* |
||||
* @returns GanacheContractAddressRegistry |
||||
*/ |
||||
getContractRegistry() { |
||||
return this.smartContractRegistry; |
||||
} |
||||
} |
||||
|
||||
module.exports = GanacheSeeder; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue