Fix #14846 - Inject provider for MV3 via app-init (#15448)

feature/default_network_editable
David Walsh 2 years ago committed by GitHub
parent b29aa44a64
commit 9cf358a82a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/manifest/v3/_base.json
  2. 15
      app/scripts/app-init.js
  3. 106
      app/scripts/contentscript.js
  4. 25
      app/scripts/inpage.js
  5. 93
      shared/modules/provider-injection.js

@ -47,6 +47,7 @@
],
"default_locale": "en",
"description": "__MSG_appDescription__",
"host_permissions": ["file://*/*", "http://*/*", "https://*/*"],
"icons": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
@ -61,6 +62,7 @@
"name": "__MSG_appName__",
"permissions": [
"storage",
"scripting",
"unlimitedStorage",
"clipboardWrite",
"http://localhost:8545/",

@ -126,3 +126,18 @@ chrome.runtime.onMessage.addListener(() => {
importAllScripts();
return false;
});
/*
* This content script is injected programmatically because
* MAIN world injection does not work properly via manifest
* https://bugs.chromium.org/p/chromium/issues/detail?id=634381
*/
chrome.scripting.registerContentScripts([
{
id: 'inpage',
matches: ['file://*/*', 'http://*/*', 'https://*/*'],
js: ['inpage.js'],
runAt: 'document_start',
world: 'MAIN',
},
]);

@ -6,6 +6,7 @@ import PortStream from 'extension-port-stream';
import { obj as createThoughStream } from 'through2';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import shouldInjectProvider from '../../shared/modules/provider-injection';
// These require calls need to use require to be statically recognized by browserify
const fs = require('fs');
@ -43,7 +44,9 @@ if (
) {
setupPhishingStream();
} else if (shouldInjectProvider()) {
injectScript(inpageBundle);
if (!isManifestV3()) {
injectScript(inpageBundle);
}
setupStreams();
}
@ -57,12 +60,7 @@ function injectScript(content) {
const container = document.head || document.documentElement;
const scriptTag = document.createElement('script');
scriptTag.setAttribute('async', 'false');
// 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;
}
scriptTag.textContent = content;
container.insertBefore(scriptTag, container.children[0]);
container.removeChild(scriptTag);
} catch (error) {
@ -264,100 +262,6 @@ function notifyInpageOfStreamFailure() {
);
}
/**
* Determines if the provider should be injected
*
* @returns {boolean} {@code true} Whether the provider should be injected
*/
function shouldInjectProvider() {
return (
doctypeCheck() &&
suffixCheck() &&
documentElementCheck() &&
!blockedDomainCheck()
);
}
/**
* Checks the doctype of the current document if it exists
*
* @returns {boolean} {@code true} if the doctype is html or if none exists
*/
function doctypeCheck() {
const { doctype } = window.document;
if (doctype) {
return doctype.name === 'html';
}
return true;
}
/**
* Returns whether or not the extension (suffix) of the current document is prohibited
*
* This checks {@code window.location.pathname} against a set of file extensions
* that we should not inject the provider into. This check is indifferent of
* query parameters in the location.
*
* @returns {boolean} whether or not the extension of the current document is prohibited
*/
function suffixCheck() {
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u];
const currentUrl = window.location.pathname;
for (let i = 0; i < prohibitedTypes.length; i++) {
if (prohibitedTypes[i].test(currentUrl)) {
return false;
}
}
return true;
}
/**
* Checks the documentElement of the current document
*
* @returns {boolean} {@code true} if the documentElement is an html node or if none exists
*/
function documentElementCheck() {
const documentElement = document.documentElement.nodeName;
if (documentElement) {
return documentElement.toLowerCase() === 'html';
}
return true;
}
/**
* Checks if the current domain is blocked
*
* @returns {boolean} {@code true} if the current domain is blocked
*/
function blockedDomainCheck() {
const blockedDomains = [
'adyen.com',
'ani.gamer.com.tw',
'blueskybooking.com',
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
'docs.google.com',
'dropbox.com',
'gravityforms.com',
'harbourair.com',
'sharefile.com',
'uscourts.gov',
'webbyawards.com',
];
const currentUrl = window.location.href;
let currentRegex;
for (let i = 0; i < blockedDomains.length; i++) {
const blockedDomain = blockedDomains[i].replace('.', '\\.');
currentRegex = new RegExp(
`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
'u',
);
if (!currentRegex.test(currentUrl)) {
return true;
}
}
return false;
}
/**
* Redirects the current page to a phishing information page
*

@ -34,6 +34,7 @@ cleanContextForImports();
import log from 'loglevel';
import { WindowPostMessageStream } from '@metamask/post-message-stream';
import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider';
import shouldInjectProvider from '../../shared/modules/provider-injection';
restoreContextAfterImports();
@ -43,14 +44,16 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn');
// setup plugin communication
//
// setup background connection
const metamaskStream = new WindowPostMessageStream({
name: 'metamask-inpage',
target: 'metamask-contentscript',
});
initializeProvider({
connectionStream: metamaskStream,
logger: log,
shouldShimWeb3: true,
});
if (shouldInjectProvider()) {
// setup background connection
const metamaskStream = new WindowPostMessageStream({
name: 'metamask-inpage',
target: 'metamask-contentscript',
});
initializeProvider({
connectionStream: metamaskStream,
logger: log,
shouldShimWeb3: true,
});
}

@ -0,0 +1,93 @@
/**
* Determines if the provider should be injected
*
* @returns {boolean} {@code true} Whether the provider should be injected
*/
export default function shouldInjectProvider() {
return (
doctypeCheck() &&
suffixCheck() &&
documentElementCheck() &&
!blockedDomainCheck()
);
}
/**
* Checks the doctype of the current document if it exists
*
* @returns {boolean} {@code true} if the doctype is html or if none exists
*/
function doctypeCheck() {
const { doctype } = window.document;
if (doctype) {
return doctype.name === 'html';
}
return true;
}
/**
* Returns whether or not the extension (suffix) of the current document is prohibited
*
* This checks {@code window.location.pathname} against a set of file extensions
* that we should not inject the provider into. This check is indifferent of
* query parameters in the location.
*
* @returns {boolean} whether or not the extension of the current document is prohibited
*/
function suffixCheck() {
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u];
const currentUrl = window.location.pathname;
for (let i = 0; i < prohibitedTypes.length; i++) {
if (prohibitedTypes[i].test(currentUrl)) {
return false;
}
}
return true;
}
/**
* Checks the documentElement of the current document
*
* @returns {boolean} {@code true} if the documentElement is an html node or if none exists
*/
function documentElementCheck() {
const documentElement = document.documentElement.nodeName;
if (documentElement) {
return documentElement.toLowerCase() === 'html';
}
return true;
}
/**
* Checks if the current domain is blocked
*
* @returns {boolean} {@code true} if the current domain is blocked
*/
function blockedDomainCheck() {
const blockedDomains = [
'uscourts.gov',
'dropbox.com',
'webbyawards.com',
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
'adyen.com',
'gravityforms.com',
'docs.google.com',
'harbourair.com',
'ani.gamer.com.tw',
'blueskybooking.com',
'sharefile.com',
];
const currentUrl = window.location.href;
let currentRegex;
for (let i = 0; i < blockedDomains.length; i++) {
const blockedDomain = blockedDomains[i].replace('.', '\\.');
currentRegex = new RegExp(
`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
'u',
);
if (!currentRegex.test(currentUrl)) {
return true;
}
}
return false;
}
Loading…
Cancel
Save