faster popup load to avoid resize flicker

This commit is contained in:
tophf 2020-11-22 22:41:36 +03:00
parent 51f125113d
commit 4d198f56e2
3 changed files with 138 additions and 112 deletions

View File

@ -16,12 +16,6 @@
}
</style>
<!-- Notes:
* Chrome doesn't garbage-collect (or even leaks) SVG <symbol> referenced via <use> so we'll embed the code directly
* inter-tag whitespace in templates is automatically removed in localization.js
* i18n-anything attribute automatically creates "anything" attribute
-->
<template data-id="style">
<div class="entry">
<div class="entry-content">
@ -178,33 +172,35 @@
<div class="search-result-empty"></div>
</template>
<link rel="stylesheet" href="vendor-overwrites/colorpicker/colorpicker.css">
<script src="vendor-overwrites/colorpicker/colorconverter.js"></script>
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
<link rel="stylesheet" href="msgbox/msgbox.css">
<script src="msgbox/msgbox.js"></script>
<link rel="stylesheet" href="options/onoffswitch.css">
<link rel="stylesheet" href="manage/config-dialog.css">
<script src="manage/config-dialog.js"></script>
<script src="js/polyfill.js"></script>
<script src="js/dom.js"></script>
<script src="js/messaging.js"></script>
<script src="js/localization.js"></script>
<script src="js/msg.js"></script>
<script src="js/messaging.js"></script>
<script src="popup/popup-preinit.js"></script>
<script src="js/dom.js"></script>
<script src="js/localization.js"></script>
<script src="js/prefs.js"></script>
<script src="content/style-injector.js"></script>
<script src="content/apply.js"></script>
<!-- async hack to load auxiliary stylesheets later so they don't delay the UI -->
<link rel="stylesheet" href="vendor-overwrites/colorpicker/colorpicker.css" media="print">
<link rel="stylesheet" href="msgbox/msgbox.css" media="print">
<link rel="stylesheet" href="manage/config-dialog.css" media="print">
<link rel="stylesheet" href="popup/search-results.css" media="print">
<link rel="stylesheet" href="options/onoffswitch.css">
<link rel="stylesheet" href="popup/popup.css">
<link rel="stylesheet" href="popup/search-results.css">
<script src="popup/popup.js"></script>
<script src="popup/search-results.js"></script>
<script src="popup/hotkeys.js"></script>
<script src="js/script-loader.js" async></script>
<script src="js/storage-util.js" async></script>
<script src="vendor-overwrites/colorpicker/colorconverter.js" async></script>
<script src="vendor-overwrites/colorpicker/colorpicker.js" async></script>
<script src="msgbox/msgbox.js" async></script>
<script src="manage/config-dialog.js" async></script>
</head>
<body id="stylus-popup">

87
popup/popup-preinit.js Normal file
View File

@ -0,0 +1,87 @@
/* global
API
URLS
*/
'use strict';
const ABOUT_BLANK = 'about:blank';
/* exported tabURL */
/** @type string */
let tabURL;
/* exported initializing */
const initializing = (async () => {
let [tab] = await browser.tabs.query({currentWindow: true, active: true});
if (!chrome.app && tab.status === 'loading' && tab.url === 'about:blank') {
tab = await waitForTabUrlFF(tab);
}
const frames = sortTabFrames(await browser.webNavigation.getAllFrames({tabId: tab.id}));
let url = tab.pendingUrl || tab.url || ''; // new Chrome uses pendingUrl while connecting
if (url === 'chrome://newtab/' && !URLS.chromeProtectsNTP) {
url = frames[0].url || '';
}
if (!URLS.supported(url)) {
url = '';
frames.length = 1;
}
tabURL = frames[0].url = url;
const uniqFrames = frames.filter(f => f.url && !f.isDupe);
const styles = await Promise.all(uniqFrames.map(getFrameStyles));
return {frames, styles};
async function getFrameStyles({url}) {
return {
url,
styles: await getStyleDataMerged(url),
};
}
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
function sortTabFrames(frames) {
const unknown = new Map(frames.map(f => [f.frameId, f]));
const known = new Map([[0, unknown.get(0) || {frameId: 0, url: ''}]]);
unknown.delete(0);
let lastSize = 0;
while (unknown.size !== lastSize) {
for (const [frameId, f] of unknown) {
if (known.has(f.parentFrameId)) {
unknown.delete(frameId);
if (!f.errorOccurred) known.set(frameId, f);
if (f.url === ABOUT_BLANK) f.url = known.get(f.parentFrameId).url;
}
}
lastSize = unknown.size; // guard against an infinite loop due to a weird frame structure
}
const sortedFrames = [...known.values(), ...unknown.values()];
const urls = new Set([ABOUT_BLANK]);
for (const f of sortedFrames) {
if (!f.url) f.url = '';
f.isDupe = urls.has(f.url);
urls.add(f.url);
}
return sortedFrames;
}
function waitForTabUrlFF(tab) {
return new Promise(resolve => {
browser.tabs.onUpdated.addListener(...[
function onUpdated(tabId, info, updatedTab) {
if (info.url && tabId === tab.id) {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve(updatedTab);
}
},
...'UpdateFilter' in browser.tabs ? [{tabId: tab.id}] : [],
// TODO: remove both spreads and tabId check when strict_min_version >= 61
]);
});
}
})();
/* Merges the extra props from API into style data.
* When `id` is specified returns a single object otherwise an array */
async function getStyleDataMerged(url, id) {
const styles = (await API.getStylesByUrl(url, id))
.map(r => Object.assign(r.data, r));
return id ? styles[0] : styles;
}

View File

