When a new site is visited, the WebExtension creates a new ContentScript
in that page's context, which can be seen at app/scripts/contentscript.js
. This script represents a per-page setup process, which creates the per-page web3
api, connects it to the background script via the Port API (wrapped in a stream abstraction), and injected into the DOM before anything loads.
当访问一个新站点时,WebExtension会在该页面的上下文中创建一个新的ContentScript,可以在app/scripts/ ContentScript .js中看到其的代码。这个脚本表示每个页面的设置过程,它创建每个页面的web3api,通过端口API(封装在流抽象中)将其连接到后台脚本,并在加载之前注入DOM。
它其实就是在页面与metamask之间进行交互前先通过contentscript来对页面的内容进行查看并判断是否提供web3给页面
metamask-extension/app/scripts/contentscript.js
它的作用就是将inpage.js这个脚本写到浏览器<script>上,使得浏览器能够调用这个脚本处理json rpc
const fs = require('fs') const path = require('path') const pump = require('pump') const LocalMessageDuplexStream = require('post-message-stream') const PongStream = require('ping-pong-stream/pong') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') const PortStream = require('./lib/port-stream.js') const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + ' ' const inpageBundle = inpageContent + inpageSuffix // Eventually this streaming injection could be replaced with: // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction // // But for now that is only Firefox // If we create a FireFox-only code path using that API, // MetaMask will be much faster loading and performant on Firefox. if (shouldInjectWeb3()) {//判断传过来的访问页面如果判断满足能够InjectWeb3就将web3 inject并且建立从contentscript到inpage的双向流 setupInjection() //然后这样传给inpage后,其web3才能使用,然后再传给页面使用 setupStreams() } /** * Creates a script tag that injects inpage.js */ function setupInjection () {//将inpage.js脚本嵌入页面<script>中 try { // inject in-page script var scriptTag = document.createElement('script') scriptTag.textContent = inpageBundle scriptTag.onload = function () { this.parentNode.removeChild(this) } var container = document.head || document.documentElement // append as first child container.insertBefore(scriptTag, container.children[0]) } catch (e) { console.error('Metamask injection failed.', e) } } /** * Sets up two-way communication streams between the * browser extension and local per-page browser context */ function setupStreams () { //建立contentscript到inpage的双向流,inpage.js是创建inpage到contentscript的双向流 // setup communication to page and plugin const pageStream = new LocalMessageDuplexStream({ name: 'contentscript', target: 'inpage', }) const pluginPort = extension.runtime.connect({ name: 'contentscript' }) const pluginStream = new PortStream(pluginPort) // forward communication plugin->inpage pump( pageStream, pluginStream, pageStream, (err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err) ) // setup local multistream channels const mux = new ObjectMultiplex() mux.setMaxListeners(25) pump( mux, pageStream, mux, (err) => logStreamDisconnectWarning('MetaMask Inpage', err) ) pump( mux, pluginStream, mux, (err) => logStreamDisconnectWarning('MetaMask Background', err) ) // connect ping stream const pongStream = new PongStream({ objectMode: true }) pump( mux, pongStream, mux, (err) => logStreamDisconnectWarning('MetaMask PingPongStream', err) ) // connect phishing warning stream const phishingStream = mux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) // ignore unused channels (handled by background, inpage) mux.ignoreStream('provider') mux.ignoreStream('publicConfig') } /** * Error handler for page to plugin stream disconnections * * @param {string} remoteLabel Remote stream name * @param {Error} err Stream connection error */ function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` if (err) warningMsg += ' ' + err.stack console.warn(warningMsg) } /** * Determines if Web3 should be injected * * @returns {boolean} {@code true} if Web3 should be injected */ function shouldInjectWeb3 () {决定是否应该插入web3 return doctypeCheck() && suffixCheck() && documentElementCheck() && !blacklistedDomainCheck() // } /** * 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.doctype //指定文档类型节点是否为html或不存在 if (doctype) { return doctype.name === 'html' } else { return true } } /** * Checks the current document extension * * @returns {boolean} {@code true} if the current extension is not prohibited */ function suffixCheck () {//检查当前文档扩展名(不为'xml', 'pdf') var prohibitedTypes = ['xml', 'pdf'] var currentUrl = window.location.href var currentRegex for (let i = 0; i < prohibitedTypes.length; i++) { currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`) if (currentRegex.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 () { var documentElement = document.documentElement.nodeName //返回文档的根节点是否为html或不存在 if (documentElement) { return documentElement.toLowerCase() === 'html' } return true } /** * Checks if the current domain is blacklisted * * @returns {boolean} {@code true} if the current domain is blacklisted */ function blacklistedDomainCheck () {//黑名单 var blacklistedDomains = [ 'uscourts.gov', 'dropbox.com', 'webbyawards.com', 'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html', 'adyen.com', 'gravityforms.com', 'harbourair.com', 'ani.gamer.com.tw', 'blueskybooking.com', ] var currentUrl = window.location.href var currentRegex for (let i = 0; i < blacklistedDomains.length; i++) { const blacklistedDomain = blacklistedDomains[i].replace('.', '\.') currentRegex = new RegExp(`(?:https?:\/\/)(?:(?!${blacklistedDomain}).)*$`) if (!currentRegex.test(currentUrl)) { return true } } return false } /** * Redirects the current page to a phishing information page */ function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') window.location.href = extensionURL }