show write-style entries for iframes in popup (#861)
* account for iframes in popup list/write-style and badge * fix and simplify openURL + onTabReady + message from popup * fixup! resolve about:blank iframes to their parent URL * fixup! don't underline iframe links until hovered * fix width bug in popup only when needed (Chrome 66-69) * fixup! reset styleIds on main page navigation * fixup! call updateCount explicitly on extension pages * fixup! ensure frame url is present * fixup! frameResults entry may be empty * fixup! init main frame first * fixup! track iframes via ports * fixup! reduce badge update rate during page load * fixup! cosmetics * fixup! don't add frames with errors * fixup! cosmetics
This commit is contained in:
parent
4bbce7cb9f
commit
8192fab1b8
|
@ -2,7 +2,7 @@
|
||||||
URLS ignoreChromeError usercssHelper
|
URLS ignoreChromeError usercssHelper
|
||||||
styleManager msg navigatorUtil workerUtil contentScripts sync
|
styleManager msg navigatorUtil workerUtil contentScripts sync
|
||||||
findExistingTab createTab activateTab isTabReplaceable getActiveTab
|
findExistingTab createTab activateTab isTabReplaceable getActiveTab
|
||||||
iconManager tabManager */
|
tabManager */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -49,16 +49,27 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
|
|
||||||
openEditor,
|
openEditor,
|
||||||
|
|
||||||
updateIconBadge(count) {
|
/* Same as openURL, the only extra prop in `opts` is `message` - it'll be sent when the tab is ready,
|
||||||
iconManager.updateIconBadge(this.sender.tab.id, count);
|
which is needed in the popup, otherwise another extension could force the tab to open in foreground
|
||||||
return true;
|
thus auto-closing the popup (in Chrome at least) and preventing the sendMessage code from running */
|
||||||
|
openURL(opts) {
|
||||||
|
const {message} = opts;
|
||||||
|
return openURL(opts) // will pass the resolved value untouched when `message` is absent or falsy
|
||||||
|
.then(message && (tab => tab.status === 'complete' ? tab : onTabReady(tab)))
|
||||||
|
.then(message && (tab => msg.sendTab(tab.id, opts.message)));
|
||||||
|
function onTabReady(tab) {
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
setTimeout(function ping(numTries = 10, delay = 100) {
|
||||||
|
msg.sendTab(tab.id, {method: 'ping'})
|
||||||
|
.catch(() => false)
|
||||||
|
.then(pong => pong
|
||||||
|
? resolve(tab)
|
||||||
|
: numTries && setTimeout(ping, delay, numTries - 1, delay * 1.5) ||
|
||||||
|
reject('timeout'));
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// exposed for stuff that requires followup sendMessage() like popup::openSettings
|
|
||||||
// that would fail otherwise if another extension forced the tab to open
|
|
||||||
// in the foreground thus auto-closing the popup (in Chrome)
|
|
||||||
openURL,
|
|
||||||
|
|
||||||
optionsCustomizeHotkeys() {
|
optionsCustomizeHotkeys() {
|
||||||
return browser.runtime.openOptionsPage()
|
return browser.runtime.openOptionsPage()
|
||||||
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* global prefs debounce iconUtil FIREFOX CHROME VIVALDI tabManager */
|
/* global prefs debounce iconUtil FIREFOX CHROME VIVALDI tabManager navigatorUtil API_METHODS */
|
||||||
/* exported iconManager */
|
/* exported iconManager */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const iconManager = (() => {
|
const iconManager = (() => {
|
||||||
const ICON_SIZES = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
|
const ICON_SIZES = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
|
||||||
|
const staleBadges = new Set();
|
||||||
|
|
||||||
prefs.subscribe([
|
prefs.subscribe([
|
||||||
'disableAll',
|
'disableAll',
|
||||||
|
@ -26,32 +27,51 @@ const iconManager = (() => {
|
||||||
refreshAllIcons();
|
refreshAllIcons();
|
||||||
});
|
});
|
||||||
|
|
||||||
return {updateIconBadge};
|
Object.assign(API_METHODS, {
|
||||||
|
/** @param {(number|string)[]} styleIds
|
||||||
|
* @param {boolean} [lazyBadge=false] preventing flicker during page load */
|
||||||
|
updateIconBadge(styleIds, {lazyBadge} = {}) {
|
||||||
// FIXME: in some cases, we only have to redraw the badge. is it worth a optimization?
|
// FIXME: in some cases, we only have to redraw the badge. is it worth a optimization?
|
||||||
function updateIconBadge(tabId, count, force = true) {
|
const {frameId, tab: {id: tabId}} = this.sender;
|
||||||
tabManager.set(tabId, 'count', count);
|
const value = styleIds.length ? styleIds.map(Number) : undefined;
|
||||||
refreshIconBadgeText(tabId);
|
tabManager.set(tabId, 'styleIds', frameId, value);
|
||||||
refreshIcon(tabId, force);
|
debounce(refreshStaleBadges, frameId && lazyBadge ? 250 : 0);
|
||||||
|
staleBadges.add(tabId);
|
||||||
|
if (!frameId) refreshIcon(tabId, true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
navigatorUtil.onCommitted(({tabId, frameId}) => {
|
||||||
|
if (!frameId) tabManager.set(tabId, 'styleIds', undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.runtime.onConnect.addListener(port => {
|
||||||
|
if (port.name === 'iframe') {
|
||||||
|
port.onDisconnect.addListener(onPortDisconnected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function onPortDisconnected({sender}) {
|
||||||
|
if (tabManager.get(sender.tab.id, 'styleIds')) {
|
||||||
|
API_METHODS.updateIconBadge.call({sender}, [], {lazyBadge: true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshIconBadgeText(tabId) {
|
function refreshIconBadgeText(tabId) {
|
||||||
const count = tabManager.get(tabId, 'count');
|
const text = prefs.get('show-badge') ? `${getStyleCount(tabId)}` : '';
|
||||||
iconUtil.setBadgeText({
|
iconUtil.setBadgeText({tabId, text});
|
||||||
text: prefs.get('show-badge') && count ? String(count) : '',
|
|
||||||
tabId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIconName(count = 0) {
|
function getIconName(hasStyles = false) {
|
||||||
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
|
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
|
||||||
const postfix = prefs.get('disableAll') ? 'x' : !count ? 'w' : '';
|
const postfix = prefs.get('disableAll') ? 'x' : !hasStyles ? 'w' : '';
|
||||||
return `${iconset}$SIZE$${postfix}`;
|
return `${iconset}$SIZE$${postfix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshIcon(tabId, force = false) {
|
function refreshIcon(tabId, force = false) {
|
||||||
const oldIcon = tabManager.get(tabId, 'icon');
|
const oldIcon = tabManager.get(tabId, 'icon');
|
||||||
const newIcon = getIconName(tabManager.get(tabId, 'count'));
|
const newIcon = getIconName(tabManager.get(tabId, 'styleIds', 0));
|
||||||
|
// (changing the icon only for the main page, frameId = 0)
|
||||||
|
|
||||||
if (!force && oldIcon === newIcon) {
|
if (!force && oldIcon === newIcon) {
|
||||||
return;
|
return;
|
||||||
|
@ -73,6 +93,14 @@ const iconManager = (() => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return {number | ''} */
|
||||||
|
function getStyleCount(tabId) {
|
||||||
|
const allIds = new Set();
|
||||||
|
const data = tabManager.get(tabId, 'styleIds') || {};
|
||||||
|
Object.values(data).forEach(frameIds => frameIds.forEach(id => allIds.add(id)));
|
||||||
|
return allIds.size || '';
|
||||||
|
}
|
||||||
|
|
||||||
function refreshGlobalIcon() {
|
function refreshGlobalIcon() {
|
||||||
iconUtil.setIcon({
|
iconUtil.setIcon({
|
||||||
path: getIconPath(getIconName())
|
path: getIconPath(getIconName())
|
||||||
|
@ -98,4 +126,11 @@ const iconManager = (() => {
|
||||||
refreshIconBadgeText(tabId);
|
refreshIconBadgeText(tabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshStaleBadges() {
|
||||||
|
for (const tabId of staleBadges) {
|
||||||
|
refreshIconBadgeText(tabId);
|
||||||
|
}
|
||||||
|
staleBadges.clear();
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global API_METHODS styleManager CHROME prefs iconManager */
|
/* global API_METHODS styleManager CHROME prefs */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
API_METHODS.styleViaAPI = !CHROME && (() => {
|
API_METHODS.styleViaAPI = !CHROME && (() => {
|
||||||
|
@ -31,12 +31,13 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
|
||||||
.then(maybeToggleObserver);
|
.then(maybeToggleObserver);
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateCount(request, {tab, frameId}) {
|
function updateCount(request, sender) {
|
||||||
|
const {tab, frameId} = sender;
|
||||||
if (frameId) {
|
if (frameId) {
|
||||||
throw new Error('we do not count styles for frames');
|
throw new Error('we do not count styles for frames');
|
||||||
}
|
}
|
||||||
const {frameStyles} = getCachedData(tab.id, frameId);
|
const {frameStyles} = getCachedData(tab.id, frameId);
|
||||||
iconManager.updateIconBadge(tab.id, Object.keys(frameStyles).length);
|
API_METHODS.updateIconBadge.call({sender}, Object.keys(frameStyles));
|
||||||
}
|
}
|
||||||
|
|
||||||
function styleApply({id = null, ignoreUrlCheck = false}, {tab, frameId, url}) {
|
function styleApply({id = null, ignoreUrlCheck = false}, {tab, frameId, url}) {
|
||||||
|
|
|
@ -24,17 +24,28 @@ const tabManager = (() => {
|
||||||
onUpdate(fn) {
|
onUpdate(fn) {
|
||||||
listeners.push(fn);
|
listeners.push(fn);
|
||||||
},
|
},
|
||||||
get(tabId, key) {
|
get(tabId, ...keys) {
|
||||||
const meta = cache.get(tabId);
|
return keys.reduce((meta, key) => meta && meta[key], cache.get(tabId));
|
||||||
return meta && meta[key];
|
|
||||||
},
|
},
|
||||||
set(tabId, key, value) {
|
/**
|
||||||
|
* number of keys is arbitrary, last arg is value, `undefined` will delete the last key from meta
|
||||||
|
* (tabId, 'foo', 123) will set tabId's meta to {foo: 123},
|
||||||
|
* (tabId, 'foo', 'bar', 'etc', 123) will set tabId's meta to {foo: {bar: {etc: 123}}}
|
||||||
|
*/
|
||||||
|
set(tabId, ...args) {
|
||||||
let meta = cache.get(tabId);
|
let meta = cache.get(tabId);
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
meta = {};
|
meta = {};
|
||||||
cache.set(tabId, meta);
|
cache.set(tabId, meta);
|
||||||
}
|
}
|
||||||
meta[key] = value;
|
const value = args.pop();
|
||||||
|
const lastKey = args.pop();
|
||||||
|
for (const key of args) meta = meta[key] || (meta[key] = {});
|
||||||
|
if (value === undefined) {
|
||||||
|
delete meta[lastKey];
|
||||||
|
} else {
|
||||||
|
meta[lastKey] = value;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
list() {
|
list() {
|
||||||
return cache.keys();
|
return cache.keys();
|
||||||
|
|
|
@ -9,12 +9,25 @@
|
||||||
self.INJECTED !== 1 && (() => {
|
self.INJECTED !== 1 && (() => {
|
||||||
self.INJECTED = 1;
|
self.INJECTED = 1;
|
||||||
|
|
||||||
|
let IS_TAB = !chrome.tabs || location.pathname !== '/popup.html';
|
||||||
|
const IS_FRAME = window !== parent;
|
||||||
const STYLE_VIA_API = !chrome.app && document instanceof XMLDocument;
|
const STYLE_VIA_API = !chrome.app && document instanceof XMLDocument;
|
||||||
const styleInjector = createStyleInjector({
|
const styleInjector = createStyleInjector({
|
||||||
compare: (a, b) => a.id - b.id,
|
compare: (a, b) => a.id - b.id,
|
||||||
onUpdate: onInjectorUpdate,
|
onUpdate: onInjectorUpdate,
|
||||||
});
|
});
|
||||||
const initializing = init();
|
const initializing = init();
|
||||||
|
/** @type chrome.runtime.Port */
|
||||||
|
let port;
|
||||||
|
let lazyBadge = IS_FRAME;
|
||||||
|
|
||||||
|
// the popup needs a check as it's not a tab but can be opened in a tab manually for whatever reason
|
||||||
|
if (!IS_TAB) {
|
||||||
|
chrome.tabs.getCurrent(tab => {
|
||||||
|
IS_TAB = Boolean(tab);
|
||||||
|
if (tab && styleInjector.list.length) updateCount();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// save it now because chrome.runtime will be unavailable in the orphaned script
|
// save it now because chrome.runtime will be unavailable in the orphaned script
|
||||||
const orphanEventId = chrome.runtime.id;
|
const orphanEventId = chrome.runtime.id;
|
||||||
|
@ -32,7 +45,7 @@ self.INJECTED !== 1 && (() => {
|
||||||
let parentDomain;
|
let parentDomain;
|
||||||
|
|
||||||
prefs.subscribe(['disableAll'], (key, value) => doDisableAll(value));
|
prefs.subscribe(['disableAll'], (key, value) => doDisableAll(value));
|
||||||
if (window !== parent) {
|
if (IS_FRAME) {
|
||||||
prefs.subscribe(['exposeIframes'], updateExposeIframes);
|
prefs.subscribe(['exposeIframes'], updateExposeIframes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +68,7 @@ self.INJECTED !== 1 && (() => {
|
||||||
// dynamic about: and javascript: iframes don't have an URL yet
|
// dynamic about: and javascript: iframes don't have an URL yet
|
||||||
// so we'll try the parent frame which is guaranteed to have a real URL
|
// so we'll try the parent frame which is guaranteed to have a real URL
|
||||||
try {
|
try {
|
||||||
if (window !== parent) {
|
if (IS_FRAME) {
|
||||||
matchUrl = parent.location.href;
|
matchUrl = parent.location.href;
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
@ -153,19 +166,19 @@ self.INJECTED !== 1 && (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCount() {
|
function updateCount() {
|
||||||
if (window !== parent) {
|
if (!IS_TAB) return;
|
||||||
// we don't care about iframes
|
if (IS_FRAME) {
|
||||||
return;
|
if (!port && styleInjector.list.length) {
|
||||||
|
port = chrome.runtime.connect({name: 'iframe'});
|
||||||
|
} else if (port && !styleInjector.list.length) {
|
||||||
|
port.disconnect();
|
||||||
}
|
}
|
||||||
if (/^\w+?-extension:\/\/.+(popup|options)\.html$/.test(location.href)) {
|
if (lazyBadge && performance.now() > 1000) lazyBadge = false;
|
||||||
// popup and the option page are not tabs
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (STYLE_VIA_API) {
|
|
||||||
API.styleViaAPI({method: 'updateCount'}).catch(msg.ignoreError);
|
|
||||||
} else {
|
|
||||||
API.updateIconBadge(styleInjector.list.length).catch(console.error);
|
|
||||||
}
|
}
|
||||||
|
(STYLE_VIA_API ?
|
||||||
|
API.styleViaAPI({method: 'updateCount'}) :
|
||||||
|
API.updateIconBadge(styleInjector.list.map(style => style.id), {lazyBadge})
|
||||||
|
).catch(msg.ignoreError);
|
||||||
}
|
}
|
||||||
|
|
||||||
function orphanCheck() {
|
function orphanCheck() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* exported getActiveTab onTabReady stringAsRegExp getTabRealURL openURL
|
/* exported getTab getActiveTab onTabReady stringAsRegExp openURL ignoreChromeError
|
||||||
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
||||||
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
|
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
|
||||||
/* global promisify */
|
/* global promisify */
|
||||||
|
@ -125,82 +125,6 @@ function getActiveTab() {
|
||||||
.then(tabs => tabs[0]);
|
.then(tabs => tabs[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTabRealURL(tab) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (tab.url !== 'chrome://newtab/' || URLS.chromeProtectsNTP) {
|
|
||||||
resolve(tab.url);
|
|
||||||
} else {
|
|
||||||
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => {
|
|
||||||
resolve(frame && frame.url || '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves when the [just created] tab is ready for communication.
|
|
||||||
* @param {Number|Tab} tabOrId
|
|
||||||
* @returns {Promise<?Tab>}
|
|
||||||
*/
|
|
||||||
function onTabReady(tabOrId) {
|
|
||||||
let tabId, tab;
|
|
||||||
if (Number.isInteger(tabOrId)) {
|
|
||||||
tabId = tabOrId;
|
|
||||||
} else {
|
|
||||||
tab = tabOrId;
|
|
||||||
tabId = tab && tab.id;
|
|
||||||
}
|
|
||||||
if (!tab) {
|
|
||||||
return getTab(tabId).then(onTabReady);
|
|
||||||
}
|
|
||||||
if (tab.status === 'complete') {
|
|
||||||
if (!FIREFOX || tab.url !== 'about:blank') {
|
|
||||||
return Promise.resolve(tab);
|
|
||||||
} else {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
chrome.webNavigation.getFrame({tabId, frameId: 0}, frame => {
|
|
||||||
ignoreChromeError();
|
|
||||||
if (frame) {
|
|
||||||
onTabReady(tab).then(resolve);
|
|
||||||
} else {
|
|
||||||
setTimeout(() => onTabReady(tabId).then(resolve));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
chrome.webNavigation.onCommitted.addListener(onCommitted);
|
|
||||||
chrome.webNavigation.onErrorOccurred.addListener(onErrorOccurred);
|
|
||||||
chrome.tabs.onRemoved.addListener(onTabRemoved);
|
|
||||||
chrome.tabs.onReplaced.addListener(onTabReplaced);
|
|
||||||
function onCommitted(info) {
|
|
||||||
if (info.tabId !== tabId) return;
|
|
||||||
unregister();
|
|
||||||
getTab(tab.id).then(resolve);
|
|
||||||
}
|
|
||||||
function onErrorOccurred(info) {
|
|
||||||
if (info.tabId !== tabId) return;
|
|
||||||
unregister();
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
function onTabRemoved(removedTabId) {
|
|
||||||
if (removedTabId !== tabId) return;
|
|
||||||
unregister();
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
function onTabReplaced(addedTabId, removedTabId) {
|
|
||||||
onTabRemoved(removedTabId);
|
|
||||||
}
|
|
||||||
function unregister() {
|
|
||||||
chrome.webNavigation.onCommitted.removeListener(onCommitted);
|
|
||||||
chrome.webNavigation.onErrorOccurred.removeListener(onErrorOccurred);
|
|
||||||
chrome.tabs.onRemoved.removeListener(onTabRemoved);
|
|
||||||
chrome.tabs.onReplaced.removeListener(onTabReplaced);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function urlToMatchPattern(url, ignoreSearch) {
|
function urlToMatchPattern(url, ignoreSearch) {
|
||||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
|
||||||
if (!/^(http|https|ws|wss|ftp|data|file)$/.test(url.protocol)) {
|
if (!/^(http|https|ws|wss|ftp|data|file)$/.test(url.protocol)) {
|
||||||
|
|
|
@ -235,6 +235,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="write-style">
|
<div id="write-style">
|
||||||
|
<a id="write-for-frames" href="#" title="<IFRAME>..." hidden></a>
|
||||||
<span id="write-style-for" i18n-text="writeStyleFor"></span>
|
<span id="write-style-for" i18n-text="writeStyleFor"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,12 +8,6 @@
|
||||||
--outer-padding: 9px;
|
--outer-padding: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
|
||||||
/* Chrome 66-?? adds a gap equal to the scrollbar width,
|
|
||||||
which looks like a bug, see https://crbug.com/821143 */
|
|
||||||
overflow: overlay;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: min-content;
|
height: min-content;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
|
@ -313,6 +307,15 @@ a.configure[target="_blank"] .svg-icon.config {
|
||||||
color: darkred;
|
color: darkred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.frame-url::before {
|
||||||
|
content: "iframe: ";
|
||||||
|
color: lightslategray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame .style-name {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
/* entry menu */
|
/* entry menu */
|
||||||
.entry .menu {
|
.entry .menu {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -516,13 +519,85 @@ body.blocked .actions > .main-controls {
|
||||||
content: "\00ad"; /* "soft" hyphen */
|
content: "\00ad"; /* "soft" hyphen */
|
||||||
}
|
}
|
||||||
|
|
||||||
#match {
|
.about-blank > .breadcrumbs {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-blank > .breadcrumbs a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
display: block;
|
display: block;
|
||||||
flex-grow: 9;
|
flex-grow: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match[data-frame-id="0"] {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.match[data-frame-id="0"] > .match {
|
||||||
|
margin-top: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match:not([data-frame-id="0"]) a {
|
||||||
|
text-decoration: none; /* not underlining iframe links until hovered to reduce visual noise */
|
||||||
|
}
|
||||||
|
|
||||||
|
.match .match {
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match .match::before {
|
||||||
|
content: "";
|
||||||
|
width: .25rem;
|
||||||
|
height: .25rem;
|
||||||
|
margin-left: -.5rem;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: none none solid solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dupe > .breadcrumbs {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dupe:not([data-children]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write-for-frames {
|
||||||
|
position: absolute;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
margin-left: -12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
--dash: transparent 2px, currentColor 2px, currentColor 3px, transparent 3px;
|
||||||
|
background: linear-gradient(var(--dash)), linear-gradient(90deg, var(--dash));
|
||||||
|
}
|
||||||
|
|
||||||
|
#write-for-frames.expanded {
|
||||||
|
background: linear-gradient(var(--dash));
|
||||||
|
}
|
||||||
|
|
||||||
|
#write-for-frames::after {
|
||||||
|
position: absolute;
|
||||||
|
margin: -2px;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
content: "";
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#write-for-frames:not(.expanded) ~ .match:not([data-frame-id="0"]),
|
||||||
|
#write-for-frames:not(.expanded) ~ .match .match {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* "breadcrumbs" 'new style' links */
|
/* "breadcrumbs" 'new style' links */
|
||||||
.breadcrumbs > .write-style-link {
|
.breadcrumbs > .write-style-link {
|
||||||
margin-left: 0
|
margin-left: 0
|
||||||
|
|
262
popup/popup.js
262
popup/popup.js
|
@ -1,39 +1,41 @@
|
||||||
/* global configDialog hotkeys onTabReady msg
|
/* global configDialog hotkeys msg
|
||||||
getActiveTab FIREFOX getTabRealURL URLS API onDOMready $ $$ prefs
|
getActiveTab CHROME FIREFOX URLS API onDOMready $ $$ prefs
|
||||||
setupLivePrefs template t $create animateElement
|
setupLivePrefs template t $create animateElement
|
||||||
tryJSONparse debounce CHROME_HAS_BORDER_BUG */
|
tryJSONparse CHROME_HAS_BORDER_BUG */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/** @type Element */
|
||||||
let installed;
|
let installed;
|
||||||
|
/** @type string */
|
||||||
let tabURL;
|
let tabURL;
|
||||||
let unsupportedURL;
|
|
||||||
const handleEvent = {};
|
const handleEvent = {};
|
||||||
|
|
||||||
|
const ABOUT_BLANK = 'about:blank';
|
||||||
const ENTRY_ID_PREFIX_RAW = 'style-';
|
const ENTRY_ID_PREFIX_RAW = 'style-';
|
||||||
const ENTRY_ID_PREFIX = '#' + ENTRY_ID_PREFIX_RAW;
|
const ENTRY_ID_PREFIX = '#' + ENTRY_ID_PREFIX_RAW;
|
||||||
|
|
||||||
|
if (CHROME >= 3345 && CHROME < 3533) { // Chrome 66-69 adds a gap, https://crbug.com/821143
|
||||||
|
document.head.appendChild($create('style', 'html { overflow: overlay }'));
|
||||||
|
}
|
||||||
|
|
||||||
toggleSideBorders();
|
toggleSideBorders();
|
||||||
|
|
||||||
getActiveTab()
|
initTabUrls()
|
||||||
.then(tab =>
|
.then(frames =>
|
||||||
FIREFOX && tab.url === 'about:blank' && tab.status === 'loading'
|
Promise.all([
|
||||||
? getTabRealURLFirefox(tab)
|
onDOMready().then(() => initPopup(frames)),
|
||||||
: getTabRealURL(tab)
|
...frames
|
||||||
)
|
.filter(f => f.url && !f.isDupe)
|
||||||
.then(url => Promise.all([
|
.map(({url}) => API.getStylesByUrl(url).then(styles => ({styles, url}))),
|
||||||
(tabURL = URLS.supported(url) ? url : '') &&
|
|
||||||
API.getStylesByUrl(tabURL),
|
|
||||||
onDOMready().then(initPopup),
|
|
||||||
]))
|
]))
|
||||||
.then(([results]) => {
|
.then(([, ...results]) => {
|
||||||
if (!results) {
|
if (results[0]) {
|
||||||
|
showStyles(results);
|
||||||
|
} else {
|
||||||
// unsupported URL;
|
// unsupported URL;
|
||||||
unsupportedURL = true;
|
|
||||||
$('#popup-manage-button').removeAttribute('title');
|
$('#popup-manage-button').removeAttribute('title');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
showStyles(results.map(r => Object.assign(r.data, r)));
|
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
|
||||||
|
@ -83,8 +85,32 @@ function toggleSideBorders(state = prefs.get('popup.borders')) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initTabUrls() {
|
||||||
|
return getActiveTab()
|
||||||
|
.then((tab = {}) =>
|
||||||
|
FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK
|
||||||
|
? waitForTabUrlFF(tab)
|
||||||
|
: tab)
|
||||||
|
.then(tab => new Promise(resolve =>
|
||||||
|
chrome.webNavigation.getAllFrames({tabId: tab.id}, frames =>
|
||||||
|
resolve({frames, tab}))))
|
||||||
|
.then(({frames, tab}) => {
|
||||||
|
let url = tab.pendingUrl || tab.url || ''; // new Chrome uses pendingUrl while connecting
|
||||||
|
frames = sortTabFrames(frames);
|
||||||
|
if (url === 'chrome://newtab/' && !URLS.chromeProtectsNTP) {
|
||||||
|
url = frames[0].url || '';
|
||||||
|
}
|
||||||
|
if (!URLS.supported(url)) {
|
||||||
|
url = '';
|
||||||
|
frames.length = 1;
|
||||||
|
}
|
||||||
|
tabURL = frames[0].url = url;
|
||||||
|
return frames;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initPopup() {
|
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
|
||||||
|
function initPopup(frames) {
|
||||||
installed = $('#installed');
|
installed = $('#installed');
|
||||||
|
|
||||||
setPopupWidth();
|
setPopupWidth();
|
||||||
|
@ -120,6 +146,13 @@ function initPopup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frames.forEach(createWriterElement);
|
||||||
|
if (frames.length > 1) {
|
||||||
|
const el = $('#write-for-frames');
|
||||||
|
el.hidden = false;
|
||||||
|
el.onclick = () => el.classList.toggle('expanded');
|
||||||
|
}
|
||||||
|
|
||||||
getActiveTab().then(function ping(tab, retryCountdown = 10) {
|
getActiveTab().then(function ping(tab, retryCountdown = 10) {
|
||||||
msg.sendTab(tab.id, {method: 'ping'}, {frameId: 0})
|
msg.sendTab(tab.id, {method: 'ping'}, {frameId: 0})
|
||||||
.catch(() => false)
|
.catch(() => false)
|
||||||
|
@ -131,7 +164,7 @@ function initPopup() {
|
||||||
// so we'll wait a bit to handle popup being invoked right after switching
|
// so we'll wait a bit to handle popup being invoked right after switching
|
||||||
if (retryCountdown > 0 && (
|
if (retryCountdown > 0 && (
|
||||||
tab.status !== 'complete' ||
|
tab.status !== 'complete' ||
|
||||||
FIREFOX && tab.url === 'about:blank')) {
|
FIREFOX && tab.url === ABOUT_BLANK)) {
|
||||||
setTimeout(ping, 100, tab, --retryCountdown);
|
setTimeout(ping, 100, tab, --retryCountdown);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -166,24 +199,26 @@ function initPopup() {
|
||||||
document.body.insertBefore(info, document.body.firstChild);
|
document.body.insertBefore(info, document.body.firstChild);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Write new style links
|
/** @param {chrome.webNavigation.GetAllFrameResultDetails} frame */
|
||||||
const writeStyle = $('#write-style');
|
function createWriterElement(frame) {
|
||||||
const matchTargets = document.createElement('span');
|
const {url, frameId, parentFrameId, isDupe} = frame;
|
||||||
const matchWrapper = document.createElement('span');
|
const targets = $create('span');
|
||||||
matchWrapper.id = 'match';
|
|
||||||
matchWrapper.appendChild(matchTargets);
|
|
||||||
|
|
||||||
// For this URL
|
// For this URL
|
||||||
const urlLink = template.writeStyle.cloneNode(true);
|
const urlLink = template.writeStyle.cloneNode(true);
|
||||||
|
const isAboutBlank = url === ABOUT_BLANK;
|
||||||
Object.assign(urlLink, {
|
Object.assign(urlLink, {
|
||||||
href: 'edit.html?url-prefix=' + encodeURIComponent(tabURL),
|
href: 'edit.html?url-prefix=' + encodeURIComponent(url),
|
||||||
title: `url-prefix("${tabURL}")`,
|
title: `url-prefix("${url}")`,
|
||||||
|
tabIndex: isAboutBlank ? -1 : 0,
|
||||||
textContent: prefs.get('popup.breadcrumbs.usePath')
|
textContent: prefs.get('popup.breadcrumbs.usePath')
|
||||||
? new URL(tabURL).pathname.slice(1)
|
? new URL(url).pathname.slice(1)
|
||||||
// this URL
|
: frameId
|
||||||
: t('writeStyleForURL').replace(/ /g, '\u00a0'),
|
? isAboutBlank ? url : 'URL'
|
||||||
onclick: e => handleEvent.openEditor(e, {'url-prefix': tabURL}),
|
: t('writeStyleForURL').replace(/ /g, '\u00a0'), // this URL
|
||||||
|
onclick: e => handleEvent.openEditor(e, {'url-prefix': url}),
|
||||||
});
|
});
|
||||||
if (prefs.get('popup.breadcrumbs')) {
|
if (prefs.get('popup.breadcrumbs')) {
|
||||||
urlLink.onmouseenter =
|
urlLink.onmouseenter =
|
||||||
|
@ -191,10 +226,10 @@ function initPopup() {
|
||||||
urlLink.onmouseleave =
|
urlLink.onmouseleave =
|
||||||
urlLink.onblur = () => urlLink.parentNode.classList.remove('url()');
|
urlLink.onblur = () => urlLink.parentNode.classList.remove('url()');
|
||||||
}
|
}
|
||||||
matchTargets.appendChild(urlLink);
|
targets.appendChild(urlLink);
|
||||||
|
|
||||||
// For domain
|
// For domain
|
||||||
const domains = getDomains(tabURL);
|
const domains = getDomains(url);
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
const numParts = domain.length - domain.replace(/\./g, '').length + 1;
|
const numParts = domain.length - domain.replace(/\./g, '').length + 1;
|
||||||
// Don't include TLD
|
// Don't include TLD
|
||||||
|
@ -209,64 +244,90 @@ function initPopup() {
|
||||||
onclick: e => handleEvent.openEditor(e, {domain}),
|
onclick: e => handleEvent.openEditor(e, {domain}),
|
||||||
});
|
});
|
||||||
domainLink.setAttribute('subdomain', numParts > 1 ? 'true' : '');
|
domainLink.setAttribute('subdomain', numParts > 1 ? 'true' : '');
|
||||||
matchTargets.appendChild(domainLink);
|
targets.appendChild(domainLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefs.get('popup.breadcrumbs')) {
|
if (prefs.get('popup.breadcrumbs')) {
|
||||||
matchTargets.classList.add('breadcrumbs');
|
targets.classList.add('breadcrumbs');
|
||||||
matchTargets.appendChild(matchTargets.removeChild(matchTargets.firstElementChild));
|
targets.appendChild(urlLink); // making it the last element
|
||||||
}
|
}
|
||||||
writeStyle.appendChild(matchWrapper);
|
|
||||||
|
|
||||||
function getDomains(url) {
|
const root = $('#write-style');
|
||||||
let d = /.*?:\/*([^/:]+)|$/.exec(url)[1];
|
const parent = $(`[data-frame-id="${parentFrameId}"]`, root) || root;
|
||||||
|
const child = $create({
|
||||||
|
tag: 'span',
|
||||||
|
className: `match${isDupe ? ' dupe' : ''}${isAboutBlank ? ' about-blank' : ''}`,
|
||||||
|
dataset: {frameId},
|
||||||
|
appendChild: targets,
|
||||||
|
});
|
||||||
|
parent.appendChild(child);
|
||||||
|
parent.dataset.children = (Number(parent.dataset.children) || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDomains(url) {
|
||||||
|
let d = url.split(/[/:]+/, 2)[1];
|
||||||
if (!d || url.startsWith('file:')) {
|
if (!d || url.startsWith('file:')) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const domains = [d];
|
const domains = [d];
|
||||||
while (d.indexOf('.') !== -1) {
|
while (d.includes('.')) {
|
||||||
d = d.substring(d.indexOf('.') + 1);
|
d = d.substring(d.indexOf('.') + 1);
|
||||||
domains.push(d);
|
domains.push(d);
|
||||||
}
|
}
|
||||||
return domains;
|
return domains;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
|
||||||
|
function sortTabFrames(frames) {
|
||||||
|
const unknown = new Map(frames.map(f => [f.frameId, f]));
|
||||||
|
const known = new Map([[0, unknown.get(0) || {frameId: 0, url: ''}]]);
|
||||||
|
unknown.delete(0);
|
||||||
|
let lastSize = 0;
|
||||||
|
while (unknown.size !== lastSize) {
|
||||||
|
for (const [frameId, f] of unknown) {
|
||||||
|
if (known.has(f.parentFrameId)) {
|
||||||
|
unknown.delete(frameId);
|
||||||
|
if (!f.errorOccurred) known.set(frameId, f);
|
||||||
|
if (f.url === ABOUT_BLANK) f.url = known.get(f.parentFrameId).url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastSize = unknown.size; // guard against an infinite loop due to a weird frame structure
|
||||||
|
}
|
||||||
|
const sortedFrames = [...known.values(), ...unknown.values()];
|
||||||
|
const urls = new Set([ABOUT_BLANK]);
|
||||||
|
for (const f of sortedFrames) {
|
||||||
|
if (!f.url) f.url = '';
|
||||||
|
f.isDupe = urls.has(f.url);
|
||||||
|
urls.add(f.url);
|
||||||
|
}
|
||||||
|
return sortedFrames;
|
||||||
|
}
|
||||||
|
|
||||||
function sortStyles(entries) {
|
function sortStyles(entries) {
|
||||||
const enabledFirst = prefs.get('popup.enabledFirst');
|
const enabledFirst = prefs.get('popup.enabledFirst');
|
||||||
entries.sort((a, b) =>
|
return entries.sort(({styleMeta: a}, {styleMeta: b}) =>
|
||||||
enabledFirst && a.styleMeta.enabled !== b.styleMeta.enabled ?
|
Boolean(a.frameUrl) - Boolean(b.frameUrl) ||
|
||||||
(a.styleMeta.enabled ? -1 : 1) :
|
enabledFirst && Boolean(b.enabled) - Boolean(a.enabled) ||
|
||||||
a.styleMeta.name.localeCompare(b.styleMeta.name)
|
a.name.localeCompare(b.name));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showStyles(styles) {
|
function showStyles(frameResults) {
|
||||||
if (!styles) {
|
const entries = new Map();
|
||||||
return;
|
frameResults.forEach(({styles = [], url}, index) => {
|
||||||
|
styles.forEach(style => {
|
||||||
|
const {id} = style.data;
|
||||||
|
if (!entries.has(id)) {
|
||||||
|
style.frameUrl = index === 0 ? '' : url;
|
||||||
|
entries.set(id, createStyleElement(Object.assign(style.data, style)));
|
||||||
}
|
}
|
||||||
if (!styles.length) {
|
});
|
||||||
|
});
|
||||||
|
if (entries.size) {
|
||||||
|
installed.append(...sortStyles([...entries.values()]));
|
||||||
|
} else {
|
||||||
installed.appendChild(template.noStyles.cloneNode(true));
|
installed.appendChild(template.noStyles.cloneNode(true));
|
||||||
|
}
|
||||||
window.dispatchEvent(new Event('showStyles:done'));
|
window.dispatchEvent(new Event('showStyles:done'));
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entries = styles.map(createStyleElement);
|
|
||||||
sortStyles(entries);
|
|
||||||
entries.forEach(e => installed.appendChild(e));
|
|
||||||
window.dispatchEvent(new Event('showStyles:done'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortStylesInPlace() {
|
|
||||||
if (!prefs.get('popup.autoResort')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entries = $$('.entry', installed);
|
|
||||||
if (!entries.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sortStyles(entries);
|
|
||||||
entries.forEach(e => installed.appendChild(e));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,6 +417,14 @@ function createStyleElement(style) {
|
||||||
$('.exclude-by-domain', entry).title = getExcludeRule('domain');
|
$('.exclude-by-domain', entry).title = getExcludeRule('domain');
|
||||||
$('.exclude-by-url', entry).title = getExcludeRule('url');
|
$('.exclude-by-url', entry).title = getExcludeRule('url');
|
||||||
|
|
||||||
|
const {frameUrl} = style;
|
||||||
|
if (frameUrl) {
|
||||||
|
const sel = 'span.frame-url';
|
||||||
|
const frameEl = $(sel, entry) || styleName.insertBefore($create(sel), styleName.lastChild);
|
||||||
|
frameEl.title = frameUrl;
|
||||||
|
}
|
||||||
|
entry.classList.toggle('frame', Boolean(frameUrl));
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,7 +469,11 @@ Object.assign(handleEvent, {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
API
|
API
|
||||||
.toggleStyle(handleEvent.getClickedStyleId(event), this.checked)
|
.toggleStyle(handleEvent.getClickedStyleId(event), this.checked)
|
||||||
.then(sortStylesInPlace);
|
.then(() => {
|
||||||
|
if (prefs.get('popup.autoResort')) {
|
||||||
|
installed.append(...sortStyles($$('.entry', installed)));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleExclude(event, type) {
|
toggleExclude(event, type) {
|
||||||
|
@ -561,23 +634,17 @@ Object.assign(handleEvent, {
|
||||||
|
|
||||||
openURLandHide(event) {
|
openURLandHide(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const message = tryJSONparse(this.dataset.sendMessage);
|
|
||||||
getActiveTab()
|
getActiveTab()
|
||||||
.then(activeTab => API.openURL({
|
.then(activeTab => API.openURL({
|
||||||
url: this.href || this.dataset.href,
|
url: this.href || this.dataset.href,
|
||||||
index: activeTab.index + 1
|
index: activeTab.index + 1,
|
||||||
|
message: tryJSONparse(this.dataset.sendMessage),
|
||||||
}))
|
}))
|
||||||
.then(tab => {
|
|
||||||
if (message) {
|
|
||||||
return onTabReady(tab)
|
|
||||||
.then(() => msg.sendTab(tab.id, message));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(window.close);
|
.then(window.close);
|
||||||
},
|
},
|
||||||
|
|
||||||
openManager(event) {
|
openManager(event) {
|
||||||
if (event.button === 2 && unsupportedURL) return;
|
if (event.button === 2 && !tabURL) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!this.eventHandled) {
|
if (!this.eventHandled) {
|
||||||
// FIXME: this only works if popup is closed
|
// FIXME: this only works if popup is closed
|
||||||
|
@ -640,32 +707,17 @@ function handleDelete(id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTabRealURLFirefox(tab) {
|
function waitForTabUrlFF(tab) {
|
||||||
// wait for FF tab-on-demand to get a real URL (initially about:blank), 5 sec max
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
function onNavigation({tabId, url, frameId}) {
|
browser.tabs.onUpdated.addListener(...[
|
||||||
if (tabId === tab.id && frameId === 0) {
|
function onUpdated(tabId, info, updatedTab) {
|
||||||
detach();
|
if (info.url && tabId === tab.id) {
|
||||||
resolve(url);
|
chrome.tabs.onUpdated.removeListener(onUpdated);
|
||||||
|
resolve(updatedTab);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
...'UpdateFilter' in browser.tabs ? [{tabId: tab.id}] : [],
|
||||||
function detach(timedOut) {
|
// TODO: remove both spreads and tabId check when strict_min_version >= 61
|
||||||
if (timedOut) {
|
]);
|
||||||
resolve(tab.url);
|
|
||||||
} else {
|
|
||||||
debounce.unregister(detach);
|
|
||||||
}
|
|
||||||
chrome.webNavigation.onBeforeNavigate.removeListener(onNavigation);
|
|
||||||
chrome.webNavigation.onCommitted.removeListener(onNavigation);
|
|
||||||
chrome.tabs.onRemoved.removeListener(detach);
|
|
||||||
chrome.tabs.onReplaced.removeListener(detach);
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.webNavigation.onBeforeNavigate.addListener(onNavigation);
|
|
||||||
chrome.webNavigation.onCommitted.addListener(onNavigation);
|
|
||||||
chrome.tabs.onRemoved.addListener(detach);
|
|
||||||
chrome.tabs.onReplaced.addListener(detach);
|
|
||||||
debounce(detach, 5000, {timedOut: true});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user