This week marks the beginning of my work on a new feature, as outlined in my previous blog post on Planning Step. The goal is to enhance user experience by introducing a toggle switch button, allowing users to enable or disable the display of detailed information about a played video and its associated channel.
To implement this feature, I began by diving into the foundational files used in developing a Chrome Extension, specially:
manifest.json
content.js
background.js
Each of these files plays a critical role in defining the behavior and structure of the extension. Below, I'll delve deeper into how I explored and utilized these components.
Exploring the manifest.json
file
The manifest.json
file is the cornerstone of any Chrome Extension. It serves as the configuration file that defines essential settings for the extension. For ImprovedTub, this file includes configurations like "background" scripts, content_scripts
, permission
and more. Without a properly configured manifest.json
file, the extension simply cannot function.
These fields describes the extension:
permissions
field specify the scope of the extension's functionality. For example, to interact with Youtube's DOM or store user preferences, we need to explicitly request access in the manifest:
The host_permissions
field tells that this extension is only dealing with Youtube pages:
The background
field specify scripts that run in the background, maintaining the persistent state of the extension. For ImprovedTub, this is crucial for managing event listeners and controlling features like toggle button. The background.js
is a JavaScript file that will run separately from the web browser thread, the service_worker
would not have access to content of web page, but it has capabilities to speak with the extension using the extension messaging system.
The content_scripts
field define the scripts that will run within the context of the web pages. This is where we inject the logic to manipulate the user interface, such as adding the toggle button and displaying or hiding the details block.
The default_popup
field tells me what html
file will be served as the Extension's UI:
Adding Switch buton
After having some knowledges about files' structure and purpose via the manifest.json
, I started adding a switch button named Details
inside the menu/skeleton-parts/channel.js
file:
channel_details_button: {
component: 'switch',
text: 'Details',
value: false, // Default state is off
on: {
change: async function (event) {
const apiKey = YOUTUBE_API_KEY;
const switchElement = event.target.closest('.satus-switch');
const isChecked = switchElement && switchElement.dataset.value === 'true';
console.log(isChecked)
try {
const videoId = await getCurrentVideoId();
const videoInfo = await getVideoInfo(apiKey, videoId);
const channelId = videoInfo.snippet.channelId
const channelInfo = await getChannelInfo(apiKey, channelId);
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
console.log("Sending message to content.js");
chrome.tabs.sendMessage(tabs[0].id, {
action: isChecked ? 'append-channel-info' : 'remove-channel-info',
channelName: channelInfo.channelName,
uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(),
videoCount: channelInfo.videoCount,
customUrl: channelInfo.customUrl
})
});
} catch (error){
console.error(error);
}
}
}
}
And it will look as:
When switch is off:
When switch is on:
At this stage, I am unsure how to use environment variables to store the YOUTUBE_API_KEY
, as the Chrome Extension project is not built with Node.js, making it impossible to use the dotenv
package. As a workaround, I plan to leave a note for the maintainer to manually add an API key to the placeholder for enabling API requests. Within the change
function, I attempted to fetch video information using the Youtube API via the videoID
. Once the data is retrieved, I used the chrome.tabs.query
function to send it to content.js
, enabling the UI to display the video details
Adding content.js
file
To implement the toggle functionality, I added a new JavaScript file under js
field in the manifest.json
file. This content.js
file will serves as a bridge for interacting with the main thread of web pages, such as injecting UI elements or handling DOM manipulations. However, at this stage, I faced a decision regarding the file's placement within the existing project structure.
While exploring the repository, I noticed a pattern: all JavaScript files referenced in the js
field for web page interactions were located under the js&css/extension/
directory. Although this structure wasn't explicitly documented, it appeared to be the convention followed by the maintainers.
Based on this observation, I decided to create the content.js
file within the same directory to maintain consistency.
Adding event listener in background.js
file
This file will need to handle and send response back to the content.js
. Inside this file, maintainer marked a note for MESSAGE LISTENER
part where I will code to handle the message type from content.js
:
/*--------------------------------------------------------------
# MESSAGE LISTENER
--------------------------------------------------------------*/
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
//console.log(message);
//console.log(sender);
console.log('Message received in background:', message);
switch (message.action || message.name || message) {
...
case 'store-video-id':
console.log('Storing video ID:', message.videoId); // Debugging log
if (message.videoId) {
chrome.storage.local.set({ videoId: message.videoId }, function() {
console.log('Video ID stored:', message.videoId); // Debugging log
sendResponse({ status: 'success' });
});
} else {
sendResponse({ status: 'error', message: 'No video ID provided' });
}
return true; // Indicates that sendResponse will be called asynchronously
case 'fetch-new-data':
chrome.storage.local.get('videoId', async function(result) {
const videoId = result.videoId;
const apiKey = "AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI"; // Replace with your YouTube Data API key
try {
const videoInfo = await getVideoInfo(apiKey, videoId);
const channelId = videoInfo.snippet.channelId;
const channelInfo = await getChannelInfo(apiKey, channelId);
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
console.log("Sending message to content.js");
chrome.tabs.sendMessage(tabs[0].id, {
action: 'append-channel-info',
channelName: channelInfo.channelName,
uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(),
videoCount: channelInfo.videoCount,
customUrl: channelInfo.customUrl
});
});
} catch (error) {
console.error(error);
}
});
return true;
}
});
I added 2 cases to handle events as store-video-id
and fetch-new-data
. As explained in manifest.json
file that I used the chrome.storage
to store the videoId
to browser, and will fetch video's data based on this id.
Conclusion
So far, I’ve implemented most of the feature, but there’s still work to be done. I need to address a bug where the details section does not always reflect the toggle switch's status, especially when the switch is toggled on or off from a non-YouTube page. To fix this, I’ll need to add a mechanism to consistently check the switch’s state and ensure the UI aligns with it. Additionally, I need to dive deeper into the project’s code structure to determine the best place for my changes. Currently, my implementation is explicit and self-contained, but the maintainers follow a different coding style. To align with their approach, I’ll need to refactor my code to integrate seamlessly with the existing structure while maintaining the new feature’s functionality.
Top comments (0)