mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-20 06:29:53 +01:00
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:
parent
e2bfe2fd10
commit
3ad1d0a74f
5 changed files with 20 additions and 60 deletions
|
@ -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"
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
Loading…
Reference in a new issue