Quick Key Indicators on Firefox Sites
Many of us have in mind a set of sites that we periodically open not for attentive reading, but in order to quickly familiarize ourselves with some small piece of information, see if there are any new articles or comments, check if any parameter has changed and etc. Sites often provide rss or mailing list for such needs, but this is not always the case. I will try to describe one of the ways to automate such a routine.
Most Firefox users are probably familiar with the extensions that signal updates on popular network resources (mail, news aggregator, social network, weather site, and so on). But on all sites and on all types of information extensions can not be stocked. Fortunately, if you are a little familiar with JavaScript, some simplified and flexible similarity of such extensions can be created in minutes using the intermediary extension: Custom Buttons . It allows you to quickly create buttons for toolbars that execute arbitrary code when pressed or can initialize certain behavior when the browser starts. Moreover, they can execute code both in the context of the page (which reminds user scripts) and in the context of the browser (which are compared to full-fledged extensions).
The purpose of this article does not include an explanation of working with Custom Buttons: you can find enough links to documentation and forums on the link. However, everything is arranged quite simply: a button is created through the context menu of the toolbar, a code is entered in the dialog of the new button, some additional buns are selected, then the button is dragged to any convenient toolbar - and now it is ready for work (by the way, the extension introduces an additional protocol custombutton: // - it allows you to easily publish buttons on the network and share them in the same way as we use regular links or bookmarklets).
I’ll try to share some of the code recipes dedicated to these indicators. We will move from simple to more complex. The sample code should work in Firefox starting with version 12. I realize that different elements of the code may seem silly to different people, but the examples do not pretend to be perfect: I only share everyday experiences with the help of an amateur code and sincerely apologize for not being able to do this better.
It happens that we need only a small part of the data from the entire site. And to get this key information, it’s not at all necessary to load the entire page with heavy additional content (ads, pictures, flash, etc.) and resource-intensive scripts, and then search for the desired site on the page. You can get pure lightweight html via Ajax (at the same time, nothing is loaded and executed), convert it to DOM, automatically find the area of interest to us and display only the necessary information.
For an example we will take not too tempting, but simple and bright case. We will demand the current number of articles on the English Wikipedia (which is now approaching four million). To do this, in the very first tab of the dialog to create a button, insert the following code (the tab is called “Code”):
In the future, for another site and other information we will need to change just a few lines. Let's try to figure out what the code does.
The button code is executed through new Function (), so there is no need to somehow protect yourself from interference in the browser namespace.
The very first variables in the function are those variables that need to be reassigned for other conditions. The first is the page address, the second is the XPath of the key element containing the required information. Next, we set two embedded pictures: the first is the pulsator that appears on the button during the request (you do not need to reassign it), the second is the site icon that appears on the button at the end of the request (the same icon can be set as the default button icon in the create dialog).
XPath of a key element is very easy to install using the
XPather extension : it not only integrates into the well-known DOM Inspector, but also adds its position to the context menu, allowing any element on the page to be opened in the window of this extension and get its XPath. The extension gives the full address with all links and sometimes it can be shortened by relying, for example, on the id of the elements. In our example, we just reduced the address (of course, when the key element has an id, you can not use XPath, but here we proceed from the universality of the code and apply a method suitable for all cases).
When creating XPath, you need to remember that XHR does not execute scripts on the loaded page. Therefore, in the process of creating the code, you need to analyze the page with JavaScript disabled or the primary html. It happens that a key element is formed by a script: then our method is not suitable and we need to use a more complex approach with hidden background browsers, which we will not touch on in this article. However, such cases are not so frequent.
The button creation window has a convenient tool for creating data: -addresses for embedding resources; you can easily convert website icon addresses to base64 strings. Of course, nothing prevents you from entering the URL, just in this way we create completely autonomous code and reduce network and local requests.
Next, we assign a future variable for the button, which is first represented as this. And we turn to two subfunctions: one of them will receive the document code, the second will disassemble it and provide the necessary information.
At the beginning of the first subfunction, we start the pulsator, then create an XHR with its parameters: set the background request flag (so that the browser doesn’t bother us with messages designed for normal page loading), timeout boundaries and cache bypass flag just in case. To force Firefox to send cookies with XHR even when the user has disabled cookies from third-party sites, set the flag for forced sending (without this, cookies are not accepted and are not sent).
Hoping for the best, we use xhr.responseType = "document" to immediately access the DOM. However, with this approach, you can run into the error caused by an infrequent confluence of conditions and leading to pseudo-timeouts ( see discussion with links to bugzilla ). Therefore, we insert the safety code: at the first timeout, we try to request the page code again and parse it using DOMParser - this is a little more complicated and wasteful (DOMParser for some reason causes the loading of some additional resources, although in theory it should only parse the code - fixed for night builds, but I don’t know whether this fix will be included in the 14th release), but it’s more reliable until the error with xhr.responseType = “document” is fixed (possibly by the 16th release).
As a result, we have several outcomes of the request: the first successful call (with the argument pureXHR == true) with the direct receipt of the DOM leads to the call of the document parsing function, the first timeout leads to the recursive re-request of the clean code (with the argument pureXHR == false) followed by conversion in the DOM and again by calling the document parsing function, a second timeout or communication error leads to the corresponding failure messages.
The parsing subfunction is pretty simple. First we try to get the key element. If an element is found, we issue the necessary information. If there is no element, we suspect that the site has given some kind of wrong page and report an error. The reasons can be different: a redirect (while XHR receives an empty document), authorization has flown and the site has given another page for unauthorized users, the developers have changed the DOM structure - or something else. Then you need to load the page in the browser, check the cause of the failure (including with the help of extensions exploring HTTP activity) and make the necessary adjustments.
This is a simple way to economically request information on 50 lines of code with a trifle. Now try to enrich the tool a little.
I would like the button itself at intervals to request information and display its status: there is news - there is no news - an error. You can apply this button, for example, to forums that do not provide email alerts about new topics, comments, private messages, and similar events. However, they add clear signs to the pages that such news has appeared (it may be a special element or a change in the attributes of an element).
Take personal messages on rutracker.org as an example. The extension will check the personal messages page (any other one is possible, we will be interested in the header common to most pages; just a private messages page may be the most economical option), search for the desired item, check its properties and report the status by changing the icon on the button and in addition a pop-up hint (you can add a sound notification or a pop-up window above the tray, but in this article we will not talk about such difficulties).
In this example, we will slightly change the principle of action: we need not the first, but the second tab of the button creation dialog. The code in this tab is not executed when the button is clicked, but immediately after launching the browser. This gives us the opportunity to assign the necessary variables and functions for the whole time of requests by the timer, as well as set handlers for different clicks of the button for greater flexibility and convenience. Further we will analyze only changes in the code compared to the first option.
Two more were added to the pair of initial parameters: delay (the number of seconds between launching the browser and the first timer request) and interval (intervals between further requests in minutes). In our example, it is ten seconds and an hour, respectively.
Now we have not two, but four whole icons: the indicator of the ongoing request, indicators of the presence of news and absence of absence, as well as an icon for errors.
We also create a special variable for the DOMParser safety object, so as not to recreate it again with every request.
Then we add a handler for clicking the button with the mouse. It will be enough for us to press the left and middle mouse buttons. The first time we press, we will initiate an extraordinary request (the timer will restart), when we press the middle button, we will open the desired page in the browser to familiarize ourselves with the news in more detail. The middle button handler will check the current tab and, if it is empty and at the same time nothing starts loading, it will open the page in it, otherwise in a new tab. Also, at such an opening, we will restart the timer and reset the news rate until the next request, implying that opening the page is equal to an extraordinary request and will still lead to reading (i.e. resetting) the news on the site.
The XHR result handlers in this example do not generate alerts, but changes to icons and tooltips (the latter include the request time and brief information about the result).
If there are new messages, the server adds an attribute of a certain class to the key element (link to the inbox in the upper right corner of the page). We will check it. If authorization on the site crashes, the server issues a redirect to the login page: in this case, XHR receives an empty document, the element is not found and we get an error, common for other cases of changes in the DOM.
After defining the variables and functions, we attach the click handler to the button, call the first pending request and start the timer.
By the way, in Custom Buttons there is a small bug with event handlers: if you do not add the removal of the handler via onDestroy, the handlers are layered (duplicated) each time you call up the toolbar settings (open and close the tool palette).
In the previous example, it was enough for us to load the page to understand if there was news: the server took care of remembering what the user had read and from what time to count unread news. However, not all sites have such news indicators.
I will give an example. Recently, subtitle tools have replenished with one great program - Subtitle Edit. On the developer's siteCommunicated with users who report errors and request new features. The site has rss for new posts by the author, including with messages about new versions of the program. However, in the comments to the last post, the discussion of the new version usually takes place, and the author often posts links to assemblies with current corrections. To keep abreast of such intermediate changes, you have to periodically open the main page and look at the link of the last post for comments.
There are no subscriptions to post comments. Similarly, there are no obvious signs on the site that there are unread comments. You need to remember the previous number of comments and check if the number in the top link has increased. We will overload this comparison to the button. She will remember the link to the last post and the number of comments in it. If the top post changes or comments are added to it, the button will inform about the news.
Resetting news will also change a bit. In the second example, the user read new posts on the site and their site metric was reset. The reset button only anticipated this. In our third example, the site itself does not change after we read the new comments. Therefore, we will introduce an additional flag variable for the presence / absence of news: as soon as news appears, the variable changes and remains in this state for the duration of all requests, until the user responds and opening the site through the button clears this flag.
Now about the additions in the code.
As we can see, variables have been added for some internal mechanisms: all of them are useful to us for saving data between sessions. We will use the same settings database as the extensions (we will not use simple files or databases to avoid unnecessary complexity). We have chosen the most universal way of storing string data (*** ComplexValue), so that you can store unicode in the database if necessary.
Instructions for resetting the news flag were added to the handler for clicking the button (the button requests the key from the settings, turns it into an object via JSON, changes the flag, packs the object into a string and saves it again in the settings database).
Most added to the document processing subfunction. After checking for the presence of a key element, we request an object with information about the previous state from the settings database (link to the last post, number of comments and whether the news flag was raised for the last time: after all, all subsequent requests may come across the same results, which all the same should remain news until the user pays attention to the button and opens the site). If the current and previous state are the same and the news flag is not activated, we leave the indicator in a neutral state and do not change anything in the settings. If a new post appears, comments are added, or the news flag is raised from previous requests, we replace the previous state with the current one, save the changed object with parameters to the settings database and signal the news.
After defining the variables and functions and before binding the handlers, deferred calling and starting the timer, we need to do one more thing: check whether there is a key in the settings database for storing our data. If the button initialization is started for the first time (we just created the button), the key is not there yet, and then we create an object blank with empty parameter values and an inactive news flag.
Here, in general, and all the main types of indicators. In each of them, you only need to change the page address and XPath of the key element. In the second and third cases, it is also necessary to change the subject of verification in the parsing function of the document. In the third case, you must still remember to set a new key for storing the data of the new button so that the two buttons do not accidentally overwrite each other's data store.
Examples can be expanded as needed: complicate the methods of news notification, add key elements and comparison parameters, create new interface details on the fly (for example, panels with dynamic text information next to a button) and so on. You can read about all this on the Firefox developers website or look at examples of ready-made buttons on the Custom Buttons developer website.
Thanks to everyone who read it. And successful experiments for you)
Most Firefox users are probably familiar with the extensions that signal updates on popular network resources (mail, news aggregator, social network, weather site, and so on). But on all sites and on all types of information extensions can not be stocked. Fortunately, if you are a little familiar with JavaScript, some simplified and flexible similarity of such extensions can be created in minutes using the intermediary extension: Custom Buttons . It allows you to quickly create buttons for toolbars that execute arbitrary code when pressed or can initialize certain behavior when the browser starts. Moreover, they can execute code both in the context of the page (which reminds user scripts) and in the context of the browser (which are compared to full-fledged extensions).
The purpose of this article does not include an explanation of working with Custom Buttons: you can find enough links to documentation and forums on the link. However, everything is arranged quite simply: a button is created through the context menu of the toolbar, a code is entered in the dialog of the new button, some additional buns are selected, then the button is dragged to any convenient toolbar - and now it is ready for work (by the way, the extension introduces an additional protocol custombutton: // - it allows you to easily publish buttons on the network and share them in the same way as we use regular links or bookmarklets).
I’ll try to share some of the code recipes dedicated to these indicators. We will move from simple to more complex. The sample code should work in Firefox starting with version 12. I realize that different elements of the code may seem silly to different people, but the examples do not pretend to be perfect: I only share everyday experiences with the help of an amateur code and sincerely apologize for not being able to do this better.
1. A simple request for information.
It happens that we need only a small part of the data from the entire site. And to get this key information, it’s not at all necessary to load the entire page with heavy additional content (ads, pictures, flash, etc.) and resource-intensive scripts, and then search for the desired site on the page. You can get pure lightweight html via Ajax (at the same time, nothing is loaded and executed), convert it to DOM, automatically find the area of interest to us and display only the necessary information.
For an example we will take not too tempting, but simple and bright case. We will demand the current number of articles on the English Wikipedia (which is now approaching four million). To do this, in the very first tab of the dialog to create a button, insert the following code (the tab is called “Code”):
var pageURL = "http://en.wikipedia.org/wiki/Main_Page";
var keyElementXPath = "/html/body//div[@id='articlecount']";
var imgThrobber = "data:image/gif;base64,R0lGODlhEAAQAOMIAAAAABoaGjMzM0xMTGZmZoCAgJmZmbKysv///////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgAIACwAAAAAEAAQAAAESBDJiQCgmFqbZwjVhhwH9n3hSJbeSa1sm5GUIHSTYSC2jeu63q0D3PlwCB1lMMgUChgmk/J8LqUIAgFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UKgmFqbpxDV9gAA9n3hSJbeSa1sm5HUMHTTcTy2jeu63q0D3PlwDx2FQMgYDBgmk/J8LqWPQuFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YSgmFqb5xjV9gQB9n3hSJbeSa1sm5EUQXQTADy2jeu63q0D3PlwDx2lUMgcDhgmk/J8LqUPg+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+cagmFqbJyHV9ggC9n3hSJbeSa1sm5FUUXRTEDy2jeu63q0D3PlwDx3FYMgAABgmk/J8LqWPw+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+QihmFqbZynV9gwD9n3hSJbeSa1sm5GUYXSTIDy2jeu63q0D3PlwDx3lcMgEAhgmk/J8LqUPAOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UqhmFqbpzHV9hAE9n3hSJbeSa1sm5HUcXTTMDy2jeu63q0D3PlwDx0FAMgIBBgmk/J8LqWPQOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YyhmFqb5znV9hQF9n3hSJbeSa1sm5EUAHQTQTy2jeu63q0D3PlwDx0lEMgMBhgmk/J8LqUPgeBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+c6hmFqbJwDV9hgG9n3hSJbeSa1sm5FUEHRTUTy2jeu63q0D3PlwDx1FIMgQCBgmk/J8LqWPweBRhV6z2q0VF94iJ9pOBAA7";
var imgMain = "data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9/AAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB/WvXoYiIiIfEZfWBSIiIEGi/foqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF+iDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var btn = this;
function getDoc(pageURL, pureXHR) {
btn.image = imgThrobber;
var xhr = new XMLHttpRequest();
xhr.mozBackgroundRequest = true;
xhr.open("GET", pageURL, true);
xhr.timeout = 3000;
xhr.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
xhr.channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal)
.forceAllowThirdPartyCookie = true;
if (pureXHR) {
xhr.responseType = "document";
xhr.onload = function() {
btn.image = imgMain;
processDoc(this.responseXML);
}
xhr.ontimeout = function() {
getDoc(pageURL, false);
}
}
else {
xhr.onload = function() {
btn.image = imgMain;
processDoc((new DOMParser()).parseFromString(this.responseText, "text/html"));
}
xhr.ontimeout = function() {
btn.image = imgMain;
alert("Timeout");
}
}
xhr.onerror = function() {
btn.image = imgMain;
alert("HTTP error");
}
xhr.send(null);
}
function processDoc(doc) {
var keyElement = doc.evaluate(keyElementXPath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (keyElement) {
alert(keyElement.textContent);
}
else {
alert("Parsing error");
}
}
getDoc(pageURL, true);
In the future, for another site and other information we will need to change just a few lines. Let's try to figure out what the code does.
The button code is executed through new Function (), so there is no need to somehow protect yourself from interference in the browser namespace.
The very first variables in the function are those variables that need to be reassigned for other conditions. The first is the page address, the second is the XPath of the key element containing the required information. Next, we set two embedded pictures: the first is the pulsator that appears on the button during the request (you do not need to reassign it), the second is the site icon that appears on the button at the end of the request (the same icon can be set as the default button icon in the create dialog).
XPath of a key element is very easy to install using the
XPather extension : it not only integrates into the well-known DOM Inspector, but also adds its position to the context menu, allowing any element on the page to be opened in the window of this extension and get its XPath. The extension gives the full address with all links and sometimes it can be shortened by relying, for example, on the id of the elements. In our example, we just reduced the address (of course, when the key element has an id, you can not use XPath, but here we proceed from the universality of the code and apply a method suitable for all cases).
When creating XPath, you need to remember that XHR does not execute scripts on the loaded page. Therefore, in the process of creating the code, you need to analyze the page with JavaScript disabled or the primary html. It happens that a key element is formed by a script: then our method is not suitable and we need to use a more complex approach with hidden background browsers, which we will not touch on in this article. However, such cases are not so frequent.
The button creation window has a convenient tool for creating data: -addresses for embedding resources; you can easily convert website icon addresses to base64 strings. Of course, nothing prevents you from entering the URL, just in this way we create completely autonomous code and reduce network and local requests.
Next, we assign a future variable for the button, which is first represented as this. And we turn to two subfunctions: one of them will receive the document code, the second will disassemble it and provide the necessary information.
At the beginning of the first subfunction, we start the pulsator, then create an XHR with its parameters: set the background request flag (so that the browser doesn’t bother us with messages designed for normal page loading), timeout boundaries and cache bypass flag just in case. To force Firefox to send cookies with XHR even when the user has disabled cookies from third-party sites, set the flag for forced sending (without this, cookies are not accepted and are not sent).
Hoping for the best, we use xhr.responseType = "document" to immediately access the DOM. However, with this approach, you can run into the error caused by an infrequent confluence of conditions and leading to pseudo-timeouts ( see discussion with links to bugzilla ). Therefore, we insert the safety code: at the first timeout, we try to request the page code again and parse it using DOMParser - this is a little more complicated and wasteful (
As a result, we have several outcomes of the request: the first successful call (with the argument pureXHR == true) with the direct receipt of the DOM leads to the call of the document parsing function, the first timeout leads to the recursive re-request of the clean code (with the argument pureXHR == false) followed by conversion in the DOM and again by calling the document parsing function, a second timeout or communication error leads to the corresponding failure messages.
The parsing subfunction is pretty simple. First we try to get the key element. If an element is found, we issue the necessary information. If there is no element, we suspect that the site has given some kind of wrong page and report an error. The reasons can be different: a redirect (while XHR receives an empty document), authorization has flown and the site has given another page for unauthorized users, the developers have changed the DOM structure - or something else. Then you need to load the page in the browser, check the cause of the failure (including with the help of extensions exploring HTTP activity) and make the necessary adjustments.
This is a simple way to economically request information on 50 lines of code with a trifle. Now try to enrich the tool a little.
2. News indicators with timer checks.
I would like the button itself at intervals to request information and display its status: there is news - there is no news - an error. You can apply this button, for example, to forums that do not provide email alerts about new topics, comments, private messages, and similar events. However, they add clear signs to the pages that such news has appeared (it may be a special element or a change in the attributes of an element).
Take personal messages on rutracker.org as an example. The extension will check the personal messages page (any other one is possible, we will be interested in the header common to most pages; just a private messages page may be the most economical option), search for the desired item, check its properties and report the status by changing the icon on the button and in addition a pop-up hint (you can add a sound notification or a pop-up window above the tray, but in this article we will not talk about such difficulties).
In this example, we will slightly change the principle of action: we need not the first, but the second tab of the button creation dialog. The code in this tab is not executed when the button is clicked, but immediately after launching the browser. This gives us the opportunity to assign the necessary variables and functions for the whole time of requests by the timer, as well as set handlers for different clicks of the button for greater flexibility and convenience. Further we will analyze only changes in the code compared to the first option.
var pageURL = "http://pm.rutracker.org/forum/privmsg.php?folder=inbox";
var keyElementXPath = "/html/body/div[@id='body_container']/div[@id='page_container']/div[@id='page_header']/div[@id='main-nav']/table/tbody/tr/td[2]/a";
var delay = 10 * 1000;
var interval = 60 * 60000;
var imgThrobber = "data:image/gif;base64,R0lGODlhEAAQAOMIAAAAABoaGjMzM0xMTGZmZoCAgJmZmbKysv///////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgAIACwAAAAAEAAQAAAESBDJiQCgmFqbZwjVhhwH9n3hSJbeSa1sm5GUIHSTYSC2jeu63q0D3PlwCB1lMMgUChgmk/J8LqUIAgFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UKgmFqbpxDV9gAA9n3hSJbeSa1sm5HUMHTTcTy2jeu63q0D3PlwDx2FQMgYDBgmk/J8LqWPQuFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YSgmFqb5xjV9gQB9n3hSJbeSa1sm5EUQXQTADy2jeu63q0D3PlwDx2lUMgcDhgmk/J8LqUPg+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+cagmFqbJyHV9ggC9n3hSJbeSa1sm5FUUXRTEDy2jeu63q0D3PlwDx3FYMgAABgmk/J8LqWPw+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+QihmFqbZynV9gwD9n3hSJbeSa1sm5GUYXSTIDy2jeu63q0D3PlwDx3lcMgEAhgmk/J8LqUPAOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UqhmFqbpzHV9hAE9n3hSJbeSa1sm5HUcXTTMDy2jeu63q0D3PlwDx0FAMgIBBgmk/J8LqWPQOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YyhmFqb5znV9hQF9n3hSJbeSa1sm5EUAHQTQTy2jeu63q0D3PlwDx0lEMgMBhgmk/J8LqUPgeBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+c6hmFqbJwDV9hgG9n3hSJbeSa1sm5FUEHRTUTy2jeu63q0D3PlwDx1FIMgQCBgmk/J8LqWPweBRhV6z2q0VF94iJ9pOBAA7";
var imgNews = "data:image/gif;base64,R0lGODlhEAAQAOZAAPv/++Xh5THeANvf293Z3dXR1f4AOuvv66Ono6WhpZ2Zne3p7Zj/cwAQ+G1pbc3JzZOXk/Xx9TriACAs7zs/O9PX03t/e/P38/8HPP+rvk1JTY2JjbWxtVNXU/+XriAkUNnb/wES//8zWVn/GwkV/wAMoGNnY/8LRf8TSUtPS/9/nkf/C/8fUn7/S0RQ44T/X/+LpsvPy3T/QwAIYIWBhZGY//9HcQAM4DE9/xkq/3VxdQAOuKuvq6Gk/9r/w1Fd/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEAALAAAAAAQABAAAAezgECCg4IVC4SIgwcAAEABB4mEFTA2jAADAYkDBxcsBgYYABYEiAEADyKfBhkaCpErBxAYBigqEIQEDC8yAgAIJxkAHgMchAACAhICLQAKFCYFCAOCpsoCDAAOMzsTLgUJggMAIwy9FB8lNw0934ILEQg8PtkTDQ0hAB0Pgt8KEDQRNAwgkeNHjQAFBB0gEIAAAiAbAuAAYalYIgIEClxI4UCHhUiDYmxIwDDAIZABHkxDFAgAOw==";
var imgNoNews = "data:image/gif;base64,R0lGODlhEAAQANU1AP39/erq6pOTk5+fn+Xl5eTk5N7e3vHx8bu7u7q6ut/f36KiotjY2LS0tPDw8JycnK+vr5CQkKioqMrKym1tbfb29pSUlJ2dnaOjo3d3d6Wlpff398bGxoSEhH9/f+jo6KmpqbKystPT05aWlmlpab29vaurq8/Pz7i4uKamprGxsYuLi66urnt7e9bW1sPDw8HBwXl5ednZ2WNjY9zc3P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAADUALAAAAAAQABAAAAajwJpwKFQ4iMjhAQCoBQ5JokKUYAIIgSThsDENBgvApYAMABih70CRaUQxB8hikDpBiIXJCyUAIDAKZwQcRAACAhYCCAANFCsGCARCZokCEwARMx0aLAYJQgQAIBN9FCQtAg80nkIOFQgwH5gaDw8DAB4MQp4NchUZBAsSJS4BBkIHBQEFCDUSASpLTIRJBQUGGzERIxdRQzISCcoBR94BDJJIQQA7";
var imgErrorOrTimeout = "data:image/gif;base64,R0lGODlhEAAQANU6AP98fP+AgP/m5v/ExP+2tv+cnP9ycv/k5P8rK/88PP8CAv9AQP/v75MAAP9HR//y8v8uLv8QELUAAP+Tk/9OTnQAAP89Pf+xsf81Nf+trf/V1f9sbP9YWP9RUf+Li/96ev+Ojv/l5f9ubsAAAP9lZf+EhNwAAPMAAP+5uf+lpbkAAP8WFv+9vf9KSv8NDc0AAIYAAP9UVP8yMv/Bwf/Q0P8MDP/Y2P/9/f9mZv/MzP///wAAAAAAAAAAAAAAAAAAACH5BAEAADoALAAAAAAQABAAAAajQJ1wKBwciMih4HbT2QRJ4iADYN5otiRN8ODIZIkbJIe03Qiir2wgMUQXAlxC1krhiLkC6FO7BRYDZzQTRDc1NRE1ATcGDSczATRCZok1BTcKFSYOJDMAQjQ3MQV9DTAjLggsnkIHDAEeGpgOCAgYNy8EQp4GcgwSNBYdJRc2M0ICOTY5AToUNhshVoRJOTkzDyoKKxBRQygUAMo2R942BJJIQQA7";
var btn = this;
var parser = new DOMParser();
function clickBtn(event) {
if (event.button == 0) {
event.preventDefault();
window.clearInterval(checker);
checker = window.setInterval(getDoc, interval, pageURL, true);
getDoc(pageURL, true);
}
else if (event.button == 1) {
event.preventDefault();
window.clearInterval(checker);
checker = window.setInterval(getDoc, interval, pageURL, true);
btn.image = imgNoNews;
btn.tooltipText = ((new Date()).toLocaleString() + " No news");
if (gBrowser.selectedBrowser.currentURI.spec == "about:blank" && !gBrowser.selectedBrowser.webProgress.isLoadingDocument) {
gBrowser.selectedBrowser.loadURI(pageURL);
}
else {
gBrowser.selectedTab = gBrowser.addTab(pageURL);
}
}
}
function getDoc(pageURL, pureXHR) {
btn.image = imgThrobber;
var xhr = new XMLHttpRequest();
xhr.mozBackgroundRequest = true;
xhr.open("GET", pageURL, true);
xhr.timeout = 3000;
xhr.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
xhr.channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal)
.forceAllowThirdPartyCookie = true;
if (pureXHR) {
xhr.responseType = "document";
xhr.onload = function() {
processDoc(this.responseXML);
}
xhr.ontimeout = function() {
getDoc(pageURL, false);
}
}
else {
xhr.onload = function() {
processDoc(parser.parseFromString(this.responseText, "text/html"));
}
xhr.ontimeout = function() {
btn.image = imgErrorOrTimeout;
btn.tooltipText = ((new Date()).toLocaleString() + " Timeout");
}
}
xhr.onerror = function() {
btn.image = imgErrorOrTimeout;
btn.tooltipText = ((new Date()).toLocaleString() + " HTTP error");
}
xhr.send(null);
}
function processDoc(doc) {
var keyElement = doc.evaluate(keyElementXPath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (keyElement) {
if (keyElement.className.indexOf("new-pm-link") > -1) {
btn.image = imgNews;
btn.tooltipText = ((new Date()).toLocaleString() + " News");
}
else {
btn.image = imgNoNews;
btn.tooltipText = ((new Date()).toLocaleString() + " No news");
}
}
else {
btn.image = imgErrorOrTimeout;
btn.tooltipText = ((new Date()).toLocaleString() + " Authentication or parsing error");
}
}
btn.addEventListener("click", clickBtn, true);
btn.onDestroy = function() {
btn.removeEventListener("click", clickBtn, true);
}
window.setTimeout(getDoc, delay, pageURL, true);
var checker = window.setInterval(getDoc, interval, pageURL, true);
Two more were added to the pair of initial parameters: delay (the number of seconds between launching the browser and the first timer request) and interval (intervals between further requests in minutes). In our example, it is ten seconds and an hour, respectively.
Now we have not two, but four whole icons: the indicator of the ongoing request, indicators of the presence of news and absence of absence, as well as an icon for errors.
We also create a special variable for the DOMParser safety object, so as not to recreate it again with every request.
Then we add a handler for clicking the button with the mouse. It will be enough for us to press the left and middle mouse buttons. The first time we press, we will initiate an extraordinary request (the timer will restart), when we press the middle button, we will open the desired page in the browser to familiarize ourselves with the news in more detail. The middle button handler will check the current tab and, if it is empty and at the same time nothing starts loading, it will open the page in it, otherwise in a new tab. Also, at such an opening, we will restart the timer and reset the news rate until the next request, implying that opening the page is equal to an extraordinary request and will still lead to reading (i.e. resetting) the news on the site.
The XHR result handlers in this example do not generate alerts, but changes to icons and tooltips (the latter include the request time and brief information about the result).
If there are new messages, the server adds an attribute of a certain class to the key element (link to the inbox in the upper right corner of the page). We will check it. If authorization on the site crashes, the server issues a redirect to the login page: in this case, XHR receives an empty document, the element is not found and we get an error, common for other cases of changes in the DOM.
After defining the variables and functions, we attach the click handler to the button, call the first pending request and start the timer.
By the way, in Custom Buttons there is a small bug with event handlers: if you do not add the removal of the handler via onDestroy, the handlers are layered (duplicated) each time you call up the toolbar settings (open and close the tool palette).
3. News indicators with timer checks and state preservation between browser sessions.
In the previous example, it was enough for us to load the page to understand if there was news: the server took care of remembering what the user had read and from what time to count unread news. However, not all sites have such news indicators.
I will give an example. Recently, subtitle tools have replenished with one great program - Subtitle Edit. On the developer's siteCommunicated with users who report errors and request new features. The site has rss for new posts by the author, including with messages about new versions of the program. However, in the comments to the last post, the discussion of the new version usually takes place, and the author often posts links to assemblies with current corrections. To keep abreast of such intermediate changes, you have to periodically open the main page and look at the link of the last post for comments.
There are no subscriptions to post comments. Similarly, there are no obvious signs on the site that there are unread comments. You need to remember the previous number of comments and check if the number in the top link has increased. We will overload this comparison to the button. She will remember the link to the last post and the number of comments in it. If the top post changes or comments are added to it, the button will inform about the news.
Resetting news will also change a bit. In the second example, the user read new posts on the site and their site metric was reset. The reset button only anticipated this. In our third example, the site itself does not change after we read the new comments. Therefore, we will introduce an additional flag variable for the presence / absence of news: as soon as news appears, the variable changes and remains in this state for the duration of all requests, until the user responds and opening the site through the button clears this flag.
Now about the additions in the code.
var pageURL = "http://www.nikse.dk/";
var keyElementXPath = "/html/body/table/tbody/tr/td[2]/table/tbody/tr[1]/td[3]/a";
var delay = 20 * 1000;
var interval = 60 * 60000;
var imgThrobber = "data:image/gif;base64,R0lGODlhEAAQAOMIAAAAABoaGjMzM0xMTGZmZoCAgJmZmbKysv///////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgAIACwAAAAAEAAQAAAESBDJiQCgmFqbZwjVhhwH9n3hSJbeSa1sm5GUIHSTYSC2jeu63q0D3PlwCB1lMMgUChgmk/J8LqUIAgFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UKgmFqbpxDV9gAA9n3hSJbeSa1sm5HUMHTTcTy2jeu63q0D3PlwDx2FQMgYDBgmk/J8LqWPQuFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YSgmFqb5xjV9gQB9n3hSJbeSa1sm5EUQXQTADy2jeu63q0D3PlwDx2lUMgcDhgmk/J8LqUPg+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+cagmFqbJyHV9ggC9n3hSJbeSa1sm5FUUXRTEDy2jeu63q0D3PlwDx3FYMgAABgmk/J8LqWPw+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+QihmFqbZynV9gwD9n3hSJbeSa1sm5GUYXSTIDy2jeu63q0D3PlwDx3lcMgEAhgmk/J8LqUPAOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UqhmFqbpzHV9hAE9n3hSJbeSa1sm5HUcXTTMDy2jeu63q0D3PlwDx0FAMgIBBgmk/J8LqWPQOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YyhmFqb5znV9hQF9n3hSJbeSa1sm5EUAHQTQTy2jeu63q0D3PlwDx0lEMgMBhgmk/J8LqUPgeBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+c6hmFqbJwDV9hgG9n3hSJbeSa1sm5FUEHRTUTy2jeu63q0D3PlwDx1FIMgQCBgmk/J8LqWPweBRhV6z2q0VF94iJ9pOBAA7";
var imgNews = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAJKSURBVHjalJI/axRRFMXvve/NzO7M7oaQNe5uig2aSExIiPgRFBttkhCihfkCYmNjY6NgYyWIlYWFVjFFIKRIHwNCMIkiZCF/Vth1dl1IMjtmd17ezFyLkQhq4+0u/A73nMsB+M9BAJBZ2XetL11OA/+LEEgWtVZb/iefI0aZk6PPR4uzRY4ZAJAw4ZIVGDhmYOCQdx7u1N/UZe5Kzr5oVx5V/M8+mYQSySQUCPRbjAKz49nCdKG12pJWwfI2PG/Dy1/PZ8ezKPH4w7FyVelO6cyVu+D2XO0J6oFIC0KBsYpTpVT5XjmoB8pVl55cQolzN+ZUU6mmUg2FAs3zJiAAggT+FQsQgloQtsP62zpZ1Ib26fdTjlkfaTTw7BpxxIAQfAsai42BuwNjL8dYM0qchMnCTKE4W8yMZFDgWR4ZBzEAyJwEhP1n+5SiqVdTyw+W12Ct+qKaPAoSmBOBjpGQLCrMFFIDKTKp0qlwzBMwsT69nnDuOzfxDAzCGXLsC7b30evsdYAh9MPmUjPqRNvedngchl6oDzUZNH9zfnN7s7nUlLGKhSNY80nlpFvtUorIJDKpvdW2Sla6nHaGnPJEeaWz0lptRSeR1EfaGXb6b/XrQ525nLGH7NxkbiQ3YoPtgXegDoJasPd+r/a65i64HDKa58zS7dLw42Gj14g6kT7S0Y8oVjFKFLZAAxuLjd2nu6xZexoYEADMvGn0GlbRGrw/iAaKtOh+7ba32v4Xv1vtqobiiDnkP1tLFiVfMvOmsAVKRIF/l/fnAIYyDbro6Ua2AAAAAElFTkSuQmCC";
var imgNoNews = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAI4SURBVHjalJK/aipREMbPnDNn/YMYdGWJgiLRIqWVhYqB1PoG1nkKX8E3ECysEtLZGUgKt14RQiAoKiKI/3YXXNTsuntusVzhkupON/Ab5vu+GUL+s4AQEgqFCoWCoihCiN8EpZRzrmnabDbzPA/C4fDT01OlUvFpAPA5vxV/y3XdTqfz/v6OuVwumUx2u935fI6IjDFEpJRSSq/DlNJsNlsqlTRNw1gsNh6Px+NxoVDIZrOMse/vb13XHx4erqoGg0E+n9/v95IkUUqp4zjxeLxWq+33e13XG40GY6xer5umaZqmYRiU0lgsBgAAgEII3xYA7Ha74/H48fHBObdt2zRNIYRlWYh43Yae5wGAruuqqj4+PmYymX6/zxhLJBLlcpkQ8vn5OZlMrn7Qtm1CSDgcBoDX11dJkprNZqvVWq1WvV7PD8qn/dzQdV0A4JyXy2VZlhFxu90KIWRZLpVKPqeqqq9ZCIH+nGEYLy8viqJwzkejkeM47XbbP4vneYqiFIvFr68vy7LQcZxgMHi5XJbL5Xq9liQJERFxOp3KsqwoSjKZvL+/n0wmmqadz2e0LCuVShWLxcPhkE6nU6nU3d3d7e0tItq2bRjGbrcbDodvb2+DwcB1Xbi5ualWq41GIxKJ/Pz8WJZ1Op0cx2GMBQIBRFRV9fn5+XK5HI9HIQQQQqLRaCQSicfj9XodESVJ2mw20+l0sVis12vDMDzPc13336cF4Jz7KUWj0UAgwBijlP5+3j8DAOp7HGipYoAdAAAAAElFTkSuQmCC";
var imgErrorOrTimeout = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAJtSURBVHjalFLPSxRxHP18f8zMOuu67JCLm4m2omzMIthhQaxDXjwKHuzSRZEgQoigg9AtqINIV+nSHyCJhyAMBGXNX2yoyIqJG26u6+gqszUzzszuzPfbYVUKuvRuH3iP93mPB/CfQAAQFsXBeLwjHOb/YhCE6iidzuVWNM3nHEUk6UN//4PeXuAcAAChS2Lt5BwYA87B817Mzr7d2qL3YjFVUd7MzKyfnkqECBhLhBCECEL4SkwxTkWjI4nEdC5HbwWDi8XiYrE4GI+nolGRkPlC4YdpPk0mr7+aymbvx2IHhiFTiinGju+3hkKP+/oODCNvGGNDQwLGbcPDR5Z1ZFmHpkkQaqmvxwhhhCjjvBYLEPr+65fuuvL8fIAQsO2CaXKAkm1LhFy7UZ9zjFDeMD6trT3r6sKq+nluTsAYWloedncDQHpvL318DFcaeuF5ABCRJIzQ8+VleX399fj4xuQk7O9PpNMAwDivZWecAwCt+D4CCBDSn0q1hUISIZDPM86huXkkkQAADvBuZwdEESPEOCeqotyJRL5oWkHTGOc/K5WPGxtmtSptb587ju66JccRMVZHR/Orq+93d6nteQ2i6Pr+5tnZt3JZplQiRMR4WdNuNzR0hMOqopCeHshkpnM5o1qlZ46TVJRHnZ0nFxd3GxuTihJNJKC9HQQBbBs0DQ4PzxcWJjY3p7LZKmPoZjD4RFVfDgyAooBlga6DYfiVCqEUZBkEYSaTGVtacn1fd93LAppk+UYg0BoKvUqlJEJkSvfK5ZWTk6+l0q6uFyzLY6zK2F+TxAjVUVprqUmW6wVBwJhcD/EP/B4AKOgP3B2vzLEAAAAASUVORK5CYII=";
var btn = this;
var parser = new DOMParser();
var prefService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
var prefBranch = prefService.getBranch("cb_storage.");
var supportsString = Components.interfaces.nsISupportsString;
var uStr = Components.classes["@mozilla.org/supports-string;1"].createInstance(supportsString);
function clickBtn(event) {
if (event.button == 0) {
event.preventDefault();
window.clearInterval(checker);
checker = window.setInterval(getDoc, interval, pageURL, true);
getDoc(pageURL, true);
}
else if (event.button == 1) {
event.preventDefault();
window.clearInterval(checker);
checker = window.setInterval(getDoc, interval, pageURL, true);
var previousStat = JSON.parse(prefBranch.getComplexValue("Subtitle_Edit", supportsString).data);
previousStat.isNew = false;
uStr.data = JSON.stringify(previousStat);
prefBranch.setComplexValue("Subtitle_Edit", supportsString, uStr);
prefService.savePrefFile(null);
btn.image = imgNoNews;
btn.tooltipText = ((new Date()).toLocaleString() + " No news: " + previousStat.commentsInfo);
if (gBrowser.selectedBrowser.currentURI.spec == "about:blank" && !gBrowser.selectedBrowser.webProgress.isLoadingDocument) {
gBrowser.selectedBrowser.loadURI(pageURL);
}
else {
gBrowser.selectedTab = gBrowser.addTab(pageURL);
}
}
}
function getDoc(pageURL, pureXHR) {
btn.image = imgThrobber;
var xhr = new XMLHttpRequest();
xhr.mozBackgroundRequest = true;
xhr.open("GET", pageURL, true);
xhr.timeout = 3000;
xhr.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
xhr.channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal)
.forceAllowThirdPartyCookie = true;
if (pureXHR) {
xhr.responseType = "document";
xhr.onload = function() {
processDoc(this.responseXML);
}
xhr.ontimeout = function() {
getDoc(pageURL, false);
}
}
else {
xhr.onload = function() {
processDoc(parser.parseFromString(this.responseText, "text/html"));
}
xhr.ontimeout = function() {
btn.image = imgErrorOrTimeout;
btn.tooltipText = ((new Date()).toLocaleString() + " Timeout");
}
}
xhr.onerror = function() {
btn.image = imgErrorOrTimeout;
btn.tooltipText = ((new Date()).toLocaleString() + " HTTP error");
}
xhr.send(null);
}
function processDoc(doc) {
var keyElement = doc.evaluate(keyElementXPath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (keyElement) {
var previousStat = JSON.parse(prefBranch.getComplexValue("Subtitle_Edit", supportsString).data);
if (previousStat.pageURL != keyElement.href || previousStat.commentsInfo != keyElement.textContent || previousStat.isNew) {
previousStat.pageURL = keyElement.href;
previousStat.commentsInfo = keyElement.textContent;
previousStat.isNew = true;
uStr.data = JSON.stringify(previousStat);
prefBranch.setComplexValue("Subtitle_Edit", supportsString, uStr);
prefService.savePrefFile(null);
btn.image = imgNews;
btn.tooltipText = ((new Date()).toLocaleString() + " News: " + keyElement.textContent);
}
else {
btn.image = imgNoNews;
btn.tooltipText = ((new Date()).toLocaleString() + " No news: " + keyElement.textContent);
}
}
else {
btn.image = imgErrorOrTimeout;
btn.tooltipText = ((new Date()).toLocaleString() + " Parsing error");
}
}
if (!prefBranch.prefHasUserValue("Subtitle_Edit")) {
uStr.data = JSON.stringify({"pageURL": "", "commentsInfo": "", "isNew": false});
prefBranch.setComplexValue("Subtitle_Edit", supportsString, uStr);
prefService.savePrefFile(null);
}
btn.addEventListener("click", clickBtn, true);
btn.onDestroy = function() {
btn.removeEventListener("click", clickBtn, true);
}
window.setTimeout(getDoc, delay, pageURL, true);
var checker = window.setInterval(getDoc, interval, pageURL, true);
As we can see, variables have been added for some internal mechanisms: all of them are useful to us for saving data between sessions. We will use the same settings database as the extensions (we will not use simple files or databases to avoid unnecessary complexity). We have chosen the most universal way of storing string data (*** ComplexValue), so that you can store unicode in the database if necessary.
Instructions for resetting the news flag were added to the handler for clicking the button (the button requests the key from the settings, turns it into an object via JSON, changes the flag, packs the object into a string and saves it again in the settings database).
Most added to the document processing subfunction. After checking for the presence of a key element, we request an object with information about the previous state from the settings database (link to the last post, number of comments and whether the news flag was raised for the last time: after all, all subsequent requests may come across the same results, which all the same should remain news until the user pays attention to the button and opens the site). If the current and previous state are the same and the news flag is not activated, we leave the indicator in a neutral state and do not change anything in the settings. If a new post appears, comments are added, or the news flag is raised from previous requests, we replace the previous state with the current one, save the changed object with parameters to the settings database and signal the news.
After defining the variables and functions and before binding the handlers, deferred calling and starting the timer, we need to do one more thing: check whether there is a key in the settings database for storing our data. If the button initialization is started for the first time (we just created the button), the key is not there yet, and then we create an object blank with empty parameter values and an inactive news flag.
Here, in general, and all the main types of indicators. In each of them, you only need to change the page address and XPath of the key element. In the second and third cases, it is also necessary to change the subject of verification in the parsing function of the document. In the third case, you must still remember to set a new key for storing the data of the new button so that the two buttons do not accidentally overwrite each other's data store.
Examples can be expanded as needed: complicate the methods of news notification, add key elements and comparison parameters, create new interface details on the fly (for example, panels with dynamic text information next to a button) and so on. You can read about all this on the Firefox developers website or look at examples of ready-made buttons on the Custom Buttons developer website.
Thanks to everyone who read it. And successful experiments for you)