Refactor: openURL

This commit is contained in:
eight 2020-02-01 04:32:29 +08:00
parent 24552269fe
commit 9c8cb9b928
6 changed files with 76 additions and 102 deletions

View File

@ -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 {

View File

@ -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) {

View File

@ -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()];

View File

@ -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<Tab>} 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})
]);
}

View File

@ -21,8 +21,8 @@
<script src="js/polyfill.js"></script>
<script src="js/dom.js"></script>
<script src="js/messaging.js"></script>
<script src="js/promisify.js"></script>
<script src="js/messaging.js"></script>
<script src="js/msg.js"></script>
<script src="js/localization.js"></script>
<script src="js/prefs.js"></script>
@ -33,7 +33,7 @@
</head>
<body id="stylus-options">
<div id="options-header">
<div id="options-title">
<div id="options-close-icon"><svg viewBox="0 0 20 20" class="svg-icon"><path d="M11.69,10l4.55,4.55-1.69,1.69L10,11.69,5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z"></path></svg></div>

View File

@ -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&nbsp;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();
},