diff --git a/manifest.firefox.json b/manifest.firefox.json
index fab6932..064a3a4 100644
--- a/manifest.firefox.json
+++ b/manifest.firefox.json
@@ -54,7 +54,7 @@
}
],
"content_security_policy": {
- "extension_pages": "script-src 'self'; object-src 'self'"
+ "extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' https: data: blob:"
},
"icons": {
"16": "assets/icons/icon16.png",
diff --git a/manifest.json b/manifest.json
index cc5ed6f..917322f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -36,7 +36,7 @@
}
],
"content_security_policy": {
- "extension_pages": "script-src 'self'; object-src 'self'"
+ "extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' https: data: blob:"
},
"icons": {
"16": "assets/icons/icon16.png",
diff --git a/manifest.opera.json b/manifest.opera.json
index 942930d..8e2f2e8 100644
--- a/manifest.opera.json
+++ b/manifest.opera.json
@@ -51,7 +51,7 @@
},
"content_security_policy": {
- "extension_pages": "script-src 'self'; object-src 'self'"
+ "extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' https: data: blob:"
},
"icons": {
"16": "assets/icons/icon16.png",
diff --git a/newtab.html b/newtab.html
index 0826aab..04bea63 100644
--- a/newtab.html
+++ b/newtab.html
@@ -381,9 +381,10 @@
Hinweis: Ein per URL eingebundenes Bild wird bei jedem Öffnen vom fremden Server geladen.
Datei hochladen
diff --git a/src/css/main.css b/src/css/main.css
index ccb0bab..72a9f65 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -2387,6 +2387,10 @@ body.show-desc .bm-desc { display: block; }
.theme-modal-section .setting-row {
padding: 8px 0;
}
+.bg-url-hint {
+ padding: 2px 0 6px;
+ line-height: 1.4;
+}
/* ============================================
ACCORDION SETTINGS
diff --git a/src/js/i18n.js b/src/js/i18n.js
index f1c3c74..8aeaa95 100644
--- a/src/js/i18n.js
+++ b/src/js/i18n.js
@@ -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
diff --git a/src/js/settings.js b/src/js/settings.js
index ec94088..358dbfb 100644
--- a/src/js/settings.js
+++ b/src/js/settings.js
@@ -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 = () => {