From de558e5da01e5fb11f5c4c3dcccec40a2e7933c9 Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Tue, 23 Nov 2021 10:42:38 +0000 Subject: [PATCH] [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 --- .../static/src/js/mass_mailing_widget.js | 2 +- .../static/src/js/backend/convert_inline.js | 291 +++++++++++------- .../static/tests/convert_inline_tests.js | 80 ++--- 3 files changed, 209 insertions(+), 164 deletions(-) diff --git a/addons/mass_mailing/static/src/js/mass_mailing_widget.js b/addons/mass_mailing/static/src/js/mass_mailing_widget.js index 6dbad3c14d1..d8000b201eb 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_widget.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_widget.js @@ -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); diff --git a/addons/web_editor/static/src/js/backend/convert_inline.js b/addons/web_editor/static/src/js/backend/convert_inline.js index 08160c259fe..fac3d6c4d62 100644 --- a/addons/web_editor/static/src/js/backend/convert_inline.js +++ b/addons/web_editor/static/src/js/backend/convert_inline.js @@ -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(``); $target.append(``); - } - else if (node.nodeName === 'IMG' && $target.is('.mx-auto.d-block')) { + } else if (node.nodeName === 'IMG' && $target.is('.mx-auto.d-block')) { $target.wrap('

'); } }); @@ -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, diff --git a/addons/web_editor/static/tests/convert_inline_tests.js b/addons/web_editor/static/tests/convert_inline_tests.js index 7e39855b6b7..3d9c2f6ab59 100644 --- a/addons/web_editor/static/tests/convert_inline_tests.js +++ b/addons/web_editor/static/tests/convert_inline_tests.js @@ -521,7 +521,7 @@ QUnit.module('convert_inline', {}, function () { assert.expect(1); $editable.html(`

Hello
`); - 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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted border-[position]-radius styles (from class) to border-radius"); @@ -566,8 +565,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted border-[position]-style styles (from class) to border-style"); @@ -581,8 +579,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted margin-[position] styles (from class) to margin"); @@ -596,8 +593,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted padding-[position] styles (from class) to padding"); @@ -614,8 +610,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted uniform margin-[position] styles (from class) to margin"); @@ -646,8 +640,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted uniform padding-[position] styles (from class) to padding"); @@ -664,8 +657,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "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(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have removed border initial"); @@ -789,13 +775,12 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have removed display block"); $editable.html(`
`); - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should not have removed display block for .btn-block"); @@ -808,8 +793,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted a simple color"); @@ -819,8 +803,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have converted an important color and removed the !important"); @@ -834,8 +817,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have removed animation style"); @@ -851,8 +833,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have removed all specific animation styles"); @@ -866,8 +847,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have removed all flex styles"); @@ -883,8 +863,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have removed all specific flex styles"); @@ -911,8 +890,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $iframeEditable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($iframeEditable); + convertInline.classToStyle($iframeEditable, convertInline.getCSSRules($iframeEditable[0].ownerDocument)); assert.strictEqual($iframeEditable.html(), `
`, "should have given all styles of body to .o_layout"); @@ -944,20 +922,19 @@ QUnit.module('convert_inline', {}, function () { `, 2); $editable.html(``); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), ``, "should have prioritized the last defined style"); $editable.html(`
`); - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have prioritized the more specific style"); $editable.html(`
`); - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have prioritized the inline style"); @@ -968,8 +945,7 @@ QUnit.module('convert_inline', {}, function () { } `, 0); $editable.html(`
`); - document._rulesCache = undefined; - convertInline.classToStyle($editable); + convertInline.classToStyle($editable, convertInline.getCSSRules($editable[0].ownerDocument)); assert.strictEqual($editable.html(), `
`, "should have prioritized the important style");