diff --git a/background/background.js b/background/background.js
index 5bc6535a..b14da773 100644
--- a/background/background.js
+++ b/background/background.js
@@ -1,6 +1,8 @@
/* global download prefs openURL FIREFOX CHROME VIVALDI
debounce URLS ignoreChromeError getTab
- styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync */
+ styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync
+ findExistTab createTab activateTab isTabReplaceable getActiveTab */
+
'use strict';
// eslint-disable-next-line no-var
@@ -412,12 +414,32 @@ function openEditor(params) {
}
function openManage({options = false, search} = {}) {
- let url = 'manage.html';
+ let url = chrome.runtime.getURL('manage.html');
if (search) {
url += `?search=${encodeURIComponent(search)}`;
}
if (options) {
url += '#stylus-options';
}
- return openURL({url, currentWindow: null});
+ return findExistTab({
+ url,
+ currentWindow: null,
+ ignoreHash: true,
+ ignoreSearch: true
+ })
+ .then(tab => {
+ if (tab) {
+ return Promise.all([
+ activateTab(tab),
+ tab.url !== url && msg.sendTab(tab.id, {method: 'pushState', url})
+ .catch(console.warn)
+ ]);
+ }
+ return getActiveTab().then(tab => {
+ if (isTabReplaceable(tab, url)) {
+ return activateTab(tab, {url});
+ }
+ return createTab({url});
+ });
+ });
}
diff --git a/content/apply.js b/content/apply.js
index 291673c5..7b35b1fc 100644
--- a/content/apply.js
+++ b/content/apply.js
@@ -260,18 +260,6 @@ const APPLY = (() => {
case 'updateCount':
updateCount();
break;
-
- case 'trimHash':
- if (IS_OWN_PAGE) {
- // FIXME: currently we only do this in our own page. Is it safe to do
- // it on all pages?
- try {
- // history.replaceState(null, null, ' ');
- // eslint-disable-next-line no-undef
- router.updateHash('');
- } catch (err) {}
- }
- break;
}
}
diff --git a/js/messaging.js b/js/messaging.js
index e12afd3e..c2e4f704 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 CHROME_HAS_BORDER_BUG */
-/* global promisify msg */
+/* global promisify */
'use strict';
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]);
@@ -195,21 +195,38 @@ function onTabReady(tabOrId) {
});
}
-function urlToMatchPattern(url) {
+function urlToMatchPattern(url, ignoreSearch) {
// 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;
}
+ if (ignoreSearch) {
+ return [
+ `${url.protocol}//${url.hostname}/${url.pathname}`,
+ `${url.protocol}//${url.hostname}/${url.pathname}?*`
+ ];
+ }
// FIXME: is %2f allowed in pathname and search?
return `${url.protocol}//${url.hostname}/${url.pathname}${url.search}`;
}
-function findExistTab(url, currentWindow) {
+function findExistTab({url, currentWindow, ignoreHash = true, ignoreSearch = false}) {
url = new URL(url);
- const normalizedUrl = url.href.split('#')[0];
- return queryTabs({url: urlToMatchPattern(url), currentWindow})
+ return queryTabs({url: urlToMatchPattern(url, ignoreSearch), currentWindow})
// FIXME: is tab.url always normalized?
- .then(tabs => tabs.find(tab => tab.url.split('#')[0] === normalizedUrl));
+ .then(tabs => tabs.find(matchTab));
+
+ function matchTab(tab) {
+ const tabUrl = new URL(tab.url);
+ return tabUrl.protocol === url.protocol &&
+ tabUrl.username === url.username &&
+ tabUrl.password === url.password &&
+ tabUrl.hostname === url.hostname &&
+ tabUrl.port === url.port &&
+ tabUrl.pathname === url.pathname &&
+ (ignoreSearch || tabUrl.search === url.search) &&
+ (ignoreHash || tabUrl.hash === url.hash);
+ }
}
/**
@@ -246,49 +263,49 @@ function openURL(options) {
if (!url.includes('://')) {
url = chrome.runtime.getURL(url);
}
- return findExistTab(url, currentWindow)
- .then(tab => {
- if (!tab) {
- return getActiveTab().then(maybeReplace);
- }
+ return findExistTab({url, currentWindow}).then(tab => {
+ if (tab) {
// update url if only hash is different?
- if (tab.url !== url && tab.url.split('#')[0] === url.split('#')[0]) {
- if (url.includes('#')) {
- return activateTab(tab, {url, index});
- } else {
- // we can't update URL directly since it refresh the page
- return Promise.all([
- activateTab(tab, {index}),
- msg.sendTab(tab.id, {method: 'trimHash'}).catch(console.warn)
- ]);
- }
+ // we can't update URL if !url.includes('#') since it refreshes the page
+ if (tab.url !== url && tab.url.split('#')[0] === url.split('#')[0] &&
+ url.includes('#')) {
+ 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
- function maybeReplace(tab) {
- const chromeInIncognito = tab && tab.incognito && url.startsWith('chrome');
- const emptyTab = tab && URLS.emptyTab.includes(tab.url);
- if (emptyTab && !chromeInIncognito) {
- return activateTab(tab, {url, index}); // FIXME: should we move current empty tab?
}
if (newWindow) {
return createWindow(Object.assign({url}, windowPosition));
}
- 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 createTab(options);
- }
+ return getActiveTab().then(tab => {
+ if (isTabReplaceable(tab, url)) {
+ return activateTab(tab, {url, index});
+ }
+ 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 && !tab.incognito && (!FIREFOX || FIREFOX >= 57 && chrome.windows)) {
+ options.openerTabId = tab.id;
+ }
+ return createTab(options);
+ });
+ });
}
+// replace empty tab (NTP or about:blank)
+// except when new URL is chrome:// or chrome-extension:// and the empty tab is
+// in incognito
+function isTabReplaceable(tab, newUrl) {
+ if (!tab || !URLS.emptyTab.includes(tab.url)) {
+ return false;
+ }
+ // FIXME: why?
+ if (tab.incognito && newUrl.startsWith('chrome')) {
+ return false;
+ }
+ return true;
+}
-function activateTab(tab, {url, index}) {
+function activateTab(tab, {url, index} = {}) {
const options = {active: true};
if (url) {
options.url = url;
@@ -297,7 +314,8 @@ function activateTab(tab, {url, index}) {
updateTab(tab.id, options),
updateWindow(tab.windowId, {focused: true}),
index != null && moveTabs(tab.id, {index})
- ]);
+ ])
+ .then(() => tab);
}
diff --git a/js/router.js b/js/router.js
index eea8cd25..175affb5 100644
--- a/js/router.js
+++ b/js/router.js
@@ -1,4 +1,4 @@
-/* global deepEqual */
+/* global deepEqual msg */
/* exported router */
'use strict';
@@ -8,6 +8,12 @@ const router = (() => {
document.addEventListener('DOMContentLoaded', () => update());
window.addEventListener('popstate', () => update());
window.addEventListener('hashchange', () => update());
+ msg.on(e => {
+ if (e.method === 'pushState' && e.url !== location.href) {
+ history.pushState(history.state, null, e.url);
+ update();
+ }
+ });
return {watch, updateSearch, getSearch, updateHash};
function watch(options, callback) {
diff --git a/manage.html b/manage.html
index 1134343a..05903e91 100644
--- a/manage.html
+++ b/manage.html
@@ -150,9 +150,9 @@
-
+
diff --git a/manage/filters.js b/manage/filters.js
index 04212cbc..d027b815 100644
--- a/manage/filters.js
+++ b/manage/filters.js
@@ -12,9 +12,7 @@ const filtersSelector = {
let initialized = false;
router.watch({search: ['search']}, ([search]) => {
- if (search != null) {
- $('#search').value = search;
- }
+ $('#search').value = search || '';
if (!initialized) {
init();
} else {