From 1c68ac1a3a658397725b5c6afef0485ab8522b9a Mon Sep 17 00:00:00 2001 From: tophf Date: Fri, 8 Dec 2017 05:45:27 +0300 Subject: [PATCH] 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 --- .eslintrc | 1 + edit.html | 6 +- edit/codemirror-default.js | 4 +- edit/codemirror-editing-hooks.js | 37 +++++++-- edit/colorpicker-helper.js | 26 +++--- edit/edit.js | 92 ++++++++-------------- js/dom.js | 5 ++ js/script-loader.js | 57 ++++++++++++++ manage/manage.js | 4 - vendor-overwrites/colorpicker/colorview.js | 5 +- 10 files changed, 145 insertions(+), 92 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0d866fff..bcfa68e3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -51,6 +51,7 @@ globals: tWordBreak: false # dom.js onDOMready: false + onDOMscriptReady: false scrollElementIntoView: false enforceInputRange: false animateElement: false diff --git a/edit.html b/edit.html index c1966108..476c571d 100644 --- a/edit.html +++ b/edit.html @@ -33,6 +33,7 @@ + @@ -67,8 +68,11 @@ + + + + - diff --git a/edit/codemirror-default.js b/edit/codemirror-default.js index e68d9f35..3a619cc3 100644 --- a/edit/codemirror-default.js +++ b/edit/codemirror-default.js @@ -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, }; diff --git a/edit/codemirror-editing-hooks.js b/edit/codemirror-editing-hooks.js index 7cb66a69..5f2d16c0 100644 --- a/edit/codemirror-editing-hooks.js +++ b/edit/codemirror-editing-hooks.js @@ -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; - // N.B. the event listener should be registered before setupLivePrefs() - $('#options').addEventListener('change', onOptionElementChanged); - buildOptionsElements(); - setupLivePrefs(); - rerouteHotkeys(true); + onDOMready().then(() => { + prefs.subscribe(['editor.keyMap'], showKeyInSaveButtonTooltip); + showKeyInSaveButtonTooltip(); + + // N.B. the event listener should be registered before setupLivePrefs() + $('#options').addEventListener('change', onOptionElementChanged); + buildOptionsElements(); + + 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 ''; + } }); diff --git a/edit/colorpicker-helper.js b/edit/colorpicker-helper.js index ef53452a..03714092 100644 --- a/edit/colorpicker-helper.js +++ b/edit/colorpicker-helper.js @@ -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; } } -}; +}); diff --git a/edit/edit.js b/edit/edit.js index a050e2fe..873841a3 100644 --- a/edit/edit.js +++ b/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; diff --git a/js/dom.js b/js/dom.js index c9a6246f..fc0abbbe 100644 --- a/js/dom.js +++ b/js/dom.js @@ -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
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; diff --git a/js/script-loader.js b/js/script-loader.js index 35d0ecde..8352bb38 100644 --- a/js/script-loader.js +++ b/js/script-loader.js @@ -44,3 +44,60 @@ var loadScript = (() => { return Promise.all(files.map(f => (typeof f === 'string' ? inject(f) : f))); }; })(); + + +(() => { + let subscribers, observer; + // natively declared