cssmanager: Refactor CSS manager creation to avoid race condition

Safari takes a while to initialize `document.styleSheets`, which
results in a race condition when loading the pad. Avoid the race
condition by accessing the CSSStyleSheet objects directly from the
HTMLStyleElement DOM objects.
This commit is contained in:
Richard Hansen 2021-03-19 20:50:01 -04:00 committed by John McLear
parent e2bfe2fd10
commit 3ad1d0a74f
5 changed files with 20 additions and 60 deletions

View file

@ -14,6 +14,7 @@
, "pad_automatic_reconnect.js" , "pad_automatic_reconnect.js"
, "ace.js" , "ace.js"
, "collab_client.js" , "collab_client.js"
, "cssmanager.js"
, "pad_userlist.js" , "pad_userlist.js"
, "pad_impexp.js" , "pad_impexp.js"
, "pad_savedrevs.js" , "pad_savedrevs.js"
@ -61,7 +62,6 @@
, "Changeset.js" , "Changeset.js"
, "ChangesetUtils.js" , "ChangesetUtils.js"
, "skiplist.js" , "skiplist.js"
, "cssmanager.js"
, "colorutils.js" , "colorutils.js"
, "undomodule.js" , "undomodule.js"
, "$unorm/lib/unorm.js" , "$unorm/lib/unorm.js"

View file

