diff --git a/background/usercss-helper.js b/background/usercss-helper.js index 6eb3684a..ae37ec56 100644 --- a/background/usercss-helper.js +++ b/background/usercss-helper.js @@ -116,6 +116,7 @@ var usercssHelper = (() => { (direct ? '&direct=yes' : ''), index: tab.index + 1, openerTabId: tab.id, + currentWindow: null, })); } diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js index e7c266a6..0f5ff973 100644 --- a/install-usercss/install-usercss.js +++ b/install-usercss/install-usercss.js @@ -9,6 +9,7 @@ let installed = false; const tabId = Number(params.get('tabId')); + let tabUrl; let port; if (params.has('direct')) { @@ -35,7 +36,7 @@ break; } }); - port.onDisconnect.addListener(closeCurrentTab); + port.onDisconnect.addListener(onPortDisconnected); } const cm = CodeMirror($('.main'), {readOnly: true}); @@ -49,6 +50,12 @@ } }, 200); + getTab(tabId).then(tab => (tabUrl = tab.url)); + chrome.tabs.onUpdated.addListener((id, {url}) => + id === tabId && + url && url !== tabUrl && + closeCurrentTab()); + function liveReloadUpdate(sourceCode) { liveReloadPending = liveReloadPending.then(() => { const scrollInfo = cm.getScrollInfo(); @@ -391,4 +398,14 @@ .catch(err => messageBox.alert(t('styleInstallFailed', String(err)))); }); } + + function onPortDisconnected() { + chrome.tabs.get(tabId, tab => { + if (chrome.runtime.lastError) { + closeCurrentTab(); + } else if (tab.url === tabUrl) { + location.reload(); + } + }); + } })(); diff --git a/js/messaging.js b/js/messaging.js index fab5d692..e9b11a64 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -210,43 +210,63 @@ function getTabRealURL(tab) { }); } +/** + * Opens a tab or activates an existing one, + * reuses the New Tab page or about:blank if it's focused now + * @param {Object} params - or just a string e.g. openURL('foo') + * @param {string} params.url - if relative, it's auto-expanded to the full extension URL + * @param {number} [params.index] - move the tab to this index in the tab strip, -1 = last + * @param {Boolean} [params.active=tue] - true to activate the tab, false to open in background + * @param {?Boolean} [params.currentWindow=true] - pass null to check all windows + * @returns {Promise} + */ +function openURL({ + url = arguments[0], + index, + active, + currentWindow = true, +}) { + url = url.includes('://') ? url : chrome.runtime.getURL(url); + // [some] chromium forks don't handle their fake branded protocols + url = url.replace(/^(opera|vivaldi)/, 'chrome'); + // FF doesn't handle moz-extension:// URLs (bug) + // FF decodes %2F in encoded parameters (bug) + // API doesn't handle the hash-fragment part + const urlQuery = url.startsWith('moz-extension') ? undefined : + FIREFOX && url.includes('%2F') ? + url.replace(/%2F.*/, '*').replace(/#.*/, '') : + url.replace(/#.*/, ''); + return queryTabs({url: urlQuery, currentWindow}).then(maybeSwitch); -// opens a tab or activates the already opened one, -// reuses the New Tab page if it's focused now -function openURL({url, index, active, currentWindow = true}) { - if (!url.includes('://')) { - url = chrome.runtime.getURL(url); + function maybeSwitch(tabs = []) { + const urlFF = FIREFOX && url.replace(/%2F/g, '/'); + const tab = tabs.find(tab => tab.url === url || tab.url === urlFF); + if (!tab) { + return getActiveTab().then(maybeReplace); + } + if (index !== undefined && tab.index !== index) { + chrome.tabs.move(tab.id, {index}); + } + return activateTab(tab); + } + + // update current NTP or about:blank + // except when 'url' is chrome:// or chrome-extension:// in incognito + function maybeReplace(tab) { + const chromeInIncognito = tab && tab.incognito && url.startsWith('chrome'); + const emptyTab = tab && (tab.url === 'chrome://newtab/' || tab.url === 'about:newtab'); + if (emptyTab && !chromeInIncognito) { + return new Promise(resolve => + chrome.tabs.update({url}, resolve)); + } + const options = {url, index, active}; + // FF57+ supports openerTabId, but not in Android (indicated by the absence of chrome.windows) + if (tab && (!FIREFOX || FIREFOX >= 57 && chrome.windows) && !chromeInIncognito) { + options.openerTabId = tab.id; + } + return new Promise(resolve => + chrome.tabs.create(options, resolve)); } - return new Promise(resolve => { - // [some] chromium forks don't handle their fake branded protocols - url = url.replace(/^(opera|vivaldi)/, 'chrome'); - // FF doesn't handle moz-extension:// URLs (bug) - // API doesn't handle the hash-fragment part - const urlQuery = url.startsWith('moz-extension') ? undefined : url.replace(/#.*/, ''); - queryTabs({url: urlQuery, currentWindow}).then(tabs => { - for (const tab of tabs) { - if (tab.url === url) { - activateTab(tab).then(resolve); - return; - } - } - getActiveTab().then(tab => { - const chromeInIncognito = tab && tab.incognito && url.startsWith('chrome'); - if (tab && (tab.url === 'chrome://newtab/' || tab.url === 'about:newtab') && !chromeInIncognito) { - // update current NTP, except for chrome:// or chrome-extension:// in incognito - chrome.tabs.update({url}, resolve); - } else { - // create a new tab - const options = {url, index, active}; - // FF57+ supports openerTabId, but not in Android (indicated by the absence of chrome.windows) - if (tab && (!FIREFOX || FIREFOX >= 57 && chrome.windows) && !chromeInIncognito) { - options.openerTabId = tab.id; - } - chrome.tabs.create(options, resolve); - } - }); - }); - }); }