As you know, a bookmarklet is a small javascript code which, being stored in browser
bookmarks , is used to perform any actions on the contents of the current web page.
But why in the title of the post:
part one ? Because the modern bookmarklet “with blackjack and whores”
* usually
consists of several interacting parts:
- The first part of the bookmarklet, which is actually a bookmarklet, is a compact javscript code - no more than 2000 characters, the main, but not the only task of which is to download the second part ;
- The second part of the bookmarklet: this is a javscript code of arbitrary size that does the rest of the work;
- backup part of the bookmark - which is launched into action, if the second part of the bookmarklet has not been loaded.
And, as you probably already guessed, this publication will discuss the
first part of the bookmarklet,
Part one usually performs the following simple actions:
- Defines the variables to be used in the bookmarklet.
- Initiates the start of work of the bookmarklet or stops its work with the cleaning of everything embedded on another page in the incl mode. on / off and also checks for the special conditions of the bookmarklet.
- Turns on the load indicator so that the user is not nervous, while all the wealth of functionality continues to load.
- It loads the second part of the bookmarklet that ensures the implementation of all further work.
- If the second part of the bookmarklet cannot be loaded, it receives the data on the current page, which is necessary for transfer to the backup part of the bookmarklet.
- It calls the backup part of the bookmarklet and transfers the necessary data to it.
For brevity, the subject of the publication of the
first part of the bookmarklet will be simply called:
bookmarklet .
')
Real example
As an example, “from real life” we will use
TheOnlyPage bookmarklet (a service for storing bookmarks, notes and html fragments).
To install the bookmarklet in your browser, simply go to
the TheOnlyPage help system page and drag the corresponding link to the browser's bookmarks bar.
You can proceed to the bookmarklet by completing the following 4 steps:
Step 1: Click on the bookmarklet link, if you have not already entered
TheOnlyPage , then go to Step 2, if you have already entered, then go directly to the final Step 4.
Step 2: Click on the
Login to TheOnlyPage button in the form that opens.
Step 3: As a result, the login form is displayed in a separate window. For quick registration / login, you can use the login buttons through social services.
Step 4: The form for saving a bookmark / note / html fragment (image) received from the currently viewed page is displayed.
Now let's go through the javascript code of the bookmarklet and see how it all happens.
The bookmarklet code is as follows:
javascript:(function(){var w=this,d=w.document,l=w.location,u=l.hostname,s=w.getSelection(),g=d.getElementById('theonlypageAjaxLoaderGif'),e=encodeURIComponent,i,r,c='';if(u==='www.theonlypage.com'){return void(0);}if(g){g.parentNode.removeChild(g);return void(0);}g=new Image();d.body.appendChild(g);g.id='theonlypageAjaxLoaderGif';g.style.cssText='position:fixed;z-index:2147483647';g.style.left=Math.floor((w.innerWidth-66)/2)+'px';g.style.top=Math.floor((w.innerHeight-66)/3)+'px';g.src='//d2wlh3lh0sssu9.cloudfront.net/img/ajax-loader.gif';r=d.createElement('script');r.src='//d2wlh3lh0sssu9.cloudfront.net/js/mini.bookmarklet.js';r.async=true;r.addEventListener('error',function(){if(s.rangeCount){c=d.createElement('div');for(i=0;i<s.rangeCount;i+=1){c.appendChild(s.getRangeAt(i).cloneContents());}c=c.innerHTML;}l.assign('http://www.theonlypage.com/b/?t='+e(d.title)+'&h='+e(l.href)+'&c='+e(c)+'&u='+e(u))},true);d.body.appendChild(r);})()
The same code is readable. (function(){ var w=this, d=w.document, l=w.location, u=l.hostname, s=w.getSelection(), g=d.getElementById('theonlypageAjaxLoaderGif'), e=encodeURIComponent, i, r, c=''; if(u==='www.theonlypage.com'){ return void(0); } if(g){ g.parentNode.removeChild(g);return void(0); } g=new Image(); d.body.appendChild(g); g.id='theonlypageAjaxLoaderGif'; g.style.cssText='position:fixed;z-index:2147483647'; g.style.left=Math.floor((w.innerWidth-66)/2)+'px'; g.style.top=Math.floor((w.innerHeight-66)/3)+'px'; g.src='//d2wlh3lh0sssu9.cloudfront.net/img/ajax-loader.gif'; r=d.createElement('script'); r.src='//d2wlh3lh0sssu9.cloudfront.net/js/mini.bookmarklet.js'; r.async=true; r.addEventListener('error', function(){ if(s.rangeCount){ c=d.createElement('div'); for(i=0;i<s.rangeCount;i+=1){ c.appendChild(s.getRangeAt(i).cloneContents()); } c=c.innerHTML; } l.assign('http://www.theonlypage.com/b/?t='+e(d.title)+'&h='+e(l.href)+'&c='+e(c)+'&u='+e(u))},true); d.body.appendChild(r); })()
The important point is to prevent bookmarklet code conflicts with executable scripts on the current page. Standard approaches are used for this:
- placing all the code inside an anonymous function ;
- using only local variables declared inside this function, which excludes the possibility of conflicts with the external environment.
It would be possible to isolate the code like this:
but this creates the
bookmarlet_code
variable, which is a potential source of conflict with the scripts on the current page. To prevent the emergence of a global variable, the
declaration and
execution of the function combine:
Variable definition
The main thing, when declaring variables, is saving, you should try to meet the 2000 allotted characters, and for this:
- all variables are declared in one place, one
var
keyword; - variable names are specified by one character;
- global variables, parameters, and functions that will be used repeatedly should be assigned single-character aliases.
Of course, you can not bother with manually compressing the code, but use one of the many available utilities for compressing javascript code.
In our example, variables are declared as follows:
Turn on / off, check special bookmarklet conditions
Perhaps the bookmarklet should not work on certain pages, for example, the bookmarklet of the web service
TheOnlyPage cannot work on the pages of its own website
www.theonlypage.com .
These restrictions are implemented by checking and shutting down, when the completion conditions are met.
In our example, if the hostname of the current document is:
www.theonlypage.com
www.theonlypage.com
- the bookmarklet ends work by returning an empty value:
void(0)
.
if(u==='www.theonlypage.com'){ return void(0);
The important point is to return exactly the empty value of
void(0)
. Because otherwise, the current document, that is, the content of the viewed web page will be replaced with the
return value .
The on / off mechanism is used to ensure that repeated clicks of the bookmarklet link do not launch the bookmarklet again and again. It is much more convenient, on the contrary, to be able to repeat the work of this bookmarklet by repeated clicking on the same link.
The on / off mechanism, in our example, is implemented as follows.
If you
clicked the first time : we introduce a picture-indicator of loading into the document you are viewing.
If you
clicked a second time : we find an already loaded picture-indicator of the load, delete this picture and complete the work by returning an empty value: void (0).
We determined the loading indicator picture at the beginning when declaring all variables.
g=d.getElementById('theonlypageAjaxLoaderGif')
If you
clicked the first time , there are no images yet,
g=undefined
If the
second time , then the variable
g
contains a picture and the work is completed as follows:
if(g){
Connecting the download indicator
Connecting the download indicator

in our example, as follows
When creating the load indicator, we assigned it an
id
to be able to:
- in the code of the second part of the bookmarklet , after the download is finished, detect the download indicator and disable it;
- when you click the bookmarklet link again, find the download indicator and turn it off.
It is necessary to take into account that when introducing a new element onto another page, its
id
should not coincide with the
id
any element already present on the page.
The important point is the choice of such an
id
which, almost certainly, no one except you will use, for example, containing the name of your service. So, in our example, the
id
is the string
'theonlypageAjaxLoaderGif'
Another
important point is the fact that if the image address protocol is
http://
then errors will occur when working on the current page, the protocol of which address is
https://
. The reason for this is: restrictions imposed on
mixed passive display content (
mixed passive / display content ), which, in particular, prohibit uploading pictures from unprotected addresses to a document protected by TLS (SSL) protocol. There are 2 ways to guarantee a normal load:
- or place it at the address with the
https://
protocol - or use a special
//
notation, for example:
script.src='//d2wlh3lh0sssu9.cloudfront.net/img/ajax-loader.gif'
which means using the same protocol as the parent document. Then, depending on the protocol of the current web page http://
or https://
, the corresponding address will be used:
d2wlh3lh0sssu9.cloudfront.net/img/ajax-loader.gif
or
d2wlh3lh0sssu9.cloudfront.net/img/ajax-loader.gif
Loading the second part of the bookmarklet
To download the javascript code of the
second part of the bookmarklet , you should do actions similar to those in the case of embedding the picture-indicator of the load.
The important point is to attach the script to the body (
body
), and not to the header (
head
) of the document. For HTML 5, the header is not a required attribute and problems are possible if
document.body.appendChild
used instead of
document.head.appendChild
.
Another
important point is the fact that if the downloadable javascript code contains the address protocol:
http://
then there will be errors when working on the current page, the address protocol of which is:
https://
. The reason for this is: restrictions imposed on
mixed active content (
mixed active content ), which prohibit downloading code from unprotected addresses into a document protected by TLS (SSL). There are 2 ways to ensure that the javascript code loads normally:
- or place it at the address with the
https://
protocol - or use a special
//
notation, for example:
script.src='//d2wlh3lh0sssu9.cloudfront.net/js/mini.bookmarklet.js'
which means using the same protocol as the parent document. Then, depending on the protocol of the current web page http://
or https://
, the corresponding address will be used:
d2wlh3lh0sssu9.cloudfront.net/js/mini.bookmarklet.js
or
d2wlh3lh0sssu9.cloudfront.net/js/mini.bookmarklet.js
Backing up bookmarklet
Unfortunately, there are situations when, in principle, the script of the
second part of the bookmarklet cannot be attached to the current document.
In addition to exotic cases, such as viewing a
pdf
file by the
Firefox browser, the main cause of the script loading error is a new security mechanism for Web pages
Content Security Policy .
The main purpose of the new
Content Security Policy standard is to protect the user from cross-site scripting. It is fully supported by
Firefox and
Google Chrome browsers.
Among website builders, this standard is not used very widely, special HTTP headers are used for its implementation, which goes beyond the usual scope of duties. But some advanced experts have already involved this standard. For example, the following sites prohibit the use of scripts from foreign servers:
www.facebook.com and
GitHub.com .
What can not but grieve, and sometimes cause bewilderment and fair anger from the creator of the bookmarklet. Of course, you can advise
Opera browser as an alternative, in which this standard has not yet been implemented, but a solution is also needed for dedicated users of
Firefox and
Google Chrome browsers.
It is worth noting that if the site is equipped with
Content Security Policy then the “blackjack with whores”
* will not always succeed in stirring up, but some backup option, with reduced
functionality and
presentability is possible.
For clarity, let's go, for example, on the page
www.facebook.com and click on the link of the bookmarklet
TheOnlyPage . The bookmarklet will work, but not in the usual way.
This time, the bookmarklet form does not appear directly above the current page of the site, but instead loads a new page at:
http://www.theonlypage.com/b/?t=(3)%20Facebook&h=https%3A%2F%2Fwww.facebook.com%2F&c=&u=www.facebook.com
That is, we get to a special page of the web service
TheOnlyPage , on which a very familiar form is displayed, to create a new bookmark, note, or picture.
As you can see, the parameters for the
backup part of the bookmarklet are transmitted in the
address bar . In our case, the following 4 parameters were passed:
- Coded signature line:
(3) Facebook
:
t=(3)%20Facebok
- encoded address line of the page being viewed
www.facebook.com
h=https%A%2F%2Fwww.facebookcom%2F
- Html-code of the selected area - nothing is selected, no data
=
- hostname of the page
u=www.facebook.com
We made sure that the
fallback works, it remains to see how it gets started in the code in the bookmarklet.
It is absolutely clear that the
backup version of the bookmarklet is launched for execution only if it was not possible to attach the code for the
second part of the bookmarklet . In order to catch the script loading error, install the appropriate
error
event handler.
r.addEventListener('error', function(){
And, in conclusion, another
important point that needs to be considered when writing the bookmarklet code. When displaying a bookmarklet code in
html markup , for example, so that the user can copy the code presented on the page and use it to create a bookmarklet, do not forget to replace the characters:
<
on <
>
to >
"
on "
if such characters are found in the javascript code of the bookmarklet.
The second (loadable) part and the
backup part of the bookmarklet will be discussed separately, in subsequent posts on
habrahabr.ru
“With blackjack and hookers”
* (with blackjack and hookers) is the phrase of the robot Bender from the second episode of the first season of “Futurama”.