Chrome Extension For Full Page Screenshot

I’m currently facing a challenge with modifying my Chrome extension to capture full-page screenshots instead of just the visible area. This extension is being developed for a team within my company, where the goal is to take a sequence of screenshots and compile them into a single PDF. Most existing screenshot extensions, like GoFullPage, only allow for singular screenshots, but my use case requires capturing multiple screenshots in a sequence.

I’ve tried several methods to achieve this, but I consistently encounter issues with stitching the screenshots together when scrolling through the page. Additionally, I’ve attempted to use Chrome’s native command for full-size screenshots but haven’t been able to recreate it in JavaScript. I’ve also reviewed a few stackoverflow threads relating to this topic but they have not helped thus far with this case.

Below are the relevant files of my extension. This version currently works for taking screenshots of the visible area, but I need help extending its functionality to capture full-page screenshots.

background.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'startCapture') {
    console.log('Received startCapture message:', message.settings);
    startCapture(message.settings);
  } else if (message.action === 'stopCapture') {
    stopCapture();
  } else if (message.action === 'capture') {
    chrome.tabs.captureVisibleTab((screenshotUrl) => {
      sendResponse(screenshotUrl);
    });
    return true;
  } else if (message.action === 'download') {
    const { screenshotUrl, folder } = message;
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const filename = `${folder}/screenshot-${timestamp}.png`;
    console.log('Downloading screenshot to filename:', filename);
    chrome.downloads.download({
      url: screenshotUrl,
      filename: filename
    });
  }
});

let capturing = false;
let settings = {};

async function startCapture(newSettings) {
  capturing = true;
  settings = newSettings;

  console.log('Starting capture process with settings:', settings);

  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    if (tabs.length === 0) {
      console.error('No active tab found');
      return;
    }
    const activeTabId = tabs[0].id;
    console.log('Active Tab ID:', activeTabId);

    if (settings.mode === 'automatic') {
      if (settings.navigationMode === 'htmlSelector' && settings.selector) {
        navigateAndCaptureBySelector(activeTabId, settings.selector);
      } else if (settings.navigationMode === 'extractedUrls') {
        chrome.storage.local.get('extractedLinks', (result) => {
          const links = result.extractedLinks || [];
          if (links.length > 0) {
            navigateAndCaptureByLinks(activeTabId, links);
          }
        });
      }
    } else {
      chrome.scripting.executeScript({
        target: { tabId: activeTabId },
        func: takeScreenshot,
        args: [settings.folder]
      });
    }
  });
}

function stopCapture() {
  capturing = false;
  console.log('Capture process stopped');
}

function navigateAndCaptureBySelector(tabId, selector) {
  if (!capturing) return;

  console.log('Navigating and capturing by selector:', selector);

  chrome.scripting.executeScript({
    target: { tabId: tabId },
    func: (selector) => {
      const nextButton = document.querySelector(selector);
      if (nextButton) {
        nextButton.click();
      } else {
        console.error('Next button not found for selector:', selector);
      }
    },
    args: [selector]
  }, () => {
    setTimeout(() => {
      chrome.scripting.executeScript({
        target: { tabId: tabId },
        func: takeScreenshot,
        args: [settings.folder]
      }, () => {
        navigateAndCaptureBySelector(tabId, selector);
      });
    }, 3000);
  });
}

function navigateAndCaptureByLinks(tabId, links, index = 0) {
  if (!capturing || index >= links.length) return;

  console.log('Navigating and capturing by links:', links[index]);

  chrome.tabs.update(tabId, { url: links[index] }, () => {
    setTimeout(() => {
      chrome.scripting.executeScript({
        target: { tabId: tabId },
        func: takeScreenshot,
        args: [settings.folder]
      }, () => {
        navigateAndCaptureByLinks(tabId, links, index + 1);
      });
    }, 3000);
  });
}

function takeScreenshot(folder) {
  console.log('Taking screenshot, folder:', folder);
  chrome.runtime.sendMessage({ action: 'capture' }, (screenshotUrl) => {
    if (screenshotUrl) {
      console.log('Screenshot URL:', screenshotUrl);
      chrome.runtime.sendMessage({ action: 'download', screenshotUrl, folder });
    }
  });
}

content.js

// Function to take a screenshot and send the URL to the background script for downloading
function takeScreenshot(folder) {
  console.log('Taking screenshot, folder:', folder);
  chrome.runtime.sendMessage({ action: 'capture' }, (screenshotUrl) => {
    if (screenshotUrl) {
      console.log('Screenshot URL:', screenshotUrl);
      chrome.runtime.sendMessage({ action: 'download', screenshotUrl, folder });
    }
  });
}

// Function to click the next button if in automatic mode
function clickNextButton(selector) {
  const nextButton = document.querySelector(selector);
  if (nextButton) {
    nextButton.click();
  }
}

// Function to extract URLs
function extractUrls() {
  const links = Array.from(document.querySelectorAll('a.page')).map(a => a.href);
  return links;
}

// Listener for messages from the background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'takeScreenshot') {
    takeScreenshot(message.folder);
  } else if (message.action === 'clickNextButton') {
    clickNextButton(message.selector);
  } else if (message.action === 'extractUrls') {
    const urls = extractUrls();
    sendResponse(urls);
  }
  return true;
});

popup.js

document.addEventListener('DOMContentLoaded', () => {
  const selectorInput = document.getElementById('selector');
  const modeSelect = document.getElementById('mode');
  const navigationModeSelect = document.getElementById('navigationMode');
  const folderInput = document.getElementById('folder');
  const startButton = document.getElementById('startCapture');
  const stopButton = document.getElementById('stopCapture');
  const openSettingsButton = document.getElementById('openSettings');

  // Load settings
  chrome.storage.local.get(['selector', 'mode', 'navigationMode', 'folder'], (result) => {
    if (result.selector) selectorInput.value = result.selector;
    if (result.mode) modeSelect.value = result.mode;
    if (result.navigationMode) navigationModeSelect.value = result.navigationMode;
    if (result.folder) folderInput.value = result.folder;
    toggleSelectorField(result.navigationMode || 'htmlSelector');
  });

  // Save settings on change
  selectorInput.addEventListener('input', () => {
    chrome.storage.local.set({ selector: selectorInput.value });
  });

  modeSelect.addEventListener('change', () => {
    chrome.storage.local.set({ mode: modeSelect.value });
  });

  navigationModeSelect.addEventListener('change', (event) => {
    const navigationMode = event.target.value;
    chrome.storage.local.set({ navigationMode: navigationMode });
    toggleSelectorField(navigationMode);
  });

  folderInput.addEventListener('input', () => {
    chrome.storage.local.set({ folder: folderInput.value });
  });

  startButton.addEventListener('click', () => {
    chrome.storage.local.get(['selector', 'mode', 'navigationMode', 'folder'], (result) => {
      console.log('Starting capture with settings:', result);
      chrome.runtime.sendMessage({
        action: 'startCapture',
        settings: {
          selector: result.selector,
          mode: result.mode,
          navigationMode: result.navigationMode,
          folder: result.folder
        }
      });
    });
  });

  stopButton.addEventListener('click', () => {
    chrome.runtime.sendMessage({ action: 'stopCapture' });
  });

  openSettingsButton.addEventListener('click', () => {
    chrome.runtime.openOptionsPage();
  });

  function toggleSelectorField(navigationMode) {
    if (navigationMode === 'htmlSelector') {
      selectorInput.style.display = 'block';
    } else {
      selectorInput.style.display = 'none';
    }
  }
});

Any insights would be greatly appreciated.