Fix: don't recreate element when style update in popup

This commit is contained in:
eight 2018-10-06 16:38:04 +08:00
parent 583ca31d97
commit 75f2561154
4 changed files with 101 additions and 91 deletions

View File

@ -372,8 +372,7 @@ const styleManager = (() => {
function compileRe(text) {
let re = compiledRe.get(text);
if (!re) {
// FIXME: it should be `$({text})$` but we don't use the standard for compatibility
re = tryRegExp(`^${text}$`);
re = tryRegExp(`^(${text})$`);
if (!re) {
re = BAD_MATCHER;
}

View File

@ -31,7 +31,7 @@
// if (needTransitionPatch(styles)) {
// applyTransitionPatch();
// }
applyStyles(styles);
applyStyles(styles.filter(s => s.enabled));
});
msg.onTab(applyOnMessage);
window.applyOnMessage = applyOnMessage;

View File

@ -87,7 +87,7 @@ const msg = (() => {
}
function broadcastTab(data, filter, options, ignoreExtension = false, target = 'tab') {
return tabQuery()
return tabQuery({})
// TODO: send to activated tabs first?
.then(tabs => {
const requests = [];
@ -100,7 +100,7 @@ const msg = (() => {
!tab.url.startsWith('chrome://newtab/') &&
!isExtension ||
isExtension && ignoreExtension ||
!filter(tab.url)
!filter(tab)
) {
continue;
}

View File

@ -112,34 +112,36 @@ function initPopup() {
}
getActiveTab().then(function ping(tab, retryCountdown = 10) {
msg.sendTab(tab.id, {method: 'ping'}, {frameId: 0}).then(pong => {
if (pong) {
return;
}
ignoreChromeError();
// FF and some Chrome forks (e.g. CentBrowser) implement tab-on-demand
// so we'll wait a bit to handle popup being invoked right after switching
if (retryCountdown > 0 && (
tab.status !== 'complete' ||
FIREFOX && tab.url === 'about:blank')) {
setTimeout(ping, 100, tab, --retryCountdown);
return;
}
const info = template.unreachableInfo;
if (FIREFOX && tabURL.startsWith(URLS.browserWebStore)) {
$('label', info).textContent = t('unreachableAMO');
const note = (FIREFOX < 59 ? t('unreachableAMOHintOldFF') : t('unreachableAMOHint')) +
(FIREFOX < 60 ? '' : '\n' + t('unreachableAMOHintNewFF'));
const renderToken = s => s[0] === '<' ? $create('b', tWordBreak(s.slice(1, -1))) : s;
const renderLine = line => $create('p', line.split(/(<.*?>)/).map(renderToken));
const noteNode = $create('fragment', note.split('\n').map(renderLine));
const target = $('p', info);
target.parentNode.insertBefore(noteNode, target);
target.remove();
}
document.body.classList.add('unreachable');
document.body.insertBefore(info, document.body.firstChild);
});
msg.sendTab(tab.id, {method: 'ping'}, {frameId: 0})
.catch(() => false)
.then(pong => {
if (pong) {
return;
}
ignoreChromeError();
// FF and some Chrome forks (e.g. CentBrowser) implement tab-on-demand
// so we'll wait a bit to handle popup being invoked right after switching
if (retryCountdown > 0 && (
tab.status !== 'complete' ||
FIREFOX && tab.url === 'about:blank')) {
setTimeout(ping, 100, tab, --retryCountdown);
return;
}
const info = template.unreachableInfo;
if (FIREFOX && tabURL.startsWith(URLS.browserWebStore)) {
$('label', info).textContent = t('unreachableAMO');
const note = (FIREFOX < 59 ? t('unreachableAMOHintOldFF') : t('unreachableAMOHint')) +
(FIREFOX < 60 ? '' : '\n' + t('unreachableAMOHintNewFF'));
const renderToken = s => s[0] === '<' ? $create('b', tWordBreak(s.slice(1, -1))) : s;
const renderLine = line => $create('p', line.split(/(<.*?>)/).map(renderToken));
const noteNode = $create('fragment', note.split('\n').map(renderLine));
const target = $('p', info);
target.parentNode.insertBefore(noteNode, target);
target.remove();
}
document.body.classList.add('unreachable');
document.body.insertBefore(info, document.body.firstChild);
});
});
// Write new style links
@ -251,77 +253,86 @@ function createStyleElement({
check = false,
container = installed,
}) {
const entry = template.style.cloneNode(true);
let entry = $(ENTRY_ID_PREFIX + style.id);
if (!entry) {
entry = template.style.cloneNode(true);
entry.setAttribute('style-id', style.id);
Object.assign(entry, {
id: ENTRY_ID_PREFIX_RAW + style.id,
styleId: style.id,
styleIsUsercss: Boolean(style.usercssData),
onmousedown: handleEvent.maybeEdit,
styleMeta: style
});
const checkbox = $('.checker', entry);
Object.assign(checkbox, {
id: ENTRY_ID_PREFIX_RAW + style.id,
title: t('exclusionsPopupTip'),
onclick: handleEvent.toggle,
oncontextmenu: handleEvent.openExcludeMenu
});
const editLink = $('.style-edit-link', entry);
Object.assign(editLink, {
href: editLink.getAttribute('href') + style.id,
onclick: handleEvent.openLink,
});
const styleName = $('.style-name', entry);
Object.assign(styleName, {
htmlFor: ENTRY_ID_PREFIX_RAW + style.id,
onclick: handleEvent.name,
});
styleName.checkbox = checkbox;
styleName.appendChild(document.createTextNode(' '));
const config = $('.configure', entry);
if (!style.usercssData && style.updateUrl && style.updateUrl.includes('?') && style.url) {
config.target = '_blank';
config.title = t('configureStyleOnHomepage');
config.dataset.sendMessage = JSON.stringify({method: 'openSettings'});
$('use', config).attributes['xlink:href'].nodeValue = '#svg-icon-config-uso';
}
$('.enable', entry).onclick = handleEvent.toggle;
$('.disable', entry).onclick = handleEvent.toggle;
$('.delete', entry).onclick = handleEvent.delete;
$('.configure', entry).onclick = handleEvent.configure;
}
style = Object.assign(entry.styleMeta, style);
if (style.enabled) {
entry.classList.remove('disabled');
entry.classList.add('enabled');
$('.checker', entry).checked = true;
} else {
entry.classList.add('disabled');
entry.classList.remove('enabled');
$('.checker', entry).checked = false;
}
const excluded = popupExclusions.isExcluded(tabURL, style.exclusions);
entry.setAttribute('style-id', style.id);
Object.assign(entry, {
id: ENTRY_ID_PREFIX_RAW + style.id,
styleId: style.id,
styleIsUsercss: Boolean(style.usercssData),
className: entry.className + ' ' +
(style.enabled ? 'enabled' : 'disabled') +
(excluded ? ' excluded' : ''),
onmousedown: handleEvent.maybeEdit,
styleMeta: style
});
const checkbox = $('.checker', entry);
Object.assign(checkbox, {
id: ENTRY_ID_PREFIX_RAW + style.id,
title: t('exclusionsPopupTip'),
checked: style.enabled,
onclick: handleEvent.toggle,
oncontextmenu: handleEvent.openExcludeMenu
});
const editLink = $('.style-edit-link', entry);
Object.assign(editLink, {
href: editLink.getAttribute('href') + style.id,
onclick: handleEvent.openLink,
});
entry.classList.toggle('excluded', excluded);
const styleName = $('.style-name', entry);
Object.assign(styleName, {
htmlFor: ENTRY_ID_PREFIX_RAW + style.id,
onclick: handleEvent.name,
});
styleName.checkbox = checkbox;
styleName.appendChild(document.createTextNode(style.name));
setTimeout((el = styleName) => {
if (el.scrollWidth > el.clientWidth + 1) {
el.title = el.textContent;
styleName.lastChild.textContent = style.name;
setTimeout(() => {
if (styleName.scrollWidth > styleName.clientWidth + 1) {
styleName.title = styleName.textContent;
}
});
const config = $('.configure', entry);
if (!style.usercssData && style.updateUrl && style.updateUrl.includes('?') && style.url) {
config.href = style.url;
config.target = '_blank';
config.title = t('configureStyleOnHomepage');
config.dataset.sendMessage = JSON.stringify({method: 'openSettings'});
$('use', config).attributes['xlink:href'].nodeValue = '#svg-icon-config-uso';
} else if (!style.usercssData || !Object.keys(style.usercssData.vars || {}).length) {
config.style.display = 'none';
} else {
config.href = '';
}
$('.enable', entry).onclick = handleEvent.toggle;
$('.disable', entry).onclick = handleEvent.toggle;
$('.delete', entry).onclick = handleEvent.delete;
$('.configure', entry).onclick = handleEvent.configure;
config.style.display =
!style.usercssData && config.href ||
style.usercssData && Object.keys(style.usercssData.vars || {}).length ?
'' : 'none';
if (check) detectSloppyRegexps([style]);
const oldElement = $(ENTRY_ID_PREFIX + style.id);
if (oldElement && oldElement.contains(document.activeElement)) {
// preserve the focused element inside
const {className} = document.activeElement;
oldElement.parentNode.replaceChild(entry, oldElement);
// we're not using $() since className may contain multiple tokens
const el = entry.getElementsByClassName(className)[0];
if (el) el.focus();
} else if (oldElement) {
oldElement.parentNode.replaceChild(entry, oldElement);
} else {
if (entry.parentNode !== container) {
container.appendChild(entry);
}
}