[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) <qsm@odoo.com>
This commit is contained in:
qsm-odoo
2024-02-25 08:40:49 +00:00
parent 4f4ca50e8d
commit c91ffa3bdb
3 changed files with 86 additions and 0 deletions
@@ -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.
@@ -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');
}
},
},
]);
+37
View File
@@ -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': """
<section class="s_404_snippet">
<div class="container">
<img class="img-responsive img-thumbnail" src="/web/image/website.404_ir_attachment"/>
</div>
</section>
""",
}, {
'type': 'qweb',
'inherit_id': website_snippets.id,
'arch': """
<xpath expr="//t[@t-snippet='website.s_parallax']" position="after">
<t t-snippet="website.s_404_snippet"
t-thumbnail="/website/static/src/img/snippets_thumbs/s_website_form.svg"/>
</xpath>
""",
}])
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')