diff --git a/odoo/addons/base/models/assetsbundle.py b/odoo/addons/base/models/assetsbundle.py index fc963d518ad..2ae9cd3c222 100644 --- a/odoo/addons/base/models/assetsbundle.py +++ b/odoo/addons/base/models/assetsbundle.py @@ -140,56 +140,41 @@ class AssetsBundle(object): :returns [(tagName, attributes, content)] if the tag is auto close """ response = [] - if debug and 'assets' in debug: - if css and self.stylesheets: - is_css_preprocessed, old_attachments = self.is_css_preprocessed() - if not is_css_preprocessed: - self.preprocess_css(debug=debug, old_attachments=old_attachments) - if self.css_errors: - msg = '\n'.join(self.css_errors) - response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node()) - response.append(StylesheetAsset(self, url="/web/static/lib/bootstrap/css/bootstrap.css").to_node()) - if not self.css_errors: - for style in self.stylesheets: - response.append(style.to_node()) - - if js and self.javascripts: - self.js(is_minified=False) + is_debug_assets = debug and 'assets' in debug + if css and self.stylesheets: + css_attachments = self.css(is_minified=not is_debug_assets) or [] + for attachment in css_attachments: + if is_debug_assets: + href = self.get_debug_asset_url(extra='rtl/' if self.user_direction == 'rtl' else '', + name=css_attachments.name, + extension='') + else: + href = attachment.url attr = OrderedDict([ - ["async", "async" if async_load else None], - ["defer", "defer" if defer_load or lazy_load else None], - ["type", "text/javascript"], - ["data-src" if lazy_load else "src", self.get_debug_asset_url(name=self.name, extension=".js")], + ["type", "text/css"], + ["rel", "stylesheet"], + ["href", href], ['data-asset-xmlid', self.name], ['data-asset-version', self.version], ]) - response.append(("script", attr, None)) - else: - if css and self.stylesheets: - css_attachments = self.css() or [] - for attachment in css_attachments: - attr = OrderedDict([ - ["type", "text/css"], - ["rel", "stylesheet"], - ["href", attachment.url], - ['data-asset-xmlid', self.name], - ['data-asset-version', self.version], - ]) - response.append(("link", attr, None)) - if self.css_errors: - msg = '\n'.join(self.css_errors) - response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node()) + response.append(("link", attr, None)) + if self.css_errors: + msg = '\n'.join(self.css_errors) + response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node()) + response.append(StylesheetAsset(self, url="/web/static/lib/bootstrap/css/bootstrap.css").to_node()) - if js and self.javascripts: - attr = OrderedDict([ - ["async", "async" if async_load else None], - ["defer", "defer" if defer_load or lazy_load else None], - ["type", "text/javascript"], - ["data-src" if lazy_load else "src", self.js(is_minified=True)[0].url], - ['data-asset-xmlid', self.name], - ['data-asset-version', self.version], - ]) - response.append(("script", attr, None)) + if js and self.javascripts: + js_attachment = self.js(is_minified=not is_debug_assets) + src = self.get_debug_asset_url(name=js_attachment.name, extension='') if is_debug_assets else js_attachment[0].url + attr = OrderedDict([ + ["async", "async" if async_load else None], + ["defer", "defer" if defer_load or lazy_load else None], + ["type", "text/javascript"], + ["data-src" if lazy_load else "src", src], + ['data-asset-xmlid', self.name], + ['data-asset-version', self.version], + ]) + response.append(("script", attr, None)) return response @@ -232,9 +217,6 @@ class AssetsBundle(object): **self._get_asset_url_values(id=id, unique=unique, extra=extra, name=name, sep=sep, extension=extension) ) - def _get_debug_asset_template_url(self): - return "/web/assets/{id}-{unique}/{extra}{name}{sep}{extension}" - def get_debug_asset_url(self, extra='', name='%', extension='%'): return f"/web/assets/debug/{extra}{name}{extension}" @@ -250,7 +232,7 @@ class AssetsBundle(object): """ ira = self.env['ir.attachment'] url = self.get_asset_url( - extra='%s' % ('rtl/' if extension == 'css' and self.user_direction == 'rtl' else ''), + extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''), name=self.name, sep='', extension='.%s' % extension @@ -288,7 +270,7 @@ class AssetsBundle(object): url_pattern = self.get_asset_url( unique=unique, - extra='%s' % ('rtl/' if extension == 'css' and self.user_direction == 'rtl' else ''), + extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''), name=self.name, sep='', extension='.%s' % extension @@ -314,7 +296,7 @@ class AssetsBundle(object): :return the ir.attachment records for a given bundle. """ - assert extension in ('js', 'min.js', 'js.map', 'css') + assert extension in ('js', 'min.js', 'js.map', 'css', 'min.css', 'css.map') ira = self.env['ir.attachment'] # Set user direction in name to store two bundles @@ -323,8 +305,8 @@ class AssetsBundle(object): # (this applies to css bundles only) fname = '%s.%s' % (self.name, extension) mimetype = ( - 'text/css' if extension == 'css' else - 'application/json' if extension == 'js.map' else + 'text/css' if extension in ['css', 'min.css'] else + 'application/json' if extension in ['js.map', 'css.map'] else 'application/javascript' ) values = { @@ -340,7 +322,7 @@ class AssetsBundle(object): url = self.get_asset_url( id=attachment.id, unique=self.version, - extra='%s' % ('rtl/' if extension == 'css' and self.user_direction == 'rtl' else ''), + extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''), name=fname, sep='', # included in fname extension='' @@ -379,19 +361,13 @@ class AssetsBundle(object): return attachments[0] def js_with_sourcemap(self): - """Create/modify the ir.attachment representing the not-minified content of the bundleJS - and the ir.attachment representing the linked sourcemap. - To avoid the loss of break points in the development tool when the bundleJS - has to be rebuilt, the bundleJS url must not change so we will not include - 'self.version' in the ir.attachment url. - If any ir.attachment exists for the bundleJS, we create a new one (bundleJS and sourcemap), - else, we modify the 'raw' in the existing ir.attachments. + """Create the ir.attachment representing the not-minified content of the bundleJS + and create/modify the ir.attachment representing the linked sourcemap. :return ir.attachment representing the un-minified content of the bundleJS """ sourcemap_attachment = self.get_attachments('js.map') \ or self.save_attachment('js.map', '') - generator = SourceMapGenerator( source_root="/".join( [".." for i in range(0, len(self.get_debug_asset_url(name=self.name).split("/")) - 2)] @@ -414,41 +390,79 @@ class AssetsBundle(object): content_line_count += len(asset.content.split("\n")) + line_header content_bundle = ';\n'.join(content_bundle_list) + "\n//# sourceMappingURL=" + sourcemap_attachment.url - - js_attachment = self.get_attachments('js') - if js_attachment: - js_attachment.write({ - "raw": content_bundle.encode('utf8'), - }) - else: - js_attachment = self.save_attachment('js', content_bundle) + js_attachment = self.save_attachment('js', content_bundle) generator._file = js_attachment.url sourcemap_attachment.write({ - # Store with XSSI-prevention prefix - "raw": b")]}'\n" + json.dumps(generator.to_json()).encode('utf8'), + "raw": generator.get_content() }) return js_attachment - def css(self): - attachments = self.get_attachments('css') + def css(self, is_minified=True): + extension = 'min.css' if is_minified else 'css' + attachments = self.get_attachments(extension) if not attachments: # get css content css = self.preprocess_css() if self.css_errors: - return self.get_attachments('css', ignore_version=True) + return self.get_attachments(extension, ignore_version=True) - # move up all @import rules to the top matches = [] css = re.sub(self.rx_css_import, lambda matchobj: matches.append(matchobj.group(0)) and '', css) - matches.append(css) - css = u'\n'.join(matches) - self.save_attachment("css", css) - attachments = self.get_attachments('css') + if is_minified: + # move up all @import rules to the top + matches.append(css) + css = u'\n'.join(matches) + + self.save_attachment(extension, css) + attachments = self.get_attachments(extension) + else: + return self.css_with_sourcemap(u'\n'.join(matches)) return attachments + def css_with_sourcemap(self, content_import_rules): + """Create the ir.attachment representing the not-minified content of the bundleCSS + and create/modify the ir.attachment representing the linked sourcemap. + + :param content_import_rules: string containing all the @import rules to put at the beginning of the bundle + :return ir.attachment representing the un-minified content of the bundleCSS + """ + sourcemap_attachment = self.get_attachments('css.map') \ + or self.save_attachment('css.map', '') + debug_asset_url = self.get_debug_asset_url(name=self.name, + extra='rtl/' if self.user_direction == 'rtl' else '') + generator = SourceMapGenerator( + source_root="/".join( + [".." for i in range(0, len(debug_asset_url.split("/")) - 2)] + ) + "/", + ) + self.preprocess_css() + + # adds the @import rules at the beginning of the bundle + content_bundle_list = [content_import_rules] + content_line_count = len(content_import_rules.split("\n")) + for asset in self.stylesheets: + if asset.content: + content = asset.with_header(asset.content) + if asset.url: + generator.add_source(asset.url, content, content_line_count) + # comments all @import rules that have been added at the beginning of the bundle + content = re.sub(self.rx_css_import, lambda matchobj: f"/* {matchobj.group(0)} */", content) + content_bundle_list.append(content) + content_line_count += len(content.split("\n")) + + content_bundle = '\n'.join(content_bundle_list) + f"\n//*# sourceMappingURL={sourcemap_attachment.url} */" + css_attachment = self.save_attachment('css', content_bundle) + + generator._file = css_attachment.url + sourcemap_attachment.write({ + "raw": generator.get_content(), + }) + + return css_attachment + def dialog_message(self, message): """ Returns a JS script which shows a warning to the user on page load. @@ -588,26 +602,6 @@ class AssetsBundle(object): asset = next(asset for asset in self.stylesheets if asset.id == asset_id) asset._content = fragments.pop(0) - if debug: - try: - fname = os.path.basename(asset.url) - url = asset.html_url - with self.env.cr.savepoint(): - self.env['ir.attachment'].sudo().create(dict( - raw=asset.content.encode('utf8'), - mimetype='text/css', - type='binary', - name=fname, - url=url, - res_model=False, - res_id=False, - )) - - if self.env.context.get('commit_assetsbundle') is True: - self.env.cr.commit() - except psycopg2.Error: - pass - return '\n'.join(asset.minify() for asset in self.stylesheets) def compile_css(self, compiler, source): diff --git a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py index b2f462d25c6..2ac03de2f8e 100644 --- a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py +++ b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py @@ -47,7 +47,7 @@ class TestJavascriptAssetsBundle(FileTouchable): """ user_direction = self.env['res.lang']._lang_get(lang or self.env.user.lang).direction bundle = self.jsbundle_xmlid if extension in ['js', 'min.js'] else self.cssbundle_xmlid - rtl = 'rtl/' if extension == 'css' and user_direction == 'rtl' else '' + rtl = 'rtl/' if extension in ['css', 'min.css'] and user_direction == 'rtl' else '' url = f'/web/assets/%-%/{rtl}{bundle}.{extension}' domain = [('url', '=like', url)] return self.env['ir.attachment'].search(domain) @@ -249,8 +249,8 @@ class TestJavascriptAssetsBundle(FileTouchable): # self.cssbundle_xlmid contains 3 rules self.bundle = self._get_asset(self.cssbundle_xmlid) self.bundle.css() - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) - self.assertEqual(len(self.bundle.get_attachments('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) + self.assertEqual(len(self.bundle.get_attachments('min.css')), 1) def test_09_css_access(self): """ Checks that the bundle's cache is working, i.e. that a bundle creates only enough @@ -259,19 +259,19 @@ class TestJavascriptAssetsBundle(FileTouchable): bundle0 = self._get_asset(self.cssbundle_xmlid) bundle0.css() - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) version0 = bundle0.version - ira0 = self._any_ira_for_bundle('css') + ira0 = self._any_ira_for_bundle('min.css') date0 = ira0.create_date bundle1 = self._get_asset(self.cssbundle_xmlid) bundle1.css() - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) version1 = bundle1.version - ira1 = self._any_ira_for_bundle('css') + ira1 = self._any_ira_for_bundle('min.css') date1 = ira1.create_date self.assertEqual(version0, version1) @@ -286,7 +286,7 @@ class TestJavascriptAssetsBundle(FileTouchable): files0 = bundle0.files version0 = bundle0.version - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) view_arch = """ @@ -312,7 +312,7 @@ class TestJavascriptAssetsBundle(FileTouchable): self.assertNotEqual(version0, version1) # check if the previous attachment are correctly cleaned - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) def test_12_css_debug(self): """ Check that a bundle in debug mode outputs non-minified assets. @@ -321,10 +321,11 @@ class TestJavascriptAssetsBundle(FileTouchable): nodes = debug_bundle.to_node(debug='assets') content = self._node_to_list(nodes) # find back one of the original asset file - self.assertIn('/test_assetsbundle/static/src/css/test_cssfile1.css', content) + self.assertIn('/web/assets/debug/test_assetsbundle.bundle2.css', content) - # there shouldn't be any assets created in debug mode - self.assertEqual(len(self._any_ira_for_bundle('css')), 0) + # there should be one css asset created in debug mode + self.assertEqual(len(self._any_ira_for_bundle('css')), 1, + 'there should be one css asset created in debug mode') def test_14_duplicated_css_assets(self): """ Checks that if the bundle's ir.attachment record is duplicated, the bundle is only sourced once. This could @@ -332,18 +333,18 @@ class TestJavascriptAssetsBundle(FileTouchable): """ bundle0 = self._get_asset(self.cssbundle_xmlid) bundle0.css() - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) # duplicate the asset bundle - ira0 = self._any_ira_for_bundle('css') + ira0 = self._any_ira_for_bundle('min.css') ira1 = ira0.copy() - self.assertEqual(len(self._any_ira_for_bundle('css')), 2) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 2) self.assertEqual(ira0.store_fname, ira1.store_fname) # the ir.attachment records should be deduplicated in the bundle's content nodes = bundle0.to_node() content = self._node_to_list(nodes) - self.assertEqual(content[2].count('test_assetsbundle.bundle2.css'), 1) + self.assertEqual(content[2].count('test_assetsbundle.bundle2.min.css'), 1) # Language direction specific tests @@ -354,15 +355,15 @@ class TestJavascriptAssetsBundle(FileTouchable): self.bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'})) # there shouldn't be any attachment associated to this bundle - self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 0) - self.assertEqual(len(self.bundle.get_attachments('css')), 0) + self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 0) + self.assertEqual(len(self.bundle.get_attachments('min.css')), 0) # trigger the first generation and, thus, the first save in database self.bundle.css() # there should be one attachment associated to this bundle - self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1) - self.assertEqual(len(self.bundle.get_attachments('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 1) + self.assertEqual(len(self.bundle.get_attachments('min.css')), 1) def test_16_ltr_and_rtl_css_access(self): """ Checks that the bundle's cache is working, i.e. that the bundle creates only one @@ -373,19 +374,19 @@ class TestJavascriptAssetsBundle(FileTouchable): ltr_bundle0 = self._get_asset(self.cssbundle_xmlid) ltr_bundle0.css() - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) ltr_version0 = ltr_bundle0.version - ltr_ira0 = self._any_ira_for_bundle('css') + ltr_ira0 = self._any_ira_for_bundle('min.css') ltr_date0 = ltr_ira0.create_date ltr_bundle1 = self._get_asset(self.cssbundle_xmlid) ltr_bundle1.css() - self.assertEqual(len(self._any_ira_for_bundle('css')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) ltr_version1 = ltr_bundle1.version - ltr_ira1 = self._any_ira_for_bundle('css') + ltr_ira1 = self._any_ira_for_bundle('min.css') ltr_date1 = ltr_ira1.create_date self.assertEqual(ltr_version0, ltr_version1) @@ -395,19 +396,19 @@ class TestJavascriptAssetsBundle(FileTouchable): rtl_bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'})) rtl_bundle0.css() - self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 1) rtl_version0 = rtl_bundle0.version - rtl_ira0 = self._any_ira_for_bundle('css', lang='ar_SY') + rtl_ira0 = self._any_ira_for_bundle('min.css', lang='ar_SY') rtl_date0 = rtl_ira0.create_date rtl_bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'})) rtl_bundle1.css() - self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1) + self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 1) rtl_version1 = rtl_bundle1.version - rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY') + rtl_ira1 = self._any_ira_for_bundle('min.css', lang='ar_SY') rtl_date1 = rtl_ira1.create_date self.assertEqual(rtl_version0, rtl_version1) @@ -418,7 +419,7 @@ class TestJavascriptAssetsBundle(FileTouchable): # Check two bundles are available, one for ltr and one for rtl css_bundles = self.env['ir.attachment'].search([ - ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css')) + ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css')) ]) self.assertEqual(len(css_bundles), 2) @@ -446,7 +447,7 @@ class TestJavascriptAssetsBundle(FileTouchable): ltr_bundle1.css() ltr_last_modified1 = ltr_bundle1.last_modified ltr_version1 = ltr_bundle1.version - ltr_ira1 = self._any_ira_for_bundle('css') + ltr_ira1 = self._any_ira_for_bundle('min.css') self.assertNotEqual(ltr_last_modified0, ltr_last_modified1) self.assertNotEqual(ltr_version0, ltr_version1) @@ -455,7 +456,7 @@ class TestJavascriptAssetsBundle(FileTouchable): rtl_bundle1.css() rtl_last_modified1 = rtl_bundle1.last_modified rtl_version1 = rtl_bundle1.version - rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY') + rtl_ira1 = self._any_ira_for_bundle('min.css', lang='ar_SY') self.assertNotEqual(rtl_last_modified0, rtl_last_modified1) self.assertNotEqual(rtl_version0, rtl_version1) @@ -464,7 +465,7 @@ class TestJavascriptAssetsBundle(FileTouchable): # check if the previous attachment is correctly cleaned css_bundles = self.env['ir.attachment'].search([ - ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css')) + ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css')) ]) self.assertEqual(len(css_bundles), 2) @@ -484,7 +485,7 @@ class TestJavascriptAssetsBundle(FileTouchable): rtl_version0 = rtl_bundle0.version css_bundles = self.env['ir.attachment'].search([ - ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css')) + ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css')) ]) self.assertEqual(len(css_bundles), 2) @@ -507,7 +508,7 @@ class TestJavascriptAssetsBundle(FileTouchable): ltr_bundle1.css() ltr_files1 = ltr_bundle1.files ltr_version1 = ltr_bundle1.version - ltr_ira1 = self._any_ira_for_bundle('css') + ltr_ira1 = self._any_ira_for_bundle('min.css') self.assertNotEqual(ltr_files0, ltr_files1) self.assertNotEqual(ltr_version0, ltr_version1) @@ -516,7 +517,7 @@ class TestJavascriptAssetsBundle(FileTouchable): rtl_bundle1.css() rtl_files1 = rtl_bundle1.files rtl_version1 = rtl_bundle1.version - rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY') + rtl_ira1 = self._any_ira_for_bundle('min.css', lang='ar_SY') self.assertNotEqual(rtl_files0, rtl_files1) self.assertNotEqual(rtl_version0, rtl_version1) @@ -526,7 +527,7 @@ class TestJavascriptAssetsBundle(FileTouchable): # check if the previous attachment are correctly cleaned css_bundles = self.env['ir.attachment'].search([ - ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css')) + ('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css')) ]) self.assertEqual(len(css_bundles), 2) @@ -537,14 +538,16 @@ class TestJavascriptAssetsBundle(FileTouchable): nodes = debug_bundle.to_node(debug='assets') content = self._node_to_list(nodes) - # css file should be available in assets bundle as user's lang direction is rtl - self.assertIn('/test_assetsbundle/static/src/css/test_cssfile1/rtl/{0}.css'.format(self.cssbundle_xmlid), content) + # there should be an css assets bundle in /debug/rtl if user's lang direction is rtl and debug=assets + self.assertIn('/web/assets/debug/rtl/{0}.css'.format(self.cssbundle_xmlid), content, + "there should be an css assets bundle in /debug/rtl if user's lang direction is rtl and debug=assets") - # there should be assets(css) created in debug mode as user's lang direction is rtl + # there should be an css assets bundle created in /rtl if user's lang direction is rtl and debug=assets css_bundle = self.env['ir.attachment'].search([ - ('url', '=', '/test_assetsbundle/static/src/css/test_cssfile1/rtl/{0}.css'.format(self.cssbundle_xmlid)) + ('url', '=like', '/web/assets/%-%/rtl/{0}.css'.format(self.cssbundle_xmlid)) ]) - self.assertEqual(len(css_bundle), 1) + self.assertEqual(len(css_bundle), 1, + "there should be an css assets bundle created in /rtl if user's lang direction is rtl and debug=assets") def test_20_exteral_lib_assets(self): html = self.env['ir.ui.view']._render_template('test_assetsbundle.template2') @@ -585,13 +588,14 @@ class TestJavascriptAssetsBundle(FileTouchable): self.assertEqual(len(attachments), 1) asset_data_css = etree.HTML(html).xpath('//*[@data-asset-xmlid]')[0] - asset_data_js = etree.HTML(html).xpath('//*[@data-asset-xmlid]')[2] + asset_data_js = etree.HTML(html).xpath('//*[@data-asset-xmlid]')[1] format_data = { "asset_xmlid_css": asset_data_css.attrib.get('data-asset-xmlid'), "asset_version_css": asset_data_css.attrib.get('data-asset-version'), "asset_xmlid_js": asset_data_js.attrib.get('data-asset-xmlid'), "asset_version_js": asset_data_js.attrib.get('data-asset-version'), + "css": '/web/assets/debug/test_assetsbundle.bundle4.css', "js": '/web/assets/debug/test_assetsbundle.bundle4.js', } self.assertEqual(html.strip(), (""" @@ -599,8 +603,7 @@ class TestJavascriptAssetsBundle(FileTouchable): - - + @@ -678,8 +681,8 @@ class TestAssetsBundleWithIRAMock(FileTouchable): def _bundle(self, asset, should_create, should_unlink): self.counter.clear() asset.to_node(debug='assets') - self.assertEqual(self.counter['create'], int(should_create)) - self.assertEqual(self.counter['unlink'], int(should_unlink)) + self.assertEqual(self.counter['create'], 2 if should_create else 0) + self.assertEqual(self.counter['unlink'], 2 if should_unlink else 0) def test_01_debug_mode_assets(self): """ Checks that the ir.attachments records created for compiled assets in debug mode diff --git a/odoo/http.py b/odoo/http.py index 6f912fefa9e..525945900db 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -1252,7 +1252,7 @@ class DisableCacheMiddleware(object): root.setup_session(req) if req.session and req.session.debug and not 'wkhtmltopdf' in req.headers.get('User-Agent'): - if "assets" in req.session.debug and ".js" in req.base_url: + if "assets" in req.session.debug and (".js" in req.base_url or ".css" in req.base_url): new_headers = [('Cache-Control', 'no-store')] else: new_headers = [('Cache-Control', 'no-cache')] diff --git a/odoo/tools/__init__.py b/odoo/tools/__init__.py index b4a69053fb5..18916faa16b 100644 --- a/odoo/tools/__init__.py +++ b/odoo/tools/__init__.py @@ -22,4 +22,4 @@ from .convert import * from .template_inheritance import * from . import osutil from .js_transpiler import transpile_javascript, is_odoo_module, URL_RE, ODOO_MODULE_RE -from .js_sourcemap import SourceMapGenerator +from .sourcemap_generator import SourceMapGenerator diff --git a/odoo/tools/js_sourcemap.py b/odoo/tools/sourcemap_generator.py similarity index 95% rename from odoo/tools/js_sourcemap.py rename to odoo/tools/sourcemap_generator.py index 12936ddf9fa..1d82582157c 100644 --- a/odoo/tools/js_sourcemap.py +++ b/odoo/tools/sourcemap_generator.py @@ -1,9 +1,10 @@ from functools import lru_cache +import json class SourceMapGenerator: """ - The SourceMapGenerator creates the sourcemap maps the asset bundle to the js files. + The SourceMapGenerator creates the sourcemap maps the asset bundle to the js/css files. What is a sourcemap ? (https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map) In brief: a source map is what makes possible to debug your processed/compiled/minified code as if you were @@ -83,6 +84,14 @@ class SourceMapGenerator: return mapping + def get_content(self): + """Generates the content of the sourcemap. + + :return the content of the sourcemap as a string encoded in UTF-8. + """ + # Store with XSSI-prevention prefix + return b")]}'\n" + json.dumps(self.to_json()).encode('utf8') + def add_source(self, source_name, source_content, last_index, start_offset=0): """Adds a new source file in the sourcemap. All the lines of the source file will be mapped line by line to the generated file from the (last_index + start_offset). All lines between