Skip to content
Walid Karray
Go back

A Step-by-Step Tutorial on Building Your Own Safari Extension (Part 2)

From the archive: first published in June 2013 on my old blog, Tux86’s Web Tech Blog. Versions, links and screenshots are from that time.

Part 2: Building the extension

Getting started

This is the second part of the tutorial. If you missed the first part, you can find it here.

Open these files in your preferred IDE or text editor:

The three extension files, global.html, listener.js and popover.html, open in Sublime Text 2

The End Script

We use an End Script because it has access to the DOM of the web page. This is the only place where we can put our JS code and attach the event listener to the elements of the visited page.

listener.js source code:

function clickHandler(o,recursive) {
	var e=(recursive)?o:o.target;
	if ($(e).is('a')) { 
		/*
		the clicked element is a link, so
		send a message with the link information
	    to the global page (calls the handleMessage function)
	    */
	    safari.self.tab.dispatchMessage("link", $(e).get(0).href);
	} else {
		/*
		if the clicked element is not a link,
		we need to go up to the parent element
		recursively until a link tag is found.
	    */
		var parent = $(e).parents("a").get(0);
		if(parent) {
			clickHandler(parent,true);
		}
	}
}
/*
bind the clickHandler function for each
existing element of the document.
clickHandler will be triggered when the user
clicks on an element.
*/
$(document).on("click",clickHandler);

As you can see in the source code above, on line 28, the advantage of using jQuery.on() is that the attached click event handler, clickHandler(), works for both current and FUTURE elements (such as a new element created by a script). When the user clicks on an element, clickHandler() is executed and the clicked element (the object) itself is passed as a parameter to this function. Inside this function we check the element type. If it is a link, or an element wrapped in a link tag <a>, we dispatch a message containing the link information to the Global Page.

The Global Page

global.html source code:

<!DOCTYPE html>
<html>
   <head>
    <script type="text/javascript" charset="utf-8">
        const gui = safari.extension.popovers[0].contentWindow //Create the popover instance
		function handleMessage(msg){
				if (msg.name == "link"){
					//execute addLink() inside the popover
					gui.addLink(msg.message);
				}
		}
		//create the event listener
		safari.application.addEventListener("message",handleMessage, false);
     </script>
   </head>
   <body></body>
</html>

As shown above in the Global Page source code, the event listener on line 13 is registered once, when Safari launches or when the extension is enabled. It tells handleMessage() to catch incoming messages from external scripts. When dispatchMessage() is executed inside “listener.js”, the message is immediately caught by handleMessage() in the global page. Inside this function the message type is checked, then addLink() from “popover.html” is called with the link URL as a parameter. addLink() updates the Popover’s container by adding a new row. Note that you can’t update the Popover interface from the global page directly, because it doesn’t have access to the Popover’s DOM.

The Popover

popover.html is the user interface. It’s built with the usual web technologies: HTML, CSS and JavaScript. As mentioned earlier, we placed addLink() in this file because this is the only place where we can access the Popover’s DOM.

popover.html source code:

<!DOCTYPE html>
<html>
   <head>
  <script type="text/javascript" charset="utf-8" src="jquery-1.7.min.js"></script>
  <style type="text/css">
		body {
			color: #333;
			font-family: tahoma;
			font-size: 10px;
			background: #F7F7F7;
		}

		h1 {
			text-align: center;;
			font-size: 12px;
			color: #990000;
		}

		#container {
			outline: 1px solid #AAA;
			padding: 4px;
			background: #FFF;
			height: 350px;
			overflow-y: auto;

		 } 
	    .entry {
	    	padding-top: 6px;
	    }

	    .link {
			cursor: hand; 
			cursor: pointer;
			color: #0099CC;
	    }
	    .link:hover {
			color: blue;
	    }

	    .time {
	    	padding-right: 4px;
	    }
  </style>
	<script type="text/javascript" charset="utf-8">
			function addLink(link) {
				$("#start").after('<div class="entry"><span class="time">'+getDateTime()+'</span><span class="link" onclick=\'openLink("'+link+'")\'>'+getShortString(link,34)+'</span></div>');
			}

			function getShortString( str, limit ) {
				if (str.length > limit) {
				 return str.substring(0,limit)+"...";
				} else {
					return str;
				}

			}
			function openLink(link) {
					safari.application.activeBrowserWindow.openTab().url=link;
			}

			function getDateTime() {
				var currentdate = new Date(); 
				var datetime =  ('0' +currentdate.getDate()).slice(-2) + "/"
				            + ('0' +(currentdate.getMonth()+1)).slice(-2)  + "/" 
				            + currentdate.getFullYear() + " "  
				            + ('0' +currentdate.getHours()).slice(-2) + ":"  
				            + ('0' +currentdate.getMinutes()).slice(-2);
				return datetime;
			}

	</script>
   </head>
   <h1>Visited Links Logger</h1>
   <body>
   	<div id="container">
   		<span id="start" style="display:none"/>
   	</div>
   </body>
</html>

The picture below summarizes all of the above.

Architecture diagram: listener.js (End Script, with jQuery as Start Script) sends dispatchMessage() to the event listener and handleMessage() in global.html, which calls addLink() to update popover.html

Testing the extension

Development of the Visited Links Logger Safari extension is finished, and it’s now time to test it. Ready? Go to the Extension Builder and click the Reload button, which updates the extension with the changes we made.

The Reload button highlighted next to Visited Links Logger in Extension Builder

Everything should now work. Visit a few web pages and click on the links inside them, and you will get a result like the one in the picture below.

The finished Visited Links Logger popover in Safari, listing the date, time and URL of each clicked link, most recent first

Building the extension package

At this point we are still in the development stage and don’t have a finished extension yet: it is stored as a folder containing some source files and an icon, and it can’t be delivered to end users as it is. What we need to do next is generate a compact package. Once again the Extension Builder makes this very easy: a single click on the Build Package button (see the picture below), then choose where you want to save your package.

The Build Package button highlighted next to Visited Links Logger in Extension Builder

As a result, you should have a new package named “VisitedLinksLogger.safariextz”. It can be installed by opening the file or dragging it onto Safari. You can also publish the extension in Apple’s extension gallery.

I hope this tutorial was helpful. If you need more help, don’t hesitate to contact me or leave a comment, and I will try to reply as soon as possible. To finish, I would like to thank Paola Pereira for reviewing the English.


Share this post:

Related posts

Follow new posts by RSS

Previous Post
Facebook Graph API Workaround: Get a Group's ID from Its URL
Next Post
A Step-by-Step Tutorial on Building Your Own Safari Extension (Part 1)