1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Adding flag for MV3 (#14762)

This commit is contained in:
Jyoti Puri 2022-05-26 10:18:23 +05:30 committed by GitHub
parent 51986a4724
commit 25082ae272
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 361 additions and 21 deletions

View File

@ -8,7 +8,7 @@
<link rel="stylesheet" type="text/css" href="./index-rtl.css" title="rtl" disabled>
</head>
<body>
<div id="app-content"></div>
<div id="app-content"><div id="app-loader">Loading...</div></div>
<div id="popover-content"></div>
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
<script src="./sentry-install.js" type="text/javascript" charset="utf-8"></script>

View File

@ -0,0 +1,81 @@
{
"action": {
"default_icon": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
"32": "images/icon-32.png",
"38": "images/icon-38.png",
"64": "images/icon-64.png",
"128": "images/icon-128.png",
"512": "images/icon-512.png"
},
"default_title": "MetaMask",
"default_popup": "popup.html"
},
"author": "https://metamask.io",
"background": {
"service_worker": "app-init.js"
},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"windows": "Alt+Shift+M",
"mac": "Alt+Shift+M",
"chromeos": "Alt+Shift+M",
"linux": "Alt+Shift+M"
}
}
},
"content_scripts": [
{
"matches": ["file://*/*", "http://*/*", "https://*/*"],
"js": [
"disable-console.js",
"globalthis.js",
"lockdown-install.js",
"lockdown-run.js",
"lockdown-more.js",
"contentscript.js"
],
"run_at": "document_start",
"all_frames": true
},
{
"matches": ["*://connect.trezor.io/*/popup.html"],
"js": ["vendor/trezor/content-script.js"]
}
],
"default_locale": "en",
"description": "__MSG_appDescription__",
"icons": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
"32": "images/icon-32.png",
"38": "images/icon-38.png",
"48": "images/icon-48.png",
"64": "images/icon-64.png",
"128": "images/icon-128.png",
"512": "images/icon-512.png"
},
"manifest_version": 3,
"name": "__MSG_appName__",
"permissions": [
"storage",
"unlimitedStorage",
"clipboardWrite",
"http://localhost:8545/",
"https://*.infura.io/",
"https://lattice.gridplus.io/*",
"activeTab",
"webRequest",
"*://*.eth/",
"notifications"
],
"short_name": "__MSG_appName__",
"web_accessible_resources": [
{
"resources": ["inpage.js", "phishing.html"],
"matches": ["http://*/*", "https://*/*"]
}
]
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,7 @@
{
"externally_connectable": {
"matches": ["https://metamask.io/*"],
"ids": ["*"]
},
"minimum_chrome_version": "66"
}

View File

@ -0,0 +1,26 @@
{
"applications": {
"gecko": {
"id": "webextension@metamask.io",
"strict_min_version": "68.0"
}
},
"background": {
"page": "background.html",
"persistent": true
},
"browser_action": {
"default_icon": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
"32": "images/icon-32.png",
"38": "images/icon-38.png",
"64": "images/icon-64.png",
"128": "images/icon-128.png",
"512": "images/icon-512.png"
},
"default_title": "MetaMask",
"default_popup": "popup.html"
},
"manifest_version": 2
}

View File

@ -0,0 +1,9 @@
{
"permissions": [
"storage",
"tabs",
"clipboardWrite",
"clipboardRead",
"http://localhost:8545/"
]
}

55
app/scripts/app-init.js Normal file
View File

