From 4ed94bf3c4280032261b6996b79f7773fd825235 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 9 Oct 2017 21:44:58 +0200 Subject: [PATCH] feat: normalize dweb: in `href` and `src` Content script detects IPFS-related protocols in `href` and `src` attributes of Elements such as `` or `` and replaces them with URL at the user-specified public HTTP gateway. Note that if IPFS API is online, HTTP request will be redirected to custom gateway. Closes #286 --- add-on/src/lib/common.js | 26 +++++- .../normalizeLinksWithUnhandledProtocols.js | 93 +++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 add-on/src/lib/normalizeLinksWithUnhandledProtocols.js diff --git a/add-on/src/lib/common.js b/add-on/src/lib/common.js index 960240b86..df0afa95b 100644 --- a/add-on/src/lib/common.js +++ b/add-on/src/lib/common.js @@ -460,8 +460,8 @@ function isIpfsPageActionsContext (url) { } async function onUpdatedTab (tabId, changeInfo, tab) { - if (tab && tab.url && !tab.url.startsWith('chrome://')) { - if (state.linkify && changeInfo.status === 'complete') { + if (changeInfo.status === 'complete' && tab.url && tab.url.startsWith('http')) { + if (state.linkify) { console.log(`[ipfs-companion] Running linkfyDOM for ${tab.url}`) try { await browser.tabs.executeScript(tabId, { @@ -480,6 +480,28 @@ async function onUpdatedTab (tabId, changeInfo, tab) { console.error(`Unable to linkify DOM at '${tab.url}' due to`, error) } } + if (state.catchUnhandledProtocols) { + // console.log(`[ipfs-companion] Normalizing links with unhandled protocols at ${tab.url}`) + // See: https://github.com/ipfs/ipfs-companion/issues/286 + try { + // pass the URL of user-preffered public gateway + await browser.tabs.executeScript(tabId, { + code: `window.ipfsCompanionPubGwURL = '${state.pubGwURLString}'`, + matchAboutBlank: false, + allFrames: true, + runAt: 'document_start' + }) + // inject script that normalizes `href` and `src` containing unhandled protocols + await browser.tabs.executeScript(tabId, { + file: '/src/lib/normalizeLinksWithUnhandledProtocols.js', + matchAboutBlank: false, + allFrames: true, + runAt: 'document_end' + }) + } catch (error) { + console.error(`Unable to normalize links at '${tab.url}' due to`, error) + } + } } } diff --git a/add-on/src/lib/normalizeLinksWithUnhandledProtocols.js b/add-on/src/lib/normalizeLinksWithUnhandledProtocols.js new file mode 100644 index 000000000..5bf5e803a --- /dev/null +++ b/add-on/src/lib/normalizeLinksWithUnhandledProtocols.js @@ -0,0 +1,93 @@ +'use strict' +/* eslint-env browser, webextensions */ + +/* + * This content script detects IPFS-related protocols in `href` and `src` + * attributes and replaces them with URL at the user-specified public HTTP gateway. + * Note that if IPFS API is online, HTTP request will be redirected to custom gateway. + * + * For more background see: https://github.com/ipfs/ipfs-companion/issues/286 + * + * Test page: http://bit.ly/2hXiuUz + */ + +;(function (alreadyLoaded) { + if (alreadyLoaded) { + return + } + + // Limit contentType to "text/plain" or "text/html" + if (document.contentType !== undefined && document.contentType !== 'text/plain' && document.contentType !== 'text/html') { + return + } + + // prevent double init + window.ipfsCompanionNormalizedUnhandledProtocols = true + + // XPath selects all elements that have `href` of `src` attribute starting with one of IPFS-related protocols + const xpath = ".//*[starts-with(@href, 'ipfs://') or starts-with(@href, 'ipns://') or starts-with(@href, 'dweb:') " + + " or starts-with(@src, 'ipfs://') or starts-with(@src, 'ipns://') or starts-with(@src, 'dweb:')]" + + const pubGwURL = window.ipfsCompanionPubGwURL + + function init () { + // initial run + normalizeTree(document.body) + + // listen for future DOM changes + new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + if (mutation.type === 'childList') { + for (let addedNode of mutation.addedNodes) { + if (addedNode.nodeType === Node.ELEMENT_NODE) { + setTimeout(() => normalizeTree(addedNode), 0) + } + } + } + }) + }).observe(document.body, { + characterData: false, + childList: true, + subtree: true + }) + } + + function normalizeElement (element) { + if (element.href) { + // console.log('normalizeElement.href: ' + element.href) + element.href = normalizeAddress(element.href) + } else if (element.src) { + // console.log('normalizeElement.src: ' + element.src) + element.src = normalizeAddress(element.src) + } + } + + // replaces unhandled protocol with a regular URL at a public gateway + function normalizeAddress (addr) { + return addr + .replace(/^dweb:\//i, pubGwURL) // dweb:/ipfs/Qm → /ipfs/Qm + .replace(/^ipfs:\/\//i, `${pubGwURL}ipfs/`) // ipfs://Qm → /ipfs/Qm + .replace(/^ipns:\/\//i, `${pubGwURL}ipns/`) // ipns://Qm → /ipns/Qm + } + + function normalizeTree (root) { + const xpathResult = document.evaluate(xpath, root, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null) + let i = 0 + function continuation () { + let node = null + let counter = 0 + while ((node = xpathResult.snapshotItem(i++))) { + const parent = node.parentNode + // Skip if no longer in visible DOM + if (!parent || !document.body.contains(node)) continue + normalizeElement(node) + if (++counter > 10) { + return setTimeout(continuation, 0) + } + } + } + window.requestAnimationFrame(continuation) + } + + init() +}(window.ipfsCompanionNormalizedUnhandledProtocols))