faster popup load to avoid resize flicker
This commit is contained in:
parent
51f125113d
commit
4d198f56e2
38
popup.html
38
popup.html
|
@ -16,12 +16,6 @@
|
||||||
}
|
}
|
||||||
</style>
|
</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">
|
<template data-id="style">
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<div class="entry-content">
|
<div class="entry-content">
|
||||||
|
@ -178,33 +172,35 @@
|
||||||
<div class="search-result-empty"></div>
|
<div class="search-result-empty"></div>
|
||||||
</template>
|
</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/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/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="js/prefs.js"></script>
|
||||||
<script src="content/style-injector.js"></script>
|
<script src="content/style-injector.js"></script>
|
||||||
<script src="content/apply.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/popup.css">
|
||||||
<link rel="stylesheet" href="popup/search-results.css">
|
|
||||||
<script src="popup/popup.js"></script>
|
<script src="popup/popup.js"></script>
|
||||||
<script src="popup/search-results.js"></script>
|
<script src="popup/search-results.js"></script>
|
||||||
<script src="popup/hotkeys.js"></script>
|
<script src="popup/hotkeys.js"></script>
|
||||||
|
|
||||||
<script src="js/script-loader.js" async></script>
|
<script src="js/script-loader.js" async></script>
|
||||||
<script src="js/storage-util.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>
|
</head>
|
||||||
|
|
||||||
<body id="stylus-popup">
|
<body id="stylus-popup">
|
||||||
|
|
87
popup/popup-preinit.js
Normal file
87
popup/popup-preinit.js
Normal 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;
|
||||||
|
}
|
115
popup/popup.js
115
popup/popup.js
|
@ -3,18 +3,22 @@
|
||||||
$$
|
$$
|
||||||
$create
|
$create
|
||||||
animateElement
|
animateElement
|
||||||
|
ABOUT_BLANK
|
||||||
API
|
API
|
||||||
CHROME
|
CHROME
|
||||||
CHROME_HAS_BORDER_BUG
|
CHROME_HAS_BORDER_BUG
|
||||||
configDialog
|
configDialog
|
||||||
FIREFOX
|
FIREFOX
|
||||||
getActiveTab
|
getActiveTab
|
||||||
|
getStyleDataMerged
|
||||||
hotkeys
|
hotkeys
|
||||||
|
initializing
|
||||||
msg
|
msg
|
||||||
onDOMready
|
onDOMready
|
||||||
prefs
|
prefs
|
||||||
setupLivePrefs
|
setupLivePrefs
|
||||||
t
|
t
|
||||||
|
tabURL
|
||||||
tryJSONparse
|
tryJSONparse
|
||||||
URLS
|
URLS
|
||||||
*/
|
*/
|
||||||
|
@ -23,11 +27,8 @@
|
||||||
|
|
||||||
/** @type Element */
|
/** @type Element */
|
||||||
let installed;
|
let installed;
|
||||||
/** @type string */
|
|
||||||
let tabURL;
|
|
||||||
const handleEvent = {};
|
const handleEvent = {};
|
||||||
|
|
||||||
const ABOUT_BLANK = 'about:blank';
|
|
||||||
const ENTRY_ID_PREFIX_RAW = 'style-';
|
const ENTRY_ID_PREFIX_RAW = 'style-';
|
||||||
|
|
||||||
$.entry = styleOrId => $(`#${ENTRY_ID_PREFIX_RAW}${styleOrId.id || styleOrId}`);
|
$.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();
|
toggleSideBorders();
|
||||||
|
|
||||||
initTabUrls()
|
|
||||||
.then(frames =>
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
onDOMready().then(() => initPopup(frames)),
|
initializing,
|
||||||
...frames
|
onDOMready(),
|
||||||
.filter(f => f.url && !f.isDupe)
|
]).then(([
|
||||||
.map(({url}) => getStyleDataMerged(url).then(styles => ({styles, url}))),
|
{frames, styles},
|
||||||
]))
|
]) => {
|
||||||
.then(([, ...results]) => {
|
toggleUiSliders();
|
||||||
const sliders = prefs.get('ui.sliders');
|
initPopup(frames);
|
||||||
const slot = $('toggle', t.template.style);
|
if (styles[0]) {
|
||||||
const toggle = t.template[sliders ? 'toggleSlider' : 'toggleChecker'];
|
showStyles(styles);
|
||||||
slot.parentElement.replaceChild(toggle.cloneNode(true), slot);
|
|
||||||
document.body.classList.toggle('has-sliders', sliders);
|
|
||||||
if (results[0]) {
|
|
||||||
showStyles(results);
|
|
||||||
} else {
|
} else {
|
||||||
// unsupported URL;
|
// unsupported URL;
|
||||||
$('#popup-manage-button').removeAttribute('title');
|
$('#popup-manage-button').removeAttribute('title');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.catch(console.error);
|
|
||||||
|
|
||||||
msg.onExtension(onRuntimeMessage);
|
msg.onExtension(onRuntimeMessage);
|
||||||
|
|
||||||
|
@ -109,23 +103,12 @@ function toggleSideBorders(state = prefs.get('popup.borders')) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initTabUrls() {
|
function toggleUiSliders() {
|
||||||
let tab = await getActiveTab();
|
const sliders = prefs.get('ui.sliders');
|
||||||
if (FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK) {
|
const slot = $('toggle', t.template.style);
|
||||||
tab = await waitForTabUrlFF(tab);
|
const toggle = t.template[sliders ? 'toggleSlider' : 'toggleChecker'];
|
||||||
}
|
slot.parentElement.replaceChild(toggle.cloneNode(true), slot);
|
||||||
let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
|
document.body.classList.toggle('has-sliders', sliders);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
|
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
|
||||||
|
@ -158,6 +141,10 @@ async function initPopup(frames) {
|
||||||
installed);
|
installed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const el of $$('link[media=print]')) {
|
||||||
|
el.removeAttribute('media');
|
||||||
|
}
|
||||||
|
|
||||||
if (!tabURL) {
|
if (!tabURL) {
|
||||||
blockPopup();
|
blockPopup();
|
||||||
return;
|
return;
|
||||||
|
@ -188,6 +175,11 @@ async function initPopup(frames) {
|
||||||
// so we'll wait a bit to handle popup being invoked right after switching
|
// so we'll wait a bit to handle popup being invoked right after switching
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initUnreachable(isStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initUnreachable(isStore) {
|
||||||
const info = t.template.unreachableInfo;
|
const info = t.template.unreachableInfo;
|
||||||
if (!FIREFOX) {
|
if (!FIREFOX) {
|
||||||
// Chrome "Allow access to file URLs" in chrome://extensions message
|
// Chrome "Allow access to file URLs" in chrome://extensions message
|
||||||
|
@ -294,32 +286,6 @@ function getDomains(url) {
|
||||||
return domains;
|
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) {
|
function sortStyles(entries) {
|
||||||
const enabledFirst = prefs.get('popup.enabledFirst');
|
const enabledFirst = prefs.get('popup.enabledFirst');
|
||||||
return entries.sort(({styleMeta: a}, {styleMeta: b}) =>
|
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) {
|
function blockPopup(isBlocked = true) {
|
||||||
document.body.classList.toggle('blocked', isBlocked);
|
document.body.classList.toggle('blocked', isBlocked);
|
||||||
if (isBlocked) {
|
if (isBlocked) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user