I am building my first Chrome extension. The feature set will be bigger, but for the moment, I am trying to get an HTML element from the active tab. The tab will always be a greenhouse webpage.
For example, this job from GoDaddy: https://boards.greenhouse.io/godaddy/jobs/5681152003?gh_jid=5681152003#app
All these kinds of links have an HTML element with id="content". This division includes paragraphs and unordered lists mainly. I just want to print this content in my popup.html. It does not work, no matter how much I try.
I have some hypotheses on where the code could go wrong (but I was unable to prove it):
- Problem with finding the : Maybe I should not use the document.querySelector("#content").querySelectorAll("p, ul > li") method to find the content I want. There might be other html elements inside the division apart from <p> and <ul> (like <hr>) and this is causing trouble.
- Problem with handling the information inside <div>: The code in content.js fetches the information inside the <div> as a NodeList, and then iterates through that NodeList to push text content into the requirements array. Maybe this is not the correct way of doing this.
- Async communication: I had to make the functions async to handle the asyn nature of chrome.runtime.sendMessage() method. In my code, all communications between the popup.js and content.js are done through the background.js file. I suspect that there might be some problem there, but I can not understand what it is.
- Problem with Tab information: I don't know if I am sending the Tab information correctly to the content.js.
Is there any logical error in my code?
My code is the following:
manifest.json
Notice that I added "run_at": "document_end" inside the "content_script" to try to handle timing issues.
{
"manifest_version": 3,
"name": "Experience Tracker",
"version": "1.0",
"description": "Track your work experiences and match with job requirements.",
"permissions": [
"activeTab"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_end"
}
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
}
popup.html
<!DOCTYPE html>
<html>
<head>
<title>Experience Tracker</title>
<script src="popup.js"></script>
</head>
<body>
<!-- Button that triggers the fetching of job requirements -->
<button id="fetchRequirements">Fetch Job Requirements</button>
<hr>
<h2>Matched Requirements</h2>
<!-- Paragraph element where all the content from <div id="content"> from the Active Tab will be displayed -->
<p id="matchedRequirements"></p>
</body>
</html>
popup.js
The popup.js code defines a function called fetchRequirements that fetches job requirements from the background script. When the user clicks the button fetchRequirements in the Chrome extension popup, this function is triggered. The code sends a message to the background script, requesting the matched requirements. Upon receiving the response, which should include the content inside the <div id="content">, it generates HTML elements for each requirement and displays them in the <p id="matchedRequirements"> from popup.html. The code also ensures that the fetching process aligns with the asynchronous nature of the extension environment and handles button click events.
const fetchRequirements = async () => {
const request = {
action: "fetchRequirements"
};
const response = await chrome.runtime.sendMessage(request);
if (response.matchedRequirements) {
const requirementsHtml = response.matchedRequirements.map(requirement => {
return `<div>${requirement}</div><br>`;
}).join("");
document.getElementById("matchedRequirements").innerHTML = requirementsHtml;
}
};
document.addEventListener("DOMContentLoaded", function() {
const fetchRequirementsButton = document.getElementById("fetchRequirements");
const matchedRequirementsList = document.getElementById("matchedRequirements");
fetchRequirementsButton.addEventListener("click", async () => {
await fetchRequirements();
});
});
content.js
This content.js script is responsible for extracting the job requirements from the content of the webpage. It finds the element with the ID "content" and looks for paragraphs (<p>) and list items (<li>) within it. It then iterates through these elements, extracts their text content, and adds it to the requirements array. Finally, it sends a message to the background script, passing along the extracted requirements using the "fetchRequirements" action identifier.
function fetchRequirements() {
const requirements = [];
const contentElements = document.querySelector("#content").querySelectorAll("p, ul > li");
for (const contentElement of contentElements) {
requirements.push(contentElement.textContent);
}
chrome.runtime.sendMessage({
action: "fetchRequirements",
matchedRequirements: requirements
});
}
background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.requirements) {
chrome.storage.local.set({ matchedRequirements: request.requirements });
}
});
I expected that when the button fetchRequirements is clicked, all the content in the active tab under the division with id="content" would be displayed in my paragraph with id="matchedRequirements". Now, when I click the fetchRequirements button, nothing happens, and I get the Error:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'matchedRequirements')
(Posted the solution on behalf of the question author to move it to the answer space).
The problem was that I was not properly communicating between files. The popup.js was not sending TabId information, and also popup.js was waiting for a response that never came! It is working now.