So what is a Chrome Extensions? According to chrome developer documentation
A manifiest.json file specifies the actions that the extension will perform. Browser actions apply to all web pages as opposed to page actions which only apply to specific pages. I want to have an extension that will let me highlight a clojure function on any web page and let me look up the documentation on the Clojure CheatSheet so browser action it is.
The json in the manifest describes the three pieces of the browser action. The icon displays next to the address bar.
The title is optional and shows up as a tool tip when you hover over the icon. When you click the icon, the popup html page is activated.
I was certainly not the first person who wanted to be able to read highlighted text in a chrome extension and since I was not going to be doing anything special to find it, I looked to Stack Overflow to find an already working solution.
The popup html page
Clojurescript Quick Start
instead, they give a simple build.clj source file
and a java command to run the compile.
Handling the highlighted text involves checking to see if the text matches a link from the clojure cheatsheet and if it does, redirect the popup to the page referenced in that link
The original goal of the extension was to dynamically parse the Clojure Cheat Sheet to find all the links, but the hurdles put in place to prevent cross site scripting concerns threats make that difficult so I decided to parse the cheatsheet separate from the Chrome extension and manually move the output of that parse into the extension code.
Parsing the Cheat Sheet html into a clojure data structure could not be easier than with clj-tagsoup. Only one command. Finding the links in that data structure took a little more code using the clojure.walk library.
The API documentation says that clojure.walk
makes it fairly easy to write recursive search-and-replace functions, as shown in the examples.
Based on the descriptions of the API, I was pretty sure that I wanted to use postwalk to process the urls. I need to find a complete url structure and, once it has been found, I want to extract the text on the link, and the URL. The postwalk function “performs a depth first post-order traversal” which means that the entire link structure will be passed to my function. Based on the advice of the API documentation, I looked to the examples to make sure I had the correct function. In the case of postwalk, even the example took some unwinding.
At first, this simple looking example is a bit hard to follow with single digit numbers representing two different things. Each time the anonymous function is called, it returns a vector with the increased counter as the first element and the element that it is processing as second. In this case, the tree that is being walked is a map with two key-value pairs. Since the postwalk is depth first, it sees that the map has sub elements and so it recursively goes into the first key-value pair. That first key-value pair also has sub elements so it recursively calls the the key :a. Since :a does not have any children, it calls the anonymous function which returns a vector containing the incremented counter and :a
[0 :a] It does the same for the second key-value pair, then starts working it’s way outward. Counter 1 is assigned to the value of the first pair, counter 2 is assigned to the first key-value pair, counters 3, 4, and 5 are similarly applied to the second key-value pair, and finally counter 6 is assigned to the entire map.
The function passed to postwalk needs to do two things. First, it needs to identify the links, and second, it needs to extract out the text and url. All the links on the page are in the form of a vector where the first value is an anchor tag, :a, the second element is a map containing the title, url, and other properties, and the third element of the vector is the text displayed on the link.
Selecting vectors that :a for the first element applied to more links than were wanted, so I added another check to verify that a :title element was present. If an element started with :a, and had an :href and a :title in the map that was the second element of the vector, then I add the text from the link and that url to a map
This is a bit of a hack, but I printed that map, and brought that into the clojurescript code to lookup urls.
Putting it all together
Learnings and Demonstrations
Live demos will always teach a lesson. I tried to present this work at the Clojre.mn meeting where I learned one more important fact. There is a piece of code which finds the current tab in chrome. It is used by the extension to know where to ask for highlighted text. This code did not work while inside a Google Hangout while sharing the desktop. I think the reason for this is that Google puts a small window on top of everything when the desktop is shared. I believe this is where the request for highlighted code was being sent, and the highlight was never found.