Skip to content

About

Navigateur portable Linux (Qt6) oriente economie de donnees et vie prive

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

BrowserECO-Linux — DataSaver Browser (Qt6 C++)

Build et tests Licence GPL-3.0 Qt

Navigateur portable Linux orienté économie de données et vie privée. Version 1.4 — signal vie privée Sec-GPC, exception d'images par site, robustesse, blocage adverts conforme, build et tests propres.

L'historique complet du projet est dans les tags Git : v1.2.0 (base de référence « éco + robustesse ») et v1.3.0 (exception d'images par site).


Ce que la 1.4 apporte

Sec-GPC: 1 — le signal de vie privée

Save-Data demande un mode économie à qui accepte de l'honorer. Sec-GPC (Global Privacy Control) est le signal normalisé qui exprime le consentement de la personne : il est envoyé sur toutes les requêtes, en plus de Save-Data, et coûte 1 octet.

Les deux sont indépendants (deux interrupteurs distincts, ou un seul pour Save-Data qui suit l'économie de données). Mesure faite avant d'écrire quoi que ce soit, sur la machine de développement :

Échantillon Sans Sec-GPC Avec Sec-GPC Écart
5 images statiques (Google, Bing, YouTube, GitHub) 134 274 o 134 274 o 0 %
Vignette Bing extraite d'une recherche d'images 6 651 o 6 651 o 0 %

Conclusion honnête : aucun gain mesurable sur les ressources joignables depuis cette machine. L'effet documenté de GPC concerne le service de Google et Bing qui choisit la résolution des images ; ces pages n'ont pas livré de vignettes exploitables depuis cette IP (réponse sans image pour Google). Donc :

  • on l'active parce que c'est le signal correct et gratuit, pas parce qu'il ferait économiser des octets — aucun chiffre d'économie n'est revendiqué ;
  • pour mesurer sur votre propre connexion : onglet Eco → Signal vie privée, puis DevTools → Network → comparer le poids des images avec l'interrupteur activé puis désactivé ;
  • désactivable : secGpc=0 dans settings.txt.

Le test test_headers le prouve de bout en bout : un serveur HTTP local reçoit les requêtes du navigateur et lit les en-têtes — Sec-GPC: 1 est présent sur la page et sur les images, absent une fois désactivé, et Save-Data reste indépendant.

Ce que la 1.3 apporte

Blocage contournable / mal interprété

Symptôme Cause corrigée
…/ads.js?a=.pdf passait la liste de blocage l'extension était cherchée dans l'URL entière : elle est désormais testée sur url.path() seulement
74 règles ALLOW (-règle) bloquaient au contraire le marqueur - n'était pas géré : ces lignes sont ignorées
24 019 règles ## masquage d'élément bloquaient des URL ##, #@# sont ignorées (elles ne sont jamais réseau)
1 059 règles $domain=… appliquées partout elles sont ignorées : appliquées globalement, elles bloquaient des sites sans rapport
Une exception @@ par chemin désactivait tout un domaine seules les exceptions « hôte seul » sont prises en compte

Robustesse

  • shouldBlock() : 1 900 µs → 9 µs par requête (mesuré, thread IO de QtWebEngine, vraies listes). Index par hôte exact + index des règles libres par jeton, plus de parcours linéaire de toutes les règles à chaque requête.
  • Plus de QRegularExpression reconstruite à chaque requête (156 µs → 1 µs).
  • La ligne d'état n'est plus écrasée 350 ms après le message : une erreur de chargement ou de téléchargement restait invisible (le timer réécrivait le texte au-dessus). Les messages temporaires ont maintenant la main.
  • Compteurs interrogés sur événement et non toutes les 350 ms : c'est ce qui gonflait history.db-wal à 4 Mo pour 300 lignes. journal_size_limit + wal_checkpoint(TRUNCATE) à la fermeture, écritures en transaction.
  • La croix de fermeture d'onglet suivait un index figé : après un déplacement d'onglet, elle fermait le mauvais onglet.
  • m_tabInfos n'était jamais réordonné alors qu'il était indexé par position : la session était sauvegardée dans le désordre.
  • F6 ouvrait la page d'erreur de Chromium (setUrl("about:home") au lieu du setHtml utilisé partout ailleurs) ; le bouton « Recharger » affichait l'icône « Arrêter » mais ne faisait que recharger.
  • Crash du renderer : aucun gestionnaire → un onglet blanc définitivement. Une page d'erreur avec « Réessayer » s'affiche maintenant.
  • Téléchargements : deleteLater() sur l'objet émetteur depuis son propre signal, et boîte de dialogue modale imbriquée dans le handler (source du SIGSEGV historique). Résolu par QPointer et exécution différée.
  • Mode privé : il ne désactivait que les cookies persistants — le profil Qt continuait d'écrire localStorage/cache sur le disque. Le profil est maintenant hors disque.
  • Écran null (QPA headless, machine sans sortie) : primaryScreen()->… sans test → démarrage en crash.
  • Session restaurée : liste blanche de schémas (javascript:, file:, data: sont ignorés) et limite de 12 onglets.
  • Recherche dans la page, zoom et réglages : une écriture de fichier par cran de molette → écriture groupée. Filtrage de l'historique groupé (250 ms).
  • Suggestions réseau : délai maximal (8 s), statut HTTP vérifié, et les résultats sont rattachés à la frappe qui a déclenché la requête (aupour- vant, une frappe rapide affichait les résultats de la requête précédente).
  • SearXNG / endpoint de suggestions : javascript: et autres schémas refusés.
  • Hygiène du dépôt : .gitignore (profil QtWebEngine, bases SQLite, backups, exports, filtres), settings.txt réel et WebEngineProfile/ exclus du dépôt, settings.txt.exemple vierge (le fichier réel contenait une URL de session privée), LICENCE GPL-3.0, attribution des listes de filtres, CI GitHub Actions, .deb et métadonnées AppStream.

Images : exception par site (palier A)

« Images OFF » est le réglage le plus rentable — mesuré sur une photo 1600×1067 (308 Ko) : 182 Ko en 1280 px, 76 Ko en 800 px, 29 Ko en 480 px, soit −41 % à −91 %. Mais il rend les sites photos, recettes ou documentation illisibles.

Le clic droit propose donc « Images de ce site » (entrée cochable qui reflète l'état réel), et l'autorisation est mémorisée :

  • la décision porte sur le site visité, pas sur l'hôte de l'image : les images viennent presque toujours d'un CDN (i.ytimg.com, cloudfront…) ;
  • la correspondance se fait sur les frontières de label : example.com couvre www.example.com et cdn.example.com, mais pas notexample.com ;
  • le réglage est persisté dans settings.txt (imageAllow=) ; une entrée contenant un / ou sans point est ignorée à la lecture ;
  • la page est rechargée après bascule, sinon les images resteraient absentes de l'écran et le réglage semblerait sans effet ;
  • la barre d'état indique le nombre d'exceptions : Eco ON +NoImg/2 ;
  • remise à zéro : Menu → Images autorisées : tout réinitialiser (entrée présente seulement s'il existe au moins une exception).

Le curseur « qualité des images » reste, lui, sans effet tant que la compression n'est pas branchée (voir Limite connue).

Ergonomie

  • Menu contextuel au clic droit : il n'y en avait aucun. Ouverture d'un lien ou d'une image dans un onglet (avant ou de fond), copie du lien / de l'image / de la sélection, recherche de la sélection avec le moteur courant, recharger ou arrêter, zoom, code source, inspecteur, réglages Data saver. Les entrées n'apparaissent que si elles ont du sens (pas de menu à moitié gris), le tout en français — le menu par défaut de Qt est en anglais et QWebEngineView ne le remplace pas de façon fiable.
  • Bouton bouclier enfin utile : il était setEnabled(false) depuis sa création, donc un contrôle mort dans la barre d'outils et dans le parcours de tabulation. Il bascule maintenant le mode économie de données, avec une icône qui reflète l'état.
  • Infobulles corrigées : elles annonçaient « Alt+clic = nouvel onglet », ce que le code ne fait pas (BrowserPage::createWindow renvoie volontairement l'onglet courant).

Résumé des changements

1. Moteur de recherche : plus précis, toujours léger

Avant : un seul moteur, Mojeek (~5,5 Ko de HTML, mais un index indépendant très réduit → beaucoup de résultats manqués).

Maintenant : 9 moteurs au choix, chacun avec son poids réel mesuré, et une chaîne de repli automatique quand un moteur sert un mur anti-bot.

Moteur Poids JS Précision
DuckDuckGo (HTML) ~14 Ko non index Bing — défaut
SearXNG (méta) ~10 Ko non agrège Google/Bing/DDG — la plus précise
Mojeek ~6 Ko non index indépendant, réduit
Wiby ~4 Ko non web indie
Marginalia ~40 Ko non index niche
Startpage ~22 Ko oui résultats Google
Yep ~20 Ko oui index communautaire
Brave Search ~300 Ko oui très précise, coûteuse
Google ~500 Ko oui très coûteuse

La règle eco tient : le repli automatique ne bascule jamais vers un moteur qui nécessite du JavaScript. Un test le vérifie.

Changer de moteur : Alt+1 … Alt+9, le badge à droite de la barre d'adresse, ou le panneau Eco.

Suggestions

Deux sources, fusionnées :

  • Historique local : 0 octet, instantané.
  • API DuckDuckGo : ~200 octets par frappe pour 8 propositions, sans JS ni traçage. Désactivable (panneau Eco → Service de suggestions).

Le debounce est de 170 ms et chaque réponse obsolète est ignorée : taper vite n'attrape pas de rafale de requêtes.

Bangs et raccourcis de sites

Résolus localement : !w, !so, !gh, !mdn, !yt, !wp, !npm… Un site direct coûte une requête au lieu d'une page de résultats complète.

!w linux kernel        → fr.wikipedia.org  (1 requête)
so lambda              → stackoverflow.com  (1 requête)
gh qt6                 → github.com         (1 requête)
!sxiu test             → moteur (typo gérée : le "!" est retiré)

Repli anti-bot

Certains moteurs répondent 200/202 avec une page de vérification au lieu des résultats. Sans détection, la recherche paraît marcher mais affiche du vide. Un script JS de ~700 octets inspecte la page chargée ; si aucun résultat n'est trouvé, le navigateur bascule sur le moteur suivant et l'indique dans la barre d'état. Le moteur qui répond est alors adopté : le badge change et les recherches suivantes l'utilisent, au lieu d'annoncer « DDG » en affichant du SearXNG.

Challenge anti-bot : l'image du captcha doit s'afficher

Quand un moteur sert une page de vérification, le blocage d'images est levé sur cette page. Sans cela, l'utilisateur voit « challenge » mais ne peut pas le résoudre : l'image du captcha est bloquée par le réglage « Images OFF » et le navigateur devient inutilisable. C'est le seul cas où l'économie de données passe au second plan.

Si les moteurs légers sont tous bloqués, le navigateur ne boucle plus : il laisse la vérification affichée (maintenant résolvable) et l'indique.

2. Panneau du haut

[←][→][⟳][⌂]  [🔒 Rechercher ou saisir une URL      ]  [🔍DDG ▾]  [🛡]  [Eco]  [☰]
 ─────────────────────────────────────────────────────────────────────────────────
  [🔍 Nouvel onglet ×][🔍 second onglet ×][+]           ← barre d'onglets
 ─────────────────────────────────────────────────────────────────────────────────
 Filtres : 114310 · Cache : 0 · Historique : 3          Eco ON · 47 bloqués · ≈2,1Mo  Zoom 100 %

Ce qui a changé :

  • Barre d'adresse : icône de sécurité (cadenas HTTPS / avertissement HTTP), favicon de l'onglet, badge du moteur cliquable.
  • Panneau Eco (Ctrl+E) : les 4 réglages et le curseur de qualité qui squashaient la barre sont regroupés. Le panneau affiche aussi les compteurs par catégorie (pubs, trackers, polices, médias, images, préchargement, streaming) et l'économie estimée.
  • Icônes vectorielles dessinées à la volée (src/ui/icons.cpp) : nettes à toute taille, colorées selon le thème, 0 octet téléchargé, aucun emoji.
  • Croix de fermeture dessinée (l'icône par défaut de Fusion était un pavé rouge illisible).
  • Bouton + en fin de barre d'onglets, croix qui suit l'onglet déplacé.
  • Suggestions : liste déroulante avec historique local, sélection au clavier (↑/↓/Tab), repli automatique à la perte de focus ou au changement d'onglet.
  • Menu hamburger et raccourcis (F1) : un seul générateur, testé contre les QShortcut réels — impossible d'annoncer un raccourci inexistant.

3. Corrections trouvées en chemin

Elles n'étaient pas visibles avant les tests :

Symptôme Cause
Plantage après 1 frappe dans la barre d'adresse deleteLater() appelé deux fois sur le même QNetworkReply
Page d'accueil illisible {{ littéral : double échappement au lieu d'un jeton {...}
localhost:8080 traité comme un schéma QUrl lit localhost comme scheme
% mangés dans le CSS de l'accueil .arg() interpreting les % du gradient
Doublon deleteLater sur la popup collision popup ↔ QListWidget
Statistiques décalées dans « À propos » %1 utilisé deux fois dans le même arg()
Ctrl+D annoncé mais inexistant raccourci retiré, liste régénérée depuis le code
10 moteurs pour 9 raccourcis le 10ᵉ était inaccessible au clavier
SIGSEGV à la fermeture pages détruites après le profil QtWebEngine
« Menu → Téléchargements » semble figé bouton Fermer sans connexion, QMenu encore vivant pendant le dialogue modal, raccourcis dupliqués menu + fenêtre
Challenge anti-bot à chaque recherche images du captcha bloquées par « Images OFF » → vérification impossible à résoudre ; badge moteur resté sur l'ancien moteur alors que le repli en changeait

Installation

Prérequis

sudo apt install qt6-base-dev qt6-webengine-dev libqt6sql6-sqlite cmake g++

libqt6sql6-sqlite apporte le moteur SQLite : sans lui, Qt ne sait pas ouvrir les bases et l'application perd l'historique et le cache silencieusement (c'est ce que la CI a révélé : aucune base, aucun message). Ce n'est pas un paquet de développement, il est aussi nécessaire à l'exécution — d'où son présence dans les dépendances du paquet .deb.

Qt 6.4 minimum (validé par la CI sur Qt 6.4.2 / Ubuntu 24.04 ; testé en développement sur Qt 6.8). Les rares API plus récentes — délai maximal d'une requête réseau et politique de permissions persistantes, toutes deux depuis Qt 6.8 — sont protégées par #if QT_VERSION : le comportement est identique quelle que soit la version installée.

Build

cd ~/Documents/BrowserECO-Linux
cmake -B build          # Release par défaut si rien n'est précisé
cmake --build build -j$(nproc)
./build/BrowserECO

Ou en une passe, build + tests + installation portable :

./INSTALL.sh                              # installe dans le dossier du projet
./INSTALL.sh --dest /chemin/portable      # ailleurs
./INSTALL.sh --no-tests
./UNINSTALL.sh /chemin/portable           # ne supprime que ce qui a été installé

Ligne de commande

./build/BrowserECO --version
./build/BrowserECO --help
./build/BrowserECO --private              # profil hors disque, rien n'est écrit
./build/BrowserECO --data-dir ~/Beco     # réglages et bases dans un dossier choisi
./build/BrowserECO https://exemple.fr    # ouvre l'URL au démarrage

Une seule instance par dossier de profil : un second lancement s'arrête avec un message au lieu de se disputer settings.txt et les bases SQLite avec le premier.

Tests

./run_tests.sh                              # build + toute la suite
ctest --test-dir build --output-on-failure   # tests seuls (déjà construits)
ctest --test-dir build -R test_adblock      # une suite

Les tests sont des cibles CMake (tests/CMakeLists.txt) : plus de linkage manuel des .o. Chaque test a un timeout (120 s pour les tests purs, 300 s pour ceux qui démarrent QtWebEngine) et s'exécute dans son propre dossier vide, recréé à chaque exécution — aucun test ne peut voir un settings.txt, une base ou un profil réel, ni les réglages laissés par un autre test.

18 suites, exécution headless (QT_QPA_PLATFORM=offscreen) :

Suite Couverture
test_adblock sémantique ABP (règles -, ##, $domain=, @@, ancres), absence de bypass par query string, performance (µs par requête sur les vraies listes)
test_searchengine résolution URL/recherche, bangs, repli, moteurs JS écartés, refus des URL javascript:
test_settings ancien format (5 lignes) → clé=valeur, bornes, fichiers corrompus
test_history jokers LIKE échappés, suppression, effacement
test_homepage aucun placeholder résiduel, CSS intact
test_icons chaque icône dessine bien des pixels
test_eco_stats cohérence et bornage des compteurs d'économie
test_images exceptions d'images par site : frontières de label, sous-domaines, faux positifs (notexample.com), persistance, idempotence
test_imagecache cache LRU borné en octets (et non en entrées), clé incluant la transformation, compteurs, 4 threads en parallèle
test_ecoimageurl URL signées du serveur local : aller-retour, URL > 400 caractères, et tentatives de forge (signature absente/falsifiée, charge utile remplacée, w/q altérés, mauvais port)
test_lazy_images chargement a la demande : zero requete avant clic, une seule apres, marqueur _eco absent du CDN, compteur exact, page sans image intacte
test_image_serve bout en bout : vrai navigateur + vrai serveur HTTP. Image 1800 px reçue en 1600 px, une seule requête réseau, octets servis < téléchargés, compression off = inchangé, image 404 = page intacte, rechargement bien recompressé
test_imagecodec politique de compression seule (aucun réseau) : refus (seuil, qualité 0, GIF/SVG, données illisibles), redimensionnement (plafond, jamais d'agrandissement, aspect), choix de format, et l'invariant « compressé ⇒ plus léger » vérifié sur 120 combinaisons
test_headers serveur HTTP local qui lit les en-têtes reçus : Sec-GPC et Save-Data partent vraiment, se coupent vraiment, restent indépendants (page et images)
serp_test repli anti-challenge, détection de mur, exemption d'images sur les pages de vérification. 2 cas font de vraies requêtes réseau : ignorés sauf si BROWSERECO_NET_TESTS=1 (un runner CI, en adresse cloud, reçoit un mur anti-bot différent et rendrait le test instable)
freeze_test anti-gel : Menu → Téléchargements, thread témoin
ui_test 21 tests d'interface (dont menu contextuel et entrée d'exception d'images) : barre d'adresse, suggestions, panneau Eco, historique, raccourcis, onglets, zoom, raccourcis annoncés = réels, menu contextuel, bouton Eco

Développement

Les données de développement ne sont jamais dans l'arbre source : elles vont dans ~/.local/share/BrowserECO/dev. On peut donc supprimer build/, reconfigurer ou faire n'importe quelle manip dans le dépôt sans toucher à un profil de navigation.

./dev.sh                 # compile si besoin et lance l'application (données isolées)
./dev.sh test            # compile + 12 suites de tests
./dev.sh check           # compilation propre (0 warning) + tests + .desktop + version
./dev.sh install [préf.] # installe (par défaut ~/Applications/BrowserECO-dev)
./dev.sh clean           # supprime build/
./dev.sh purge-data      # supprime les données de dev (demande confirmation)

Deux installations, deux profils

Copie personnelle Développement
Version figée sur un tag en cours
Installation ~/Applications/BrowserECO-<version> ./dev.sh install
Données ~/.local/share/BrowserECO/perso ~/.local/share/BrowserECO/dev

Le dossier de données est choisi par --data-dir (ou BROWSERECO_DATA_DIR) : on peut donc mettre le binaire sur une clé USB ou une partition NTFS sans risque, les bases SQLite (WAL) restant sur un disque ext4.

Règles de contribution

  • settings.txt, les bases, WebEngineProfile/ et les données de navigation ne doivent jamais être committés (.gitignore s'en charge) ;
  • toute correction de bug s'accompagne d'un test dans tests/ ;
  • le format de settings.txt est centralisé dans SettingsStore, testé par test_settings — ne pas dupliquer la logique de lecture ailleurs ;
  • ./dev.sh check doit sortir « TOUT EST VERT » avant un commit ;
  • version : une seule source, CMakeLists.txt ; les tags Git marquent les versions.

Structure

BrowserECO-Linux/
├── CMakeLists.txt                  ← cible unique, warnings, tests, install, CPack
├── LICENCE                         ← GPL-3.0 (texte intégral)
├── run_tests.sh                    ← build + ctest
├── dev.sh                          ← point d'entrée du développement (run/test/check/install)
├── INSTALL.sh / UNINSTALL.sh       ← installation portable (+ .deb via cpack)
├── settings.txt.exemple            ← modèle de réglages (sans session)
├── BrowserECO.desktop.in           ← modèle d'entrée de menu (généré à l'install)
├── BrowserECO.desktop.example      ← idem pour un déploiement portable manuel
├── cmake/                          ← version.h.in, install_desktop.cmake.in
├── packaging/                      ← AppStream (métadonnées .deb)
├── Filters/update.sh               ← récupère EasyList/EasyPrivacy
├── src/
│   ├── main.cpp
│   ├── mainwindow.{h,cpp,ui}       ← barre d'adresse, barre d'onglets, menu
│   ├── browserpage.{h,cpp}         ← liens internes browseeco://
│   ├── ecointerceptor.{h,cpp}      ← blocage + compteurs par catégorie
│   ├── services/
│   │   ├── searchengine.{h,cpp}    ← 9 moteurs, bangs, suggestions
│   │   ├── serpguard.{h,cpp}       ← détection des murs anti-bot
│   │   ├── settingsstore.{h,cpp}   ← lecture/écriture atomique de settings.txt
│   │   ├── adblocker.{h,cpp}
│   │   ├── cachemanager.{h,cpp}
│   │   ├── historymanager.{h,cpp}
│   │   ├── imagecodec.{h,cpp}         ← politique de compression (testée seule)
│   │   ├── imagecache.{h,cpp}         ← cache LRU des images servies
│   │   ├── ecoimageurl.{h,cpp}        ← URL signées du serveur d'images
│   │   └── ecoimageserver.{h,cpp}     ← serveur 127.0.0.1 qui sert les images
│   └── ui/
│       ├── omnibox.{h,cpp}         ← barre d'adresse + suggestions
│       ├── ecopanel.{h,cpp}        ← panneau Data saver
│       └── icons.{h,cpp}           ← icônes vectorielles (QPainter)
├── tests/                          ← 18 tests + CMakeLists.txt (CTest)
├── Filters/                        ← EasyList + EasyPrivacy (~116 000 règles)
│   ├── update.sh                   ← téléchargement des listes (non versionnées)
│   ├── LICENSE                     ← attribution obligatoire
│   └── README.md
└── .github/workflows/ci.yml        ← build, tests, .desktop, .deb

La version (1.2.0) est écrite une seule fois, dans CMakeLists.txt : elle est générée dans browsereco_version.h et utilisée par --version, la fenêtre « À propos » et le User-Agent. Plus de « 1.0.0 » oublié dans main.cpp pendant que le README annonçait 1.1.


Réglages (settings.txt, à côté du binaire)

dataSaver=1          # en-tête Save-Data + bloqueurs
imagesOff=1          # pas d'images
ultraEco=0           # + streaming/favicons bloqués
favicons=1           # sauf en mode Ultra
quality=65           # qualité WebP 0-85 (0 = pas de compression)
zoom=1.00
engine=ddg           # ddg | searx | mojeek | wiby | marginalia | startpage | yep | brave | google
searxUrl=https://searx.be/search
providerUrl=https://duckduckgo.com/ac/
autoFallback=1       # bascule si mur anti-bot
remoteSuggest=1      # suggestions réseau (~200 o)
restoreSession=0
secGpc=1             # signal Global Privacy Control (Sec-GPC: 1)
imageAllow=          # sites dont les images sont autorisées (clic droit)
session=             # écrit automatiquement à la sortie (URLs séparées par « | »)

Priorité du dossier de profil : --data-dir > variable BROWSERECO_DATA_DIR

dossier du binaire (si inscriptible) > ~/.local/share/BrowserECO.

L'ancien format (5 lignes, qualité seule en 2ᵉ ligne) est lu sans erreur : le test test_settings couvre ce cas.


Raccourcis

Navigation
Ctrl+T / Ctrl+W nouvel onglet / fermer
Ctrl+Tab / Ctrl+Shift+Tab onglet suivant / précédent
Ctrl+L barre d'adresse
Alt+← / Alt+→ retour / avancer
F5 / Ctrl+Shift+R recharger / recharger sans cache
Échap arrêter le chargement, fermer les suggestions
Ctrl+F rechercher dans la page
F6 / F11 accueil / plein écran
Ctrl+molette, Ctrl+0, Ctrl+=, Ctrl+- zoom
Économie de données
Ctrl+E panneau Data saver
Alt+1 … Alt+9 changer de moteur
Ctrl+H / Ctrl+J historique / téléchargements
F1 raccourcis

Portabilité

  • settings.txt, cache.db, history.db et WebEngineProfile/ à côté du binaire : aucun écrit dans ~ si le dossier est inscriptible, repli automatique sur ~/.local/share/BrowserECO sinon.
  • --private : profil hors disque (aucun WebEngineProfile, aucun localStorage, aucun cache écrit), cache mémoire 20 Mo, historique non écrit.
  • Dépend des libs Qt6 système, aucun bundling.
# Menu d'applications (installation système : l'entrée est générée avec le
# bon préfixe, y compris Exec= et Icon=)
sudo cmake --install build
update-desktop-database /usr/share/applications

Paquet .deb

cmake -S . -B build-deb -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=/usr -DBROWSERECO_DEB_LAYOUT=ON \
      -DBROWSERECO_BUILD_TESTS=OFF
cmake --build build-deb -j$(nproc)
cpack --config build-deb/CPackConfig.cmake -G DEB

Licences

  • Code : GPL-3.0 — texte intégral dans LICENCE.
  • Listes de filtres : EasyList / EasyPrivacy (tiers, GPLv3 ou CC BY-SA 3.0) — attribution et conditions dans Filters/LICENSE et Filters/README.md. Elles ne sont pas incluses dans le dépôt (3,5 Mo) ; sans elles l'application démarre et l'indique (« Filtres : 0 »).

Chargement d'images à la demande (active)

Interrupteur « Images à la demande » dans le panneau Eco : les images sont bloquées au niveau réseau, remplacées visuellement par une bordure pointillée, et un clic ne charge que celle-là.

Décision Pourquoi
Blocage dans l'intercepteur, pas en JS Un script DOM arrive trop tard pour les images du HTML initial : le parseur a déjà lancé les requêtes. L'intercepteur est synchrone, donc jamais raté.
AutoLoadImages n'est PAS coupé Mesuré : avec ce réglage off, Chromium refuse tout chargement d'image déclenché par script (restaurer src puis decode() ne charge rien), et un clic ne recharge pas une image dont la src a été remplacée. C'est un impasse.
Marqueur _eco=1 à la demande Le script ne peut pas parler à l'intercepteur autrement qu'en changeant l'URL. L'intercepteur retire le marqueur avant toute requête sortante : le CDN d'origine ne le voit jamais (testé).
URLs chargées mémorisées Sans cela, recharger la page rebloquerait des images que l'utilisateur avait explicitement demandées. Plafonnées à 2048 entrées.
Script en monde applicatif La page ne peut ni voir le script ni le désactiver (un site hostile ne doit pas pouvoir forcer le chargement).

Ce qui n'est pas couvert (hors périmètre, documenté) : srcset / <picture>, images en CSS background-image, et tout ce qui n'est pas une balise <img>.

test_lazy_images le prouve dans un vrai navigateur : zéro requête vers le serveur d'origine tant que l'utilisateur n'a pas cliqué, une seule requête après clic, marqueur absent du CDN, compteur exact, page sans image intacte, réglage désactivé = chargement normal.

Compression des images (active)

Le curseur de qualité est branché : chaque image est réécrite vers un petit serveur HTTP local (127.0.0.1, port éphémère), qui récupère l'original, le compresse et le sert. Aucune connexion ne sort de la machine.

page ──▶ intercepteur ──redirection──▶ 127.0.0.1:port/i?u=<original>&s=<signature>
                                            │  QNAM + en-têtes rejoués
                                            ▼
                                        ImageCodec  ──▶  cache LRU  ──▶ navigateur
Décision Pourquoi
Serveur local, pas schéma privé Qt 6.8 n'honore pas la redirection d'un intercepteur vers un schéma personnalisé pour une sous-ressource : l'original est annulé et rien n'est demandé (mesuré). http://127.0.0.1 est de plus explicitement exempté du blocage « mixed content ».
URL signées Notre URL est une vraie URL http:// : une page pourrait l'appeler. Chaque URL générée est signée avec une clé aléatoire créée au démarrage du processus. Une page peut seulement rejouer une URL qu'elle a déjà vue passer, jamais demander une autre cible (testé : charge utile remplacée, signature falsifiée, w/q altérés, autre port).
Jamais de seconde requête Quand la compression ne sert à rien (GIF, SVG, image sous le seuil), le serveur répond avec les octets déjà reçus. Rediriger vers l'original ferait télécharger l'image deux fois.
Échec ouvert Erreur réseau ou délai dépassé → 502, le navigateur retente l'original et l'image s'affiche. Une image en 404 ne casse pas la page (testé).
Pas d'Authorization rejouée Une redirection du serveur vers un autre hôte la lui transmettrait.
CORS absent Une page ne doit pas pouvoir lire la réponse, même d'une URL signée qu'elle a vue passer.

Limites assumées, mesurées et documentées :

  • Cookies non rejoués : QWebEngineCookieStore n'expose aucune lecture dans ce Qt. Une image derrière une authentification échoue en 403 → l'échec ouvert la fait retenter par le navigateur, donc elle s'affiche, simplement non compressée.
  • Chrome 142+ : la boucle locale demandera une permission — voir la section dédiée ci-dessous. Sans effet sur le Qt 6.8 embarqué, mais c'est la contrainte qui pèserait le plus lourd si le projet montait en version de Qt.
  • Pas de WebP ici : cette installation n'a pas d'encodeur WebP, le codec bascule en JPEG. Avec qt6-image-formats-plugins, le gain gagne ~20 % de plus.
  • Les images en CSS (background-image) ou construites en JavaScript ne passent pas par l'intercepteur : seules les balises <img> sont réécrites.

Gain mesuré sur de vraies images (fonds d'écran 3840×2160 en PNG) :

Largeur servie Octets économisés
1600 px −79 % à −95 %
1200 px −87 % à −97 %
800 px −94 % à −98 %

Le test test_image_serve rejoue tout cela dans un vrai navigateur contre un vrai serveur : image reçue en 1600 px au lieu de 1800, une seule requête réseau pour l'image, et octets servis < octets téléchargés.


Chrome 142+ : le point de vigilance de cette architecture

C'est la seule partie du projet qui repose sur la documentation et non sur une mesure. Elle mérite donc une explication complète.

Ce qui se passe

Notre serveur d'images écoute sur 127.0.0.1, avec un port éphémère. Google durecit progressivement l'accès des pages web au réseau local, parce que « page web → adresse locale » est une vraie surface d'attaque (CSRF vers une box, un routeur, un appareil connecté).

À partir de Chrome 142, une requête vers la boucle locale demande une permission explicite à l'utilisateur :

Navigateur Ce que voit l'utilisateur
Chromium de Qt 6.8 (le nôtre) rien : le trafic passe
Chrome 142 et plus une demande de permission « loopback-network »

Si l'utilisateur refuse, plus aucune compression d'image. Le navigateur continue de fonctionner normalement : les images se chargent, sans compression.

Pourquoi ce n'est pas bloquant aujourd'hui

Trois faits, dont deux mesurés :

  1. Le schéma privé ecoimg:// ne fonctionne pas (mesuré) : Qt 6.8 annule silencieusement la redirection d'un intercepteur vers un schéma personnalisé pour une sous-ressource. L'original est annulé, rien n'est demandé, et le gestionnaire n'est jamais sollicité. Le serveur local n'est donc pas un choix de confort, c'est la seule voie qui marche sous ce Qt.
  2. http://127.0.0.1 est explicitement exempté du blocage « mixed content », y compris depuis une page HTTPS en contexte sécurisé (documenté par Chrome).
  3. Le précurseur de cette règle, la PNA, est en mode simple avertissement dans le Chromium embarqué : le trafic passe, seul un avertissement est journalisé dans la console du site.

Ce qu'il faudrait faire le jour où Qt monte

Le jour où le projet passe à un Qt embarquant Chromium ≥ 142, deux pistes :

  • Demander la permission — techniquement simple, mais l'utilisateur voit une demande à chaque visite, ce qui nuit à la crédibilité d'un navigateur « éco ».
  • Revenir au schéma privé — c'était le plan initial, et il serait de nouveau possible si le blocage Qt 6.8 était contourné. Attention : ce blocage n'est pas documenté par Qt, il a été déduit experimentally. Il faudrait donc vérifier si un Qt plus récent le corrige, et ne pas supposer que le schéma privé fonctionnerait du premier coup.

Comment surveiller

Au moment de toute montée de version de Qt :

# Chromium embarqué dans le Qt installé
strings /usr/lib/*/libQt6WebEngineCore.so.* | grep -m1 -oE 'Chrome/[0-9]+'

Si le numéro ≥ 142, il faut traiter ce point avant de publier la version.

Ce qui n'est PAS concerné

Le chargement d'images à la demande ne dépend pas du réseau local : il bloque les images au niveau de l'intercepteur et libère au clic. Il n'a aucun rapport avec cette contrainte.

About

Navigateur portable Linux (Qt6) oriente economie de donnees et vie prive

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages