Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Broken Website] www.mozilla.org, SVG background image is not handled correctly #11069

Open
2 tasks done
ngoclong19 opened this issue Apr 8, 2023 · 1 comment
Open
2 tasks done

Comments

@ngoclong19
Copy link
Contributor

Prerequisites

  • I searched for any existing report about this website issue to avoid opening a duplicate.
  • I can reproduce this website issue in a new, unmodified web browser profile with Dark Reader installed as the only extension.

Website Issue Description

The footer icons are not inverted correctly.

On Firefox, some are not inverted.
On Edge, some are vanished, and some are not inverted.

Website Address

https://www.mozilla.org/en-US/firefox/

Steps To Reproduce

  1. Open the website address
  2. Scroll down to the bottom, next to "Follow @mozilla".

Screenshots

DR off DR on (Firefox) DR on (Edge)
ảnh ảnh ảnh
The second icon and the last three icons are not inverted. The second icon is vanished. The last three icons are not inverted.

Operating System

Windows 10 Pro 19045.2728 64-bit

Web Browser name and version

Mozilla Firefox 111.0.1 (64-bit), Microsoft Edge 112.0.1722.34 (64-bit)

Dark Reader version

4.9.62

Additional Context

To fix the issue, I tried the following in the Dynamic Theme Editor:

================================

www.mozilla.org

INVERT
.mzp-c-footer-links-social a

IGNORE IMAGE ANALYSIS
.mzp-c-footer-links-social a

================================

The INVERT works, but IGNORE IMAGE ANALYSIS don't.
DR still injects its SVG background images into the first and third icons.

@ngoclong19
Copy link
Contributor Author

ngoclong19 commented Apr 8, 2023

Further debugging, it seems that this issue is the combination of how different browsers handle SVG image without width or height attribute, and how DR analyzes image.

Debug code, adapted from DR's code. Please navigate to the website and run this code in the console.
'use strict'

const MAX_ANALIZE_PIXELS_COUNT = 32 * 32;
const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
let canvas;
let context;

async function readResponseAsDataURL(response) {
    const blob = await response.blob();
    const dataURL = await (new Promise((resolve) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.readAsDataURL(blob);
    }));
    return dataURL;
}

function createCanvas() {
    const maxWidth = MAX_ANALIZE_PIXELS_COUNT;
    const maxHeight = MAX_ANALIZE_PIXELS_COUNT;
    canvas = document.createElement('canvas');
    canvas.width = maxWidth;
    canvas.height = maxHeight;
    context = canvas.getContext('2d', {willReadFrequently: true});
    context.imageSmoothingEnabled = false;
}

async function urlToImage(url) {
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.onload = () => resolve(image);
        image.onerror = () => reject(`Unable to load image ${url}`);
        image.src = url;
    });
}

function getSRGBLightness(r, g, b) {
    return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
}

function analyzeImage(image) {
    if (!canvas) {
        createCanvas();
    }
    const {naturalWidth, naturalHeight} = image;
    if (naturalHeight === 0 || naturalWidth === 0) {
        console.log(`logWarn(Image is empty ${image.currentSrc})`);
        return {
            isDark: false,
            isLight: false,
            isTransparent: false,
            isLarge: false,
            isTooLarge: false,
        };
    }

    const size = naturalWidth * naturalHeight * 4;
    if (size > MAX_IMAGE_SIZE) {
        console.log('Skipped large image analyzing(Larger than 5mb in memory)');
        return {
            isDark: false,
            isLight: false,
            isTransparent: false,
            isLarge: false,
            isTooLarge: true,
        };
    }

    const naturalPixelsCount = naturalWidth * naturalHeight;
    const k = Math.min(1, Math.sqrt(MAX_ANALIZE_PIXELS_COUNT / naturalPixelsCount));
    const width = Math.ceil(naturalWidth * k);
    const height = Math.ceil(naturalHeight * k);
    context.clearRect(0, 0, width, height);

    context.drawImage(image, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height);
    const imageData = context.getImageData(0, 0, width, height);
    const d = imageData.data;

    const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
    const DARK_LIGHTNESS_THRESHOLD = 0.4;
    const LIGHT_LIGHTNESS_THRESHOLD = 0.7;

    let transparentPixelsCount = 0;
    let darkPixelsCount = 0;
    let lightPixelsCount = 0;

    let i, x, y;
    let r, g, b, a;
    let l;
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            i = 4 * (y * width + x);
            r = d[i + 0];
            g = d[i + 1];
            b = d[i + 2];
            a = d[i + 3];

            if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
                transparentPixelsCount++;
            } else {
                l = getSRGBLightness(r, g, b);
                if (l < DARK_LIGHTNESS_THRESHOLD) {
                    darkPixelsCount++;
                }
                if (l > LIGHT_LIGHTNESS_THRESHOLD) {
                    lightPixelsCount++;
                }
            }
        }
    }

    const totalPixelsCount = width * height;
    const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;

    const DARK_IMAGE_THRESHOLD = 0.7;
    const LIGHT_IMAGE_THRESHOLD = 0.7;
    const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
    const LARGE_IMAGE_PIXELS_COUNT = 800 * 600;

    return {
        isDark: ((darkPixelsCount / opaquePixelsCount) >= DARK_IMAGE_THRESHOLD),
        isLight: ((lightPixelsCount / opaquePixelsCount) >= LIGHT_IMAGE_THRESHOLD),
        isTransparent: ((transparentPixelsCount / totalPixelsCount) >= TRANSPARENT_IMAGE_THRESHOLD),
        isLarge: (naturalPixelsCount >= LARGE_IMAGE_PIXELS_COUNT),
        isTooLarge: false,
    };
}

