[REF] mass_mailing, web_editor: speed up class to inline conversion
When saving an email, its contents pass through convert_inline to ensure wide e-mail client compatibility. One phase of that process is the conversion of classes to inline styles, which involves parsing through all the styles in all the stylesheets of the document, and matching them with each element in the DOM. That is an extremely expensive process and because of that, saving an e-mail can take a very long time depending on how large the DOM is. This refactors it for performance gains. Tested on an exaggeratingly big DOM, what used to take about 12s now takes about 5s. - The process of parsing through the document's stylesheets is now done on opening the email (about 500ms) rather than on saving it so we split the processing time between the two. - When parsing the stylesheets, we now also preprocess them by grouping whatever can be grouped together and precomputing the specificity so as to sort them appropriately. This has the benefit of considerably reducing the number of rules to iterate over for each element (from 11k+ to 3k+). - In the process, some things were re-written for improved readability. X-original-commit: 10e749537186dfb80aee1f2537f3b5804f66cfe5 Part-of: odoo/odoo#81540
This commit is contained in:
@@ -84,7 +84,7 @@ var MassMailingFieldHtml = FieldHtml.extend({
|
||||
|
||||
convertInline.attachmentThumbnailToLinkImg($editable);
|
||||
convertInline.fontToImg($editable);
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, self.cssRules);
|
||||
convertInline.bootstrapToTable($editable);
|
||||
convertInline.cardToTable($editable);
|
||||
convertInline.listGroupToTable($editable);
|
||||
|
||||
@@ -6,120 +6,183 @@ import { isBlock, rgbToHex } from '../../../lib/odoo-editor/src/utils/utils';
|
||||
|
||||
const SELECTORS_IGNORE = /(^\*$|:hover|:before|:after|:active|:link|::|'|\([^(),]+[,(])/;
|
||||
|
||||
/**
|
||||
* Parse through the given document's stylesheets, preprocess(*) them and return
|
||||
* the result as an array of objects, each containing a selector string , a
|
||||
* style object and a specificity number. Preprocessing involves grouping
|
||||
* whatever rules can be grouped together and precomputing their specificity so
|
||||
* as to sort them appropriately.
|
||||
*
|
||||
* @param {Document} doc
|
||||
* @returns {Object[]} Array<{selector: string;
|
||||
* style: {[styleName]: string};
|
||||
* specificity: number;}>
|
||||
*/
|
||||
function getCSSRules(doc) {
|
||||
const cssRules = [];
|
||||
for (const sheet of doc.styleSheets) {
|
||||
// try...catch because browser may not able to enumerate rules for cross-domain sheets
|
||||
let rules;
|
||||
try {
|
||||
rules = sheet.rules || sheet.cssRules;
|
||||
} catch (e) {
|
||||
console.log("Can't read the css rules of: " + sheet.href, e);
|
||||
continue;
|
||||
}
|
||||
for (const rule of (rules || [])) {
|
||||
const subRules = [rule];
|
||||
const conditionText = rule.conditionText;
|
||||
const minWidthMatch = conditionText && conditionText.match(/\(min-width *: *(\d+)/);
|
||||
const minWidth = minWidthMatch && +(minWidthMatch[1] || '0');
|
||||
if (minWidth && minWidth >= 1200) {
|
||||
// Large min-width media queries should be included.
|
||||
// eg., .container has a default max-width for all screens.
|
||||
let mediaRules;
|
||||
try {
|
||||
mediaRules = rule.rules || rule.cssRules;
|
||||
subRules.push(...mediaRules);
|
||||
} catch (e) {
|
||||
console.log(`Can't read the css rules of: ${sheet.href} (${conditionText})`, e);
|
||||
}
|
||||
}
|
||||
for (const subRule of subRules) {
|
||||
const selectorText = subRule.selectorText;
|
||||
if (selectorText && !SELECTORS_IGNORE.test(selectorText)) {
|
||||
const style = _normalizeStyle(subRule.style);
|
||||
if (Object.keys(style).length) {
|
||||
for (let selector of selectorText.split(',')) {
|
||||
selector = selector.trim();
|
||||
cssRules.push({ selector, style, specificity: _computeSpecificity(selector) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group together rules with the same selector.
|
||||
for (let i = cssRules.length - 1; i >= 0; i--) {
|
||||
for (let j = cssRules.length - 1; j >= 0; j--) {
|
||||
if (i > j && cssRules[i].selector === cssRules[j].selector) {
|
||||
// Styles of "later" selector override styles of "earlier" one.
|
||||
const importantJStyles = {};
|
||||
for (const [key, value] of Object.entries(cssRules[j].style)) {
|
||||
if (value.endsWith('!important')) {
|
||||
importantJStyles[key] = value;
|
||||
}
|
||||
}
|
||||
cssRules[i].style = {...cssRules[j].style, ...cssRules[i].style};
|
||||
for (const [key, value] of Object.entries(importantJStyles)) {
|
||||
cssRules[i].style[key] = value;
|
||||
}
|
||||
cssRules.splice(j, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The top element of a mailing has the class 'o_layout'. Give it the body's
|
||||
// styles so they can trickle down.
|
||||
cssRules.unshift({
|
||||
selector: '.o_layout',
|
||||
style: {...cssRules.find(r => r.selector === 'body').style},
|
||||
specificity: 1,
|
||||
});
|
||||
|
||||
const groupedRules = [];
|
||||
const ungroupedRules = [...cssRules];
|
||||
while (ungroupedRules.length) {
|
||||
const rule = ungroupedRules.shift();
|
||||
let groupedRule = {...rule};
|
||||
for (const otherRule of ungroupedRules) {
|
||||
if (
|
||||
otherRule !== rule &&
|
||||
rule.specificity === otherRule.specificity &&
|
||||
Object.keys(rule.style).length === Object.keys(otherRule.style).length &&
|
||||
Object.keys(rule.style).every(key => key in otherRule.style && rule.style[key] === otherRule.style[key])
|
||||
) {
|
||||
if (rule.selector !== otherRule.selector) {
|
||||
groupedRule.selector = `${groupedRule.selector},${otherRule.selector}`;
|
||||
}
|
||||
ungroupedRules.splice(ungroupedRules.indexOf(otherRule), 1);
|
||||
}
|
||||
}
|
||||
groupedRules.push(groupedRule);
|
||||
}
|
||||
groupedRules.sort((a, b) => a.specificity - b.specificity);
|
||||
return groupedRules;
|
||||
}
|
||||
/**
|
||||
* Take a css style declaration return a "normalized" version of it (as a
|
||||
* standard object) for the purposes of emails. This means removing its styles
|
||||
* that are invalid, describe animations or aren't standard css (webkit
|
||||
* extensions). It also involves adding the "!important" suffix to styles that
|
||||
* have that priority, so they can be handled without access to the full
|
||||
* declaration.
|
||||
*
|
||||
* @param {CSSStyleDeclaration} style
|
||||
* @returns {Object} {[styleName]: string}
|
||||
*/
|
||||
function _normalizeStyle(style) {
|
||||
const normalizedStyle = {};
|
||||
for (const styleName of style) {
|
||||
const value = style[styleName];
|
||||
if (value && !styleName.includes('animation') && !styleName.includes('-webkit') && _.isString(value)) {
|
||||
const normalizedStyleName = styleName.replace(/-(.)/g, (a, b) => b.toUpperCase());
|
||||
normalizedStyle[styleName] = style[normalizedStyleName];
|
||||
if (style.getPropertyPriority(styleName) === 'important') {
|
||||
normalizedStyle[styleName] += ' !important';
|
||||
}
|
||||
}
|
||||
}
|
||||
return normalizedStyle;
|
||||
}
|
||||
/**
|
||||
* Take a selector and return its specificity according to the w3 specification.
|
||||
*
|
||||
* @see http://www.w3.org/TR/css3-selectors/#specificity
|
||||
* @param {string} selector
|
||||
* @returns number
|
||||
*/
|
||||
function _computeSpecificity(selector) {
|
||||
let a = 0;
|
||||
selector = selector.replace(/#[a-z0-9_-]+/gi, () => { a++; return ''; });
|
||||
let b = 0;
|
||||
selector = selector.replace(/(\.[a-z0-9_-]+)|(\[.*?\])/gi, () => { b++; return ''; });
|
||||
let c = 0;
|
||||
selector = selector.replace(/(^|\s+|:+)[a-z0-9_-]+/gi, a => { if (!a.includes(':not(')) c++; return ''; });
|
||||
return (a * 100) + (b * 10) + c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the css rules which applies on an element, tweaked so that they are
|
||||
* browser/mail client ok.
|
||||
*
|
||||
* @param {DOMElement} a
|
||||
* @param {DOMElement} node
|
||||
* @param {Object} cssRules
|
||||
* @returns {Object} css property name -> css property value
|
||||
*/
|
||||
function getMatchedCSSRules(a) {
|
||||
var i, r, k, l;
|
||||
var doc = a.ownerDocument;
|
||||
var rulesCache = a.ownerDocument._rulesCache || (a.ownerDocument._rulesCache = []);
|
||||
|
||||
if (!rulesCache.length) {
|
||||
var sheets = doc.styleSheets;
|
||||
for (i = sheets.length-1 ; i >= 0 ; i--) {
|
||||
var rules;
|
||||
// try...catch because browser may not able to enumerate rules for cross-domain sheets
|
||||
try {
|
||||
rules = sheets[i].rules || sheets[i].cssRules;
|
||||
} catch (e) {
|
||||
console.log("Can't read the css rules of: " + sheets[i].href, e);
|
||||
continue;
|
||||
}
|
||||
if (rules) {
|
||||
for (r = rules.length-1; r >= 0; r--) {
|
||||
const conditionText = rules[r].conditionText;
|
||||
const minWidthMatch = conditionText && conditionText.match(/\(min-width *: *(\d+)/);
|
||||
const minWidth = minWidthMatch && +(minWidthMatch[1] || '0');
|
||||
if (minWidth && minWidth >= 1200) {
|
||||
// Large min-width media queries should be included.
|
||||
// eg., .container has a default max-width for all
|
||||
// screens.
|
||||
let mediaRules;
|
||||
try {
|
||||
mediaRules = rules[r].rules || rules[r].cssRules;
|
||||
} catch (e) {
|
||||
console.log(`Can't read the css rules of: ${sheets[i].href} (${conditionText})`, e);
|
||||
continue;
|
||||
}
|
||||
if (mediaRules) {
|
||||
for (k = mediaRules.length-1; k >= 0; k--) {
|
||||
var selectorText = mediaRules[k].selectorText;
|
||||
if (selectorText && !SELECTORS_IGNORE.test(selectorText)) {
|
||||
var st = selectorText.split(/\s*,\s*/);
|
||||
for (l = 0 ; l < st.length ; l++) {
|
||||
rulesCache.push({ 'selector': st[l], 'style': mediaRules[k].style });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var selectorText = rules[r].selectorText;
|
||||
if (selectorText && !SELECTORS_IGNORE.test(selectorText)) {
|
||||
var st = selectorText.split(/\s*,\s*/);
|
||||
for (l = 0 ; l < st.length ; l++) {
|
||||
rulesCache.push({ 'selector': st[l], 'style': rules[r].style });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rulesCache.reverse();
|
||||
}
|
||||
|
||||
var css = [];
|
||||
var style;
|
||||
a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;
|
||||
for (r = 0; r < rulesCache.length; r++) {
|
||||
// The top element of a mailing has the class 'o_layout'. Give it the
|
||||
// body's styles so they can trickle down.
|
||||
if (a.matches(rulesCache[r].selector) || (a.classList.contains('o_layout') && rulesCache[r].selector === 'body')) {
|
||||
style = rulesCache[r].style;
|
||||
if (style.parentRule) {
|
||||
var style_obj = {};
|
||||
var len;
|
||||
for (k = 0, len = style.length ; k < len ; k++) {
|
||||
if (style[k].indexOf('animation') !== -1) {
|
||||
continue;
|
||||
}
|
||||
style_obj[style[k]] = style[style[k].replace(/-(.)/g, function (a, b) { return b.toUpperCase(); })];
|
||||
if (new RegExp(style[k] + '\s*:[^:;]+!important' ).test(style.cssText)) {
|
||||
style_obj[style[k]] += ' !important';
|
||||
}
|
||||
}
|
||||
rulesCache[r].style = style = style_obj;
|
||||
}
|
||||
css.push([rulesCache[r].selector, style]);
|
||||
function getMatchedCSSRules(node, cssRules) {
|
||||
node.matches = node.matches || node.webkitMatchesSelector || node.mozMatchesSelector || node.msMatchesSelector || node.oMatchesSelector;
|
||||
const css = [];
|
||||
for (const rule of cssRules) {
|
||||
if (node.matches(rule.selector)) {
|
||||
css.push([rule.selector, rule.style, rule.specificity]);
|
||||
}
|
||||
}
|
||||
|
||||
function specificity(selector) {
|
||||
// http://www.w3.org/TR/css3-selectors/#specificity
|
||||
let a = 0;
|
||||
selector = selector.replace(/#[a-z0-9_-]+/gi, () => { a++; return ''; });
|
||||
let b = 0;
|
||||
selector = selector.replace(/(\.[a-z0-9_-]+)|(\[.*?\])/gi, () => { b++; return ''; });
|
||||
let c = 0;
|
||||
selector = selector.replace(/(^|\s+|:+)[a-z0-9_-]+/gi, a => { if (!a.includes(':not(')) c++; return ''; });
|
||||
return (a * 100) + (b * 10) + c;
|
||||
}
|
||||
css.sort(function (a, b) { return specificity(a[0]) - specificity(b[0]); });
|
||||
// Add inline styles at the highest specificity.
|
||||
if (a.style.length) {
|
||||
if (node.style.length) {
|
||||
const inlineStyles = {};
|
||||
for (const styleName of a.style) {
|
||||
inlineStyles[styleName] = a.style[styleName];
|
||||
for (const styleName of node.style) {
|
||||
inlineStyles[styleName] = node.style[styleName];
|
||||
}
|
||||
css.push([a, inlineStyles]);
|
||||
css.push([node, inlineStyles]);
|
||||
}
|
||||
|
||||
style = {};
|
||||
const style = {};
|
||||
for (const cssValue of css) {
|
||||
for (const [key, value] of Object.entries(cssValue[1])) {
|
||||
if (value && _.isString(value) && key.indexOf('-webkit') === -1 && (!style[key] || style[key].indexOf('important') === -1 || value.indexOf('important') !== -1)) {
|
||||
if (!style[key] || !style[key].includes('important') || value.includes('important')) {
|
||||
style[key] = value;
|
||||
}
|
||||
};
|
||||
@@ -131,7 +194,7 @@ function getMatchedCSSRules(a) {
|
||||
}
|
||||
};
|
||||
|
||||
if (style.display === 'block' && !(a.classList && a.classList.contains('btn-block'))) {
|
||||
if (style.display === 'block' && !(node.classList && node.classList.contains('btn-block'))) {
|
||||
delete style.display;
|
||||
}
|
||||
if (!style['box-sizing']) {
|
||||
@@ -699,11 +762,12 @@ function formatTables($editable) {
|
||||
* the style they give as inline style).
|
||||
*
|
||||
* @param {jQuery} $editable
|
||||
* @param {Object} cssRules
|
||||
*/
|
||||
function classToStyle($editable) {
|
||||
function classToStyle($editable, cssRules) {
|
||||
applyOverDescendants($editable[0], function (node) {
|
||||
var $target = $(node);
|
||||
var css = getMatchedCSSRules(node);
|
||||
const $target = $(node);
|
||||
const css = getMatchedCSSRules(node, cssRules);
|
||||
// Flexbox
|
||||
for (const styleName in node.style) {
|
||||
if (styleName.includes('flex') || `${node.style[styleName]}`.includes('flex')) {
|
||||
@@ -712,12 +776,12 @@ function classToStyle($editable) {
|
||||
}
|
||||
|
||||
// Do not apply css that would override inline styles (which are prioritary).
|
||||
var style = $target.attr('style') || '';
|
||||
_.each(css, function (v,k) {
|
||||
if (!(new RegExp('(^|;)\\s*' + k).test(style))) {
|
||||
style = k+':'+v+';'+style;
|
||||
let style = $target.attr('style') || '';
|
||||
for (const [key, value] of Object.entries(css)) {
|
||||
if (!(new RegExp(`(^|;)\\s*${key}`).test(style))) {
|
||||
style = `${key}:${value};${style}`;
|
||||
}
|
||||
});
|
||||
};
|
||||
if (_.isEmpty(style)) {
|
||||
$target.removeAttr('style');
|
||||
} else {
|
||||
@@ -735,13 +799,11 @@ function classToStyle($editable) {
|
||||
if (node.nodeName === 'TD' && !node.childNodes.length) {
|
||||
$(node).html(' ');
|
||||
}
|
||||
|
||||
// Outlook
|
||||
if (node.nodeName === 'A' && $target.hasClass('btn') && !$target.hasClass('btn-link') && !$target.children().length) {
|
||||
$target.prepend(`<!--[if mso]><i style="letter-spacing: 25px; mso-font-width: -100%; mso-text-raise: 30pt;"> </i><![endif]-->`);
|
||||
$target.append(`<!--[if mso]><i style="letter-spacing: 25px; mso-font-width: -100%;"> </i><![endif]-->`);
|
||||
}
|
||||
else if (node.nodeName === 'IMG' && $target.is('.mx-auto.d-block')) {
|
||||
} else if (node.nodeName === 'IMG' && $target.is('.mx-auto.d-block')) {
|
||||
$target.wrap('<p class="o_outlook_hack" style="text-align:center;margin:0"/>');
|
||||
}
|
||||
});
|
||||
@@ -808,6 +870,12 @@ FieldHtml.include({
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_createWysiwygIntance: function () {
|
||||
return this._super(...arguments).then(() => {
|
||||
this.cssRules = getCSSRules(this.wysiwyg.getEditable()[0].ownerDocument);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -840,7 +908,7 @@ FieldHtml.include({
|
||||
|
||||
attachmentThumbnailToLinkImg($editable);
|
||||
fontToImg($editable);
|
||||
classToStyle($editable);
|
||||
classToStyle($editable, this.cssRules);
|
||||
bootstrapToTable($editable);
|
||||
cardToTable($editable);
|
||||
listGroupToTable($editable);
|
||||
@@ -866,6 +934,7 @@ FieldHtml.include({
|
||||
});
|
||||
|
||||
export default {
|
||||
getCSSRules: getCSSRules,
|
||||
fontToImg: fontToImg,
|
||||
bootstrapToTable: bootstrapToTable,
|
||||
cardToTable: cardToTable,
|
||||
|
||||
@@ -521,7 +521,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
assert.expect(1);
|
||||
|
||||
$editable.html(`<div class="container"><div class="row"><div class="col">Hello</div></div></div>`);
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
const containerStyle = `padding:0 16px 0 16px;margin:0 auto 0 auto;box-sizing:border-box;max-width:1140px;width:100%;`;
|
||||
const rowStyle = `margin:0 -16px 0 -16px;box-sizing:border-box;`;
|
||||
const colStyle = `padding:0 16px 0 16px;box-sizing:border-box;max-width:100%;width:100%;position:relative;`;
|
||||
@@ -549,8 +549,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-border-radius"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-border-radius" style="border-radius:30%;box-sizing:border-box;"></div>`,
|
||||
"should have converted border-[position]-radius styles (from class) to border-radius");
|
||||
@@ -566,8 +565,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-border"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-border" style="border-style:dotted dashed none solid;box-sizing:border-box;"></div>`,
|
||||
"should have converted border-[position]-style styles (from class) to border-style");
|
||||
@@ -581,8 +579,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-margin"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-margin" style="margin:0 20px 30px 40px;box-sizing:border-box;"></div>`,
|
||||
"should have converted margin-[position] styles (from class) to margin");
|
||||
@@ -596,8 +593,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-padding"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-padding" style="padding:10px 0 30px 40px;box-sizing:border-box;"></div>`,
|
||||
"should have converted padding-[position] styles (from class) to padding");
|
||||
@@ -614,8 +610,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-border-uniform"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-border-uniform" style="border-style:dotted;box-sizing:border-box;"></div>`,
|
||||
"should have converted uniform border-[position]-style styles (from class) to border-style");
|
||||
@@ -630,8 +625,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-margin-uniform"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-margin-uniform" style="margin:10px;box-sizing:border-box;"></div>`,
|
||||
"should have converted uniform margin-[position] styles (from class) to margin");
|
||||
@@ -646,8 +640,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-padding-uniform"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-padding-uniform" style="padding:10px;box-sizing:border-box;"></div>`,
|
||||
"should have converted uniform padding-[position] styles (from class) to padding");
|
||||
@@ -664,8 +657,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-border-inherit"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-border-inherit" style="box-sizing:border-box;border-left-style:solid;border-bottom-style:inherit;border-right-style:dashed;border-top-style:dotted;"></div>`,
|
||||
"should not have converted border-[position]-style styles (from class) to border-style as they include an inherit");
|
||||
@@ -680,8 +672,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-margin-inherit"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-margin-inherit" style="box-sizing:border-box;margin-left:40px;margin-bottom:30px;margin-right:inherit;margin-top:10px;"></div>`,
|
||||
"should not have converted margin-[position] styles (from class) to margin as they include an inherit");
|
||||
@@ -696,8 +687,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-padding-inherit"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-padding-inherit" style="box-sizing:border-box;padding-left:40px;padding-bottom:inherit;padding-right:20px;padding-top:10px;"></div>`,
|
||||
"should have converted padding-[position] styles (from class) to padding as they include an inherit");
|
||||
@@ -716,8 +706,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-margin-initial"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-margin-initial" style="box-sizing:border-box;margin-left:40px;margin-bottom:30px;margin-right:20px;margin-top:initial;"></div>`,
|
||||
"should not have converted margin-[position] styles (from class) to margin as they include an initial");
|
||||
@@ -732,8 +721,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-padding-initial"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-padding-initial" style="box-sizing:border-box;padding-left:initial;padding-bottom:30px;padding-right:20px;padding-top:10px;"></div>`,
|
||||
"should not have converted padding-[position] styles (from class) to padding as they include an initial");
|
||||
@@ -758,8 +746,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-decoration"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-decoration" style="text-decoration:underline;box-sizing:border-box;"></div>`,
|
||||
"should have removed all text-decoration-[prop] styles (from class) and kept a simple text-decoration");
|
||||
@@ -775,8 +762,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-border-initial"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-border-initial" style="box-sizing:border-box;border-bottom-style:double;border-right-style:dashed;border-top-style:dotted;"></div>`,
|
||||
"should have removed border initial");
|
||||
@@ -789,13 +775,12 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-block"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-block" style="box-sizing:border-box;"></div>`,
|
||||
"should have removed display block");
|
||||
$editable.html(`<div class="btn-block"></div>`);
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="btn-block" style="box-sizing:border-box;width:100%;display:block;" width="100%"></div>`,
|
||||
"should not have removed display block for .btn-block");
|
||||
@@ -808,8 +793,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-unimportant-color"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-unimportant-color" style="box-sizing:border-box;color:blue;"></div>`,
|
||||
"should have converted a simple color");
|
||||
@@ -819,8 +803,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-important-color test-unimportant-color"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-important-color test-unimportant-color" style="box-sizing:border-box;color:red;"></div>`,
|
||||
"should have converted an important color and removed the !important");
|
||||
@@ -834,8 +817,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-animation"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-animation" style="box-sizing:border-box;"></div>`,
|
||||
"should have removed animation style");
|
||||
@@ -851,8 +833,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-animation-specific"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-animation-specific" style="box-sizing:border-box;"></div>`,
|
||||
"should have removed all specific animation styles");
|
||||
@@ -866,8 +847,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-flex"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-flex" style="box-sizing:border-box;"></div>`,
|
||||
"should have removed all flex styles");
|
||||
@@ -883,8 +863,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-flex-specific"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-flex-specific" style="box-sizing:border-box;"></div>`,
|
||||
"should have removed all specific flex styles");
|
||||
@@ -911,8 +890,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$iframeEditable.html(`<div class="o_layout" style="padding: 50px;"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($iframeEditable);
|
||||
convertInline.classToStyle($iframeEditable, convertInline.getCSSRules($iframeEditable[0].ownerDocument));
|
||||
assert.strictEqual($iframeEditable.html(),
|
||||
`<div class="o_layout" style="box-sizing:border-box;font-size:50px;color:white;background-color:red;padding: 50px;"></div>`,
|
||||
"should have given all styles of body to .o_layout");
|
||||
@@ -944,20 +922,19 @@ QUnit.module('convert_inline', {}, function () {
|
||||
`, 2);
|
||||
|
||||
$editable.html(`<span class="test-color"></span>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<span class="test-color" style="box-sizing:border-box;color:blue;"></span>`,
|
||||
"should have prioritized the last defined style");
|
||||
|
||||
$editable.html(`<div class="test-color"></div>`);
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-color" style="box-sizing:border-box;color:green;"></div>`,
|
||||
"should have prioritized the more specific style");
|
||||
|
||||
$editable.html(`<div class="test-color" style="color: yellow;"></div>`);
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-color" style="box-sizing:border-box;color: yellow;"></div>`,
|
||||
"should have prioritized the inline style");
|
||||
@@ -968,8 +945,7 @@ QUnit.module('convert_inline', {}, function () {
|
||||
}
|
||||
`, 0);
|
||||
$editable.html(`<div class="test-color"></div>`);
|
||||
document._rulesCache = undefined;
|
||||
convertInline.classToStyle($editable);
|
||||
convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument));
|
||||
assert.strictEqual($editable.html(),
|
||||
`<div class="test-color" style="box-sizing:border-box;color:black;"></div>`,
|
||||
"should have prioritized the important style");
|
||||
|
||||
Reference in New Issue
Block a user