diff --git a/addons/web/static/src/js/framework/barcode_events.js b/addons/web/static/src/js/framework/barcode_events.js
new file mode 100644
index 00000000000..6aa5327a768
--- /dev/null
+++ b/addons/web/static/src/js/framework/barcode_events.js
@@ -0,0 +1,118 @@
+odoo.define('web.BarcodeEvents', function(require) {
+ "use strict";
+
+ var core = require('web.core');
+ var mixins = core.mixins;
+
+ return core.Class.extend(mixins.PropertiesMixin, {
+ timeout: null,
+ key_released: true,
+ buffered_key_events: [],
+
+ init: function(parent) {
+ mixins.PropertiesMixin.init.call(this);
+ },
+
+ handle_buffered_keys: function() {
+ var code = "";
+
+ if (this.buffered_key_events.length >= 3) {
+ for (var i = 0; i < this.buffered_key_events.length; i++) {
+ code += String.fromCharCode(this.buffered_key_events[i].which);
+ }
+
+ core.bus.trigger('barcode_scanned', code);
+
+ // Dispatch a barcode_scanned DOM event to elements that have
+ // barcode_events="true" set.
+ if (this.element_is_editable(this.buffered_key_events[0].target)) {
+ $(this.buffered_key_events[0].target).trigger('barcode_scanned', code);
+ }
+ } else {
+ this.resend_buffered_keys();
+ }
+
+ this.buffered_key_events = [];
+ },
+
+ resend_buffered_keys: function() {
+ for(var i = 0; i < this.buffered_key_events.length; i++) {
+ var old_event = this.buffered_key_events[i];
+
+ if(old_event.which !== 13) { // ignore returns
+ // We do not create a 'real' keypress event through
+ // eg. KeyboardEvent because there are several issues
+ // with them that make them very different from
+ // genuine keypresses. Chrome per example has had a
+ // bug for the longest time that causes keyCode and
+ // charCode to not be set for events created this way:
+ // https://bugs.webkit.org/show_bug.cgi?id=16735
+ var new_event = new Event("keypress", {
+ 'bubbles': old_event.bubbles,
+ 'cancelable': old_event.cancelable,
+ });
+
+ new_event.viewArg = old_event.viewArg;
+ new_event.ctrl = old_event.ctrl;
+ new_event.alt = old_event.alt;
+ new_event.shift = old_event.shift;
+ new_event.meta = old_event.meta;
+ new_event.char = old_event.char;
+ new_event.key = old_event.key;
+ new_event.charCode = old_event.charCode;
+ new_event.keyCode = old_event.keyCode || old_event.which; // Firefox doesn't set keyCode for keypresses, only keyup/down
+ new_event.which = old_event.which;
+ new_event.dispatched_by_barcode_reader = true;
+
+ old_event.target.dispatchEvent(new_event);
+ }
+ }
+ },
+
+ element_is_editable: function(element) {
+ return $(element).is('input,textarea,[contenteditable="true"]');
+ },
+
+ keyup_handler: function(e){
+ this.key_released = true;
+ },
+
+ handler: function(e){
+ if (! e.dispatched_by_barcode_reader) {
+ if (! this.key_released) { // don't allow key repeat
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ return;
+ }
+
+ this.key_released = false;
+
+ // We only stop events targeting body (meaning nothing is
+ // focused). We do not stop events targeting other elements
+ // because we have no way of redispatching 'genuine' key events
+ // that trigger native event handlers of elements. So this means
+ // that our fake events will not appear in eg. an
+ // element. The addition of the contentEditable attribute in HTML5
+ // means that this is not only limited to ,