Skip to content

Commit 8f506c2

Browse files
committed
fix: duplicated recovery tabs in Chromium
- listen only for webRequest.ResourceType=main_frame - reuse existing tab - deduplicate onErrorOccurred events on Chrome Closes #805
1 parent 3a959b1 commit 8f506c2

File tree

3 files changed

+42
-17
lines changed

3 files changed

+42
-17
lines changed

add-on/src/lib/ipfs-companion.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ module.exports = async function init () {
105105
browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, { urls: ['<all_urls>'] }, onBeforeSendInfoSpec)
106106
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: ['<all_urls>'] }, ['blocking'])
107107
browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { urls: ['<all_urls>'] }, ['blocking', 'responseHeaders'])
108-
browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'] })
109-
browser.webRequest.onCompleted.addListener(onCompleted, { urls: ['<all_urls>'] })
108+
browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'], types: ['main_frame'] })
109+
browser.webRequest.onCompleted.addListener(onCompleted, { urls: ['<all_urls>'], types: ['main_frame'] })
110110
browser.storage.onChanged.addListener(onStorageChange)
111111
browser.webNavigation.onCommitted.addListener(onNavigationCommitted)
112112
browser.webNavigation.onDOMContentLoaded.addListener(onDOMContentLoaded)

add-on/src/lib/ipfs-request.js

+39-14
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
3333

3434
// Various types of requests are identified once and cached across all browser.webRequest hooks
3535
const requestCacheCfg = { max: 128, maxAge: 1000 * 30 }
36+
const recoveredTabs = new LRU(requestCacheCfg)
3637
const ignoredRequests = new LRU(requestCacheCfg)
3738
const ignore = (id) => ignoredRequests.set(id, true)
3839
const isIgnored = (id) => ignoredRequests.get(id) !== undefined
40+
const errorInFlight = new LRU({ max: 3, maxAge: 1000 })
3941

4042
const acrhHeaders = new LRU(requestCacheCfg) // webui cors fix in Chrome
4143
const originUrls = new LRU(requestCacheCfg) // request.originUrl workaround for Chrome
@@ -365,17 +367,23 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
365367
// browser.webRequest.onErrorOccurred
366368
// Fired when a request could not be processed due to an error on network level.
367369
// For example: TCP timeout, DNS lookup failure
368-
async onErrorOccurred (request) {
370+
// NOTE: this is executed only if webRequest.ResourceType='main_frame'
371+
onErrorOccurred (request) {
369372
const state = getState()
370373
if (!state.active) return
371374

375+
// Avoid duplicates in Chromium, which fires two events instead of one
376+
// https://github.com/ipfs-shipyard/ipfs-companion/issues/805
377+
if (errorInFlight.has(request.url)) return
378+
errorInFlight.set(request.url, request.requestId)
379+
372380
// Check if error can be recovered via EthDNS
373381
if (isRecoverableViaEthDNS(request, state)) {
374382
const url = new URL(request.url)
375383
url.hostname = `${url.hostname}.link`
376384
const redirect = { redirectUrl: url.toString() }
377-
log(`onErrorOccurred: attempting to recover from DNS error (${request.error}) using EthDNS for ${request.url}`, redirect.redirectUrl)
378-
return createTabWithURL(redirect, browser)
385+
log(`onErrorOccurred: attempting to recover from DNS error (${request.error}) using EthDNS for ${request.url}${redirect.redirectUrl}`, request)
386+
return createTabWithURL(redirect, browser, recoveredTabs)
379387
}
380388

381389
// Check if error can be recovered via DNSLink
@@ -384,8 +392,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
384392
const dnslink = dnslinkResolver.readAndCacheDnslink(hostname)
385393
if (dnslink) {
386394
const redirect = dnslinkResolver.dnslinkRedirect(request.url, dnslink)
387-
log(`onErrorOccurred: attempting to recover from network error (${request.error}) using dnslink for ${request.url}`, redirect.redirectUrl)
388-
return createTabWithURL(redirect, browser)
395+
log(`onErrorOccurred: attempting to recover from network error (${request.error}) using dnslink for ${request.url}${redirect.redirectUrl}`, request)
396+
return createTabWithURL(redirect, browser, recoveredTabs)
389397
}
390398
}
391399

@@ -399,14 +407,15 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
399407
} else {
400408
redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
401409
}
402-
log(`onErrorOccurred: attempting to recover from network error (${request.error}) for ${request.url}`, redirectUrl)
403-
return createTabWithURL({ redirectUrl }, browser)
410+
log(`onErrorOccurred: attempting to recover from network error (${request.error}) for ${request.url}${redirectUrl}`, request)
411+
return createTabWithURL({ redirectUrl }, browser, recoveredTabs)
404412
}
405413
},
406414

407415
// browser.webRequest.onCompleted
408416
// Fired when HTTP request is completed (successfully or with an error code)
409-
async onCompleted (request) {
417+
// NOTE: this is executed only if webRequest.ResourceType='main_frame'
418+
onCompleted (request) {
410419
const state = getState()
411420
if (!state.active) return
412421
if (request.statusCode === 200) return // finish if no error to recover from
@@ -418,8 +427,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
418427
} else {
419428
redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
420429
}
421-
log(`onCompleted: attempting to recover from HTTP Error ${request.statusCode} for ${request.url}`, redirectUrl)
422-
return createTabWithURL({ redirectUrl }, browser)
430+
log(`onCompleted: attempting to recover from HTTP Error ${request.statusCode} for ${request.url}${redirectUrl}`, request)
431+
return createTabWithURL({ redirectUrl }, browser, recoveredTabs)
423432
}
424433
}
425434
}
@@ -560,11 +569,27 @@ function isRecoverableViaEthDNS (request, state) {
560569

561570
// We can't redirect in onErrorOccurred/onCompleted
562571
// Indead, we recover by opening URL in a new tab that replaces the failed one
563-
async function createTabWithURL (redirect, browser) {
564-
const currentTabId = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0].id)
565-
return browser.tabs.create({
572+
// TODO: display an user-friendly prompt when the very first recovery is done
573+
async function createTabWithURL (redirect, browser, recoveredTabs) {
574+
const tabKey = redirect.redirectUrl
575+
// reuse existing tab, if exists
576+
// (this avoids duplicated tabs - https://github.com/ipfs-shipyard/ipfs-companion/issues/805)
577+
try {
578+
const recoveredId = recoveredTabs.get(tabKey)
579+
const existingTab = recoveredId ? await browser.tabs.get(recoveredId) : undefined
580+
if (existingTab) {
581+
await browser.tabs.update(recoveredId, { active: true })
582+
return
583+
}
584+
} catch (_) {
585+
// tab no longer exist, let's create a new one
586+
}
587+
const failedTab = await browser.tabs.getCurrent()
588+
const openerTabId = failedTab ? failedTab.id : undefined
589+
const newTab = await browser.tabs.create({
566590
active: true,
567-
openerTabId: currentTabId,
591+
openerTabId,
568592
url: redirect.redirectUrl
569593
})
594+
if (newTab) recoveredTabs.set(tabKey, newTab.id)
570595
}

test/functional/lib/ipfs-request-gateway-recover.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('requestHandler.onCompleted:', function () { // HTTP-level errors
2727

2828
before(function () {
2929
global.URL = URL
30-
browser.tabs = { ...browser.tabs, query: sinon.stub().resolves([{ id: 20 }]) }
30+
browser.tabs = { ...browser.tabs, getCurrent: sinon.stub().resolves({ id: 20 }) }
3131
global.browser = browser
3232
})
3333

0 commit comments

Comments
 (0)