diff --git a/background.js b/background.js index ed6f495..4b2ab52 100644 --- a/background.js +++ b/background.js @@ -12,12 +12,126 @@ chrome.runtime.onInstalled.addListener(() => { }); }); +// Function to inject content script if needed (same logic as popup) +function ensureContentScriptLoaded(tabId, tabUrl, callback) { + // Check if the URL is a restricted page + const restrictedUrls = [ + 'chrome://', 'chrome-extension://', 'edge://', 'moz-extension://', + 'about:', 'file://', 'data:', 'javascript:' + ]; + + const isRestrictedUrl = restrictedUrls.some(prefix => tabUrl.startsWith(prefix)); + + if (isRestrictedUrl) { + callback(false, `Cannot run on ${tabUrl.split('://')[0]}:// pages.`); + return; + } + + // First attempt: Check if content script is already loaded + chrome.tabs.sendMessage(tabId, {action: 'ping'}, function(response) { + if (chrome.runtime.lastError) { + console.log("background.js: Content script not detected, attempting injection..."); + + // Second attempt: Try to inject content script programmatically + chrome.scripting.executeScript({ + target: { tabId: tabId }, + files: ['content.js'] + }, function(results) { + if (chrome.runtime.lastError) { + console.log("background.js: File injection failed, trying inline..."); + + // Third attempt: Inline injection + chrome.scripting.executeScript({ + target: { tabId: tabId }, + func: function() { + if (!window.pasteHumanInjected) { + window.pasteHumanInjected = true; + let currentTypingSession = null; + + chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { + if (request.action === "ping") { + sendResponse({success: true, message: "Inline content script is loaded"}); + return true; + } else if (request.action === "emulateTyping") { + navigator.clipboard.readText().then((clipText) => { + const activeElement = document.activeElement; + if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.contentEditable === 'true')) { + // Simple typing simulation + let i = 0; + function typeChar() { + if (i < clipText.length) { + const char = clipText[i]; + if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') { + activeElement.value += char; + } else { + activeElement.textContent += char; + } + activeElement.dispatchEvent(new Event('input', { bubbles: true })); + i++; + setTimeout(typeChar, Math.random() * 100 + 50); + } + } + typeChar(); + } + sendResponse({success: true}); + }).catch(err => { + sendResponse({success: false, error: err.message}); + }); + return true; + } else if (request.action === "stopTyping") { + currentTypingSession = null; + sendResponse({success: true}); + return true; + } + }); + } + } + }, function(inlineResults) { + if (chrome.runtime.lastError) { + callback(false, `All injection methods failed: ${chrome.runtime.lastError.message}`); + } else { + setTimeout(() => callback(true, 'Inline injection successful'), 200); + } + }); + } else { + setTimeout(() => callback(true, 'File injection successful'), 200); + } + }); + } else { + callback(true, 'Content script already loaded'); + } + }); +} + chrome.contextMenus.onClicked.addListener((info, tab) => { - console.log('background.js: Context menu clicked:', info.menuItemId); // Log context menu item clicked + console.log('background.js: Context menu clicked:', info.menuItemId); - if (info.menuItemId === "pasteHuman") { - chrome.tabs.sendMessage(tab.id, { action: "emulateTyping" }); - } else if (info.menuItemId === "stopPasteHuman") { - chrome.tabs.sendMessage(tab.id, { action: "stopTyping" }); - } + // Ensure content script is loaded before sending messages + ensureContentScriptLoaded(tab.id, tab.url, function(success, message) { + if (!success) { + console.error('background.js: Cannot inject content script:', message); + return; + } + + console.log('background.js:', message); + + // Now send the actual message + if (info.menuItemId === "pasteHuman") { + chrome.tabs.sendMessage(tab.id, { action: "emulateTyping" }, (response) => { + if (chrome.runtime.lastError) { + console.error('background.js: Error sending emulateTyping message:', chrome.runtime.lastError.message); + } else { + console.log('background.js: emulateTyping message sent successfully'); + } + }); + } else if (info.menuItemId === "stopPasteHuman") { + chrome.tabs.sendMessage(tab.id, { action: "stopTyping" }, (response) => { + if (chrome.runtime.lastError) { + console.error('background.js: Error sending stopTyping message:', chrome.runtime.lastError.message); + } else { + console.log('background.js: stopTyping message sent successfully'); + } + }); + } + }); }); diff --git a/content.js b/content.js index cc6decd..4764f69 100644 --- a/content.js +++ b/content.js @@ -1,9 +1,21 @@ -let currentTypingSession = null; +// Log when content script is loaded +console.log('content.js: Content script loaded and ready on:', window.location.href); + +// Prevent multiple injections +if (window.pasteHumanContentScript) { + console.log('content.js: Already loaded, skipping...'); +} else { + window.pasteHumanContentScript = true; + + let currentTypingSession = null; chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { console.log('content.js: Message received:', request.action); // Log message received - if (request.action === "emulateTyping") { + if (request.action === "ping") { + sendResponse({success: true, message: "Content script is loaded"}); + return true; + } else if (request.action === "emulateTyping") { console.log("content.js: Action received: emulateTyping"); currentTypingSession = Math.random().toString(); // Create a new unique typing session identifier navigator.clipboard @@ -11,9 +23,16 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { .then((clipText) => { console.log("content.js: Clipboard text read:", clipText); // Log clipboard text emulateTyping(clipText, currentTypingSession, request.delayedStart); + sendResponse({success: true}); + }) + .catch((error) => { + console.error("content.js: Error reading clipboard:", error); + sendResponse({success: false, error: error.message}); }); + return true; // Indicates that the response is sent asynchronously } else if (request.action === "stopTyping") { currentTypingSession = null; // Invalidate the current typing session + sendResponse({success: true}); } }); @@ -69,3 +88,5 @@ function emulateTyping(text, session, delayedStart) { startTyping(); } } + +} // End of conditional block for preventing multiple injections diff --git a/manifest.json b/manifest.json index 0c4407c..774ac06 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,8 @@ "name": "PasteHuman", "description": "Transforming plain text into human-like typing. This extension emulates typing in textboxes.", "version": "1.0", - "permissions": ["contextMenus", "activeTab", "clipboardRead"], + "permissions": ["contextMenus", "activeTab", "clipboardRead", "scripting"], + "host_permissions": ["http://*/*", "https://*/*"], "background": { "service_worker": "background.js" }, @@ -22,8 +23,10 @@ }, "content_scripts": [ { - "matches": [""], - "js": ["content.js"] + "matches": ["http://*/*", "https://*/*"], + "js": ["content.js"], + "run_at": "document_start", + "all_frames": true } ] } diff --git a/popup.js b/popup.js index 0b0f946..a1d050a 100644 --- a/popup.js +++ b/popup.js @@ -1,17 +1,143 @@ document.getElementById('startTyping').addEventListener('click', function() { console.log("popup.js: Start typing button clicked"); - // Using a timeout function to delay the message being sent by 5 seconds - setTimeout(function() { - chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - var currentTab = tabs[0]; // There should be only one in this array - console.log(`popup.js: Sending message to tab ${currentTab.id}`); - chrome.tabs.sendMessage(currentTab.id, { - action: 'emulateTyping', - delayedStart: true, - }, function(response) { - console.log("popup.js: Message sent: emulateTyping"); - }); + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + if (tabs.length === 0) { + console.error("popup.js: No active tab found"); + return; + } + + var currentTab = tabs[0]; + console.log("popup.js: Current tab URL:", currentTab.url); + + // First check if content script is loaded + checkContentScriptLoaded(currentTab.id, currentTab.url, function(isLoaded, message) { + if (!isLoaded) { + console.error("popup.js:", message); + // Show user-friendly error message + alert(`PasteHuman Error: ${message}`); + return; + } + + console.log("popup.js: Content script verified, starting delayed typing"); + + // Using a timeout function to delay the message being sent by 5 seconds + setTimeout(function() { + console.log(`popup.js: Sending message to tab ${currentTab.id}`); + chrome.tabs.sendMessage(currentTab.id, { + action: 'emulateTyping', + delayedStart: true, + }, function(response) { + if (chrome.runtime.lastError) { + console.error("popup.js: Error sending message:", chrome.runtime.lastError.message); + } else { + console.log("popup.js: Message sent successfully:", response); + } + }); + }, 5000); // 5000 milliseconds (5 seconds) delay }); - }, 5000); // 5000 milliseconds (5 seconds) delay + }); }); + +// Function to check if content script is loaded +function checkContentScriptLoaded(tabId, tabUrl, callback) { + // Check if the URL is a restricted page where content scripts can't run + const restrictedUrls = [ + 'chrome://', + 'chrome-extension://', + 'edge://', + 'moz-extension://', + 'about:', + 'file://', + 'data:', + 'javascript:' + ]; + + const isRestrictedUrl = restrictedUrls.some(prefix => tabUrl.startsWith(prefix)); + + if (isRestrictedUrl) { + callback(false, `Cannot run on ${tabUrl.split('://')[0]}:// pages. Please navigate to a regular website.`); + return; + } + + console.log("popup.js: Checking content script on:", tabUrl); + + // First attempt: Check if content script is already loaded + chrome.tabs.sendMessage(tabId, {action: 'ping'}, function(response) { + if (chrome.runtime.lastError) { + console.log("popup.js: Content script not detected, attempting injection..."); + + // Second attempt: Try to inject content script programmatically + chrome.scripting.executeScript({ + target: { tabId: tabId }, + files: ['content.js'] + }, function(results) { + if (chrome.runtime.lastError) { + console.error("popup.js: Script injection failed:", chrome.runtime.lastError.message); + + // Third attempt: Try inline code injection as fallback + chrome.scripting.executeScript({ + target: { tabId: tabId }, + func: function() { + // Minimal inline content script + if (!window.pasteHumanInjected) { + window.pasteHumanInjected = true; + console.log('Inline content script injected'); + + let currentTypingSession = null; + + chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { + if (request.action === "ping") { + sendResponse({success: true, message: "Inline content script is loaded"}); + return true; + } else if (request.action === "emulateTyping") { + navigator.clipboard.readText().then((clipText) => { + // Simple typing simulation + const activeElement = document.activeElement; + if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.contentEditable === 'true')) { + activeElement.value = clipText; + activeElement.dispatchEvent(new Event('input', { bubbles: true })); + } + sendResponse({success: true}); + }).catch(err => { + sendResponse({success: false, error: err.message}); + }); + return true; + } + }); + } + } + }, function(inlineResults) { + if (chrome.runtime.lastError) { + callback(false, `All injection methods failed: ${chrome.runtime.lastError.message}. The website may have strict security policies.`); + } else { + // Wait and test the inline injection + setTimeout(() => { + chrome.tabs.sendMessage(tabId, {action: 'ping'}, function(response) { + if (chrome.runtime.lastError) { + callback(false, `Inline injection verification failed: ${chrome.runtime.lastError.message}`); + } else { + callback(true, 'Content script injected via inline method'); + } + }); + }, 200); + } + }); + } else { + // File injection succeeded, wait and verify + setTimeout(() => { + chrome.tabs.sendMessage(tabId, {action: 'ping'}, function(response) { + if (chrome.runtime.lastError) { + callback(false, `File injection verification failed: ${chrome.runtime.lastError.message}`); + } else { + callback(true, 'Content script injected via file method'); + } + }); + }, 200); + } + }); + } else { + callback(true, 'Content script already loaded'); + } + }); +}