From 9c8cb9b9286265e19ebcdf782caa18e3a5423ee0 Mon Sep 17 00:00:00 2001 From: eight Date: Sat, 1 Feb 2020 04:32:29 +0800 Subject: [PATCH] Refactor: openURL --- background/background.js | 10 ++- background/content-scripts.js | 2 +- edit/regexp-tester.js | 2 +- js/messaging.js | 148 ++++++++++++++-------------------- options.html | 4 +- popup/popup.js | 12 +-- 6 files changed, 76 insertions(+), 102 deletions(-) diff --git a/background/background.js b/background/background.js index df3f8620..a8db6573 100644 --- a/background/background.js +++ b/background/background.js @@ -400,11 +400,13 @@ function onRuntimeMessage(msg, sender) { } // FIXME: popup.js also open editor but it doesn't use this API. -function openEditor({id}) { - let url = '/edit.html'; - if (id) { - url += `?id=${id}`; +function openEditor(params) { + const searchParams = new URLSearchParams(); // FIXME: use url.searchParams when Chrome >= 51 + for (const key in params) { + searchParams.set(key, params[key]); } + const search = searchParams.toString(); + const url = chrome.runtime.getURL('edit.html') + (search && `?${search}`); if (chrome.windows && prefs.get('openEditInWindow')) { chrome.windows.create(Object.assign({url}, prefs.get('windowPosition'))); } else { diff --git a/background/content-scripts.js b/background/content-scripts.js index 1cfecc19..d617796a 100644 --- a/background/content-scripts.js +++ b/background/content-scripts.js @@ -53,7 +53,7 @@ const contentScripts = (() => { } function injectToAllTabs() { - return queryTabs().then(tabs => { + return queryTabs({}).then(tabs => { for (const tab of tabs) { // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF if (tab.width) { diff --git a/edit/regexp-tester.js b/edit/regexp-tester.js index 348b5cb7..590714d3 100644 --- a/edit/regexp-tester.js +++ b/edit/regexp-tester.js @@ -66,7 +66,7 @@ const regExpTester = (() => { return rxData; }); const getMatchInfo = m => m && {text: m[0], pos: m.index}; - queryTabs().then(tabs => { + queryTabs({}).then(tabs => { const supported = tabs.map(tab => tab.url) .filter(url => URLS.supported(url)); const unique = [...new Set(supported).values()]; diff --git a/js/messaging.js b/js/messaging.js index d6652777..10c9602e 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -1,7 +1,7 @@ /* exported getActiveTab onTabReady stringAsRegExp getTabRealURL openURL getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual - closeCurrentTab capitalize */ -/* global prefs */ + closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */ +/* global promisify */ 'use strict'; const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]); @@ -93,12 +93,12 @@ if (IS_BG) { // Object.defineProperty(window, 'localStorage', {value: {}}); // Object.defineProperty(window, 'sessionStorage', {value: {}}); -function queryTabs(options = {}) { - return new Promise(resolve => - chrome.tabs.query(options, tabs => - resolve(tabs))); -} - +const createTab = promisify(chrome.tabs.create.bind(chrome.tabs)); +const queryTabs = promisify(chrome.tabs.query.bind(chrome.tabs)); +const updateTab = promisify(chrome.tabs.update.bind(chrome.tabs)); +const moveTabs = promisify(chrome.tabs.move.bind(chrome.tabs)); +// FIXME: is it possible that chrome.windows is undefined? +const updateWindow = promisify(chrome.windows.update.bind(chrome.windows)); function getTab(id) { return new Promise(resolve => @@ -194,6 +194,22 @@ function onTabReady(tabOrId) { }); } +function urlToMatchPattern(url) { + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns + if (!/^(http|https|ws|wss|ftp|data|file)$/.test(url.protocol)) { + return undefined; + } + // FIXME: is %2f allowed in pathname and search? + return `${url.protocol}//${url.hostname}/${url.pathname}${url.search}`; +} + +function findExistTab(url, currentWindow) { + url = new URL(url); + const normalizedUrl = url.href.split('#')[0]; + return queryTabs({url: urlToMatchPattern(url), currentWindow}) + // FIXME: is tab.url always normalized? + .then(tabs => tabs.find(tab => tab.url.split('#')[0] === normalizedUrl)); +} /** * Opens a tab or activates an existing one, @@ -213,75 +229,31 @@ function onTabReady(tabOrId) { * JSONifiable data to be sent to the tab via sendMessage() * @returns {Promise} Promise that resolves to the opened/activated tab */ -function openURL({ - // https://github.com/eslint/eslint/issues/10639 - // eslint-disable-next-line no-undef - 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'); - const editMatch = /edit\.html/.test(url); - // ignore filtered manager URLs with params & edit URLs created from popup on manager page - const manageMatch = !editMatch ? /manage\.html(#stylus-options)?$/.test(url) : null; - // 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') || - url.startsWith('chrome:') ? - undefined : - FIREFOX && url.includes('%2F') ? - url.replace(/%2F.*/, '*').replace(/#.*/, '') : - url.replace(/#.*/, ''); - - return manageMatch || editMatch ? queryTabs().then(maybeSwitch) : - queryTabs({url: urlQuery, currentWindow}).then(maybeSwitch); - - function maybeSwitch(tabs = []) { - const urlWithSlash = url + '/'; - const urlFF = FIREFOX && url.replace(/%2F/g, '/'); - const urlOptions = manageMatch ? URLS.ownOrigin + 'manage.html#stylus-options' : null; - const urlManage = manageMatch ? URLS.ownOrigin + 'manage.html' : null; - const tab = tabs.find(({url: u}) => u === url || u === urlFF || u === urlWithSlash || - u === urlOptions || u === urlManage); - if (!tab && prefs.get('openEditInWindow') && chrome.windows && editMatch) { - chrome.windows.create( - Object.assign({ - url: url - }, prefs.get('windowPosition', {})) - ); - return; - } - if (manageMatch) { - if (tab) { - const toggleOptions = url === urlOptions ? 'options-open' : 'options-close'; - chrome.tabs.sendMessage(tab.id, { - 'name': 'options', - 'data': toggleOptions - }); - } - getActiveTab() - .then(currentTab => { - if (!(tab && FIREFOX && currentTab.windowId !== tab.windowId)) { - chrome.runtime.sendMessage({ - 'name': 'popup', - 'data': 'close-popup' - }); - } - }); - } - if (!tab) { - return getActiveTab().then(maybeReplace); - } - if (index !== undefined && tab.index !== index) { - chrome.tabs.move(tab.id, {index}); - } - return activateTab(tab); +function openURL(options) { + if (typeof options === 'string') { + options = {url: options}; } + let { + url, + index, + active, + currentWindow = true, + } = options; + + if (!url.includes('://')) { + url = chrome.runtime.getURL(url); + } + return findExistTab(url, currentWindow) + .then(tab => { + if (!tab) { + return getActiveTab().then(maybeReplace); + } + // update url if only hash is different + if (tab.url !== url && tab.url.split('#')[0] === url.split('#')[0]) { + return activateTab(tab, {url, index}); + } + return activateTab(tab, {index}); + }); // update current NTP or about:blank // except when 'url' is chrome:// or chrome-extension:// in incognito @@ -289,29 +261,29 @@ function openURL({ const chromeInIncognito = tab && tab.incognito && url.startsWith('chrome'); const emptyTab = tab && URLS.emptyTab.includes(tab.url); if (emptyTab && !chromeInIncognito) { - return new Promise(resolve => - chrome.tabs.update({url}, resolve)); + return activateTab(tab, {url, index}); // FIXME: should we move current empty tab? } const options = {url, index, active}; // FF57+ supports openerTabId, but not in Android (indicated by the absence of chrome.windows) + // FIXME: is it safe to assume that the current tab is the opener? if (tab && (!FIREFOX || FIREFOX >= 57 && chrome.windows) && !chromeInIncognito) { options.openerTabId = tab.id; } - return new Promise(resolve => - chrome.tabs.create(options, resolve)); + return createTab(options); } } -function activateTab(tab) { +function activateTab(tab, {url, index}) { + const options = {active: true}; + if (url) { + options.url = url; + } return Promise.all([ - new Promise(resolve => { - chrome.tabs.update(tab.id, {active: true}, resolve); - }), - chrome.windows && new Promise(resolve => { - chrome.windows.update(tab.windowId, {focused: true}, resolve); - }), - ]).then(([tab]) => tab); + updateTab(tab.id, options), + updateWindow(tab.windowId, {focused: true}), + index != null && moveTabs(tab.id, {index}) + ]); } diff --git a/options.html b/options.html index dd86afe0..48a03f1d 100644 --- a/options.html +++ b/options.html @@ -21,8 +21,8 @@ - + @@ -33,7 +33,7 @@ - +
diff --git a/popup/popup.js b/popup/popup.js index 85622269..af72688c 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -1,5 +1,5 @@ /* global configDialog hotkeys onTabReady msg - getActiveTab FIREFOX getTabRealURL URLS API onDOMready $ $$ prefs CHROME + getActiveTab FIREFOX getTabRealURL URLS API onDOMready $ $$ prefs setupLivePrefs template t $create animateElement tryJSONparse debounce CHROME_HAS_BORDER_BUG */ @@ -187,7 +187,7 @@ function initPopup() { ? new URL(tabURL).pathname.slice(1) // this URL : t('writeStyleForURL').replace(/ /g, '\u00a0'), - onclick: handleEvent.openLink, + onclick: e => handleEvent.openEditor(e, {'url-prefix': tabURL}), }); if (prefs.get('popup.breadcrumbs')) { urlLink.onmouseenter = @@ -210,7 +210,7 @@ function initPopup() { href: 'edit.html?domain=' + encodeURIComponent(domain), textContent: numParts > 2 ? domain.split('.')[0] : domain, title: `domain("${domain}")`, - onclick: handleEvent.openLink, + onclick: e => handleEvent.openEditor(e, {domain}), }); domainLink.setAttribute('subdomain', numParts > 1 ? 'true' : ''); matchTargets.appendChild(domainLink); @@ -296,7 +296,7 @@ function createStyleElement(style) { const editLink = $('.style-edit-link', entry); Object.assign(editLink, { href: editLink.getAttribute('href') + style.id, - onclick: handleEvent.openLink, + onclick: e => handleEvent.openEditor(e, {id: style.id}), }); const styleName = $('.style-name', entry); Object.assign(styleName, { @@ -535,9 +535,9 @@ Object.assign(handleEvent, { $('#regexp-explanation').remove(); }, - openLink(event) { + openEditor(event, options) { event.preventDefault(); - API.openURL({url: this.href}); + API.openEditor(options); if (!(FIREFOX && prefs.get('openEditInWindow'))) window.close(); },