@ -25,6 +25,7 @@
// requires: undefined // requires: undefined
const hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
const makeCSSManager = require('./cssmanager').makeCSSManager;
const pluginUtils = require('./pluginfw/shared'); const pluginUtils = require('./pluginfw/shared');
const debugLog = (...args) => {}; const debugLog = (...args) => {};
@ -307,7 +308,11 @@ const Ace2Editor = function () {
await new Promise((resolve, reject) => innerWindow.plugins.ensure( await new Promise((resolve, reject) => innerWindow.plugins.ensure(
(err) => err != null ? reject(err) : resolve())); (err) => err != null ? reject(err) : resolve()));
debugLog('Ace2Editor.init() waiting for Ace2Inner.init()'); debugLog('Ace2Editor.init() waiting for Ace2Inner.init()');
await innerWindow.Ace2Inner.init(info); await innerWindow.Ace2Inner.init(info, {
inner: makeCSSManager(innerStyle.sheet),
outer: makeCSSManager(outerStyle.sheet),
parent: makeCSSManager(document.querySelector('style[title="dynamicsyntax"]').sheet),
});
debugLog('Ace2Editor.init() Ace2Inner.init() returned'); debugLog('Ace2Editor.init() Ace2Inner.init() returned');
loaded = true; loaded = true;
doActionsPendingInit(); doActionsPendingInit();

View file

@ -30,11 +30,10 @@ const htmlPrettyEscape = Ace2Common.htmlPrettyEscape;
const noop = Ace2Common.noop; const noop = Ace2Common.noop;
const hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
function Ace2Inner(editorInfo) { function Ace2Inner(editorInfo, cssManagers) {
const makeChangesetTracker = require('./changesettracker').makeChangesetTracker; const makeChangesetTracker = require('./changesettracker').makeChangesetTracker;
const colorutils = require('./colorutils').colorutils; const colorutils = require('./colorutils').colorutils;
const makeContentCollector = require('./contentcollector').makeContentCollector; const makeContentCollector = require('./contentcollector').makeContentCollector;
const makeCSSManager = require('./cssmanager').makeCSSManager;
const domline = require('./domline').domline; const domline = require('./domline').domline;
const AttribPool = require('./AttributePool'); const AttribPool = require('./AttributePool');
const Changeset = require('./Changeset'); const Changeset = require('./Changeset');
@ -157,10 +156,6 @@ function Ace2Inner(editorInfo) {
const scheduler = parent; // hack for opera required const scheduler = parent; // hack for opera required
let dynamicCSS = null;
let outerDynamicCSS = null;
let parentDynamicCSS = null;
const performDocumentReplaceRange = (start, end, newText) => { const performDocumentReplaceRange = (start, end, newText) => {
if (start === undefined) start = rep.selStart; if (start === undefined) start = rep.selStart;
if (end === undefined) end = rep.selEnd; if (end === undefined) end = rep.selEnd;
@ -181,12 +176,6 @@ function Ace2Inner(editorInfo) {
performDocumentApplyChangeset(cs); performDocumentApplyChangeset(cs);
}; };
const initDynamicCSS = () => {
dynamicCSS = makeCSSManager('dynamicsyntax');
outerDynamicCSS = makeCSSManager('dynamicsyntax', 'outer');
parentDynamicCSS = makeCSSManager('dynamicsyntax', 'parent');
};
const changesetTracker = makeChangesetTracker(scheduler, rep.apool, { const changesetTracker = makeChangesetTracker(scheduler, rep.apool, {
withCallbacks: (operationName, f) => { withCallbacks: (operationName, f) => {
inCallStackIfNecessary(operationName, () => { inCallStackIfNecessary(operationName, () => {
@ -214,15 +203,12 @@ function Ace2Inner(editorInfo) {
editorInfo.ace_getAuthorInfos = getAuthorInfos; editorInfo.ace_getAuthorInfos = getAuthorInfos;
const setAuthorStyle = (author, info) => { const setAuthorStyle = (author, info) => {
if (!dynamicCSS) {
return;
}
const authorSelector = getAuthorColorClassSelector(getAuthorClassName(author)); const authorSelector = getAuthorColorClassSelector(getAuthorClassName(author));
const authorStyleSet = hooks.callAll('aceSetAuthorStyle', { const authorStyleSet = hooks.callAll('aceSetAuthorStyle', {
dynamicCSS, dynamicCSS: cssManagers.inner,
parentDynamicCSS, outerDynamicCSS: cssManagers.outer,
outerDynamicCSS, parentDynamicCSS: cssManagers.parent,
info, info,
author, author,
authorSelector, authorSelector,
@ -234,16 +220,16 @@ function Ace2Inner(editorInfo) {
} }
if (!info) { if (!info) {
dynamicCSS.removeSelectorStyle(authorSelector); cssManagers.inner.removeSelectorStyle(authorSelector);
parentDynamicCSS.removeSelectorStyle(authorSelector); cssManagers.parent.removeSelectorStyle(authorSelector);
} else if (info.bgcolor) { } else if (info.bgcolor) {
let bgcolor = info.bgcolor; let bgcolor = info.bgcolor;
if ((typeof info.fade) === 'number') { if ((typeof info.fade) === 'number') {
bgcolor = fadeColor(bgcolor, info.fade); bgcolor = fadeColor(bgcolor, info.fade);
} }
const authorStyle = dynamicCSS.selectorStyle(authorSelector); const authorStyle = cssManagers.inner.selectorStyle(authorSelector);
const parentAuthorStyle = parentDynamicCSS.selectorStyle(authorSelector); const parentAuthorStyle = cssManagers.parent.selectorStyle(authorSelector);
// author color // author color
authorStyle.backgroundColor = bgcolor; authorStyle.backgroundColor = bgcolor;
@ -3906,8 +3892,6 @@ function Ace2Inner(editorInfo) {
root.classList.toggle('authorColors', true); root.classList.toggle('authorColors', true);
root.classList.toggle('doesWrap', doesWrap); root.classList.toggle('doesWrap', doesWrap);
initDynamicCSS();
enforceEditability(); enforceEditability();
// set up dom and rep // set up dom and rep
@ -3929,7 +3913,7 @@ function Ace2Inner(editorInfo) {
}; };
} }
exports.init = async (editorInfo) => { exports.init = async (editorInfo, cssManagers) => {
const editor = new Ace2Inner(editorInfo); const editor = new Ace2Inner(editorInfo, cssManagers);
await editor.init(); await editor.init();
}; };

View file

@ -468,14 +468,14 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
BroadcastSlider.onSlider(goToRevisionIfEnabled); BroadcastSlider.onSlider(goToRevisionIfEnabled);
const dynamicCSS = makeCSSManager('dynamicsyntax'); const dynamicCSS = makeCSSManager(document.querySelector('style[title="dynamicsyntax"]').sheet);
const authorData = {}; const authorData = {};
const receiveAuthorData = (newAuthorData) => { const receiveAuthorData = (newAuthorData) => {
for (const [author, data] of Object.entries(newAuthorData)) { for (const [author, data] of Object.entries(newAuthorData)) {
const bgcolor = typeof data.colorId === 'number' const bgcolor = typeof data.colorId === 'number'
? clientVars.colorPalette[data.colorId] : data.colorId; ? clientVars.colorPalette[data.colorId] : data.colorId;
if (bgcolor && dynamicCSS) { if (bgcolor) {
const selector = dynamicCSS.selectorStyle(`.${linestylefilter.getAuthorClassName(author)}`); const selector = dynamicCSS.selectorStyle(`.${linestylefilter.getAuthorClassName(author)}`);
selector.backgroundColor = bgcolor; selector.backgroundColor = bgcolor;
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5)

View file

@ -22,34 +22,7 @@
* limitations under the License. * limitations under the License.
*/ */
const makeCSSManager = (emptyStylesheetTitle, doc) => { exports.makeCSSManager = (browserSheet) => {
if (doc === true) {
doc = 'parent';
} else if (!doc) {
doc = 'inner';
}
const getSheetByTitle = (title) => {
let win;
if (doc === 'parent') {
win = window.parent.parent;
} else if (doc === 'inner') {
win = window;
} else if (doc === 'outer') {
win = window.parent;
} else {
throw new Error('Unknown dynamic style container');
}
for (const s of win.document.styleSheets) {
if (s.title === title) return s;
}
const err = new Error(`no sheet with title ${title} in doc ${doc}`)
console.error(err);
throw err;
};
const browserSheet = getSheetByTitle(emptyStylesheetTitle);
const browserRules = () => (browserSheet.cssRules || browserSheet.rules); const browserRules = () => (browserSheet.cssRules || browserSheet.rules);
const browserDeleteRule = (i) => { const browserDeleteRule = (i) => {
@ -97,5 +70,3 @@ const makeCSSManager = (emptyStylesheetTitle, doc) => {
info: () => `${selectorList.length}:${browserRules().length}`, info: () => `${selectorList.length}:${browserRules().length}`,
}; };
}; };
exports.makeCSSManager = makeCSSManager;