Contents
Introduction
I’ve recently started experimenting with creating Chrome extensions. Although there are a number of good tutorials out there (turning divs red was particularly helpful), it’s almost impossible to have tutorials out there ready-and-waiting for all possible topics. So, let’s help a little.
My current project involves including a context menu item into the normal Chrome right-click menu. Once that context menu item has been clicked, after some rules have been applied, the div that was clicked on should be removed from the page. This is obviously just a subset of the main extension, but as an example of including a context menu and acting on the current page in response to it it should do quite nicely.
I’m going to lay this tutorial fairly slowly – each piece of the complete solution will be presented and updated as we go through, apart from the manifest which we will present complete. If you have any questions on any piece of it and would like me to expand on any section, just leave a comment below and I’ll update the article.
Overview
The glue that binds our Chrome extension together is the manifest.json. This manifest specifies which components we’ll be accessing as well as which files which be used – icons, .html, JavaScript files – if you need to use them, they’ll generally need to be in the manifest.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ "name": "contextMenuDemo", "version": "0.0.1", "manifest_version": 2, "description" : "More effective than the most powerful detergent.", "icons": {"48": "icons/48x48.png"}, "background": { "scripts": ["background.js"] }, "permissions": [ "contextMenus" ], "content_scripts": [{ "matches": ["http://*/*", "https://*/*"], "js": ["jQuery.js", "content.js"] }] } |
The manifest.json file sits in the root of our extension’s directory. This manifest is fairly simple – starting from the top, it first specifies the name of our extension. Next up, we specify its current version.
This doesn’t matter so much at the moment, but it will be important should you wish to publish your extension in the Chrome store. Every time you publish your extension you’ll have to increment this number, so it’s convenient to have it on a major version of 0 before you begin. That way you can ensure that your first “production” version is 1.0.
We also set the version of the manifest that we’ll be conforming to. Currently, all development should be on manifest version 2 as version 1 has been deprecated.
A description of our extension is essential – this will be visible in the extensions manager as well as in the Chrome store. As the last of our setup code, we specify an icon that our extension will use. As this extension will be a purely context menu driven extension we only need an icon for display in the extension manager – it will also be used in the context menu.
You can see that I’ve specified that the icon should be placed in an icons folder – organization is key when it comes to managing larger projects, so it’s good to get into the habit now. Next up, we’ll specify the files that we’ll be loading content from.
We are going to have two JavaScript files, each with very different purposes. First off, the background script.
The background script runs in the background of Chrome, monitoring user activities in non-page specific ways. Specifically, we’ll be using it for our context menu. Our background script cannot access the DOM however.
For our DOM access we’ll be using the content script. In addition to the context script, we’ll include a jQuery script for ease of coding. When we specify content scripts, you can say which pages should have the content included. The content scripts are actually injected into each page, so some care is required when writing them.
Finally, as we are going to be inserting items into the context menu, we require the context menu permission which is specified in the permissions section.
We’ve specified where our background script is located – now it’s time to add some content to it. Specifically, of course, we want to add a menu item that will allow us to remove the element that was right-clicked on. Let’s use the Chrome API to create it, and give it a nice, descriptive name like “Zappify!”. For the moment, our entire background script looks as follows.
1 |
chrome.contextMenus.create({"title": "Zappify!"}); |
Not too complicated so far, but if you were to load this extension up and right-click, you’d see a “Zappify!” item along with the icon that you’ve specified. Easy enough – let’s associate a click handler with it so that we can actually modify whatever we’ve clicked on.
Click-click *boom*
Loading our click.
We’ve managed to insert a new context menu into the Chrome right-click menu, but as of right now it probably doesn’t do much. Let’s change that. All we need to do is pass another argument to the contextMenus.create() function that specifies which code should handle the click. Although our ultimate goal is to have the clicked upon item disappear, for now let’s just log the information we have available to us to the Developer Console. For anyone not familiar with it, you can bring up the console by pressing Ctrl + Shift + I, or F12. Our new background script, specifying a function to use as a click handler, looks as follows.
1 |
chrome.contextMenus.create({"title": "Zappify!", "onclick": doOurZapFunction}); |
Let’s add the doOurZap function and then we’ll be ready to test our extension’s new capabilities.
1 2 3 |
function doOurZap(info, tab) { alert("clicked on " + info.menuItemId + " in " + info.pageUrl); } |
Our click handler is very basic – just shows an alert that gives the ID of the menu item that was clicked, along with the current page’s URL. We’ll modify this to be somewhat more intelligent in a moment.
If you’ve been clicking around a reasonable amount you may have noticed that our menu item doesn’t always appear. For example, if you hover over a link and right-click, there’ll be no “Zappify!” no matter how hard you look. This is because Chrome allows you to specify the context under which your context menu will be displayed. There are multiple options available: “all”, “page”, “frame”, “selection”, “link”, “editable”, “image”, “video”, “audio”, or “launcher”. In our case, as we want our bleach to appear in all possible scenarios, we’ll go with “all”. This option includes all of the other options except for “launcher”, which is used only for apps. Our new context menu create script look as follows:
1 |
chrome.contextMenus.create({"title": "Zappify!", contexts:["all"], "onclick": doOurZap}); |
Now we can click on any element regardless of type and have our menu appear. We’re almost there, but there is still one problem to overcome.
From our background script that we’ve been working in, we cannot access the DOM of the page we’re on. If we cannot access it, we have no way of working out what we’ve clicked on. In addition, if we cannot access it then we cannot modify it and if we cannot modify it then we obviously cannot make changes to it. However, our content script can access the entire DOM.
What we need to do is move from our background script to our content script. This may require a slight change in the way in which we’ve been approaching our app – we are going to use our background script to work out when a user would like to zap an item. The background script will drive behavior, rather than contain behaviour.
This seems reasonable – methods which modify the DOM should stay in the area associated with the DOM. But how do we indicate to the content script that we’d like to trigger an event like this? Simply enough, we can message it.
Getting the message across
The Chrome Extensions API has built-in functionality for passing messages between our various components. We’re going to replace our click handler with a call to this messaging function.
1 2 3 |
function doOurZap(info, tab) { chrome.tabs.sendRequest(tab.id, "zapElement"); } |
We tell Chrome to send a message to the tab associated with our click handler, and that the message’s content should be “zapElement”.
Now it’s time to add some code to our content script. We’ll add a listener that will check for incoming messages with a “zapElement” content – this is as easy as it sounds.
1 2 3 4 5 |
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) { if(request == "zapElement") { console.log("zapping element"); } }); |
If you were to run our extension now, you’d see that a “zapping element” message is printed whenever we right-click on something and click “Zappify!”. We’re almost there, we just need to remove the actual element.
Now that we have access to the DOM, this is very easy to accomplish. For our own ease of coding, we’re going to use jQuery’s mousedown() event binder to work out which element is currently being clicked on. We can then remove that element when we receive the zappify message. First off, we need to create a mousedown event handler and store the object that was clicked on.
1 2 3 4 5 6 |
var clicked; $(document).mousedown(function(event) { console.log("clicked is " + event.target); clicked = event.target; }); |
Here we’ve declared a variable to store the object in, as well as a handler. Obviously storing a global variable like this is potentially dangerous, as it could overwrite other variables in the page when the script is injected, but we’re trying to keep the code as readable as possible for this article.
We log the object that was clicked on, and then store it. We can now modify our event listener to use jQuery to remove the object, as follows.
1 2 3 4 5 6 |
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) { if(request == "zapElement") { console.log("zapping element"); $(clicked).remove(); } }) |
Conclusion
There we go! If you install and run the extension, you’ll find that you can click on any element and remove it. As simple as that sounds, it has required some use of the Chrome APIs for including context menu items and message passing, some manipulation of the DOM and some event handling.
The object will obviously not stay hidden (that’s the topic of another tutorial), but we’ve made a good step forwards towards Chrome Extension proficiency.
If you have any questions feel free to post them below and I’ll try illuminate any areas that have been left dark.
Leave a Reply