@ -0,0 +1,55 @@
// eslint-disable-next-line import/unambiguous
function tryImport(...fileNames) {
try {
// eslint-disable-next-line
importScripts(...fileNames);
return true;
} catch (e) {
console.error(e);
return false;
}
}
function importAllScripts() {
const startImportScriptsTime = Date.now();
// applyLavaMoat has been hard coded to "true" as
// tryImport('./runtime-cjs.js') is giving issue with XMLHttpRequest object which is not avaialble to service worker.
// we need to dynamically inject values of applyLavaMoat once this is fixed.
const applyLavaMoat = true;
tryImport('./globalthis.js');
tryImport('./sentry-install.js');
if (applyLavaMoat) {
tryImport('./runtime-lavamoat.js');
tryImport('./lockdown-more.js');
tryImport('./policy-load.js');
} else {
tryImport('./lockdown-install.js');
tryImport('./lockdown-more.js');
tryImport('./lockdown-run.js');
tryImport('./runtime-cjs.js');
}
const fileList = [
// The list of files is injected at build time by replacing comment below with comma separated strings of file names
/** FILE NAMES */
];
fileList.forEach((fileName) => tryImport(fileName));
// for performance metrics/reference
console.log(
`SCRIPTS IMPORT COMPLETE in Seconds: ${
(Date.now() - startImportScriptsTime) / 1000
}`,
);
}
// Placing script import call here ensures that scripts are inported each time service worker is activated.
importAllScripts();
/**
* An open issue is changes in this file break during hot reloading. Reason is dynamic injection of "FILE NAMES".
* Developers need to restart local server if they change this file.
*/

View File