@ -3,18 +3,22 @@
$$
$create
animateElement
ABOUT_BLANK
API
CHROME
CHROME_HAS_BORDER_BUG
configDialog
FIREFOX
getActiveTab
getStyleDataMerged
hotkeys
initializing
msg
onDOMready
prefs
setupLivePrefs
t
tabURL
tryJSONparse
URLS
*/
@ -23,11 +27,8 @@
/** @type Element */
let installed;
/** @type string */
let tabURL;
const handleEvent = {};
const ABOUT_BLANK = 'about:blank';
const ENTRY_ID_PREFIX_RAW = 'style-';
$.entry = styleOrId => $(`#${ENTRY_ID_PREFIX_RAW}${styleOrId.id || styleOrId}`);
@ -38,28 +39,21 @@ if (CHROME >= 66 && CHROME <= 69) { // Chrome 66-69 adds a gap, https://crbug.co
toggleSideBorders();
initTabUrls()
.then(frames =>
Promise.all([
onDOMready().then(() => initPopup(frames)),
...frames
.filter(f => f.url && !f.isDupe)
.map(({url}) => getStyleDataMerged(url).then(styles => ({styles, url}))),
]))
.then(([, ...results]) => {
const sliders = prefs.get('ui.sliders');
const slot = $('toggle', t.template.style);
const toggle = t.template[sliders ? 'toggleSlider' : 'toggleChecker'];
slot.parentElement.replaceChild(toggle.cloneNode(true), slot);
document.body.classList.toggle('has-sliders', sliders);
if (results[0]) {
showStyles(results);
initializing,
onDOMready(),
]).then(([
{frames, styles},
]) => {
toggleUiSliders();
initPopup(frames);
if (styles[0]) {
showStyles(styles);
} else {
// unsupported URL;
$('#popup-manage-button').removeAttribute('title');
}
})
.catch(console.error);
});
msg.onExtension(onRuntimeMessage);
@ -109,23 +103,12 @@ function toggleSideBorders(state = prefs.get('popup.borders')) {
}
}
async function initTabUrls() {
let tab = await getActiveTab();
if (FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK) {
tab = await waitForTabUrlFF(tab);
}
let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
let url = tab.pendingUrl || tab.url || ''; // new Chrome uses pendingUrl while connecting
frames = sortTabFrames(frames);
if (url === 'chrome://newtab/' && !URLS.chromeProtectsNTP) {
url = frames[0].url || '';
}
if (!URLS.supported(url)) {
url = '';
frames.length = 1;
}
tabURL = frames[0].url = url;
return frames;
function toggleUiSliders() {
const sliders = prefs.get('ui.sliders');
const slot = $('toggle', t.template.style);
const toggle = t.template[sliders ? 'toggleSlider' : 'toggleChecker'];
slot.parentElement.replaceChild(toggle.cloneNode(true), slot);
document.body.classList.toggle('has-sliders', sliders);
}
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
@ -158,6 +141,10 @@ async function initPopup(frames) {
installed);
}
for (const el of $$('link[media=print]')) {
el.removeAttribute('media');
}
if (!tabURL) {
blockPopup();
return;
@ -188,6 +175,11 @@ async function initPopup(frames) {
// so we'll wait a bit to handle popup being invoked right after switching
await new Promise(resolve => setTimeout(resolve, 100));
}
initUnreachable(isStore);
}
function initUnreachable(isStore) {
const info = t.template.unreachableInfo;
if (!FIREFOX) {
// Chrome "Allow access to file URLs" in chrome://extensions message
@ -294,32 +286,6 @@ function getDomains(url) {
return domains;
}
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
function sortTabFrames(frames) {
const unknown = new Map(frames.map(f => [f.frameId, f]));
const known = new Map([[0, unknown.get(0) || {frameId: 0, url: ''}]]);
unknown.delete(0);
let lastSize = 0;
while (unknown.size !== lastSize) {
for (const [frameId, f] of unknown) {
if (known.has(f.parentFrameId)) {
unknown.delete(frameId);
if (!f.errorOccurred) known.set(frameId, f);
if (f.url === ABOUT_BLANK) f.url = known.get(f.parentFrameId).url;
}
}
lastSize = unknown.size; // guard against an infinite loop due to a weird frame structure
}
const sortedFrames = [...known.values(), ...unknown.values()];
const urls = new Set([ABOUT_BLANK]);
for (const f of sortedFrames) {
if (!f.url) f.url = '';
f.isDupe = urls.has(f.url);
urls.add(f.url);
}
return sortedFrames;
}
function sortStyles(entries) {
const enabledFirst = prefs.get('popup.enabledFirst');
return entries.sort(({styleMeta: a}, {styleMeta: b}) =>
@ -697,29 +663,6 @@ function handleDelete(id) {
}
}
function waitForTabUrlFF(tab) {
return new Promise(resolve => {
browser.tabs.onUpdated.addListener(...[
function onUpdated(tabId, info, updatedTab) {
if (info.url && tabId === tab.id) {
chrome.tabs.onUpdated.removeListener(onUpdated);
resolve(updatedTab);
}
},
...'UpdateFilter' in browser.tabs ? [{tabId: tab.id}] : [],
// TODO: remove both spreads and tabId check when strict_min_version >= 61
]);
});
}
/* Merges the extra props from API into style data.
* When `id` is specified returns a single object otherwise an array */
async function getStyleDataMerged(url, id) {
const styles = (await API.getStylesByUrl(url, id))
.map(r => Object.assign(r.data, r));
return id ? styles[0] : styles;
}
function blockPopup(isBlocked = true) {
document.body.classList.toggle('blocked', isBlocked);
if (isBlocked) {