Thursday, 28 January 2021

Why does this barcode detection cause Chrome or the whole Android OS to crash?

I built a barcode scanning system using Chrome's built-in BarcodeDetector. I based the work on Paul Kinlan's QR code scanner which works fine on my phone, but when I run my own code on my phone, it often causes Chrome or the whole System UI to freeze. Sometimes it gets so bad that I need to restart the phone by holding down the power button.

I have tried debugging in the Chrome developer console, but when the phone freezes, so does the developer console.

When I comment out the actual QR code detection, I can leave the page open in Chrome for 10 minutes and it just keeps running. With QR code detection running, the phone will freeze anywhere from immediately to 3 minutes later.

I put the support files (js and HTML) in a Gist - I don't think they are the issue because everything works when the barcode scanning is commented out.

My first attempt:

// Inspired by/based on on https://github.com/PaulKinlan/qrcode/blob/production/app/scripts/main.mjs

import WebCamManager from './scan/WebCamManager.js';

(function () {
    'use strict';

    var QRCodeCamera = function (element) {
        var root = document.getElementById(element);

        var cameraRoot = root.querySelector('.CameraRealtime');
        var cameraManager = new WebCamManager(cameraRoot);

        var cameraVideo = root.querySelector('.Camera-video');

        // Offscreen canvas is supposed to help with processing speed
        var cameraCanvas = new OffscreenCanvas(1,1);
        var context = cameraCanvas.getContext('2d');

        const detector = new BarcodeDetector({
            formats: ['qr_code'],
        });

        cameraManager.onframeready = async function (frameData) {
            cameraCanvas.width = cameraVideo.videoWidth;
            cameraCanvas.height = cameraVideo.videoHeight;
            context.drawImage(frameData, 0, 0, cameraVideo.videoWidth, cameraVideo.videoHeight);
            if (self.onframe) {
                // Comment out the line below to stop processing QR codes
                await self.onframe(cameraCanvas);
            }
        };

        var processingFrame = false;

        self.onframe = async function (cameraCanvas) {
            // There is a frame in the camera, what should we do with it?
            if (processingFrame == false) {
                processingFrame = true;

                let result = await detector.detect(cameraCanvas);
                processingFrame = false;

                if (result === undefined || result === null || result.length === 0) {
                    return
                };

                if ('vibrate' in navigator) {
                    navigator.vibrate([200]);
                }

                cameraManager.stop();

                var currentURL = new URL(window.location.href);
                var newURL;
                if (result[0].rawValue
                    && (newURL = new URL(result[0].rawValue))
                    && newURL.hostname == currentURL.hostname
                    && newURL.pathname.startsWith('/pickup/qr/')
                ) {
                    window.location.href = newURL;
                } else {
                    alert('Unsupported QR Code: ' + result[0].rawValue);
                }
                cameraManager.start();
            }
        };
    };

    window.addEventListener('load', function () {
        var camera = new QRCodeCamera('camera');
    });
})();

I also tried splitting the QR detection to a worker (worker code in Gist), but I have the same issue:


    const worker = new Worker('/js/scan-worker.js');
    worker.addEventListener('message', async (e) => {
        if (Array.isArray(e.data)) {
            var result = e.data;

            if (result === undefined || result === null || result.length === 0) {
                processingFrame = false;
                return
            };

            if ('vibrate' in navigator) {
                navigator.vibrate([200]);
            }

            var currentURL = new URL(window.location.href);
            var newURL;
            if (result[0].rawValue
                && (newURL = new URL(result[0].rawValue))
                && newURL.hostname == currentURL.hostname
                && newURL.pathname.startsWith('/pickup/qr/')
            ) {
                worker.terminate();
                window.location.href = newURL;
            } else {
                alert('Unsupported QR Code: ' + result[0].rawValue);
            }
        } else {
            var newError = document.createElement('div');
            newError.classList.add('alert', 'alert-danger');
            newError.innerHTML = e.data;
            errorContainer.prepend(newError);
            worker.terminate();
        }

        processingFrame = false;
    });

    cameraManager.onframeready = async function (frameData) {
        if (processingFrame == false) {
            cameraCanvas.width = cameraVideo.videoWidth;
            cameraCanvas.height = cameraVideo.videoHeight;
            context.drawImage(frameData, 0, 0, cameraVideo.videoWidth, cameraVideo.videoHeight);

            if (self.onframe) {
                await self.onframe(cameraCanvas, context);
            }
        }
    };

    self.onframe = async function (cameraCanvas, context) {
        // There is a frame in the camera, what should we do with it?
        if (processingFrame == false) {
            processingFrame = true;

            worker.postMessage(context.getImageData(0, 0, cameraCanvas.width, cameraCanvas.height));
        }
    };

Not sure if this would make a difference - all the JS code is run through Laravel Mix.



from Why does this barcode detection cause Chrome or the whole Android OS to crash?

No comments:

Post a Comment