From c91ffa3bdbff845088111bc3036500c2c03fc357 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Thu, 22 Feb 2024 15:17:55 +0100 Subject: [PATCH] [FIX] web_editor: prevent crash with some options applied on 404 images Commit [1] introduced some code that relied on a cache we currently have for processed** images. In case of 404, that cache is not filled... and relying on it thus crashes. This commit circles around the issue by taking profit of [2] which now forces `loadImage` to load a placeholder image in case of a 404. This makes sure that in case of a 404 during the initial call to `loadImage`, the rest of the stack works with the loaded placeholder image. **Note: the current cache is not filled at the `loadImage` level, but later when processing the fetched image. This should be changed in the future (a task has been created). See [3] and [4] which are example of unwanted 404 images due to other issues, which allowed to discover this bug to fix here. Steps to reproduce: - Go on the `website.s_picture_default_image` ir.attachment form view - Change the URL value to a relative path that does not exist - Go on a website page and edit - Drop the "Picture" snippet => Crash => After this fix, no crash but you can also edit the image if you were able to click on it, it would use the placeholder image as source. [1]: https://github.com/odoo/odoo/commit/567e5b58d544e4e56c3a68d148e862a516d77c4f [2]: https://github.com/odoo/odoo/commit/0639c5028c69d8e29c8438b741cc4713d875b06d [3]: https://github.com/odoo/odoo/pull/155015 [4]: https://github.com/odoo/design-themes/pull/767 Related to opw-3693055 and others (see [3] and [4]). closes odoo/odoo#155275 X-original-commit: fbc6a697c1adf67ee8a90c49b0150d6ca170e081 Signed-off-by: Quentin Smetz (qsm) --- .../static/src/js/editor/image_processing.js | 5 +++ .../tests/tours/drop_404_ir_attachment_url.js | 44 +++++++++++++++++++ addons/website/tests/test_ui.py | 37 ++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 addons/website/static/tests/tours/drop_404_ir_attachment_url.js diff --git a/addons/web_editor/static/src/js/editor/image_processing.js b/addons/web_editor/static/src/js/editor/image_processing.js index 1308c23b0e0..ee52f6ad31f 100644 --- a/addons/web_editor/static/src/js/editor/image_processing.js +++ b/addons/web_editor/static/src/js/editor/image_processing.js @@ -233,6 +233,8 @@ export async function applyModifications(img, dataOptions = {}) { // Crop const container = document.createElement('div'); const original = await loadImage(originalSrc); + // loadImage may have ended up loading a different src (see: LOAD_IMAGE_404) + originalSrc = original.getAttribute('src'); container.appendChild(original); await activateCropper(original, 0, data); let croppedImg = $(original).cropper('getCroppedCanvas', {width, height}); @@ -358,6 +360,7 @@ export function loadImage(src, img = new Image()) { img.src = source; }; // The server will return a placeholder image with the following src. + // grep: LOAD_IMAGE_404 const placeholderHref = "/web/image/__odoo__unknown__src__/"; return new Promise((resolve, reject) => { @@ -419,6 +422,8 @@ async function _updateImageData(src, key = 'objectURL') { } /** * Returns the size of a cached image. + * Warning: this supposes that the image is already in the cache, i.e. that + * _updateImageData was called before. * * @param {String} src used as a key on the image cache map. * @returns {Number} size of the image in bytes. diff --git a/addons/website/static/tests/tours/drop_404_ir_attachment_url.js b/addons/website/static/tests/tours/drop_404_ir_attachment_url.js new file mode 100644 index 00000000000..3906ac20d7c --- /dev/null +++ b/addons/website/static/tests/tours/drop_404_ir_attachment_url.js @@ -0,0 +1,44 @@ +/** @odoo-module **/ + +import wTourUtils from '@website/js/tours/tour_utils'; + +wTourUtils.registerWebsitePreviewTour('drop_404_ir_attachment_url', { + test: true, + url: '/', + edition: true, +}, () => [ + wTourUtils.dragNDrop({ + id: 's_404_snippet', + name: '404 Snippet', + }), + { + content: 'Click on the snippet image', + trigger: 'iframe .s_404_snippet img', + }, { + content: 'Once the image UI appears, check the image has no size (404)', + trigger: 'iframe .s_404_snippet img', + extra_trigger: '.snippet-option-ReplaceMedia', + run: function () { + const imgEl = this.$anchor[0]; + if (!imgEl.complete + || imgEl.naturalWidth !== 0 + || imgEl.naturalHeight !== 0) { + console.error('This is supposed to be a 404 image'); + } + }, + }, + wTourUtils.changeOption('ImageTools', 'we-select[data-name="shape_img_opt"] we-toggler'), + wTourUtils.changeOption('ImageTools', 'we-button[data-set-img-shape]'), + { + content: 'Once the shape is applied, check the image has now a size (placeholder image)', + trigger: 'iframe .s_404_snippet img[src^="data:"]', + run: function () { + const imgEl = this.$anchor[0]; + if (!imgEl.complete + || imgEl.naturalWidth === 0 + || imgEl.naturalHeight === 0) { + console.error('Even though the original image was a 404, the option should have been applied on the placeholder image'); + } + }, + }, +]); diff --git a/addons/website/tests/test_ui.py b/addons/website/tests/test_ui.py index 92ef59e4edf..fd967304945 100644 --- a/addons/website/tests/test_ui.py +++ b/addons/website/tests/test_ui.py @@ -515,3 +515,40 @@ class TestUi(odoo.tests.HttpCase): 'path': 'website/static/tests/tour_utils/widget_lifecycle_patch_wysiwyg.js', }) self.start_tour(self.env['website'].get_client_action_url('/'), 'widget_lifecycle', login='admin') + + def test_drop_404_ir_attachment_url(self): + website_snippets = self.env.ref('website.snippets') + self.env['ir.ui.view'].create([{ + 'name': '404 Snippet', + 'type': 'qweb', + 'key': 'website.s_404_snippet', + 'arch': """ +
+
+ +
+
+ """, + }, { + 'type': 'qweb', + 'inherit_id': website_snippets.id, + 'arch': """ + + + + """, + }]) + attachment = self.env['ir.attachment'].create({ + 'name': '404_ir_attachment', + 'type': 'url', + 'url': '/web/static/__some__typo__.png', + 'mimetype': 'image/png', + }) + self.env['ir.model.data'].create({ + 'name': '404_ir_attachment', + 'module': 'website', + 'model': 'ir.attachment', + 'res_id': attachment.id, + }) + self.start_tour(self.env['website'].get_client_action_url('/'), 'drop_404_ir_attachment_url', login='admin')