initialize editor page fully in First Meaningful Paint frame
* previously it wasn't the case when colorpicker option was enabled * the cost of always loading colorview is ~1ms for >200ms here
This commit is contained in:
parent
0413736a29
commit
1c68ac1a3a
|
@ -51,6 +51,7 @@ globals:
|
|||
tWordBreak: false
|
||||
# dom.js
|
||||
onDOMready: false
|
||||
onDOMscriptReady: false
|
||||
scrollElementIntoView: false
|
||||
enforceInputRange: false
|
||||
animateElement: false
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
<script src="edit/beautify.js"></script>
|
||||
<script src="edit/sections.js"></script>
|
||||
<script src="edit/show-keymap-help.js"></script>
|
||||
<script src="edit/codemirror-editing-hooks.js"></script>
|
||||
<script src="edit/edit.js"></script>
|
||||
|
||||
<script src="vendor/codemirror/lib/codemirror.js"></script>
|
||||
|
@ -67,8 +68,11 @@
|
|||
<script src="vendor/codemirror/keymap/emacs.js"></script>
|
||||
<script src="vendor/codemirror/keymap/vim.js"></script>
|
||||
|
||||
<link href="/vendor-overwrites/colorpicker/colorpicker.css" rel="stylesheet">
|
||||
<script src="/vendor-overwrites/colorpicker/colorpicker.js"></script>
|
||||
<script src="/vendor-overwrites/colorpicker/colorview.js"></script>
|
||||
|
||||
<script src="edit/match-highlighter-helper.js"></script>
|
||||
<script src="edit/codemirror-editing-hooks.js"></script>
|
||||
<script src="edit/codemirror-default.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/edit/codemirror-default.css">
|
||||
|
|
|
@ -25,12 +25,12 @@
|
|||
styleActiveLine: true,
|
||||
theme: 'default',
|
||||
keyMap: prefs.get('editor.keyMap'),
|
||||
extraKeys: {
|
||||
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
|
||||
// independent of current keyMap
|
||||
'Alt-Enter': 'toggleStyle',
|
||||
'Alt-PageDown': 'nextEditor',
|
||||
'Alt-PageUp': 'prevEditor'
|
||||
},
|
||||
}),
|
||||
maxHighlightLength: 100e3,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@ global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
addEventListener('init:allDone', function _() {
|
||||
removeEventListener('init:allDone', _);
|
||||
onDOMscriptReady('/codemirror.js').then(() => {
|
||||
|
||||
CodeMirror.defaults.lint = linterConfig.getForCodeMirror();
|
||||
|
||||
|
@ -48,11 +47,16 @@ addEventListener('init:allDone', function _() {
|
|||
// cm.state.search for last used 'find'
|
||||
let searchState;
|
||||
|
||||
onDOMready().then(() => {
|
||||
prefs.subscribe(['editor.keyMap'], showKeyInSaveButtonTooltip);
|
||||
showKeyInSaveButtonTooltip();
|
||||
|
||||
// N.B. the event listener should be registered before setupLivePrefs()
|
||||
$('#options').addEventListener('change', onOptionElementChanged);
|
||||
buildOptionsElements();
|
||||
setupLivePrefs();
|
||||
|
||||
rerouteHotkeys(true);
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
|
@ -679,4 +683,23 @@ addEventListener('init:allDone', function _() {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 '';
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,34 +1,23 @@
|
|||
/* global CodeMirror loadScript editors showHelp */
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var initColorpicker = () => {
|
||||
onDOMscriptReady('/colorview.js').then(() => {
|
||||
initOverlayHooks();
|
||||
onDOMready().then(() => {
|
||||
$('#colorpicker-settings').onclick = configureColorpicker;
|
||||
});
|
||||
const scripts = [
|
||||
'/vendor-overwrites/colorpicker/colorpicker.css',
|
||||
'/vendor-overwrites/colorpicker/colorpicker.js',
|
||||
'/vendor-overwrites/colorpicker/colorview.js',
|
||||
];
|
||||
prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey);
|
||||
prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand);
|
||||
return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true);
|
||||
|
||||
function colorpickerOnDemand(id, enabled) {
|
||||
return loadScript(enabled && scripts)
|
||||
.then(() => setColorpickerOption(id, enabled));
|
||||
}
|
||||
prefs.subscribe(['editor.colorpicker'], setColorpickerOption);
|
||||
setColorpickerOption(null, prefs.get('editor.colorpicker'));
|
||||
|
||||
function setColorpickerOption(id, enabled) {
|
||||
const defaults = CodeMirror.defaults;
|
||||
const keyName = prefs.get('editor.colorpicker.hotkey');
|
||||
delete defaults.extraKeys[keyName];
|
||||
defaults.colorpicker = enabled;
|
||||
if (enabled) {
|
||||
if (keyName) {
|
||||
CodeMirror.commands.colorpicker = invokeColorpicker;
|
||||
defaults.extraKeys = defaults.extraKeys || {};
|
||||
defaults.extraKeys[keyName] = 'colorpicker';
|
||||
}
|
||||
defaults.colorpicker = {
|
||||
|
@ -45,6 +34,11 @@ var initColorpicker = () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
CodeMirror.modeExtensions.css.unregisterColorviewHooks();
|
||||
if (defaults.extraKeys) {
|
||||
delete defaults.extraKeys[keyName];
|
||||
}
|
||||
}
|
||||
// on page load runs before CodeMirror.setOption is defined
|
||||
editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
|
||||
|
@ -162,4 +156,4 @@ var initColorpicker = () => {
|
|||
return style;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
92
edit/edit.js
92
edit/edit.js
|
@ -3,8 +3,6 @@ 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
|
||||
|
@ -17,8 +15,6 @@ let dirty = {};
|
|||
// array of all CodeMirror instances
|
||||
const editors = [];
|
||||
let saveSizeOnClose;
|
||||
// use browser history back when 'back to manage' is clicked
|
||||
let useHistoryBack;
|
||||
|
||||
// direct & reverse mapping of @-moz-document keywords and internal property names
|
||||
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
|
||||
|
@ -35,14 +31,25 @@ Promise.all([
|
|||
initStyleData(),
|
||||
onDOMready(),
|
||||
])
|
||||
.then(([style]) => Promise.all([
|
||||
style,
|
||||
initColorpicker(),
|
||||
initCollapsibles(),
|
||||
initHooksCommon(),
|
||||
dispatchEvent(new Event('init:allDone')),
|
||||
]))
|
||||
.then(createEditor);
|
||||
.then(([style]) => {
|
||||
setupLivePrefs();
|
||||
|
||||
const usercss = isUsercss(style);
|
||||
$('#heading').textContent = t(styleId ? 'editStyleHeading' : 'addStyleTitle');
|
||||
$('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
|
||||
$('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
|
||||
|
||||
$('#beautify').onclick = beautify;
|
||||
$('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
|
||||
window.addEventListener('resize', () => debounce(rememberWindowSize, 100));
|
||||
|
||||
if (usercss) {
|
||||
editor = createSourceEditor(style);
|
||||
} else {
|
||||
initWithSectionStyle({style});
|
||||
document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
|
||||
}
|
||||
});
|
||||
|
||||
function preinit() {
|
||||
// make querySelectorAll enumeration code readable
|
||||
|
@ -103,7 +110,18 @@ function preinit() {
|
|||
|
||||
getOwnTab().then(tab => {
|
||||
const ownTabId = tab.id;
|
||||
useHistoryBack = sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href;
|
||||
|
||||
// use browser history back when 'back to manage' is clicked
|
||||
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
||||
onDOMready().then(() => {
|
||||
$('#cancel-button').onclick = event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
history.back();
|
||||
};
|
||||
});
|
||||
}
|
||||
// no windows on android
|
||||
if (!chrome.windows) {
|
||||
return;
|
||||
}
|
||||
|
@ -130,20 +148,6 @@ function preinit() {
|
|||
});
|
||||
}
|
||||
|
||||
function createEditor([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 onRuntimeMessage(request) {
|
||||
switch (request.method) {
|
||||
case 'styleUpdated':
|
||||
|
@ -270,40 +274,6 @@ function initHooks() {
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -55,6 +55,7 @@ $$.remove = (selector, base = document) => {
|
|||
|
||||
onDOMready().then(() => {
|
||||
$.remove('#firefox-transitions-bug-suppressor');
|
||||
initCollapsibles();
|
||||
});
|
||||
|
||||
if (!chrome.app && chrome.windows) {
|
||||
|
@ -278,9 +279,13 @@ function $createLink(href = '', content) {
|
|||
}
|
||||
|
||||
|
||||
// makes <details> with [data-pref] save/restore their state
|
||||
function initCollapsibles({bindClickOn = 'h2'} = {}) {
|
||||
const prefMap = {};
|
||||
const elements = $$('details[data-pref]');
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const el of elements) {
|
||||
const key = el.dataset.pref;
|
||||
|
|
|
@ -44,3 +44,60 @@ var loadScript = (() => {
|
|||
return Promise.all(files.map(f => (typeof f === 'string' ? inject(f) : f)));
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
(() => {
|
||||
let subscribers, observer;
|
||||
// natively declared <script> elements in html can't have onload= attribute
|
||||
// due to the default extension CSP that forbids inline code (and we don't want to relax it),
|
||||
// so we're using MutationObserver to add onload event listener to the script element to be loaded
|
||||
window.onDOMscriptReady = (src, timeout = 1000) => {
|
||||
if (!subscribers) {
|
||||
subscribers = new Map();
|
||||
observer = new MutationObserver(observe);
|
||||
observer.observe(document.head, {childList: true});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const listeners = subscribers.get(src);
|
||||
if (listeners) {
|
||||
listeners.push(resolve);
|
||||
} else {
|
||||
subscribers.set(src, [resolve]);
|
||||
}
|
||||
// no need to clear the timer since a resolved Promise won't reject anymore
|
||||
setTimeout(reject, timeout);
|
||||
});
|
||||
};
|
||||
|
||||
return;
|
||||
|
||||
function observe(mutations) {
|
||||
for (const {addedNodes} of mutations) {
|
||||
for (const n of addedNodes) {
|
||||
if (n.src && getSubscribersForSrc(n.src)) {
|
||||
n.addEventListener('load', notifySubscribers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSubscribersForSrc(src) {
|
||||
for (const [subscribedSrc, listeners] of subscribers.entries()) {
|
||||
if (src.endsWith(subscribedSrc)) {
|
||||
return {subscribedSrc, listeners};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function notifySubscribers(event) {
|
||||
this.removeEventListener('load', notifySubscribers);
|
||||
const {subscribedSrc, listeners = []} = getSubscribersForSrc(this.src) || {};
|
||||
listeners.forEach(fn => fn(event));
|
||||
subscribers.delete(subscribedSrc);
|
||||
if (!subscribers.size) {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
subscribers = null;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/* global checkUpdate, handleUpdateInstalled */
|
||||
/* global objectDiff */
|
||||
/* global configDialog */
|
||||
/* global initCollapsibles */
|
||||
'use strict';
|
||||
|
||||
let installed;
|
||||
|
@ -83,9 +82,6 @@ function initGlobalEvents() {
|
|||
// N.B. triggers existing onchange listeners
|
||||
setupLivePrefs();
|
||||
|
||||
// the options block
|
||||
initCollapsibles();
|
||||
|
||||
$$('[id^="manage.newUI"]')
|
||||
.forEach(el => (el.oninput = (el.onchange = switchUI)));
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@
|
|||
if (!mx || mx.token !== colorizeToken) {
|
||||
CodeMirror.extendMode('css', {token: colorizeToken});
|
||||
CodeMirror.extendMode('stylus', {token: colorizeToken});
|
||||
CodeMirror.modeExtensions.css.registerColorviewHooks = registerHooks;
|
||||
CodeMirror.modeExtensions.css.unregisterColorviewHooks = unregisterHooks;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,11 +500,12 @@
|
|||
cm.state.colorpicker.destroy();
|
||||
}
|
||||
if (value) {
|
||||
registerHooks();
|
||||
cm.state.colorpicker = new ColorMarker(cm, value);
|
||||
}
|
||||
});
|
||||
|
||||
// initial runMode is performed by CodeMirror before setting our option
|
||||
// so we register the hooks right away - not a problem as our js is loaded on demand
|
||||
// so we register the hooks right away (the cost of always loading colorview is ~1ms for >200ms)
|
||||
registerHooks();
|
||||
})();
|
||||
|
|
Loading…
Reference in New Issue
Block a user