@ -23,6 +23,7 @@ import {
REJECT_NOTFICIATION_CLOSE,
REJECT_NOTFICIATION_CLOSE_SIG,
} from '../../shared/constants/metametrics';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import migrations from './migrations';
import Migrator from './lib/migrator';
import ExtensionPlatform from './platforms/extension';
@ -45,6 +46,14 @@ import { getPlatform } from './lib/util';
const { sentry } = global;
const firstTimeState = { ...rawFirstTimeState };
const metamaskInternalProcessHash = {
[ENVIRONMENT_TYPE_POPUP]: true,
[ENVIRONMENT_TYPE_NOTIFICATION]: true,
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
};
const metamaskBlockedPorts = ['trezor-connect'];
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'info');
const platform = new ExtensionPlatform();
@ -67,8 +76,23 @@ if (inTest || process.env.METAMASK_DEBUG) {
global.metamaskGetState = localStore.get.bind(localStore);
}
// initialization flow
initialize().catch(log.error);
/**
* In case of MV3 we attach a "onConnect" event listener as soon as the application is initialised.
* Reason is that in case of MV3 a delay in doing this was resulting in missing first connect event after service worker is re-activated.
*/
const initApp = async (remotePort) => {
browser.runtime.onConnect.removeListener(initApp);
await initialize(remotePort);
log.info('MetaMask initialization complete.');
};
if (isManifestV3()) {
browser.runtime.onConnect.addListener(initApp);
} else {
// initialization flow
initialize().catch(log.error);
}
/**
* @typedef {import('../../shared/constants/transaction').TransactionMeta} TransactionMeta
@ -128,12 +152,13 @@ initialize().catch(log.error);
/**
* Initializes the MetaMask controller, and sets up all platform configuration.
*
* @param {string} remotePort - remote application port connecting to extension.
* @returns {Promise} Setup complete.
*/
async function initialize() {
async function initialize(remotePort) {
const initState = await loadStateFromPersistence();
const initLangCode = await getFirstPreferredLangCode();
await setupController(initState, initLangCode);
await setupController(initState, initLangCode, remotePort);
log.info('MetaMask initialization complete.');
}
@ -205,9 +230,10 @@ async function loadStateFromPersistence() {
*
* @param {Object} initState - The initial state to start the controller with, matches the state that is emitted from the controller.
* @param {string} initLangCode - The region code for the language preferred by the current user.
* @param {string} remoteSourcePort - remote application port connecting to extension.
* @returns {Promise} After setup is complete.
*/
function setupController(initState, initLangCode) {
function setupController(initState, initLangCode, remoteSourcePort) {
//
// MetaMask Controller
//
@ -294,17 +320,13 @@ function setupController(initState, initLangCode) {
//
// connect to other contexts
//
if (isManifestV3() && remoteSourcePort) {
connectRemote(remoteSourcePort);
}
browser.runtime.onConnect.addListener(connectRemote);
browser.runtime.onConnectExternal.addListener(connectExternal);
const metamaskInternalProcessHash = {
[ENVIRONMENT_TYPE_POPUP]: true,
[ENVIRONMENT_TYPE_NOTIFICATION]: true,
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
};
const metamaskBlockedPorts = ['trezor-connect'];
const isClientOpenStatus = () => {
return (
popupIsOpen ||
@ -368,6 +390,13 @@ function setupController(initState, initLangCode) {
controller.isClientOpen = true;
controller.setupTrustedCommunication(portStream, remotePort.sender);
if (isManifestV3()) {
// Message below if captured by UI code in app/scripts/ui.js which will trigger UI initialisation
// This ensures that UI is initialised only after background is ready
// It fixes the issue of blank screen coming when extension is loaded, the issue is very frequent in MV3
remotePort.postMessage({ name: 'CONNECTION_READY' });
}
if (processName === ENVIRONMENT_TYPE_POPUP) {
popupIsOpen = true;
endOfStream(portStream, () => {
@ -480,8 +509,14 @@ function setupController(initState, initLangCode) {
if (count) {
label = String(count);
}
browser.browserAction.setBadgeText({ text: label });
browser.browserAction.setBadgeBackgroundColor({ color: '#037DD6' });
// browserAction has been replaced by action in MV3
if (isManifestV3()) {
browser.action.setBadgeText({ text: label });
browser.action.setBadgeBackgroundColor({ color: '#037DD6' });
} else {
browser.browserAction.setBadgeText({ text: label });
browser.browserAction.setBadgeBackgroundColor({ color: '#037DD6' });
}
}
function getUnapprovedTransactionCount() {

View File

@ -6,6 +6,8 @@ import browser from 'webextension-polyfill';
import PortStream from 'extension-port-stream';
import { obj as createThoughStream } from 'through2';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
// These require calls need to use require to be statically recognized by browserify
const fs = require('fs');
const path = require('path');
@ -42,7 +44,12 @@ function injectScript(content) {
const container = document.head || document.documentElement;
const scriptTag = document.createElement('script');
scriptTag.setAttribute('async', 'false');
scriptTag.textContent = content;
// Inline scripts do not work in MV3 due to more strict security policy
if (isManifestV3()) {
scriptTag.setAttribute('src', browser.runtime.getURL('inpage.js'));
} else {
scriptTag.textContent = content;
}
container.insertBefore(scriptTag, container.children[0]);
container.removeChild(scriptTag);
} catch (error) {

View File

@ -16,6 +16,7 @@ import {
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_POPUP,
} from '../../shared/constants/app';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import ExtensionPlatform from './platforms/extension';
import { setupMultiplex } from './lib/stream-utils';
import { getEnvironmentType } from './lib/util';
@ -35,7 +36,20 @@ async function start() {
const connectionStream = new PortStream(extensionPort);
const activeTab = await queryCurrentActiveTab(windowType);
initializeUiWithTab(activeTab);
/**
* In case of MV3 the issue of blank screen was very frequent, it is caused by UI initialising before background is ready to send state.
* Code below ensures that UI is rendered only after background is ready.
*/
if (isManifestV3()) {
extensionPort.onMessage.addListener((message) => {
if (message?.name === 'CONNECTION_READY') {
initializeUiWithTab(activeTab);
}
});
} else {
initializeUiWithTab(activeTab);
}
function displayCriticalError(container, err) {
container.innerHTML =

View File

@ -2,7 +2,9 @@ const { promises: fs } = require('fs');
const path = require('path');
const { mergeWith, cloneDeep } = require('lodash');
const baseManifest = require('../../app/manifest/_base.json');
const baseManifest = process.env.ENABLE_MV3
? require('../../app/manifest/v3/_base.json')
: require('../../app/manifest/v2/_base.json');
const { BuildType } = require('../lib/build-type');
const { createTask, composeSeries } = require('./task');
@ -24,7 +26,7 @@ function createManifestTasks({
'..',
'..',
'app',
'manifest',
process.env.ENABLE_MV3 ? 'manifest/v3' : 'manifest/v2',
`${platform}.json`,
),
);

View File

@ -345,6 +345,50 @@ function createScriptTasks({
}
}
// Function generates app-init.js for browsers chrome, brave and opera.
// It dynamically injects list of files generated in the build.
async function bundleMV3AppInitialiser({
jsBundles,
browserPlatforms,
buildType,
devMode,
ignoredFiles,
testing,
policyOnly,
shouldLintFenceFiles,
}) {
const label = 'app-init';
// TODO: remove this filter for firefox once MV3 is supported in it
const mv3BrowserPlatforms = browserPlatforms.filter(
(platform) => platform !== 'firefox',
);
const fileList = jsBundles.reduce(
(result, file) => `${result}'${file}',\n `,
'',
);
await createNormalBundle({
browserPlatforms: mv3BrowserPlatforms,
buildType,
destFilepath: 'app-init.js',
devMode,
entryFilepath: './app/scripts/app-init.js',
ignoredFiles,
label,
testing,
policyOnly,
shouldLintFenceFiles,
})();
mv3BrowserPlatforms.forEach((browser) => {
const appInitFile = `./dist/${browser}/app-init.js`;
const fileContent = readFileSync('./app/scripts/app-init.js', 'utf8');
const fileOutput = fileContent.replace('/** FILE NAMES */', fileList);
writeFileSync(appInitFile, fileOutput);
});
console.log(`Bundle end: service worker app-init.js`);
}
function createFactoredBuild({
applyLavaMoat,
browserPlatforms,
@ -457,7 +501,7 @@ function createFactoredBuild({
});
// wait for bundle completion for postprocessing
events.on('bundleDone', () => {
events.on('bundleDone', async () => {
// Skip HTML generation if nothing is to be written to disk
if (policyOnly) {
return;
@ -503,6 +547,22 @@ function createFactoredBuild({
browserPlatforms,
applyLavaMoat,
});
if (process.env.ENABLE_MV3) {
const jsBundles = [
...commonSet.values(),
...groupSet.values(),
].map((label) => `./${label}.js`);
await bundleMV3AppInitialiser({
jsBundles,
browserPlatforms,
buildType,
devMode,
ignoredFiles,
testing,
policyOnly,
shouldLintFenceFiles,
});
}
break;
}
case 'content-script': {

View File

@ -3511,6 +3511,7 @@
"setTimeout": true
},
"packages": {
"@metamask/snap-controllers>@metamask/controllers": true,
"@metamask/controllers": true,
"@metamask/post-message-stream": true,
"@metamask/providers>@metamask/object-multiplex": true,
@ -3533,6 +3534,43 @@
"semver": true
}
},
"@metamask/snap-controllers>@metamask/controllers": {
"packages": {
"@metamask/controllers>isomorphic-fetch": true,
"browserify>buffer": true,
"ethereumjs-util": true,
"ethjs>ethjs-unit": true,
"eth-rpc-errors": true,
"eth-ens-namehash": true,
"eth-sig-util": true,
"jsonschema": true,
"@metamask/controllers>multiformats": true,
"@storybook/api>fast-deep-equal": true,
"eth-query": true,
"@metamask/controllers>async-mutex": true,
"@metamask/snap-controllers>nanoid": true,
"immer": true,
"web3": true,
"single-call-balance-checker-abi": true,
"@metamask/metamask-eth-abis": true,
"ethereumjs-wallet": true,
"eth-keyring-controller": true,
"uuid": true,
"browserify>events": true,
"@metamask/controllers>web3-provider-engine": true,
"eth-json-rpc-infura": true,
"punycode": true,
"@metamask/controllers>eth-phishing-detect": true,
"eth-method-registry": true,
"@ethereumjs/common": true,
"@ethereumjs/tx": true,
"@metamask/contract-metadata": true,
"@metamask/controllers>abort-controller": true,
"ethers": true,
"deep-freeze-strict": true,
"json-rpc-engine": true
}
},
"@metamask/snap-controllers>@metamask/obs-store": {
"packages": {
"@metamask/snap-controllers>@metamask/obs-store>through2": true,

View File

@ -11,6 +11,7 @@
"setup:postinstall": "yarn patch-package && yarn allow-scripts",
"start": "yarn build:dev dev --apply-lavamoat=false",
"start:lavamoat": "yarn build:dev dev --apply-lavamoat=true",
"start:mv3": "ENABLE_MV3=true yarn build:dev dev --apply-lavamoat=false",
"dist": "yarn build prod",
"build": "yarn lavamoat:build",
"build:dev": "node development/build/index.js",

View File

@ -0,0 +1,4 @@
import browser from 'webextension-polyfill';
export const isManifestV3 = () =>
browser.runtime.getManifest().manifest_version === 3;