diff --git a/addons/mrp/views/ir_attachment_view.xml b/addons/mrp/views/ir_attachment_view.xml
index 9b23507d437..c27f163a7ab 100644
--- a/addons/mrp/views/ir_attachment_view.xml
+++ b/addons/mrp/views/ir_attachment_view.xml
@@ -24,7 +24,7 @@
-
+
diff --git a/addons/test_website/static/tests/tours/image_upload_progress.js b/addons/test_website/static/tests/tours/image_upload_progress.js
index 657ce28a45f..0e15a30796b 100644
--- a/addons/test_website/static/tests/tours/image_upload_progress.js
+++ b/addons/test_website/static/tests/tours/image_upload_progress.js
@@ -57,7 +57,7 @@ const setupSteps = [{
run: "drag_and_drop iframe #wrap",
}];
-const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg";
+const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg, .webp";
wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
url: '/test_image_progress',
@@ -87,7 +87,7 @@ wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
run: function () {}, // it's a check
}, {
content: "check upload progress bar is correctly shown (2)",
- trigger: `.o_we_progressbar:contains('image.webp'):contains('${formatErrorMsg}')`,
+ trigger: ".o_we_progressbar:contains('image.webp'):contains('File has been uploaded')",
in_modal: false,
run: function () {}, // it's a check
}, {
diff --git a/addons/web/static/src/legacy/js/public/public_root.js b/addons/web/static/src/legacy/js/public/public_root.js
index 6f84df144bf..b72fca991e5 100644
--- a/addons/web/static/src/legacy/js/public/public_root.js
+++ b/addons/web/static/src/legacy/js/public/public_root.js
@@ -92,7 +92,7 @@ export const PublicRoot = publicWidget.RootWidget.extend({
// Display image thumbnail
this.$(".o_image[data-mimetype^='image']").each(function () {
var $img = $(this);
- if (/gif|jpe|jpg|png/.test($img.data('mimetype')) && $img.data('src')) {
+ if (/gif|jpe|jpg|png|webp/.test($img.data('mimetype')) && $img.data('src')) {
$img.css('background-image', "url('" + $img.data('src') + "')");
}
});
diff --git a/addons/web_editor/models/ir_attachment.py b/addons/web_editor/models/ir_attachment.py
index 14462baf8dc..427a7fbab66 100644
--- a/addons/web_editor/models/ir_attachment.py
+++ b/addons/web_editor/models/ir_attachment.py
@@ -12,6 +12,7 @@ SUPPORTED_IMAGE_MIMETYPES = {
'image/jpg': '.jpg',
'image/png': '.png',
'image/svg+xml': '.svg',
+ 'image/webp': '.webp',
}
diff --git a/addons/web_editor/static/src/components/media_dialog/file_selector.js b/addons/web_editor/static/src/components/media_dialog/file_selector.js
index d0306e83edc..cec28eb796a 100644
--- a/addons/web_editor/static/src/components/media_dialog/file_selector.js
+++ b/addons/web_editor/static/src/components/media_dialog/file_selector.js
@@ -9,8 +9,8 @@ import { SearchMedia } from './search_media';
import { Component, xml, useState, useRef, onWillStart } from "@odoo/owl";
-export const IMAGE_MIMETYPES = ['image/jpg', 'image/jpeg', 'image/jpe', 'image/png', 'image/svg+xml', 'image/gif'];
-export const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.jpe', '.png', '.svg', '.gif'];
+export const IMAGE_MIMETYPES = ['image/jpg', 'image/jpeg', 'image/jpe', 'image/png', 'image/svg+xml', 'image/gif', 'image/webp'];
+export const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.jpe', '.png', '.svg', '.gif', '.webp'];
class RemoveButton extends Component {
setup() {
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 6ec823593ae..b974ae9e321 100644
--- a/addons/web_editor/static/src/js/editor/image_processing.js
+++ b/addons/web_editor/static/src/js/editor/image_processing.js
@@ -456,7 +456,7 @@ function isImageSupportedForProcessing(mimetype, strict = false) {
if (isGif(mimetype)) {
return !strict;
}
- return ['image/jpeg', 'image/png'].includes(mimetype);
+ return ['image/jpeg', 'image/png', 'image/webp'].includes(mimetype);
}
/**
* @param {HTMLImageElement} img
diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js
index 90739d5f81c..d62d45f3e89 100644
--- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js
+++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js
@@ -4597,7 +4597,7 @@ export class OdooEditor extends EventTarget {
const url = /^https?:\/\//i.test(text) ? text : 'https://' + text;
const youtubeUrl = this.options.allowCommandVideo &&YOUTUBE_URL_GET_VIDEO_ID.exec(url);
const urlFileExtention = url.split('.').pop();
- const isImageUrl = ['jpg', 'jpeg', 'png', 'gif', 'svg'].includes(urlFileExtention.toLowerCase());
+ const isImageUrl = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(urlFileExtention.toLowerCase());
// A url cannot be transformed inside an existing link.
// An image can be embedded inside an existing link, a video cannot.
if (selectionIsInsideALink) {
diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js
index 7b0fc852fc9..b5cbaf120e2 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -5781,7 +5781,7 @@ const ImageHandlerOption = SnippetOptionWidget.extend({
$select.append(`
${label}`);
});
- if (this._getImageMimetype(img) !== 'image/jpeg') {
+ if (!['image/jpeg', 'image/webp'].includes(this._getImageMimetype(img))) {
const optQuality = uiFragment.querySelector('we-range[data-set-quality]');
if (optQuality) {
optQuality.remove();
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js b/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js
index dec5b6e930c..23cd161da90 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js
@@ -35,7 +35,10 @@ const ImageCropWidget = Widget.extend({
const data = Object.assign({}, media.dataset);
this.initialSrc = src;
this.aspectRatio = data.aspectRatio || "0/0";
- const mimetype = data.mimetype || src.endsWith('.png') ? 'image/png' : 'image/jpeg';
+ const mimetype = data.mimetype ||
+ src.endsWith('.png') ? 'image/png' :
+ src.endsWith('.webp') ? 'image/webp' :
+ 'image/jpeg';
this.mimetype = options.mimetype || mimetype;
},
/**
diff --git a/odoo/addons/base/tests/test_mimetypes.py b/odoo/addons/base/tests/test_mimetypes.py
index fc9257b5ef4..948e8df9434 100644
--- a/odoo/addons/base/tests/test_mimetypes.py
+++ b/odoo/addons/base/tests/test_mimetypes.py
@@ -25,6 +25,11 @@ SVG = b"""PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/PjwhRE9DVFlQRS
NAMESPACED_SVG = b"""
"""
+
+# single pixel webp image
+WEBP = b"""UklGRjoAAABXRUJQVlA4IC4AAAAwAQCdASoBAAEAAUAmJaAAA3AA/u/uY//8s//2W/7LeM///5Bj
+/dl/pJxGAAAA"""
+
# minimal zip file with an empty `t.txt` file
ZIP = b"""UEsDBBQACAAIAGFva1AAAAAAAAAAAAAAAAAFACAAdC50eHRVVA0AB5bgaF6W4GheluBoXnV4CwABBOgDAAAE6AMAAA
MAUEsHCAAAAAACAAAAAAAAAFBLAQIUAxQACAAIAGFva1AAAAAAAgAAAAAAAAAFACAAAAAAAAAAAACkgQAAAAB0LnR4dFVUDQAHlu
@@ -105,6 +110,11 @@ class test_guess_mimetype(BaseCase):
self.assertNotIn("svg", mimetype)
+ def test_mimetype_webp(self):
+ content = base64.b64decode(WEBP)
+ mimetype = guess_mimetype(content, default='test')
+ self.assertEqual(mimetype, 'image/webp')
+
def test_mimetype_zip(self):
content = base64.b64decode(ZIP)
mimetype = guess_mimetype(content, default='test')
diff --git a/odoo/http.py b/odoo/http.py
index 5e54e00ae05..c1240270621 100644
--- a/odoo/http.py
+++ b/odoo/http.py
@@ -197,6 +197,7 @@ _logger = logging.getLogger(__name__)
mimetypes.add_type('application/font-woff', '.woff')
mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
mimetypes.add_type('application/x-font-ttf', '.ttf')
+mimetypes.add_type('image/webp', '.webp')
# Add potentially wrong (detected on windows) svg mime types
mimetypes.add_type('image/svg+xml', '.svg')
diff --git a/odoo/tools/image.py b/odoo/tools/image.py
index 1bf10e8d1f0..42a9bb41a44 100644
--- a/odoo/tools/image.py
+++ b/odoo/tools/image.py
@@ -73,8 +73,8 @@ class ImageProcess():
self.source = source or False
self.operationsCount = 0
- if not source or source[:1] == b'<':
- # don't process empty source or SVG
+ if not source or source[:1] == b'<' or (source[0:4] == b'RIFF' and source[8:15] == b'WEBPVP8'):
+ # don't process empty source or SVG or WEBP
self.image = False
else:
try:
@@ -441,8 +441,13 @@ def is_image_size_above(base64_source_1, base64_source_2):
if base64_source_1[:1] in (b'P', 'P') or base64_source_2[:1] in (b'P', 'P'):
# False for SVG
return False
- image_source = image_fix_orientation(base64_to_image(base64_source_1))
- image_target = image_fix_orientation(base64_to_image(base64_source_2))
+ source_1 = base64.b64decode(base64_source_1)
+ source_2 = base64.b64decode(base64_source_2)
+ if (source_1[0:4] == b'RIFF' and source_1[8:15] == b'WEBPVP8') or (source_2[0:4] == b'RIFF' and source_2[8:15] == b'WEBPVP8'):
+ # False for WEBP
+ return False
+ image_source = image_fix_orientation(binary_to_image(source_1))
+ image_target = image_fix_orientation(binary_to_image(source_2))
return image_source.width > image_target.width or image_source.height > image_target.height
diff --git a/odoo/tools/mimetypes.py b/odoo/tools/mimetypes.py
index 862c8c06ce7..6efcd76a2ef 100644
--- a/odoo/tools/mimetypes.py
+++ b/odoo/tools/mimetypes.py
@@ -110,6 +110,10 @@ def _check_svg(data):
if b'