feat(theme): eigenes Hintergrundbild um https-URLs und Quota-Schutz erweitern
- isValidBgUrl akzeptiert jetzt https:// zusätzlich zu data:/blob: (http bleibt ausgeschlossen wegen Mixed-Content) - CSP img-src 'self' https: data: blob: in allen 3 Manifesten, damit remote Hintergründe deterministisch laden statt still am CSP-Default zu haengen - Upload-Bilder werden vor dem Speichern per Canvas auf die Bildschirmkante (max 2560px) verkleinert und als WebP re-kodiert -> schont chrome.storage.local - URL-Feld: Platzhalter lokalisierbar (data-i18n-placeholder) + Tracking-Hinweis, dass ein per URL geladenes Bild bei jedem Oeffnen vom fremden Server kommt - i18n DE/EN: bg_url.desc + bg_invalid_url an https angepasst, 2 neue Keys
This commit is contained in:
+8
-4
@@ -385,7 +385,9 @@ const STRINGS = {
|
||||
'settings.visible_count': 'Sichtbare Bookmarks',
|
||||
'settings.visible_count.desc': 'Anzahl vor dem Ausblenden',
|
||||
'settings.bg_url': 'Bild-URL',
|
||||
'settings.bg_url.desc': 'Eigenes Hintergrundbild per URL',
|
||||
'settings.bg_url.desc': 'Eigenes Bild per https-URL oder lokalem Upload',
|
||||
'settings.bg_url.placeholder': 'https://… oder leer für Standard',
|
||||
'settings.bg_url.privacy_hint': 'Hinweis: Ein per URL eingebundenes Bild wird bei jedem Öffnen vom fremden Server geladen.',
|
||||
'settings.bg_change': 'Ändern',
|
||||
'settings.bg_apply': 'Übernehmen',
|
||||
'settings.bg_upload': 'Datei hochladen',
|
||||
@@ -396,7 +398,7 @@ const STRINGS = {
|
||||
'settings.onboarding_btn': 'Start',
|
||||
'settings.reset_btn': 'Reset',
|
||||
'settings.bg_upload_btn': 'Upload',
|
||||
'settings.bg_invalid_url': 'Nur lokale Bilder (Upload) sind als Hintergrund erlaubt.',
|
||||
'settings.bg_invalid_url': 'Nur https-URLs oder lokale Bilder (Upload) sind als Hintergrund erlaubt.',
|
||||
'settings.bg_invalid_url.title': 'Ungültige URL',
|
||||
|
||||
// Modals
|
||||
@@ -846,7 +848,9 @@ const STRINGS = {
|
||||
'settings.visible_count': 'Visible bookmarks',
|
||||
'settings.visible_count.desc': 'Number before hiding',
|
||||
'settings.bg_url': 'Image URL',
|
||||
'settings.bg_url.desc': 'Custom background image via URL',
|
||||
'settings.bg_url.desc': 'Custom image via https URL or local upload',
|
||||
'settings.bg_url.placeholder': 'https://… or empty for default',
|
||||
'settings.bg_url.privacy_hint': 'Note: an image loaded via URL is fetched from the remote server every time you open a tab.',
|
||||
'settings.bg_change': 'Change',
|
||||
'settings.bg_apply': 'Apply',
|
||||
'settings.bg_upload': 'Upload file',
|
||||
@@ -857,7 +861,7 @@ const STRINGS = {
|
||||
'settings.onboarding_btn': 'Start',
|
||||
'settings.reset_btn': 'Reset',
|
||||
'settings.bg_upload_btn': 'Upload',
|
||||
'settings.bg_invalid_url': 'Only local images (upload) are allowed as background.',
|
||||
'settings.bg_invalid_url': 'Only https URLs or local images (upload) are allowed as background.',
|
||||
'settings.bg_invalid_url.title': 'Invalid URL',
|
||||
|
||||
// Modals
|
||||
|
||||
+42
-4
@@ -113,7 +113,37 @@ function switchTheme(name) {
|
||||
*/
|
||||
function isValidBgUrl(url) {
|
||||
return typeof url === 'string' && url.length > 0 &&
|
||||
(url.startsWith('blob:') || url.startsWith('data:image/'));
|
||||
(url.startsWith('blob:') || url.startsWith('data:image/') || url.startsWith('https://'));
|
||||
}
|
||||
|
||||
// Eigenes Upload-Bild Quota-schonend verkleinern: auf die laengste Bildschirmkante
|
||||
// (× devicePixelRatio, gedeckelt) herunterrechnen und als WebP neu kodieren. Das spart
|
||||
// gegenueber dem rohen Base64-Upload locker den Grossteil der chrome.storage.local-Quota.
|
||||
// Greift nur beim lokalen Upload (data:-URL ist same-origin, Canvas wird nicht getainted);
|
||||
// https-Hintergruende liegen remote und kosten keine Quota.
|
||||
function downscaleBgImage(dataUrl) {
|
||||
const MAX_DIM = Math.min(2560, Math.round(Math.max(screen.width, screen.height) * (window.devicePixelRatio || 1)));
|
||||
const QUALITY = 0.82;
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const scale = Math.min(1, MAX_DIM / Math.max(img.naturalWidth, img.naturalHeight));
|
||||
const w = Math.max(1, Math.round(img.naturalWidth * scale));
|
||||
const h = Math.max(1, Math.round(img.naturalHeight * scale));
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) { resolve(dataUrl); return; } // kein 2D-Context -> Original behalten
|
||||
ctx.drawImage(img, 0, 0, w, h);
|
||||
// WebP wo verfuegbar (Chrome/Opera/FF142+); sonst faellt toDataURL auf PNG zurueck -> dann JPEG
|
||||
let out = canvas.toDataURL('image/webp', QUALITY);
|
||||
if (!out.startsWith('data:image/webp')) out = canvas.toDataURL('image/jpeg', QUALITY);
|
||||
resolve(out);
|
||||
};
|
||||
img.onerror = () => reject(new Error('image decode failed'));
|
||||
img.src = dataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
// ---- ACCORDION ----
|
||||
@@ -434,7 +464,9 @@ function bindSettingsEvents() {
|
||||
|
||||
// Background URL (im Theme-Modal)
|
||||
document.getElementById('btnChangeBg').addEventListener('click', () => {
|
||||
document.getElementById('bgInputRow').classList.toggle('hidden');
|
||||
// toggle() liefert true, wenn 'hidden' jetzt gesetzt ist -> Hinweis exakt parallel schalten
|
||||
const isNowHidden = document.getElementById('bgInputRow').classList.toggle('hidden');
|
||||
document.getElementById('bgUrlHint').classList.toggle('hidden', isNowHidden);
|
||||
});
|
||||
document.getElementById('btnApplyBg').addEventListener('click', async () => {
|
||||
const url = document.getElementById('bgUrlInput').value.trim();
|
||||
@@ -458,8 +490,14 @@ function bindSettingsEvents() {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async ev => {
|
||||
if (!isValidBgUrl(ev.target.result)) return;
|
||||
settings.bgUrl = ev.target.result;
|
||||
document.getElementById('bgLayer').style.backgroundImage = `url('${ev.target.result}')`;
|
||||
let bg = ev.target.result;
|
||||
try {
|
||||
bg = await downscaleBgImage(bg); // Quota-Schutz: verkleinern + WebP
|
||||
} catch {
|
||||
// Downscale fehlgeschlagen -> Original-Upload nutzen (besser als gar kein Bild)
|
||||
}
|
||||
settings.bgUrl = bg;
|
||||
document.getElementById('bgLayer').style.backgroundImage = `url('${bg}')`;
|
||||
await saveSettings();
|
||||
};
|
||||
reader.onerror = () => {
|
||||
|
||||
Reference in New Issue
Block a user