const res = await fetch('https://www.mozilla.org/media/img/logos/social/mastodon-black.eda6d9fda842.svg');
const imageURL = await readResponseAsDataURL(res);
const image = await urlToImage(imageURL);
console.log(analyzeImage(image));
console.log((({ width, height, naturalWidth, naturalHeight }) => ({ width, height, naturalWidth, naturalHeight }))(image));

Debug Result

Second Icon

About the second icon https://www.mozilla.org/media/img/logos/social/mastodon-black.eda6d9fda842.svg.

On Firefox

The result we get:

{ isDark: false, isLight: false, isTransparent: false, isLarge: false, isTooLarge: false }
{ width: 0, height: 0, naturalWidth: 0, naturalHeight: 0 }

There is an open bug here https://bugzilla.mozilla.org/show_bug.cgi?id=1607081.
Even if we relax the check at

if (naturalHeight === 0 || naturalWidth === 0) {

We still cannot analyze the image properly due to https://bugzilla.mozilla.org/show_bug.cgi?id=700533.

On Edge

The result we get:

{ isDark: true, isLight: false, isTransparent: true, isLarge: false, isTooLarge: false }
{ width: 150, height: 150, naturalWidth: 150, naturalHeight: 150 }

The image vanished because it has viewBox="0 0 24 24", while its size is 150x150 as reported by Edge.
When DR inverts and injects this image, only the top left corner show up, and this section is almost empty, so the image vanished.

My suggestion is replacing width="${width}" height="${height}" at

`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,

with viewBox="0 0 ${width} ${height}".

Fourth Icon

About the fourth icon https://www.mozilla.org/media/img/logos/social/linkedin-black.5cb4b3466aad.svg.

On Firefox

The result we get is the same as the second icon.

On Edge

The result we get:

{ isDark: true, isLight: false, isTransparent: false, isLarge: false, isTooLarge: false }
{ width: 150, height: 150, naturalWidth: 150, naturalHeight: 150 }

The image is clearly transparent but DR reports it is not, so it fails the check at

} else if (isDark && isTransparent && filter.mode === 1 && !isLarge && width > 2) {

Debug code, adapted from DR's code. Please create a simple web page. Local file does not work, I used Node package `http-server`.
<!DOCTYPE html>
<html>
<body>

<h1>The canvas element</h1>

<div>
  <img id="source" src="https://www.mozilla.org/media/img/logos/social/linkedin-black.5cb4b3466aad.svg" width="100" height="100" />
</div>
<canvas id="myCanvas">Your browser does not support the canvas tag.</canvas>

<script>
const MAX_ANALIZE_PIXELS_COUNT = 32 * 32;
let canvas;
let context;

canvas = document.getElementById('myCanvas');
canvas.width = 200;
canvas.height = 200;
context = canvas.getContext('2d', {willReadFrequently: true});
context.imageSmoothingEnabled = false;

const image = document.getElementById("source");
const {naturalWidth, naturalHeight} = image;

const naturalPixelsCount = naturalWidth * naturalHeight;
const k = Math.min(1, Math.sqrt(MAX_ANALIZE_PIXELS_COUNT / naturalPixelsCount));
const width = Math.ceil(naturalWidth * k);
const height = Math.ceil(naturalHeight * k);
context.clearRect(0, 0, width, height);

context.drawImage(image, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height);
</script>

</body>
</html>

The result:

Firefox Edge
ảnh ảnh
No canvas image, due to issue https://bugzilla.mozilla.org/show_bug.cgi?id=700533 Only part of image is displayed, so the analyzing is incorrect.

My suggestion is replacing context!.drawImage(image, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height) at

context!.drawImage(image, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height);

with context!.drawImage(image, 0, 0, width, height).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant