⬆️ ⬇️

Bookmarklet: Analysis of Essential Points, Part One

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:

  1. 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 ;
  2. The second part of the bookmarklet: this is a javscript code of arbitrary size that does the rest of the work;
  3. 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:



  1. Defines the variables to be used in the bookmarklet.
  2. 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.
  3. Turns on the load indicator so that the user is not nervous, while all the wealth of functionality continues to load.
  4. It loads the second part of the bookmarklet that ensures the implementation of all further work.
  5. 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.
  6. 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.



image



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.



image



Step 4: The form for saving a bookmark / note / html fragment (image) received from the currently viewed page is displayed.



image



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:



It would be possible to isolate the code like this:



 //   var bookmarlet_code = function() { //     } //    bookmarlet_code(); 


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:



 //           ( function() { //     })(); 


Variable definition



The main thing, when declaring variables, is saving, you should try to meet the 2000 allotted characters, and for this:

  1. all variables are declared in one place, one var keyword;
  2. variable names are specified by one character;
  3. 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:



 //   var         //   window   w //    this     widow var w=this, //      w=window //   window.document   d d=w.document, //   window.location   l l=w.location, //    //   window.location.hostname   u u=l.hostname, //     //            s=w.getSelection(), // ,   id='theonlypageAjaxLoaderGif',      g=d.getElementById('theonlypageAjaxLoaderGif'), //     encodeURIComponent   e e=encodeURIComponent, //    i     for i, //   r          r, //      html    c=''; 


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){ //   … // …  g.parentNode.removeChild(g); // …       return void(0); } 


Connecting the download indicator



Connecting the download indicator image in our example, as follows



 //   - g = new Image(); //     //  id g.id='theonlypageAjaxLoaderGif'; //    z-index //     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'; //      d.body.appendChild(g); 


When creating the load indicator, we assigned it an id to be able to:

  1. in the code of the second part of the bookmarklet , after the download is finished, detect the download indicator and disable it;
  2. 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:

  1. or place it at the address with the https:// protocol
  2. 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.



 //    script r=d.createElement('script'); //  ,        r.src='//d2wlh3lh0sssu9.cloudfront.net/js/mini.bookmarklet.js'; //     … // …         r.async=true; //         d.body.appendChild(r); 


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:

  1. or place it at the address with the https:// protocol
  2. 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.



image



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:



  1. Coded signature line: (3) Facebook :

    t=(3)%20Facebok
  2. encoded address line of the page being viewed www.facebook.com

    h=https%A%2F%2Fwww.facebookcom%2F
  3. Html-code of the selected area - nothing is selected, no data

    =
  4. 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(){ //        //    if( s.rangeCount ){ //  -   –    //    html- 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); 


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:



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”.

Source: https://habr.com/ru/post/234427/



All Articles