extract stuff from edit.js and load on demand

This commit is contained in:
tophf 2020-12-13 19:04:58 +03:00
parent 8d639b6140
commit e74a349a88
4 changed files with 199 additions and 164 deletions

View File

@ -3,19 +3,14 @@
define(require => {
const {API, msg} = require('/js/msg');
const {
FIREFOX,
closeCurrentTab,
debounce,
getOwnTab,
sessionStore,
} = require('/js/toolbox');
const {
$,
$$,
$create,
$remove,
getEventKeyName,
onDOMready,
setupLivePrefs,
} = require('/js/dom');
const t = require('/js/localization');
@ -26,14 +21,10 @@ define(require => {
const {CodeMirror, initBeautifyButton} = require('./codemirror-factory');
let headerHeight;
let isSimpleWindow;
let isWindowed;
window.on('beforeunload', beforeUnload);
msg.onExtension(onRuntimeMessage);
lazyInit();
(async function init() {
await preinit;
buildThemeElement();
@ -205,77 +196,6 @@ define(require => {
}
}
/* Stuff not needed for the main init so we can let it run at its own tempo */
function lazyInit() {
let ownTabId;
// not using `await` so we don't block the subsequent code
getOwnTab().then(patchHistoryBack);
// no windows on android
if (chrome.windows) {
detectWindowedState();
chrome.tabs.onAttached.addListener(onAttached);
}
async function patchHistoryBack(tab) {
ownTabId = tab.id;
// use browser history back when 'back to manage' is clicked
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
await onDOMready();
$('#cancel-button').onclick = event => {
event.stopPropagation();
event.preventDefault();
history.back();
};
}
}
async function detectWindowedState() {
isSimpleWindow =
(await browser.windows.getCurrent()).type === 'popup';
isWindowed = isSimpleWindow || (
prefs.get('openEditInWindow') &&
history.length === 1 &&
(await browser.windows.getAll()).length > 1 &&
(await browser.tabs.query({currentWindow: true})).length === 1
);
if (isSimpleWindow) {
await onDOMready();
initPopupButton();
}
}
function initPopupButton() {
const POPUP_HOTKEY = 'Shift-Ctrl-Alt-S';
const btn = $create('img', {
id: 'popup-button',
title: t('optionsCustomizePopup') + '\n' + POPUP_HOTKEY,
onclick: embedPopup,
});
const onIconsetChanged = (_, val) => {
const prefix = `images/icon/${val ? 'light/' : ''}`;
btn.srcset = `${prefix}16.png 1x,${prefix}32.png 2x`;
};
prefs.subscribe('iconset', onIconsetChanged, {runNow: true});
document.body.appendChild(btn);
window.on('keydown', e => getEventKeyName(e) === POPUP_HOTKEY && embedPopup());
CodeMirror.defaults.extraKeys[POPUP_HOTKEY] = 'openStylusPopup'; // adds to keymap help
}
async function onAttached(tabId, info) {
if (tabId !== ownTabId) {
return;
}
if (info.newPosition !== 0) {
prefs.set('openEditInWindow', false);
return;
}
const win = await browser.windows.get(info.newWindowId, {populate: true});
// If there's only one tab in this window, it's been dragged to new window
const openEditInWindow = win.tabs.length === 1;
// FF-only because Chrome retardedly resets the size during dragging
if (openEditInWindow && FIREFOX) {
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
}
prefs.set('openEditInWindow', openEditInWindow);
}
}
function onRuntimeMessage(request) {
const {style} = request;
switch (request.method) {
@ -322,7 +242,7 @@ define(require => {
}
function canSaveWindowPos() {
return isWindowed &&
return editor.isWindowed &&
document.visibilityState === 'visible' &&
prefs.get('openEditInWindow') &&
!isWindowMaximized();
@ -383,63 +303,4 @@ define(require => {
window.outerHeight < screen.availHeight + 10
);
}
function embedPopup() {
const ID = 'popup-iframe';
const SEL = '#' + ID;
if ($(SEL)) return;
const frame = $create('iframe', {
id: ID,
src: chrome.runtime.getManifest().browser_action.default_popup,
height: 600,
width: prefs.get('popupWidth'),
onload() {
frame.onload = null;
frame.focus();
const pw = frame.contentWindow;
const body = pw.document.body;
pw.on('keydown', e => getEventKeyName(e) === 'Escape' && embedPopup._close());
pw.close = embedPopup._close;
if (pw.IntersectionObserver) {
let loaded;
new pw.IntersectionObserver(([e]) => {
const el = pw.document.scrollingElement;
const h = e.isIntersecting && !pw.scrollY ? el.offsetHeight : el.scrollHeight;
const hasSB = h > el.offsetHeight;
const {width} = e.boundingClientRect;
frame.height = h;
if (!hasSB !== !frame._scrollbarWidth || frame.width - width) {
frame._scrollbarWidth = hasSB ? width - el.offsetWidth : 0;
frame.width = width + frame._scrollbarWidth;
}
if (!loaded) {
loaded = true;
frame.dataset.loaded = '';
}
}).observe(body.appendChild(
$create('div', {style: {height: '1px', marginTop: '-1px'}})
));
} else {
frame.dataset.loaded = '';
frame.height = body.scrollHeight;
}
new pw.MutationObserver(() => {
const bs = body.style;
const w = parseFloat(bs.minWidth || bs.width) + (frame._scrollbarWidth || 0);
const h = parseFloat(bs.minHeight || body.offsetHeight);
if (frame.width - w) frame.width = w;
if (frame.height - h) frame.height = h;
}).observe(body, {attributes: true, attributeFilter: ['style']});
},
});
// saving the listener here so it's the same function reference for window.off
if (!embedPopup._close) {
embedPopup._close = () => {
$remove(SEL);
window.off('mousedown', embedPopup._close);
};
}
window.on('mousedown', embedPopup._close);
document.body.appendChild(frame);
}
});

View File

@ -22,6 +22,8 @@ define(require => {
const editor = {
dirty,
isUsercss: false,
isWindowed: false,
isWindowSimple: false,
/** @type {'customName'|'name'} */
nameTarget: 'name',
previewDelay: 200, // Chrome devtools uses 200

114
edit/embedded-popup.js Normal file
View File

@ -0,0 +1,114 @@
'use strict';
define(require => {
const {
$,
$create,
$remove,
getEventKeyName,
} = require('/js/dom');
const t = require('/js/localization');
const prefs = require('/js/prefs');
const {CodeMirror} = require('./codemirror-factory');
const ID = 'popup-iframe';
const SEL = '#' + ID;
const URL = chrome.runtime.getManifest().browser_action.default_popup;
/** @type {HTMLIFrameElement} */
let frame;
let isLoaded;
let scrollbarWidth;
return {
initPopupButton() {
const POPUP_HOTKEY = 'Shift-Ctrl-Alt-S';
const btn = $create('img', {
id: 'popup-button',
title: t('optionsCustomizePopup') + '\n' + POPUP_HOTKEY,
onclick: embedPopup,
});
const onIconsetChanged = (_, val) => {
const prefix = `images/icon/${val ? 'light/' : ''}`;
btn.srcset = `${prefix}16.png 1x,${prefix}32.png 2x`;
};
prefs.subscribe('iconset', onIconsetChanged, {runNow: true});
document.body.appendChild(btn);
window.on('keydown', e => getEventKeyName(e) === POPUP_HOTKEY && embedPopup());
CodeMirror.defaults.extraKeys[POPUP_HOTKEY] = 'openStylusPopup'; // adds to keymap help
},
};
function embedPopup() {
if ($(SEL)) return;
isLoaded = false;
scrollbarWidth = 0;
frame = $create('iframe', {
id: ID,
src: URL,
height: 600,
width: prefs.get('popupWidth'),
onload: initFrame,
});
window.on('mousedown', removePopup);
document.body.appendChild(frame);
}
function initFrame() {
frame = this;
frame.focus();
const pw = frame.contentWindow;
const body = pw.document.body;
pw.on('keydown', removePopupOnEsc);
pw.close = removePopup;
if (pw.IntersectionObserver) {
new pw.IntersectionObserver(onIntersect).observe(body.appendChild(
$create('div', {style: {height: '1px', marginTop: '-1px'}})
));
} else {
frame.dataset.loaded = '';
frame.height = body.scrollHeight;
}
new pw.MutationObserver(onMutation).observe(body, {
attributes: true,
attributeFilter: ['style'],
});
}
function onMutation() {
const body = frame.contentDocument.body;
const bs = body.style;
const w = parseFloat(bs.minWidth || bs.width) + (scrollbarWidth || 0);
const h = parseFloat(bs.minHeight || body.offsetHeight);
if (frame.width - w) frame.width = w;
if (frame.height - h) frame.height = h;
}
function onIntersect([e]) {
const pw = frame.contentWindow;
const el = pw.document.scrollingElement;
const h = e.isIntersecting && !pw.scrollY ? el.offsetHeight : el.scrollHeight;
const hasSB = h > el.offsetHeight;
const {width} = e.boundingClientRect;
frame.height = h;
if (!hasSB !== !scrollbarWidth || frame.width - width) {
scrollbarWidth = hasSB ? width - el.offsetWidth : 0;
frame.width = width + scrollbarWidth;
}
if (!isLoaded) {
isLoaded = true;
frame.dataset.loaded = '';
}
}
function removePopup() {
frame = null;
$remove(SEL);
window.off('mousedown', removePopup);
}
function removePopupOnEsc(e) {
if (getEventKeyName(e) === 'Escape') {
removePopup();
}
}
});

View File

@ -2,8 +2,14 @@
define(require => {
const {API} = require('/js/msg');
const {sessionStore, tryCatch, tryJSONparse} = require('/js/toolbox');
const {waitForSelector} = require('/js/dom');
const {
FIREFOX,
getOwnTab,
sessionStore,
tryCatch,
tryJSONparse,
} = require('/js/toolbox');
const {$, waitForSelector} = require('/js/dom');
const prefs = require('/js/prefs');
const editor = require('./editor');
const util = require('./util');
@ -12,9 +18,12 @@ define(require => {
emacs: '/vendor/codemirror/keymap/emacs',
vim: '/vendor/codemirror/keymap/vim',
};
const domReady = waitForSelector('#sections');
let ownTabId;
// resize the window on 'undo close'
if (chrome.windows) {
initWindowedMode();
const pos = tryJSONparse(sessionStore.windowPos);
delete sessionStore.windowPos;
if (pos && pos.left != null && chrome.windows) {
@ -22,27 +31,35 @@ define(require => {
}
}
async function preinit() {
const params = new URLSearchParams(location.search);
const id = Number(params.get('id'));
const style = id && await API.styles.get(id) || {
name: params.get('domain') ||
tryCatch(() => new URL(params.get('url-prefix')).hostname) ||
'',
enabled: true,
sections: [
util.DocFuncMapper.toSection([...params], {code: ''}),
],
};
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
editor.lazyKeymaps = lazyKeymaps;
editor.style = style;
editor.updateTitle(false);
document.documentElement.classList.toggle('usercss', editor.isUsercss);
sessionStore.justEditedStyleId = style.id || '';
// no such style so let's clear the invalid URL parameters
if (!style.id) history.replaceState({}, '', location.pathname);
getOwnTab().then(async tab => {
ownTabId = tab.id;
// use browser history back when 'back to manage' is clicked
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
await domReady;
$('#cancel-button').onclick = event => {
event.stopPropagation();
event.preventDefault();
history.back();
};
}
});
async function initWindowedMode() {
chrome.tabs.onAttached.addListener(onTabAttached);
editor.isWindowSimple =
(await browser.windows.getCurrent()).type === 'popup';
if (editor.isWindowSimple) {
Promise.all([
require(['./embedded-popup']),
domReady,
]).then(([_]) => _.initPopupButton());
}
editor.isWindowed = editor.isWindowSimple || (
history.length === 1 &&
await prefs.initializing && prefs.get('openEditInWindow') &&
(await browser.windows.getAll()).length > 1 &&
(await browser.tabs.query({currentWindow: true})).length === 1
);
}
/** Preloads the theme so CodeMirror can use the correct metrics in its first render */
@ -70,6 +87,47 @@ define(require => {
/vim/i.test(km) && require([lazyKeymaps.vim]);
}
async function onTabAttached(tabId, info) {
if (tabId !== ownTabId) {
return;
}
if (info.newPosition !== 0) {
prefs.set('openEditInWindow', false);
return;
}
const win = await browser.windows.get(info.newWindowId, {populate: true});
// If there's only one tab in this window, it's been dragged to new window
const openEditInWindow = win.tabs.length === 1;
// FF-only because Chrome retardedly resets the size during dragging
if (openEditInWindow && FIREFOX) {
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
}
prefs.set('openEditInWindow', openEditInWindow);
}
async function preinit() {
const params = new URLSearchParams(location.search);
const id = Number(params.get('id'));
const style = id && await API.styles.get(id) || {
name: params.get('domain') ||
tryCatch(() => new URL(params.get('url-prefix')).hostname) ||
'',
enabled: true,
sections: [
util.DocFuncMapper.toSection([...params], {code: ''}),
],
};
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
editor.lazyKeymaps = lazyKeymaps;
editor.style = style;
editor.updateTitle(false);
document.documentElement.classList.toggle('usercss', editor.isUsercss);
sessionStore.justEditedStyleId = style.id || '';
// no such style so let's clear the invalid URL parameters
if (!style.id) history.replaceState({}, '', location.pathname);
}
return Promise.all([
preinit(),
prefs.initializing.then(() =>
@ -77,6 +135,6 @@ define(require => {
loadTheme(),
loadKeymaps(),
])),
waitForSelector('#sections'),
domReady,
]);
});