summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--background.js236
-rw-r--r--inject-css.js101
-rw-r--r--manifest.json14
-rw-r--r--styling/README.md4
4 files changed, 312 insertions, 43 deletions
diff --git a/background.js b/background.js
index 04adfd0..f823262 100644
--- a/background.js
+++ b/background.js
@@ -1,13 +1,173 @@
let SKIP_FORCE_THEMING_KEY = "skipForceThemingList";
let logging = false;
+// Create a cache for pre-processed CSS to speed up repeated visits
+const cssCache = new Map();
+const activeTabs = new Map();
+
+// Preload styles for faster injection
+async function preloadStyles() {
+ try {
+ const data = await browser.storage.local.get([
+ "styles",
+ "transparentZenSettings",
+ ]);
+ const settings = data.transparentZenSettings || {};
+
+ // No point in preloading if styling is disabled
+ if (settings.enableStyling === false) return;
+
+ // Clear the cache when reloaded to ensure fresh styles
+ cssCache.clear();
+
+ if (data.styles?.website) {
+ for (const [website, features] of Object.entries(data.styles.website)) {
+ // Process and store default CSS for each website (with all features enabled)
+ let combinedCSS = "";
+ for (const [feature, css] of Object.entries(features)) {
+ combinedCSS += css + "\n";
+ }
+ cssCache.set(website.replace(".css", ""), combinedCSS);
+ }
+ if (logging) console.log("Styles preloaded for faster injection");
+ }
+ } catch (error) {
+ console.error("Error preloading styles:", error);
+ }
+}
+
+// Handle web requests - allow injecting CSS before any content is loaded
+browser.webNavigation.onBeforeNavigate.addListener((details) => {
+ if (details.frameId === 0) {
+ // Only for main frame
+ // Track active navigations
+ activeTabs.set(details.tabId, details.url);
+
+ // Pre-fetch any styling needed for this URL
+ prepareStylesForUrl(new URL(details.url).hostname, details.tabId);
+ }
+});
+
+// Listen for content scripts announcing they're ready
+browser.runtime.onMessage.addListener(async (message, sender) => {
+ if (message.action === "contentScriptReady" && message.hostname) {
+ try {
+ // Look for cached styles for this hostname or its domain match
+ const hostname = message.hostname;
+
+ // Get settings to check if styling is enabled
+ const settingsData = await browser.storage.local.get(
+ "transparentZenSettings"
+ );
+ const settings = settingsData.transparentZenSettings || {};
+
+ if (settings.enableStyling === false) return;
+
+ const css = await getStylesForHostname(hostname, settings);
+
+ // If we found matching CSS, send it immediately to the content script
+ if (css) {
+ browser.tabs
+ .sendMessage(sender.tab.id, {
+ action: "applyStyles",
+ css: css,
+ })
+ .catch((err) => {
+ if (logging) console.log("Failed to send immediate CSS:", err);
+ });
+ }
+ } catch (error) {
+ console.error("Error handling content script ready message:", error);
+ }
+ } else if (message.action === "enableAutoUpdate") {
+ startAutoUpdate();
+ return true;
+ } else if (message.action === "disableAutoUpdate") {
+ stopAutoUpdate();
+ return true;
+ }
+
+ return false;
+});
+
+// Get appropriate styles for a hostname based on all rules
+async function getStylesForHostname(hostname, settings) {
+ // Check for exact matches
+ if (cssCache.has(hostname)) {
+ return cssCache.get(hostname);
+ } else if (cssCache.has(`www.${hostname}`)) {
+ return cssCache.get(`www.${hostname}`);
+ } else {
+ // Check for wildcard matches (+domain.com)
+ for (const [cachedSite, cachedCSS] of cssCache.entries()) {
+ if (cachedSite.startsWith("+")) {
+ const baseSite = cachedSite.slice(1);
+ if (hostname.endsWith(baseSite)) {
+ return cachedCSS;
+ }
+ } else if (hostname.endsWith(cachedSite)) {
+ return cachedCSS;
+ }
+ }
+
+ // Check for forced styles
+ if (settings.forceStyling) {
+ const skipListData = await browser.storage.local.get(
+ SKIP_FORCE_THEMING_KEY
+ );
+ const siteList = skipListData[SKIP_FORCE_THEMING_KEY] || [];
+ const isWhitelistMode = settings.whitelistMode || false;
+ const siteInList = siteList.includes(hostname);
+
+ // In whitelist mode: apply only if site is in the list
+ // In blacklist mode: apply only if site is NOT in the list
+ if (
+ (isWhitelistMode && siteInList) ||
+ (!isWhitelistMode && !siteInList)
+ ) {
+ if (cssCache.has("example.com")) {
+ return cssCache.get("example.com");
+ } else {
+ return "/* Default fallback CSS */";
+ }
+ }
+ }
+ }
+
+ return null;
+}
+
+// Prepare styles for a URL that's about to load
+async function prepareStylesForUrl(hostname, tabId) {
+ try {
+ const settingsData = await browser.storage.local.get(
+ "transparentZenSettings"
+ );
+ const settings = settingsData.transparentZenSettings || {};
+
+ if (settings.enableStyling === false) return;
+
+ const css = await getStylesForHostname(hostname, settings);
+
+ if (css && tabId) {
+ // Store the CSS to be ready as soon as the content script connects
+ activeTabs.set(tabId, {
+ hostname: hostname,
+ css: css,
+ });
+ }
+ } catch (error) {
+ console.error("Error preparing styles for URL:", error);
+ }
+}
+
async function applyCSSToTab(tab) {
if (logging) console.log("applyCSSToTab called with", tab);
- // Apply CSS to the specified tab
- const url = new URL(tab.url);
- const hostname = url.hostname;
try {
+ const url = new URL(tab.url);
+ const hostname = url.hostname;
+
const settings = await browser.storage.local.get("transparentZenSettings");
const globalSettings = settings.transparentZenSettings || {};
if (globalSettings.enableStyling === false) return;
@@ -71,7 +231,7 @@ async function applyCSSToTab(tab) {
}
}
} catch (error) {
- console.error(`Error applying CSS to ${hostname}:`, error);
+ console.error(`Error applying CSS:`, error);
}
}
@@ -90,7 +250,19 @@ async function applyCSS(tabId, hostname, features) {
}
if (combinedCSS) {
- await browser.tabs.insertCSS(tabId, { code: combinedCSS });
+ try {
+ // Try to send via messaging (most reliable for instant application)
+ await browser.tabs.sendMessage(tabId, {
+ action: "applyStyles",
+ css: combinedCSS,
+ });
+ } catch (e) {
+ // Fallback to insertCSS if messaging fails
+ await browser.tabs.insertCSS(tabId, {
+ code: combinedCSS,
+ runAt: "document_start",
+ });
+ }
console.log(`Injected custom CSS for ${hostname}`);
}
}
@@ -99,66 +271,60 @@ let autoUpdateInterval;
function startAutoUpdate() {
if (logging) console.log("startAutoUpdate called");
- // Start the auto-update interval
if (autoUpdateInterval) clearInterval(autoUpdateInterval);
autoUpdateInterval = setInterval(refetchCSS, 2 * 60 * 60 * 1000);
}
function stopAutoUpdate() {
if (logging) console.log("stopAutoUpdate called");
- // Stop the auto-update interval
if (autoUpdateInterval) clearInterval(autoUpdateInterval);
}
async function refetchCSS() {
if (logging) console.log("refetchCSS called");
- // Refetch CSS styles from the remote server
try {
const response = await fetch(
"https://sameerasw.github.io/my-internet/styles.json",
- {
- headers: { "Cache-Control": "no-cache" },
- }
+ { headers: { "Cache-Control": "no-cache" } }
);
if (!response.ok) throw new Error("Failed to fetch styles.json");
const styles = await response.json();
await browser.storage.local.set({ styles });
await browser.storage.local.set({ lastFetchedTime: Date.now() });
console.info("All styles refetched and updated from GitHub.");
+
+ // Preload the new styles
+ preloadStyles();
} catch (error) {
console.error("Error refetching styles:", error);
}
}
-browser.runtime.onMessage.addListener((message) => {
- if (logging) console.log("onMessage received", message);
- // Handle messages for enabling/disabling auto-update
- if (message.action === "enableAutoUpdate") {
- startAutoUpdate();
- } else if (message.action === "disableAutoUpdate") {
- stopAutoUpdate();
- }
-});
+// Create a directory to store CSS files
+async function initializeExtension() {
+ // Preload styles immediately
+ await preloadStyles();
-// Initialize auto-update based on stored settings
-browser.storage.local.get("transparentZenSettings").then((settings) => {
- if (logging) console.log("Initial settings loaded", settings);
+ // Initialize auto-update based on stored settings
+ const settings = await browser.storage.local.get("transparentZenSettings");
if (settings.transparentZenSettings?.autoUpdate) {
startAutoUpdate();
}
-});
+}
-browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
- // if (logging) console.log("onUpdated called with", tabId, changeInfo, tab);
- // Apply CSS when a tab is updated
- if (changeInfo.status === "complete" || changeInfo.status === "loading") {
- applyCSSToTab(tab);
+// Listen for specific navigation events to apply CSS as early as possible
+browser.webNavigation.onCommitted.addListener((details) => {
+ if (details.frameId === 0) {
+ browser.tabs
+ .get(details.tabId)
+ .then((tab) => {
+ applyCSSToTab(tab);
+ })
+ .catch((err) => {
+ console.error("Error getting tab info:", err);
+ });
}
});
-browser.tabs.onActivated.addListener(async (activeInfo) => {
- if (logging) console.log("onActivated called with", activeInfo);
- // Apply CSS when a tab is activated
- const tab = await browser.tabs.get(activeInfo.tabId);
- applyCSSToTab(tab);
-});
+// Application start
+initializeExtension();
diff --git a/inject-css.js b/inject-css.js
index d53a915..f37abd7 100644
--- a/inject-css.js
+++ b/inject-css.js
@@ -4,6 +4,91 @@ let logging = false;
if (logging) console.log("inject-css.js script loaded");
+// Run as early as possible in the document lifecycle
+const implementImmediateInjection = () => {
+ // Create a style element immediately to avoid any delay - do this before anything else
+ const styleElement = document.createElement("style");
+ styleElement.id = "zen-internet-styles";
+
+ // Set highest priority
+ styleElement.setAttribute("data-priority", "highest");
+
+ // Add !important to all rules to override any existing styles
+ styleElement.innerHTML = `
+ /* Prevent FOUC - temporarily hide content until styles are applied */
+ body { opacity: 0 !important; transition: opacity 0.1s ease-in !important; }
+ `;
+
+ // Insert as the first element of head if possible
+ if (document.head) {
+ document.head.insertBefore(styleElement, document.head.firstChild);
+ } else {
+ // If head isn't ready yet (very early execution), add to documentElement
+ document.documentElement.appendChild(styleElement);
+
+ // Set up mutation observer to move it to head when head becomes available
+ new MutationObserver((mutations, observer) => {
+ if (document.head) {
+ if (styleElement.parentNode !== document.head) {
+ document.head.insertBefore(styleElement, document.head.firstChild);
+ }
+ observer.disconnect();
+ }
+ }).observe(document.documentElement, { childList: true });
+ }
+
+ return styleElement;
+};
+
+// Create style element immediately
+const styleElement = implementImmediateInjection();
+
+// Function to apply styles immediately when available
+function applyStyles(css) {
+ if (!css) return;
+
+ // Add the CSS
+ try {
+ // For immediate application, directly set textContent
+ // as this is more reliably applied in early document stages
+ styleElement.textContent = css.trim() + `
+/* Remove FOUC prevention once styles are loaded */
+body { opacity: 1 !important; }`;
+
+ // After a very short delay (to ensure CSS application), ensure body is visible
+ setTimeout(() => {
+ if (document.body) {
+ document.body.style.opacity = "1";
+ }
+ }, 10);
+
+ if (logging) console.log("Styles applied:", css.length, "bytes");
+ } catch (e) {
+ console.error("Error applying styles:", e);
+ }
+}
+
+// Listen for style data from background script for immediate injection
+browser.runtime.onMessage.addListener((message) => {
+ if (message.action === "applyStyles" && message.css) {
+ applyStyles(message.css);
+ return true;
+ }
+});
+
+// Send hostname to background script as early as possible
+browser.runtime
+ .sendMessage({
+ action: "contentScriptReady",
+ hostname: window.location.hostname,
+ url: window.location.href,
+ })
+ .catch((err) => {
+ if (logging) console.log("Background script not ready yet:", err);
+ });
+
+// Main function - but we don't wait for this before applying styles
+// This is just a backup in case background script injection fails
(async () => {
try {
const settings = await browser.storage.local.get("transparentZenSettings");
@@ -15,7 +100,19 @@ if (logging) console.log("inject-css.js script loaded");
}
if (logging) console.log("Styling is enabled");
+
+ // Tell background script we're ready and what page we're on
+ browser.runtime.sendMessage({
+ action: "contentScriptReady",
+ hostname: window.location.hostname,
+ });
+
const data = await browser.storage.local.get("styles");
+ if (!data.styles) {
+ if (logging) console.log("No styles data available");
+ return;
+ }
+
if (logging) console.log("Styles data loaded", data);
const currentUrl = window.location.hostname;
@@ -103,9 +200,7 @@ async function injectCSS(hostname, features) {
}
if (combinedCSS) {
- const style = document.createElement("style");
- style.textContent = combinedCSS;
- document.head.appendChild(style);
+ applyStyles(combinedCSS);
if (logging) console.log(`Injected custom CSS for ${hostname}`);
}
}
diff --git a/manifest.json b/manifest.json
index 92a8578..3872bfc 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Zen Internet",
- "version": "1.5.0",
+ "version": "1.6.0",
"description": "Inject custom css from my repository in real time",
"browser_specific_settings": {
"gecko": {
@@ -17,7 +17,9 @@
"storage",
"tabs",
"<all_urls>",
- "webNavigation"
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking"
],
"browser_action": {
"default_popup": "popup/popup.html",
@@ -25,18 +27,20 @@
},
"background": {
"scripts": ["background.js"],
- "persistent": false
+ "persistent": true
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["inject-css.js"],
- "run_at": "document_start"
+ "run_at": "document_start",
+ "all_frames": true
}
],
"web_accessible_resources": [
"data-viewer/data-viewer.html",
"data-viewer/data-viewer.js",
- "data-viewer/data-viewer.css"
+ "data-viewer/data-viewer.css",
+ "styling/*"
]
}
diff --git a/styling/README.md b/styling/README.md
new file mode 100644
index 0000000..dc3847c
--- /dev/null
+++ b/styling/README.md
@@ -0,0 +1,4 @@
+# Styling Directory
+
+This directory is used to store cached CSS files for faster injection.
+The extension will generate and manage files in this directory automatically.