diff --git a/background/background.js b/background/background.js
index 13d12e2f..643cba2c 100644
--- a/background/background.js
+++ b/background/background.js
@@ -281,7 +281,10 @@ function updateIcon(tab, styles) {
// Vivaldi bug workaround: setBadgeText must follow setBadgeBackgroundColor
chrome.browserAction.setBadgeBackgroundColor({color});
getTab(tab.id).then(() => {
- chrome.browserAction.setBadgeText({text, tabId: tab.id});
+ // skip pre-rendered tabs
+ if (tab.index >= 0) {
+ chrome.browserAction.setBadgeText({text, tabId: tab.id});
+ }
});
});
}
diff --git a/edit.html b/edit.html
index 5df54d54..4684b534 100644
--- a/edit.html
+++ b/edit.html
@@ -61,7 +61,7 @@
-
+
diff --git a/edit/edit.css b/edit/edit.css
index f41e93cb..bdf53116 100644
--- a/edit/edit.css
+++ b/edit/edit.css
@@ -330,8 +330,6 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
position: absolute;
margin-left: -20px;
margin-top: -1px;
- animation: fadein 1s cubic-bezier(.03, .67, .08, .94);
- animation-fill-mode: both;
}
/************ help popup ************/
#help-popup {
diff --git a/edit/edit.js b/edit/edit.js
index dbd873fd..316b5ea5 100644
--- a/edit/edit.js
+++ b/edit/edit.js
@@ -260,24 +260,28 @@ function initCodeMirror() {
};
// initialize global editor controls
- function optionsHtmlFromArray(options) {
- return options.map(opt => '').join('');
+ function optionsFromArray(parent, options) {
+ const fragment = document.createDocumentFragment();
+ for (const opt of options) {
+ fragment.appendChild($element({tag: 'option', textContent: opt}));
+ }
+ parent.appendChild(fragment);
}
const themeControl = document.getElementById('editor.theme');
const themeList = localStorage.codeMirrorThemes;
if (themeList) {
- themeControl.innerHTML = optionsHtmlFromArray(themeList.split(/\s+/));
+ optionsFromArray(themeControl, themeList.split(/\s+/));
} else {
// Chrome is starting up and shows our edit.html, but the background page isn't loaded yet
const theme = prefs.get('editor.theme');
- themeControl.innerHTML = optionsHtmlFromArray([theme === 'default' ? t('defaultTheme') : theme]);
+ optionsFromArray(themeControl, [theme === 'default' ? t('defaultTheme') : theme]);
getCodeMirrorThemes().then(() => {
const themes = (localStorage.codeMirrorThemes || '').split(/\s+/);
- themeControl.innerHTML = optionsHtmlFromArray(themes);
+ optionsFromArray(themeControl, themes);
themeControl.selectedIndex = Math.max(0, themes.indexOf(theme));
});
}
- document.getElementById('editor.keyMap').innerHTML = optionsHtmlFromArray(Object.keys(CM.keyMap).sort());
+ optionsFromArray($('#editor.keyMap'), Object.keys(CM.keyMap).sort());
document.getElementById('options').addEventListener('change', acmeEventListener, false);
setupLivePrefs();
@@ -314,15 +318,17 @@ function acmeEventListener(event) {
break;
}
// avoid flicker: wait for the second stylesheet to load, then apply the theme
- document.head.insertAdjacentHTML('beforeend',
- '');
- (() => {
- setTimeout(() => {
- CodeMirror.setOption(option, value);
- themeLink.remove();
- document.getElementById('cm-theme2').id = 'cm-theme';
- }, 100);
- })();
+ document.head.appendChild($element({
+ tag: 'link',
+ id: 'cm-theme2',
+ rel: 'stylesheet',
+ href: url
+ }));
+ setTimeout(() => {
+ CodeMirror.setOption(option, value);
+ themeLink.remove();
+ $('#cm-theme2').id = 'cm-theme';
+ }, 100);
return;
}
case 'autocompleteOnTyping':
@@ -688,11 +694,11 @@ function setupGlobalSearch() {
return cm.state.search;
}
- // temporarily overrides the original openDialog with the provided template's innerHTML
+ // overrides the original openDialog with a clone of the provided template
function customizeOpenDialog(cm, template, callback) {
cm.openDialog = (tmpl, cb, opt) => {
// invoke 'callback' and bind 'this' to the original callback
- originalOpenDialog.call(cm, template.innerHTML, callback.bind(cb), opt);
+ originalOpenDialog.call(cm, template.cloneNode(true), callback.bind(cb), opt);
};
setTimeout(() => { cm.openDialog = originalOpenDialog; }, 0);
refocusMinidialog(cm);
@@ -871,7 +877,7 @@ function setupGlobalSearch() {
doReplace();
}
});
- originalOpenConfirm.call(cm, template.replaceConfirm.innerHTML, ovrCallbacks, opt);
+ originalOpenConfirm.call(cm, template.replaceConfirm.cloneNode(true), ovrCallbacks, opt);
};
}
}
@@ -890,7 +896,7 @@ function setupGlobalSearch() {
function jumpToLine(cm) {
const cur = cm.getCursor();
refocusMinidialog(cm);
- cm.openDialog(template.jumpToLine.innerHTML, str => {
+ cm.openDialog(template.jumpToLine.cloneNode(true), str => {
const m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/);
if (m) {
cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch);
@@ -1087,9 +1093,9 @@ function renderLintReport(someBlockChanged) {
let issueCount = 0;
editors.forEach((cm, index) => {
if (cm.state.lint && cm.state.lint.html) {
- const newBlock = newContent.appendChild(document.createElement('table'));
const html = '
' + label + ' ' + (index + 1) + '' + cm.state.lint.html;
- newBlock.innerHTML = html;
+ const newBlock = newContent.appendChild(tHTML(html, 'table'));
+
newBlock.cm = cm;
issueCount += newBlock.rows.length;
@@ -1233,7 +1239,7 @@ function init() {
const params = getParams();
if (!params.id) { // match should be 2 - one for the whole thing, one for the parentheses
// This is an add
- tE('heading', 'addStyleTitle');
+ $('#heading').textContent = t('addStyleTitle');
const section = {code: ''};
for (const i in CssToProperty) {
if (params[i]) {
@@ -1251,7 +1257,7 @@ function init() {
return;
}
// This is an edit
- tE('heading', 'editStyleHeading', null, false);
+ $('#heading').textContent = t('editStyleHeading');
getStylesSafe({id: params.id}).then(styles => {
let style = styles[0];
if (!style) {
@@ -1504,7 +1510,7 @@ function saveComplete(style) {
// Go from new style URL to edit style URL
if (location.href.indexOf('id=') === -1) {
history.replaceState({}, document.title, 'edit.html?id=' + style.id);
- tE('heading', 'editStyleHeading', null, false);
+ $('#heading').textContent = t('editStyleHeading');
}
updateTitle();
}
@@ -1534,7 +1540,7 @@ function fromMozillaFormat() {
`
- ).innerHTML);
+ ));
const contents = popup.querySelector('.contents');
contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
@@ -1610,7 +1616,7 @@ function fromMozillaFormat() {
makeSectionVisible(firstAddedCM);
firstAddedCM.focus();
- if (errors) {
+ if (errors.length) {
showHelp(t('issues'), $element({
tag: 'pre',
textContent: errors.join('\n'),
@@ -1748,16 +1754,37 @@ function showKeyMapHelp() {
function filterTable(event) {
const input = event.target;
- const query = stringAsRegExp(input.value, 'gi');
const col = input.parentNode.cellIndex;
inputs[1 - col].value = '';
table.tBodies[0].childNodes.forEach(row => {
- let cell = row.children[col];
- cell.innerHTML = cell.textContent.replace(query, '$&');
- row.style.display = query.test(cell.textContent) ? '' : 'none';
+ const cell = row.children[col];
+ const text = cell.textContent;
+ const query = stringAsRegExp(input.value, 'gi');
+ const test = query.test(text);
+ row.style.display = input.value && test === false ? 'none' : '';
+ if (input.value && test) {
+ cell.textContent = '';
+ let offset = 0;
+ text.replace(query, (match, index) => {
+ if (index > offset) {
+ cell.appendChild(document.createTextNode(text.substring(offset, index)));
+ }
+ cell.appendChild($element({tag: 'mark', textContent: match}));
+ offset = index + match.length;
+ });
+ if (offset + 1 !== text.length) {
+ cell.appendChild(document.createTextNode(text.substring(offset)));
+ }
+ }
+ else {
+ cell.textContent = text;
+ }
// clear highlight from the other column
- cell = row.children[1 - col];
- cell.innerHTML = cell.textContent;
+ const otherCell = row.children[1 - col];
+ if (otherCell.children.length) {
+ const text = otherCell.textContent;
+ otherCell.textContent = text;
+ }
});
}
function mergeKeyMaps(merged, ...more) {
@@ -1897,30 +1924,31 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
if (!data.length) {
continue;
}
- // 2nd level: regexp text
- const summary = $element({tag: 'summary', appendChild: label});
- const block = [summary];
- for (const {text, urls} of data) {
- if (!urls) {
- block.push(text, br.cloneNode());
- continue;
- }
- block.push($element({
- tag: 'details',
- open: true,
- appendChild: [
- $element({tag: 'summary', textContent: text}),
- // 3rd level: tab urls
- ...urls,
- ],
- }));
- }
- report.appendChild($element({
+ const block = report.appendChild($element({
tag: 'details',
open: true,
dataset: {type},
- appendChild: block,
+ appendChild: $element({tag: 'summary', appendChild: label}),
}));
+ // 2nd level: regexp text
+ for (const {text, urls} of data) {
+ if (urls) {
+ // type is partial or full
+ block.appendChild($element({
+ tag: 'details',
+ open: true,
+ appendChild: [
+ $element({tag: 'summary', textContent: text}),
+ // 3rd level: tab urls
+ ...urls,
+ ],
+ }));
+ } else {
+ // type is none or invalid
+ block.appendChild(document.createTextNode(text));
+ block.appendChild(br.cloneNode());
+ }
+ }
}
showHelp(t('styleRegexpTestTitle'), report);
@@ -1934,17 +1962,11 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
});
}
-function showHelp(title, text) {
+function showHelp(title, body) {
const div = $('#help-popup');
div.classList.remove('big');
-
- const contents = $('.contents', div);
- if (text instanceof HTMLElement) {
- contents.textContent = '';
- contents.appendChild(text);
- } else {
- contents.innerHTML = text;
- }
+ $('.contents', div).textContent = '';
+ $('.contents', div).appendChild(typeof body === 'string' ? tHTML(body) : body);
$('.title', div).textContent = title;
if (getComputedStyle(div).display === 'none') {
@@ -1962,7 +1984,7 @@ function showHelp(title, text) {
((e.keyCode || e.which) === 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey)
) {
div.style.display = '';
- document.querySelector('.contents').innerHTML = '';
+ document.querySelector('.contents').textContent = '';
document.removeEventListener('keydown', closeHelp);
}
}
diff --git a/js/localization.js b/js/localization.js
index 1db7577b..f7f572cf 100644
--- a/js/localization.js
+++ b/js/localization.js
@@ -17,24 +17,31 @@ function t(key, params) {
}
-function tE(id, key, attr, esc) {
- if (attr) {
- document.getElementById(id).setAttribute(attr, t(key));
- } else if (typeof esc === 'undefined' || esc) {
- document.getElementById(id).appendChild(document.createTextNode(t(key)));
- } else {
- document.getElementById(id).innerHTML = t(key);
+function tHTML(html, tag) {
+ // body is a text node without HTML tags
+ if (typeof html === 'string' && !tag && /<\w+/.test(html) === false) {
+ return document.createTextNode(html);
}
-}
-
-
-function tHTML(html) {
- const node = document.createElement('div');
- node.innerHTML = html.replace(/>\s+<'); // spaces are removed; use for an explicit space
- if (html.includes('i18n-')) {
- tNodeList(node.getElementsByTagName('*'));
+ if (typeof html === 'string') {
+ html = html.replace(/>\s+<'); // spaces are removed; use for an explicit space
+ if (tag) {
+ html = `<${tag}>${html}${tag}>`;
+ }
+ const body = t.DOMParser.parseFromString(html, 'text/html').body;
+ if (html.includes('i18n-')) {
+ tNodeList(body.getElementsByTagName('*'));
+ }
+ // the html string may contain more than one top-level elements
+ if (body.childElementCount <= 1) {
+ return body.firstElementChild;
+ }
+ const fragment = document.createDocumentFragment();
+ while (body.childElementCount) {
+ fragment.appendChild(body.firstElementChild);
+ }
+ return fragment;
}
- return node.firstElementChild;
+ return html;
}
@@ -78,7 +85,11 @@ function tNodeList(nodes) {
node.appendChild(document.createTextNode(value));
break;
case 'html':
- node.insertAdjacentHTML('afterbegin', value);
+ // localized strings only allow having text nodes and links
+ node.textContent = '';
+ [...tHTML(value, 'div').childNodes]
+ .filter(a => a.nodeType === a.TEXT_NODE || a.tagName === 'A')
+ .forEach(n => node.appendChild(n));
break;
default:
node.setAttribute(type, value);
@@ -90,6 +101,7 @@ function tNodeList(nodes) {
function tDocLoader() {
+ t.DOMParser = new DOMParser();
t.cache = tryJSONparse(localStorage.L10N) || {};
// reset L10N cache on UI language change
diff --git a/manage.html b/manage.html
index b3bb0545..1a5cae15 100644
--- a/manage.html
+++ b/manage.html
@@ -117,7 +117,7 @@
diff --git a/manage/manage.js b/manage/manage.js
index 3d11308a..08e8feec 100644
--- a/manage/manage.js
+++ b/manage/manage.js
@@ -519,7 +519,7 @@ function switchUI({styleOnly} = {}) {
const missingFavicons = newUI.enabled && newUI.favicons && !$('.applies-to img');
if (changed.enabled || (missingFavicons && !createStyleElement.parts)) {
- installed.innerHTML = '';
+ installed.textContent = '';
getStylesSafe().then(showStyles);
return;
}
diff --git a/msgbox/msgbox.js b/msgbox/msgbox.js
index 8faf8d60..df0be6ae 100644
--- a/msgbox/msgbox.js
+++ b/msgbox/msgbox.js
@@ -1,8 +1,8 @@
'use strict';
function messageBox({
- title, // [mandatory] the title string for innerHTML
- contents, // [mandatory] 1) DOM element 2) string for innerHTML
+ title, // [mandatory] string
+ contents, // [mandatory] 1) DOM element 2) string
className = '', // string, CSS class name of the message box element
buttons = [], // array of strings used as labels
onshow, // function(messageboxElement) invoked after the messagebox is shown
@@ -52,10 +52,9 @@ function messageBox({
unbindAndRemoveSelf();
}
const id = 'message-box';
- const putAs = typeof contents === 'string' ? 'innerHTML' : 'appendChild';
messageBox.element = $element({id, className, appendChild: [
$element({appendChild: [
- $element({id: `${id}-title`, innerHTML: title}),
+ $element({id: `${id}-title`, textContent: title}),
$element({id: `${id}-close-icon`, appendChild:
$element({tag: 'SVG#svg', class: 'svg-icon', viewBox: '0 0 20 20', appendChild:
$element({tag: 'SVG#path', d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' +
@@ -63,7 +62,7 @@ function messageBox({
})
}),
onclick: messageBox.listeners.closeIcon}),
- $element({id: `${id}-contents`, [putAs]: contents}),
+ $element({id: `${id}-contents`, appendChild: tHTML(contents)}),
$element({id: `${id}-buttons`, appendChild:
buttons.map((textContent, buttonIndex) => textContent &&
$element({
diff --git a/popup/popup.js b/popup/popup.js
index 52accdf1..d8d6e331 100644
--- a/popup/popup.js
+++ b/popup/popup.js
@@ -176,7 +176,8 @@ function showStyles(styles) {
return;
}
if (!styles.length) {
- installed.innerHTML = template.noStyles.outerHTML;
+ installed.textContent = '';
+ installed.appendChild(template.noStyles.cloneNode(true));
return;
}