stylus/edit/edit.js

683 lines
20 KiB
JavaScript
Raw Normal View History

/*
global CodeMirror parserlib loadScript
global CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter
global mozParser createSourceEditor
global closeCurrentTab regExpTester messageBox
global initColorpicker
global initCollapsibles
global setupCodeMirror
global beautify
global initWithSectionStyle addSections removeSection getSectionsHashes
*/
2017-07-12 20:44:59 +00:00
'use strict';
2017-07-12 20:44:59 +00:00
let styleId = null;
2017-08-20 18:56:18 +00:00
// only the actually dirty items here
let dirty = {};
// array of all CodeMirror instances
const editors = [];
2017-07-12 20:44:59 +00:00
let saveSizeOnClose;
2017-08-20 18:56:18 +00:00
// use browser history back when 'back to manage' is clicked
let useHistoryBack;
2015-01-30 17:05:06 +00:00
// direct & reverse mapping of @-moz-document keywords and internal property names
2017-07-12 20:44:59 +00:00
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
const CssToProperty = {'url': 'urls', 'url-prefix': 'urlPrefixes', 'domain': 'domains', 'regexp': 'regexps'};
2017-09-11 16:09:25 +00:00
let editor;
preinit();
window.onbeforeunload = beforeUnload;
chrome.runtime.onMessage.addListener(onRuntimeMessage);
Promise.all([
initStyleData().then(style => {
styleId = style.id;
sessionStorage.justEditedStyleId = styleId;
// we set "usercss" class on <html> when <body> is empty
// so there'll be no flickering of the elements that depend on it
if (isUsercss(style)) {
document.documentElement.classList.add('usercss');
}
// strip URL parameters when invoked for a non-existent id
if (!styleId) {
history.replaceState({}, document.title, location.pathname);
}
return style;
}),
onDOMready(),
onBackgroundReady(),
])
.then(([style]) => Promise.all([
style,
initColorpicker(),
initCollapsibles(),
initHooksCommon(),
]))
.then(([style]) => {
const usercss = isUsercss(style);
$('#heading').textContent = t(styleId ? 'editStyleHeading' : 'addStyleTitle');
$('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
$('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
$('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
if (usercss) {
editor = createSourceEditor(style);
} else {
initWithSectionStyle({style});
document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
}
});
function preinit() {
// make querySelectorAll enumeration code readable
['forEach', 'some', 'indexOf', 'map'].forEach(method => {
NodeList.prototype[method] = Array.prototype[method];
});
// eslint-disable-next-line no-extend-native
Object.defineProperties(Array.prototype, {
last: {
get() {
return this[this.length - 1];
},
},
rotate: {
value: function (amount) {
// negative amount == rotate left
const r = this.slice(-amount, this.length);
Array.prototype.push.apply(r, this.slice(0, this.length - r.length));
return r;
},
},
});
// preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs()
2017-12-05 00:54:43 +00:00
document.head.appendChild(
$create('link#cm-theme', {
rel: 'stylesheet',
href: prefs.get('editor.theme') === 'default' ? '' :
'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css'
}));
if (chrome.windows) {
queryTabs({currentWindow: true}).then(tabs => {
const windowId = tabs[0].windowId;
if (prefs.get('openEditInWindow')) {
if (
/true/.test(sessionStorage.saveSizeOnClose) &&
'left' in prefs.get('windowPosition', {}) &&
!isWindowMaximized()
) {
// window was reopened via Ctrl-Shift-T etc.
chrome.windows.update(windowId, prefs.get('windowPosition'));
}
if (tabs.length === 1 && window.history.length === 1) {
chrome.windows.getAll(windows => {
if (windows.length > 1) {
sessionStorageHash('saveSizeOnClose').set(windowId, true);
saveSizeOnClose = true;
}
});
} else {
saveSizeOnClose = sessionStorageHash('saveSizeOnClose').value[windowId];
}
2017-07-12 19:50:13 +00:00
}
});
2017-07-12 19:50:13 +00:00
}
getOwnTab().then(tab => {
const ownTabId = tab.id;
useHistoryBack = sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href;
if (!chrome.windows) {
return;
}
// When an edit page gets attached or detached, remember its state
// so we can do the same to the next one to open.
chrome.tabs.onAttached.addListener((tabId, info) => {
if (tabId !== ownTabId) {
return;
}
if (info.newPosition !== 0) {
prefs.set('openEditInWindow', false);
return;
}
chrome.windows.get(info.newWindowId, {populate: true}, win => {
// If there's only one tab in this window, it's been dragged to new window
const openEditInWindow = win.tabs.length === 1;
if (openEditInWindow && FIREFOX) {
// FF-only because Chrome retardedly resets the size during dragging
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
}
prefs.set('openEditInWindow', openEditInWindow);
});
});
});
}
function onRuntimeMessage(request) {
switch (request.method) {
case 'styleUpdated':
if (styleId && styleId === request.style.id &&
request.reason !== 'editSave' &&
request.reason !== 'config') {
// code-less style from notifyAllTabs
if ((request.style.sections[0] || {}).code === null) {
request.style = BG.cachedStyles.byId.get(request.style.id);
}
if (isUsercss(request.style)) {
editor.replaceStyle(request.style, request.codeIsUpdated);
} else {
initWithSectionStyle(request);
2017-07-12 19:50:13 +00:00
}
}
break;
case 'styleDeleted':
if (styleId === request.id || editor && editor.getStyle().id === request.id) {
window.onbeforeunload = () => {};
closeCurrentTab();
2017-07-12 19:50:13 +00:00
break;
}
2017-08-15 19:13:58 +00:00
break;
case 'prefChanged':
if ('editor.smartIndent' in request.prefs) {
CodeMirror.setOption('smartIndent', request.prefs['editor.smartIndent']);
2017-08-27 14:36:20 +00:00
}
break;
case 'editDeleteText':
document.execCommand('delete');
break;
2017-07-12 19:50:13 +00:00
}
}
function beforeUnload() {
if (saveSizeOnClose) {
rememberWindowSize();
}
2017-07-12 19:50:13 +00:00
document.activeElement.blur();
if (isClean()) {
2017-07-12 19:50:13 +00:00
return;
}
2017-08-20 14:06:17 +00:00
updateLintReportIfEnabled(null, 0);
// neither confirm() nor custom messages work in modern browsers but just in case
return t('styleChangesNotSaved');
function isClean() {
if (editor) {
return !editor.isDirty();
} else {
return isCleanGlobal();
}
}
}
function isUsercss(style) {
return (
style.usercssData ||
!style.id && prefs.get('newStyleAsUsercss')
);
2015-06-23 16:24:53 +00:00
}
function initStyleData() {
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const params = new URLSearchParams(location.search.replace(/^\?/, ''));
const id = params.get('id');
const createEmptyStyle = () => ({
id: null,
name: '',
enabled: true,
sections: [
Object.assign({code: ''},
...Object.keys(CssToProperty)
.map(name => ({
[CssToProperty[name]]: params.get(name) && [params.get(name)] || []
}))
)
],
2017-09-12 11:47:32 +00:00
});
return !id ?
Promise.resolve(createEmptyStyle()) :
getStylesSafe({id}).then(([style]) => style || createEmptyStyle());
2015-01-30 17:05:06 +00:00
}
function initHooks() {
if (initHooks.alreadyDone) {
return;
}
initHooks.alreadyDone = true;
2017-08-21 00:05:08 +00:00
$$('#header .style-contributor').forEach(node => {
2017-07-12 20:44:59 +00:00
node.addEventListener('change', onChange);
node.addEventListener('input', onChange);
2017-07-12 19:50:13 +00:00
});
2017-08-21 00:05:08 +00:00
$('#toggle-style-help').addEventListener('click', showToggleStyleHelp);
$('#to-mozilla').addEventListener('click', showMozillaFormat, false);
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp, false);
$('#from-mozilla').addEventListener('click', fromMozillaFormat);
$('#save-button').addEventListener('click', save, false);
$('#sections-help').addEventListener('click', showSectionHelp, false);
2017-08-17 19:08:48 +00:00
initLint();
2017-07-12 19:50:13 +00:00
if (!FIREFOX) {
$$([
'input:not([type])',
'input[type="text"]',
'input[type="search"]',
'input[type="number"]',
].join(','))
.forEach(e => e.addEventListener('mousedown', toggleContextMenuDelete));
}
2015-01-30 17:05:06 +00:00
}
// common for usercss and classic
function initHooksCommon() {
$('#cancel-button').addEventListener('click', goBackToManage);
$('#beautify').addEventListener('click', beautify);
prefs.subscribe(['editor.keyMap'], showKeyInSaveButtonTooltip);
showKeyInSaveButtonTooltip();
window.addEventListener('resize', () => debounce(rememberWindowSize, 100));
function goBackToManage(event) {
if (useHistoryBack) {
event.stopPropagation();
event.preventDefault();
history.back();
}
}
function showKeyInSaveButtonTooltip(prefName, value) {
$('#save-button').title = findKeyForCommand('save', value);
}
function findKeyForCommand(command, mapName = CodeMirror.defaults.keyMap) {
const map = CodeMirror.keyMap[mapName];
let key = Object.keys(map).find(k => map[k] === command);
if (key) {
return key;
}
for (const ft of Array.isArray(map.fallthrough) ? map.fallthrough : [map.fallthrough]) {
key = ft && findKeyForCommand(command, ft);
if (key) {
return key;
}
}
return '';
}
}
function onChange(event) {
const node = event.target;
if ('savedValue' in node) {
const currentValue = node.type === 'checkbox' ? node.checked : node.value;
setCleanItem(node, node.savedValue === currentValue);
} else {
// the manually added section's applies-to is dirty only when the value is non-empty
setCleanItem(node, node.localName !== 'input' || !node.value.trim());
// only valid when actually saved
delete node.savedValue;
2017-07-12 19:50:13 +00:00
}
updateTitle();
}
// Set .dirty on stylesheet contributors that have changed
function setDirtyClass(node, isDirty) {
node.classList.toggle('dirty', isDirty);
}
function setCleanItem(node, isClean) {
if (!node.id) {
node.id = Date.now().toString(32).substr(-6);
2017-07-12 19:50:13 +00:00
}
if (isClean) {
delete dirty[node.id];
// code sections have .CodeMirror property
if (node.CodeMirror) {
node.savedValue = node.CodeMirror.changeGeneration();
} else {
node.savedValue = node.type === 'checkbox' ? node.checked : node.value;
}
} else {
dirty[node.id] = true;
2017-07-12 19:50:13 +00:00
}
setDirtyClass(node, !isClean);
}
function isCleanGlobal() {
const clean = Object.keys(dirty).length === 0;
setDirtyClass(document.body, !clean);
// let saveBtn = $('#save-button')
// if (clean){
// //saveBtn.removeAttribute('disabled');
// }else{
// //saveBtn.setAttribute('disabled', true);
// }
return clean;
2015-01-30 17:05:06 +00:00
}
function setCleanGlobal() {
$$('#header, #sections > div').forEach(setCleanSection);
// forget the dirty applies-to ids from a deleted section after the style was saved
dirty = {};
2015-01-30 17:05:06 +00:00
}
function setCleanSection(section) {
$$('.style-contributor', section).forEach(node => setCleanItem(node, true));
// #header section has no codemirror
const cm = section.CodeMirror;
if (cm) {
section.savedValue = cm.changeGeneration();
updateTitle();
2017-08-20 14:06:17 +00:00
}
}
function toggleStyle() {
$('#enabled').checked = !$('#enabled').checked;
save();
2017-09-11 16:09:25 +00:00
}
function save() {
2017-08-20 14:06:17 +00:00
updateLintReportIfEnabled(null, 0);
if (!validate()) {
2017-07-12 19:50:13 +00:00
return;
}
2017-07-12 19:50:13 +00:00
saveStyleSafe({
id: styleId,
2017-12-03 17:32:50 +00:00
name: $('#name').value.trim(),
enabled: $('#enabled').checked,
2017-07-12 19:50:13 +00:00
reason: 'editSave',
sections: getSectionsHashes()
})
.then(style => {
styleId = style.id;
sessionStorage.justEditedStyleId = styleId;
setCleanGlobal();
// Go from new style URL to edit style URL
if (location.href.indexOf('id=') === -1) {
history.replaceState({}, document.title, 'edit.html?id=' + style.id);
$('#heading').textContent = t('editStyleHeading');
2017-07-12 19:50:13 +00:00
}
updateTitle();
2017-07-12 19:50:13 +00:00
});
2015-01-30 17:05:06 +00:00
}
function validate() {
const name = $('#name').value.trim();
if (!name) {
$('#name').focus();
messageBox.alert(t('styleMissingName'));
return false;
}
if ($$('.applies-to-list li:not(.applies-to-everything)')
.some(li => {
const type = $('[name=applies-type]', li).value;
const value = $('[name=applies-value]', li);
const rx = value.value.trim();
if (type === 'regexp' && rx && !tryRegExp(rx)) {
value.focus();
value.select();
return true;
}
})) {
messageBox.alert(t('styleBadRegexp'));
return false;
}
return true;
2015-01-30 17:05:06 +00:00
}
function updateTitle() {
const DIRTY_TITLE = '* $';
const name = $('#name').savedValue;
const clean = isCleanGlobal();
const title = styleId === null ? t('addStyleTitle') : t('editStyleTitle', [name]);
document.title = clean ? title : DIRTY_TITLE.replace('$', title);
}
2017-07-12 19:50:13 +00:00
function updateLintReportIfEnabled(...args) {
if (CodeMirror.defaults.lint) {
updateLintReport(...args);
2017-07-12 19:50:13 +00:00
}
2015-01-30 17:05:06 +00:00
}
function showMozillaFormat() {
2017-07-12 20:44:59 +00:00
const popup = showCodeMirrorPopup(t('styleToMozillaFormatTitle'), '', {readOnly: true});
2017-07-12 19:50:13 +00:00
popup.codebox.setValue(toMozillaFormat());
2017-07-12 20:44:59 +00:00
popup.codebox.execCommand('selectAll');
2015-01-30 17:05:06 +00:00
}
function toMozillaFormat() {
Add: install styles from *.user.css file Fix: handle dup name+namespace Fix: eslint eqeqeq Fix: trim @name's spaces Add: check update for userstyle Add: build CSS variable Fix: only check dup when id is not provided Refactor: userStyle2json -> userstyle.json Add: style for input Add: config dialog Fix: preserve config during update Fix: onchange doesn't fire on keyboard enter event Fix: remove empty file Add: validator. Metas must stay in the same line Add: warn the user if installation failed Fix: add some delay before starting installation Add: open the editor after first installation Fix: add openEditor to globals Fix: i18n Add: preprocessor. Move userstyle.build to background page. Fix: remove unused global Fix: preserved unknown prop in saveStyleSource() like saveStyle() Add: edit userstyle source Fix: load preprocessor dynamically Fix: load content script dynamically Fix: buildCode is async function Fix: drop Object.entries Fix: style.sections is undefined Fix: don't hide the name input but disable it Fix: query the style before installation Revert: changes to editor, editor.html Refactor: use term `usercss` instead of `userstyle` Fix: don't show homepage action for usercss Refactor: move script-loader to js/ Refactor: pull out mozParser Fix: code style Fix: we don't need to build meta anymore Fix: use saveUsercss instead of saveStyle to get responsed error Fix: last is undefined, load script error Fix: switch to moz-format Fix: drop injectContentScript. Move usercss check into install-user-css Fix: response -> respond Fix: globals -> global Fix: queryUsercss -> filterUsercss Fix: add processUsercss function Fix: only open editor for usercss Fix: remove findupUsercss fixme Fix: globals -> global Fix: globals -> global Fix: global pollution Revert: update.js Refactor: checkStyle Add: support usercss Fix: no need to getURL in background page Fix: merget semver.js into usercss.js Fix: drop all_urls in match pattern Fix: drop respondWithError Move stylus -> stylus-lang Add stylus-lang/readme Fix: use include_globs Fix: global pollution
2017-08-05 16:49:25 +00:00
return mozParser.format({sections: getSectionsHashes()});
2015-01-30 17:05:06 +00:00
}
function fromMozillaFormat() {
const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'),
$create([
$create('button', {
name: 'import-append',
textContent: t('importAppendLabel'),
title: 'Ctrl-Enter:\n' + t('importAppendTooltip'),
onclick: doImport,
}),
$create('button', {
name: 'import-replace',
textContent: t('importReplaceLabel'),
title: 'Ctrl-Shift-Enter:\n' + t('importReplaceTooltip'),
onclick: () => doImport({replaceOldStyle: true}),
}),
]));
2017-08-21 00:05:08 +00:00
const contents = $('.contents', popup);
2017-07-12 19:50:13 +00:00
contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
popup.codebox.focus();
popup.codebox.on('changes', cm => {
popup.classList.toggle('ready', !cm.isBlank());
cm.markClean();
2017-07-12 19:50:13 +00:00
});
// overwrite default extraKeys as those are inapplicable in popup context
popup.codebox.options.extraKeys = {
'Ctrl-Enter': doImport,
'Shift-Ctrl-Enter': () => doImport({replaceOldStyle: true}),
};
2017-07-12 19:50:13 +00:00
function doImport({replaceOldStyle = false}) {
lockPageUI(true);
new Promise(setTimeout)
.then(() => mozParser.parse(popup.codebox.getValue().trim()))
.then(sections => {
removeOldSections(replaceOldStyle);
return addSections(sections, div => setCleanItem(div, false));
})
.then(sectionDivs => {
sectionDivs.forEach(div => updateLintReportIfEnabled(div.CodeMirror, 1));
$('.dismiss', popup).onclick();
})
.catch(showError)
.then(() => lockPageUI(false));
}
function removeOldSections(removeAll) {
let toRemove;
if (removeAll) {
toRemove = editors.slice().reverse();
} else if (editors.last.isBlank() && $('.applies-to-everything', editors.last.getSection())) {
toRemove = [editors.last];
} else {
return;
}
toRemove.forEach(cm => removeSection({target: cm.getSection()}));
}
function lockPageUI(locked) {
document.documentElement.style.pointerEvents = locked ? 'none' : '';
popup.classList.toggle('ready', locked ? false : !popup.codebox.isBlank());
popup.codebox.options.readOnly = locked;
popup.codebox.display.wrapper.style.opacity = locked ? '.5' : '';
2017-07-12 19:50:13 +00:00
}
function showError(errors) {
showHelp(t('styleFromMozillaFormatError'),
$create('pre', Array.isArray(errors) ? errors.join('\n') : errors));
2017-07-12 19:50:13 +00:00
}
}
2015-01-30 17:05:06 +00:00
function showSectionHelp() {
2017-07-12 20:44:59 +00:00
showHelp(t('styleSectionsTitle'), t('sectionHelp'));
2015-01-30 17:05:06 +00:00
}
function showAppliesToHelp() {
2017-07-12 20:44:59 +00:00
showHelp(t('appliesLabel'), t('appliesHelp'));
2015-01-30 17:05:06 +00:00
}
function showToMozillaHelp() {
2017-07-12 20:44:59 +00:00
showHelp(t('styleMozillaFormatHeading'), t('styleToMozillaFormatHelp'));
}
function showToggleStyleHelp() {
2017-07-12 20:44:59 +00:00
showHelp(t('helpAlt'), t('styleEnabledToggleHint'));
}
function showHelp(title = '', body) {
const div = $('#help-popup');
div.className = '';
const contents = $('.contents', div);
contents.textContent = '';
if (body) {
contents.appendChild(typeof body === 'string' ? tHTML(body) : body);
}
$('.title', div).textContent = title;
2017-07-12 19:50:13 +00:00
2017-07-16 18:02:00 +00:00
if (getComputedStyle(div).display === 'none') {
window.addEventListener('keydown', closeHelp, true);
2017-08-20 18:56:18 +00:00
// avoid chaining on multiple showHelp() calls
2017-08-21 00:05:08 +00:00
$('.dismiss', div).onclick = closeHelp;
2017-07-12 19:50:13 +00:00
}
// reset any inline styles
div.style = 'display: block';
2017-07-12 19:50:13 +00:00
return div;
function closeHelp(event) {
const canClose =
!event ||
event.type === 'click' ||
(
event.which === 27 &&
!event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey &&
!$('.CodeMirror-hints, #message-box') &&
(
!document.activeElement ||
document.activeElement.matches(':not(input), .can-close-on-esc')
)
);
if (!canClose) {
return;
}
if (event && div.codebox && !div.codebox.options.readOnly && !div.codebox.isClean()) {
messageBox.confirm(t('confirmDiscardChanges')).then(ok => ok && closeHelp());
return;
2017-07-12 19:50:13 +00:00
}
div.style.display = '';
contents.textContent = '';
clearTimeout(contents.timer);
window.removeEventListener('keydown', closeHelp, true);
window.dispatchEvent(new Event('closeHelp'));
(editors.lastActive || editors[0]).focus();
2017-07-12 19:50:13 +00:00
}
2015-01-30 17:05:06 +00:00
}
function showCodeMirrorPopup(title, html, options) {
2017-07-12 20:44:59 +00:00
const popup = showHelp(title, html);
popup.classList.add('big');
2017-07-12 19:50:13 +00:00
const cm = popup.codebox = CodeMirror($('.contents', popup), Object.assign({
2017-07-12 20:44:59 +00:00
mode: 'css',
2017-07-12 19:50:13 +00:00
lineNumbers: true,
lineWrapping: true,
foldGutter: true,
2017-07-12 20:44:59 +00:00
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
2017-07-12 19:50:13 +00:00
matchBrackets: true,
lint: linterConfig.getForCodeMirror(),
2017-07-12 19:50:13 +00:00
styleActiveLine: true,
2017-07-12 20:44:59 +00:00
theme: prefs.get('editor.theme'),
keyMap: prefs.get('editor.keyMap')
2017-07-12 19:50:13 +00:00
}, options));
cm.focus();
cm.on('focus', () => cm.rerouteHotkeys(false));
cm.on('blur', () => cm.rerouteHotkeys(true));
2017-07-12 19:50:13 +00:00
return popup;
}
function setGlobalProgress(done, total) {
const progressElement = $('#global-progress') ||
total && document.body.appendChild($create('#global-progress'));
if (total) {
const progress = (done / Math.max(done, total) * 100).toFixed(1);
progressElement.style.borderLeftWidth = progress + 'vw';
setTimeout(() => {
progressElement.title = progress + '%';
});
} else {
$.remove(progressElement);
}
}
function scrollEntirePageOnCtrlShift(event) {
// make Shift-Ctrl-Wheel scroll entire page even when mouse is over a code editor
if (event.shiftKey && event.ctrlKey && !event.altKey && !event.metaKey) {
// Chrome scrolls horizontally when Shift is pressed but on some PCs this might be different
window.scrollBy(0, event.deltaX || event.deltaY);
event.preventDefault();
}
}
function hideLintHeaderOnScroll() {
// workaround part2 for the <details> not showing its toggle icon: hide <summary> on scroll
const newOpacity = this.scrollTop === 0 ? '' : '0';
const style = this.firstElementChild.style;
if (style.opacity !== newOpacity) {
style.opacity = newOpacity;
}
}
function rememberWindowSize() {
if (
document.visibilityState === 'visible' &&
prefs.get('openEditInWindow') &&
!isWindowMaximized()
) {
prefs.set('windowPosition', {
left: window.screenX,
top: window.screenY,
width: window.outerWidth,
height: window.outerHeight,
});
}
}
function isWindowMaximized() {
return (
window.screenX <= 0 &&
window.screenY <= 0 &&
window.outerWidth >= screen.availWidth &&
window.outerHeight >= screen.availHeight &&
window.screenX > -10 &&
window.screenY > -10 &&
window.outerWidth < screen.availWidth + 10 &&
window.outerHeight < screen.availHeight + 10
);
}
function toggleContextMenuDelete(event) {
if (chrome.contextMenus && event.button === 2 && prefs.get('editor.contextDelete')) {
chrome.contextMenus.update('editor.contextDelete', {
enabled: Boolean(
this.selectionStart !== this.selectionEnd ||
this.somethingSelected && this.somethingSelected()
),
}, ignoreChromeError);
}
}