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
|
tWordBreak: false
|
||||||
# dom.js
|
# dom.js
|
||||||
onDOMready: false
|
onDOMready: false
|
||||||
|
onDOMscriptReady: false
|
||||||
scrollElementIntoView: false
|
scrollElementIntoView: false
|
||||||
enforceInputRange: false
|
enforceInputRange: false
|
||||||
animateElement: false
|
animateElement: false
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<script src="edit/beautify.js"></script>
|
<script src="edit/beautify.js"></script>
|
||||||
<script src="edit/sections.js"></script>
|
<script src="edit/sections.js"></script>
|
||||||
<script src="edit/show-keymap-help.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="edit/edit.js"></script>
|
||||||
|
|
||||||
<script src="vendor/codemirror/lib/codemirror.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/emacs.js"></script>
|
||||||
<script src="vendor/codemirror/keymap/vim.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/match-highlighter-helper.js"></script>
|
||||||
<script src="edit/codemirror-editing-hooks.js"></script>
|
|
||||||
<script src="edit/codemirror-default.js"></script>
|
<script src="edit/codemirror-default.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/edit/codemirror-default.css">
|
<link rel="stylesheet" href="/edit/codemirror-default.css">
|
||||||
|
|
|
@ -25,12 +25,12 @@
|
||||||
styleActiveLine: true,
|
styleActiveLine: true,
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
keyMap: prefs.get('editor.keyMap'),
|
keyMap: prefs.get('editor.keyMap'),
|
||||||
extraKeys: {
|
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
|
||||||
// independent of current keyMap
|
// independent of current keyMap
|
||||||
'Alt-Enter': 'toggleStyle',
|
'Alt-Enter': 'toggleStyle',
|
||||||
'Alt-PageDown': 'nextEditor',
|
'Alt-PageDown': 'nextEditor',
|
||||||
'Alt-PageUp': 'prevEditor'
|
'Alt-PageUp': 'prevEditor'
|
||||||
},
|
}),
|
||||||
maxHighlightLength: 100e3,
|
maxHighlightLength: 100e3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
addEventListener('init:allDone', function _() {
|
onDOMscriptReady('/codemirror.js').then(() => {
|
||||||
removeEventListener('init:allDone', _);
|
|
||||||
|
|
||||||
CodeMirror.defaults.lint = linterConfig.getForCodeMirror();
|
CodeMirror.defaults.lint = linterConfig.getForCodeMirror();
|
||||||
|
|
||||||
|
@ -48,11 +47,16 @@ addEventListener('init:allDone', function _() {
|
||||||
// cm.state.search for last used 'find'
|
// cm.state.search for last used 'find'
|
||||||
let searchState;
|
let searchState;
|
||||||
|
|
||||||
// N.B. the event listener should be registered before setupLivePrefs()
|
onDOMready().then(() => {
|
||||||
$('#options').addEventListener('change', onOptionElementChanged);
|
prefs.subscribe(['editor.keyMap'], showKeyInSaveButtonTooltip);
|
||||||
buildOptionsElements();
|
showKeyInSaveButtonTooltip();
|
||||||
setupLivePrefs();
|
|
||||||
rerouteHotkeys(true);
|
// N.B. the event listener should be registered before setupLivePrefs()
|
||||||
|
$('#options').addEventListener('change', onOptionElementChanged);
|
||||||
|
buildOptionsElements();
|
||||||
|
|
||||||
|
rerouteHotkeys(true);
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
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 */
|
/* global CodeMirror loadScript editors showHelp */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
onDOMscriptReady('/colorview.js').then(() => {
|
||||||
var initColorpicker = () => {
|
|
||||||
initOverlayHooks();
|
initOverlayHooks();
|
||||||
onDOMready().then(() => {
|
onDOMready().then(() => {
|
||||||
$('#colorpicker-settings').onclick = configureColorpicker;
|
$('#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.hotkey'], registerHotkey);
|
||||||
prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand);
|
prefs.subscribe(['editor.colorpicker'], setColorpickerOption);
|
||||||
return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true);
|
setColorpickerOption(null, prefs.get('editor.colorpicker'));
|
||||||
|
|
||||||
function colorpickerOnDemand(id, enabled) {
|
|
||||||
return loadScript(enabled && scripts)
|
|
||||||
.then(() => setColorpickerOption(id, enabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
function setColorpickerOption(id, enabled) {
|
function setColorpickerOption(id, enabled) {
|
||||||
const defaults = CodeMirror.defaults;
|
const defaults = CodeMirror.defaults;
|
||||||
const keyName = prefs.get('editor.colorpicker.hotkey');
|
const keyName = prefs.get('editor.colorpicker.hotkey');
|
||||||
delete defaults.extraKeys[keyName];
|
|
||||||
defaults.colorpicker = enabled;
|
defaults.colorpicker = enabled;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (keyName) {
|
if (keyName) {
|
||||||
CodeMirror.commands.colorpicker = invokeColorpicker;
|
CodeMirror.commands.colorpicker = invokeColorpicker;
|
||||||
|
defaults.extraKeys = defaults.extraKeys || {};
|
||||||
defaults.extraKeys[keyName] = 'colorpicker';
|
defaults.extraKeys[keyName] = 'colorpicker';
|
||||||
}
|
}
|
||||||
defaults.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
|
// on page load runs before CodeMirror.setOption is defined
|
||||||
editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
|
editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
|
||||||
|
@ -162,4 +156,4 @@ var initColorpicker = () => {
|
||||||
return style;
|
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 CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter
|
||||||
global mozParser createSourceEditor
|
global mozParser createSourceEditor
|
||||||
global closeCurrentTab regExpTester messageBox
|
global closeCurrentTab regExpTester messageBox
|
||||||
global initColorpicker
|
|
||||||
global initCollapsibles
|
|
||||||
global setupCodeMirror
|
global setupCodeMirror
|
||||||
global beautify
|
global beautify
|
||||||
global initWithSectionStyle addSections removeSection getSectionsHashes
|
global initWithSectionStyle addSections removeSection getSectionsHashes
|
||||||
|
@ -17,8 +15,6 @@ let dirty = {};
|
||||||
// array of all CodeMirror instances
|
// array of all CodeMirror instances
|
||||||
const editors = [];
|
const editors = [];
|
||||||
let saveSizeOnClose;
|
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
|
// direct & reverse mapping of @-moz-document keywords and internal property names
|
||||||
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
|
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
|
||||||
|
@ -35,14 +31,25 @@ Promise.all([
|
||||||
initStyleData(),
|
initStyleData(),
|
||||||
onDOMready(),
|
onDOMready(),
|
||||||
])
|
])
|
||||||
.then(([style]) => Promise.all([
|
.then(([style]) => {
|
||||||
style,
|
setupLivePrefs();
|
||||||
initColorpicker(),
|
|
||||||
initCollapsibles(),
|
const usercss = isUsercss(style);
|
||||||
initHooksCommon(),
|
$('#heading').textContent = t(styleId ? 'editStyleHeading' : 'addStyleTitle');
|
||||||
dispatchEvent(new Event('init:allDone')),
|
$('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
|
||||||
]))
|
$('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
|
||||||
.then(createEditor);
|
|
||||||
|
$('#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() {
|
function preinit() {
|
||||||
// make querySelectorAll enumeration code readable
|
// make querySelectorAll enumeration code readable
|
||||||
|
@ -103,7 +110,18 @@ function preinit() {
|
||||||
|
|
||||||
getOwnTab().then(tab => {
|
getOwnTab().then(tab => {
|
||||||
const ownTabId = tab.id;
|
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) {
|
if (!chrome.windows) {
|
||||||
return;
|
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) {
|
function onRuntimeMessage(request) {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
|
@ -270,40 +274,6 @@ function initHooks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// common for usercss and classic
|
// 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) {
|
function onChange(event) {
|
||||||
const node = event.target;
|
const node = event.target;
|
||||||
|
|
|
@ -55,6 +55,7 @@ $$.remove = (selector, base = document) => {
|
||||||
|
|
||||||
onDOMready().then(() => {
|
onDOMready().then(() => {
|
||||||
$.remove('#firefox-transitions-bug-suppressor');
|
$.remove('#firefox-transitions-bug-suppressor');
|
||||||
|
initCollapsibles();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!chrome.app && chrome.windows) {
|
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'} = {}) {
|
function initCollapsibles({bindClickOn = 'h2'} = {}) {
|
||||||
const prefMap = {};
|
const prefMap = {};
|
||||||
const elements = $$('details[data-pref]');
|
const elements = $$('details[data-pref]');
|
||||||
|
if (!elements.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const el of elements) {
|
for (const el of elements) {
|
||||||
const key = el.dataset.pref;
|
const key = el.dataset.pref;
|
||||||
|
|
|
@ -44,3 +44,60 @@ var loadScript = (() => {
|
||||||
return Promise.all(files.map(f => (typeof f === 'string' ? inject(f) : f)));
|
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 checkUpdate, handleUpdateInstalled */
|
||||||
/* global objectDiff */
|
/* global objectDiff */
|
||||||
/* global configDialog */
|
/* global configDialog */
|
||||||
/* global initCollapsibles */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
let installed;
|
let installed;
|
||||||
|
@ -83,9 +82,6 @@ function initGlobalEvents() {
|
||||||
// N.B. triggers existing onchange listeners
|
// N.B. triggers existing onchange listeners
|
||||||
setupLivePrefs();
|
setupLivePrefs();
|
||||||
|
|
||||||
// the options block
|
|
||||||
initCollapsibles();
|
|
||||||
|
|
||||||
$$('[id^="manage.newUI"]')
|
$$('[id^="manage.newUI"]')
|
||||||
.forEach(el => (el.oninput = (el.onchange = switchUI)));
|
.forEach(el => (el.oninput = (el.onchange = switchUI)));
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,8 @@
|
||||||
if (!mx || mx.token !== colorizeToken) {
|
if (!mx || mx.token !== colorizeToken) {
|
||||||
CodeMirror.extendMode('css', {token: colorizeToken});
|
CodeMirror.extendMode('css', {token: colorizeToken});
|
||||||
CodeMirror.extendMode('stylus', {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();
|
cm.state.colorpicker.destroy();
|
||||||
}
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
|
registerHooks();
|
||||||
cm.state.colorpicker = new ColorMarker(cm, value);
|
cm.state.colorpicker = new ColorMarker(cm, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// initial runMode is performed by CodeMirror before setting our option
|
// 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();
|
registerHooks();
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user