📜 ⬆️ ⬇️

Client side CORS emulation: cross-browser solution of some user tasks without extensions

I. What is the problem



Browser extensions are a powerful tool for advanced web surfing , the most accessible, developed and widespread part of a number of tools . However, the extensions have weaknesses: each browser requires knowledge and application of its own rules and formats, and this is an additional difficulty for the creator. Extensions are not cross-browser, which immediately limits their destination. There are attempts to generalize the creation of extensions , but they can add their own additional layer of formats and rules.

When the extension improves the specific side of the browser interface, you can not do without it. But some tasks are universal, not related to private browser tools and, nevertheless, they cannot be completed without expansion too. One such task is cross-domain XMLHttpRequest requests that violate a single source policy .
')

Ii. Private Attempts



There is a type of extensions that helps in this case and even more so - it is designed to eliminate the need for many other extensions. These are the famous Greasemonkey and Tampermonkey : they allow you to do with simple JavaScript code to solve many problems and at the same time create a number of auxiliary ways to quickly install such scripts from common sources regardless of user training. However, they do not completely solve the problem of cross-browser compatibility and ease of creation and use. In addition, the description of the capabilities of one of their most powerful tools, GM_xmlhttpRequest , suggests that not all new standards are implemented in it (it’s hard to know whether you can get any type of response other than responseText , in particular, whether the document is available as DOM- tree).

Ideally, I would like pure javascript that would work in all browsers. How this is done in bookmarklets or local HTML pages with scripts. But both bookmarklets that work in the context of the current page and local files cannot make cross-domain XMLHttpRequest queries.

This is where CORS technology comes to our rescue. Not by itself: its very principle does not allow the user to influence its mechanisms, completely giving the exchange of data to the power of the server and browser. But there are mechanisms to interfere in the communication of these two elements and adjust it to the needs of the user.

Some of them are implemented again in the form of browser extensions (hm).

Firefox has a great tool for editing HTTP headers on the fly: moz-rewrite . Its most functional subtype is Rewrite HTTP Headers (JS) . The extension provides good control over the editing of headers depending on different conditions; it allows you to write rules on JavaScript itself using variables and access to the headers already received.

Google Chrome has a number of less functional extensions like Requestly : their capabilities are much more modest and poorly suited to our needs.

IE doesn't seem to have got anything appropriate here.

Iii. Universal solution.



In general, we again approached the same problem: cross-browser compatibility and simplicity suffers. A tool is needed that would be independent of particular browsers, and here a local proxy server comes to mind. Fortunately, there is a very simple and lightweight solution: it is quickly installed, requires almost no configuration, it has already been implemented for all major operating systems. This is Fiddler .

Most often it is used to study HTTP traffic on the user side. But the program can also edit HTTP headers on the fly, and even change the response bodies, which, in principle, allows it to replace extensions for user scripts and styles, such as Greasemonkey or Stylish (users of IE can try the power of such tools for the first time) - however, we will not write about this side of the application for the time being and will only focus on intercepting and editing headers.

Download the program here . I advise you to immediately add an extension (hmm ...) to it for more convenient editing of rules for intercepting and changing traffic. It has at least two names and two habitats on the site:

FiddlerScript Editor

Syntax-Highlighting Add-Ons

Among other things, it will add a good reference book of all the built-in properties and methods of Fiddler directly to the program, because even quite voluminous documentation does not mention all the wealth.

There is no need to be intimidated: Fiddler uses quite standard JScript.NET , in fact, the same JavaScript, and familiarity with additional objects, properties and methods of the application is equal to learning one small interface, like the same XMLHttpRequest or properties of DOM elements. There is a brief introduction from the author .

Iv. Fast start



Those who want to get complete control over the tool can spend several hours reading the documentation and studying the built-in reference book (or even read a book written by the author of the program ), but I will try to sketch out an answer option for this case: you wrote a bookmarklet or application as a local one (or even a hosted on the network) HTML pages and should explain to the average user how to make it all work in his browser (there is a chance that some Firefox or Chrome users, spoiled by extensions, don’t understand you t, but IE users gain here is almost the only way out into the wild).

1. Download and install Fiddler and its extension (good, both files weigh only 1.7 MB, they can even be sent as an email attachment).

2. By default, the program has almost everything configured for our needs. The only additional setting will be needed if your application uses HTTPS requests. Just check this tab:



3. IE and Google Chrome do not need to be further configured: they use the system proxy by default, if one appears, and Fiddler registers itself with such a proxy at launch time, very easy and convenient. The default Firefox settings are slightly different, but this is also nothing complicated. Everything is briefly described here:

docs.telerik.com/fiddler/Configure-Fiddler/Tasks/ConfigureBrowsers
docs.telerik.com/fiddler/Configure-Fiddler/Tasks/FirefoxHTTPS

4. After these three steps, each time you start Fiddler, the traffic will automatically go through it, and after closing the program, it will return to the normal path again. If you do not want Fiddler to be a regular broker, and plan to launch it only while running a bookmarklet or a local application, then it’s more convenient to add an option to the program launch shortcut:



and in the Fiddler settings, select minimize to icon. Then the program will simply appear for a while in the tray (you can close it from the menu of the local icon). At first it seems too complicated: run the whole application for the work of a bookmarklet or page, but you quickly get used to it. You can take Fiddler as a cross-browser extension or as a platform for cross-browser extensions, if it is psychologically more convenient. And if you keep it working all the time and set up an automatic start when you log into the system, you can forget about it at all: it works completely transparent, consumes little memory (especially if you configure it to save and display the minimum number of sessions, which doesn’t harm us at all - quietly choose “Keep 100 sessions”, after that there will always be only 100 lines of the last requests on the screen, and the memory will not be substantially filled); the processor is also not loaded, and there are no functional problems or any noticeable delays in the work of the sites.

5. One last step is left - not quite trivial for the average user, but if you carefully follow it, there is nothing to worry about either. We need to add a small code that will be executed by Fiddler for each suitable HTTP connection and change / add the headers we need. The code is universal for most cross-domain query needs.

Launch Fiddler and select this menu item in the menu (or simply press Ctrl + R):



If you installed the mentioned extension, a small built-in editor opens in response (just to the right there will be a column with a full reference book of everything that Fiddler adds to regular JavaScript; when you select a list item, a brief description of the object, property or method appears at the top of the column):



In the editor, a special script similar to user Greasemonkey scripts will be loaded: it only runs not every time a suitable page is loaded in the browser, but at every suitable event in the HTTP traffic passing through Fiddler. The script already has a preliminary code from the developers themselves, but we do not need to understand it. We just need to insert our small fragment into an already present function. To facilitate our task, the editor has a special menu item that immediately translates to this function:



We need to insert the following code there (it will be explained later):

  if (oSession.oRequest.headers.ExistsAndContains('Accept-Language', ',qya;q=0.001') && oSession.oRequest.headers.Exists('Origin')) { oSession.oResponse.headers['Access-Control-Allow-Origin'] = oSession.oRequest.headers['Origin']; oSession.oResponse.headers['Access-Control-Allow-Credentials'] = 'true'; if (oSession.oRequest.headers.Exists('Access-Control-Request-Method')) { oSession.oResponse.headers['Access-Control-Allow-Methods'] = oSession.oRequest.headers['Access-Control-Request-Method']; } if (oSession.oRequest.headers.Exists('Access-Control-Request-Headers')) { oSession.oResponse.headers['Access-Control-Allow-Headers'] = oSession.oRequest.headers['Access-Control-Request-Headers']; } if (oSession.oResponse.headers.Exists('Vary')) { oSession.oResponse.headers['Vary'] += ', Origin'; } else { oSession.oResponse.headers['Vary'] = 'Origin'; } } 


After insertion, the function code should look like this (before the insertion there was already one small block, you need to insert it after it):



After saving the file, Fiddler will automatically reload and analyze the script and, if everything is in order, will make an approving sound and will report on the acceptance of the new version in the status bar. If the insertion breaks something, an error message will be displayed. If something went wrong and you can not return the file to its original position, simply delete it, and the next time the program starts, it will restore it to its original form (the address of the file in Win 7 is c:\Users\[ ]\Documents\Fiddler2\Scripts\CustomRules.js , see disaster recovery in the help or comments in the very beginning of the file). In the end, if you need to transfer your application to a person who will be afraid to edit the rule file himself, give him the finished one, already with an insert, and let him replace it at the right address.

That's all. After these steps, the “Fiddler + bookmarklet / local page” bundle can be considered as a cross-browser extension or application (at the end of the article I will present some primitive examples).

V. Some features of the mechanism



1. First of all, I set out to somehow highlight requests from my applications in the general flow so that Fiddler would not have to change all the headers and perform extra checks within inappropriate sessions. I had to consistently test and discard several options.

but. Adding to the addresses of requests for a special hash ( #fiddler-- ). It will not work, since the hash is not sent via HTTP, it only makes sense for the browser (therefore, this method would be useful if we had to deal with the advanced extension of HTTP editing for the browser: it could intercept the hashes and change the headers based on this condition ( the mentioned Rewrite HTTP Headers (JS) can); if such an extension is ever developed with an implementation for all major browsers including Edge, you can think about this option).

b. Explicit addition of the default port will be cut off by the browser. To add a non-standard port so that Fiddler intercepted it and changed it to a regular one, I considered it a risky business.

at. Adding to the domain of the authorization part of the URL of the form user:password@ with arbitrary strings in the hope that the server will ignore them. It turned out that browsers for some reason do not send this part via HTTP, it does not reach Fiddler. In addition, this part, as well as the port number, would still disappear during redirects, which for XMLHttpRequest are transparent, without interception, and the exchange of headers with the new address would have already passed without Fiddler's participation.

Adding a marker to the query / search part ( &fiddler=1 ). It was found that some servers, finding an unfamiliar option in this part, are redirecting to a URL without this option. For such cases, it would be possible to add a marker, say, &&& , which for the server would not be visible (when parsing would turn into three empty options and would not cause server concerns), but, again, if normal forwarding occurs, He will disappear from the address.

D. Cookies . Will disappear when redirecting to another domain.

e. Adding a custom custom header (some X-Hello-Fiddler ). It turned out that the presence of such a header causes a so-called Preflighted request , which can be intercepted and edited, but if it is followed by a redirect, the browser will generally interrupt the request - Preflighted request with redirection is incompatible.

. Adding a standard header . It turned out that there is not so simple. The browser does not allow to add some of the headers for security reasons; it can only create them by itself. Part of the headers, even the standard ones, still cause Preflighted request. To prevent this from happening, you need to limit yourself to a very small number of headers that are considered “simple”. See about them: www.w3.org/TR/cors/#terminology ( "simple header" ).

So I came to the remaining way: making safe changes to the usual headers that the browser allows you to change. The only such title was Accept-Language . It is configured by the browser according to the preferences of the preferred languages ​​in the browser options. You can get an idea of ​​them in JavaScript code using the navigator.languages ​​property, but it is not supported in IE11 (the navigator property supported by all browsers only gives the browser interface language). For example, in my three browsers, the JSON.stringify([navigator.language, navigator.languages]) code in the console displays the following:

 Chrome: "[ "en-US", [ "en-US", "en", "ru", "uk" ] ]" Firefox: "[ "en-US", [ "en-US", "en", "ru", "uk" ] ]" IE11: "[ "ru-RU", null ]" 


The most convenient way is to look in the web console, which header the browser itself sends, and use this code in your scripts, adding a token to it. My browsers give the following Accept-Language headers:

 Chrome: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4 Firefox: en-US,en;q=0.8,ru;q=0.5,uk;q=0.3 IE11: en-US,en;q=0.8,ru;q=0.5,uk;q=0.3 


It remains to add to them some rare language with the smallest coefficient of preference. I chose Quenya , since it is already included in the standard under the qya code. So, we need to add something like qya;q=0.001 to the header (the minimum number allowed by the standard), which we see in the very first checked condition for Fiddler a little higher (along with checking the Origin header, which is a cross-domain query, - without it, there is no point in interfering with headlines, everything will work this way).

2. What do insert directives do after checking two conditions:

but. Mirror sent by the Origin request - without this, the browser will not give the answer to the script (when querying for local pages, it will be null , but the server must return it);

b. add authorization support for requests ( Access-Control-Allow-Credentials - without this, the browser will not give access to the received information related to cookies, that is, you will not get the page that you get when you authorize on the site);

at. for securing, mirroring browser requests for special methods and headers , if you suddenly manage to add them to the script or the browser itself wants to add them (I have not yet encountered such cases);

add the Vary header, without which the browser simply caches the response and will receive it from the cache, bypassing Fiddler, but it will come across another source in Access-Control-Allow-Origin and terminate the request - the Vary header will just put the caching such responses depending on the source of the request.

3. Some possible problems (mainly in IE) and attempts to solve them.

but. IE11 is still not using the popular P3P technology. If the server does not implement it (as it happens in most cases), IE will not send cookies to such a server if they are not basic (which is exactly the rule for cross-domain XMLHttpRequest requests). Firefox has an old bug that prevents even extensions from sending cookies if third-party cookies are not allowed in the browser settings. In IE, we have something similar. The P3P rules are very complicated, not controlled by the user and interfered with them with the help of Fiddler. applications). However, there is an easy way out. In IE settings, you need to open these windows and check such options:



After that, you will get authorization support in XMLHttpRequest requests for IE. By the way, I stumbled upon a strange glitch: after a single use of this option and the subsequent cancellation of it, IE still continued to send third-party cookies. Perhaps it was a unique set of circumstances.

You can also try to add each necessary site to the exceptions in the same dialogue or in the privacy policy dialogue, but this is a long and not universal way, albeit a little soothing, useful or excessive paranoia.

In Chrome and Firefox, third-party cookies are enabled by default (in Firefox there is a useful option to accept only cookies of visited sites, which is enough in most cases), so here you can additionally not configure anything.

b. In IE11 there is a very strange bug, the nature of which I did not understand. :

SEC7120: null Access-Control-Allow-Origin.

XMLHttpRequest: 0x80070005, .

: , , null ( ). , null , . , .

at. , , Fiddler- ( Vary Origin ), . , , ( ), — , : Chrome Firefox , IE11 . , .

. : Content Security Policy . Firefox , , XMLHttpRequest , CSP (, Twitter GitHab). CSP . ( CSP , , — ):

— Fiddlera- Content-Security-Policy ( ):

  if (/[?&]tempnocsp=1\b/i.test(oSession.PathAndQuery)) { oSession.oResponse.headers.Remove('Content-Security-Policy'); oSession.oResponse.headers.Remove('X-Content-Security-Policy'); } 


— , query- tempnocsp=1 URL ( , ; , , — Fiddler-). :

 javascript:(function(l) { if (!/[?&]tempnocsp=1\b/i.test(l.href)) { l.href += (/\?/.test(l.href) ? "&" : "?") + "tempnocsp=1"; } })(location); 


, . , . - Fiddler- , Firefox Chrome — . , . Fiddler, — .

Vi.



, , . , , , , ( IE). — .

1. , , / .

HEAD , Content-Type , Last-Modified Content-Length . , . , . responseURL IE11 ( , , , Edge).

: Content-Type Last-Modified , Content-Length . , Chrome :

VM117:1 Refused to get unsafe header "Content-Length"

Fiddler-, CORS: , . , :

  if (oSession.RequestMethod == 'HEAD') { if (oSession.oResponse.headers.Exists('Access-Control-Expose-Headers')) { oSession.oResponse.headers['Access-Control-Expose-Headers'] += ', Content-Length'; } else { oSession.oResponse.headers['Access-Control-Expose-Headers'] = 'Content-Length'; } } 


, , Fiddler- , :

OnBeforeResponse
  if (oSession.oRequest.headers.ExistsAndContains('Accept-Language', ',qya;q=0.001') && oSession.oRequest.headers.Exists('Origin')) { oSession.oResponse.headers['Access-Control-Allow-Origin'] = oSession.oRequest.headers['Origin']; oSession.oResponse.headers['Access-Control-Allow-Credentials'] = 'true'; if (oSession.oRequest.headers.Exists('Access-Control-Request-Method')) { oSession.oResponse.headers['Access-Control-Allow-Methods'] = oSession.oRequest.headers['Access-Control-Request-Method']; } if (oSession.oRequest.headers.Exists('Access-Control-Request-Headers')) { oSession.oResponse.headers['Access-Control-Allow-Headers'] = oSession.oRequest.headers['Access-Control-Request-Headers']; } if (oSession.oResponse.headers.Exists('Vary')) { oSession.oResponse.headers['Vary'] += ', Origin'; } else { oSession.oResponse.headers['Vary'] = 'Origin'; } if (oSession.RequestMethod == 'HEAD') { if (oSession.oResponse.headers.Exists('Access-Control-Expose-Headers')) { oSession.oResponse.headers['Access-Control-Expose-Headers'] += ', Content-Length'; } else { oSession.oResponse.headers['Access-Control-Expose-Headers'] = 'Content-Length'; } } } if (/[?&]tempnocsp=1\b/i.test(oSession.PathAndQuery)) { oSession.oResponse.headers.Remove('Content-Security-Policy'); oSession.oResponse.headers.Remove('X-Content-Security-Policy'); } 


, , , — , , :

 javascript:(function(url, xhr) { if(!url) {return;} xhr = new XMLHttpRequest(); try {xhr.open('HEAD', url, true);} catch (e) { alert(e.name + ': ' + e.message); return; } xhr.setRequestHeader('Accept-Language', 'en-US,en;q=0.8,ru;q=0.6,uk;q=0.4,qya;q=0.001'); xhr.responseType = 'document'; xhr.timeout = 10000; xhr.withCredentials = true; xhr.onload = function(evt, l) { l = this.getResponseHeader('Content-Length'); alert([ this.responseURL || '?', this.getResponseHeader('Content-Type') || '?', this.getResponseHeader('Last-Modified') || '?', l ? [l + ' B', (l / 1024) .toFixed(3) + ' kB', (l / 1048576) .toFixed(3) + ' MB', (l / 1073741824).toFixed(3) + ' GB'].join(' \u2248 ') : '?' ].join('\n\n') + '\n\n'); }; xhr.ontimeout = xhr.onerror = function(evt) {alert(evt.type.charAt(0).toUpperCase() + evt.type.slice(1) + '.');}; try {xhr.send(null);} catch (e) {alert(e.name + ': ' + e.message); return;} })(document.activeElement.href || prompt('URL:')) 


, URL , . , ( IE11 — habrastorage.org/files/5c2/1e7/e48/5c21e7e4838b43b0b4f275d041a4e234.png ) Chrome :



Firefox Nightly :



, — . Chrome xhr.onerror :

XMLHttpRequest cannot load file:///... Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.

Firefox xhr.send() , , , . :

NS_ERROR_DOM_BAD_URI: Access to restricted URI denied

IE11 , xhr.open() . . IE :

Error: .

2. « »

«Breadcrumbs» , Total Commander, .

Firefox Advanced Locationbar . Chrome Breadcrumb Navigator . IE11, , ( IE Explorer- ).

, , . , , . : ( ), ( ), ( ). , — , XMLHttpRequest . ( — ).

 javascript:(function(d, l, throbber, div, url, ar, arl, i, str, el, info_url, info_title){ if (d.activeElement.href) {l = d.activeElement;} else {l = location;} throbber = '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'; div = d.createElement('div'); div.setAttribute('data-breadcrumbs-main', ''); div.appendChild(d.createElement('style')).textContent=[ 'div[data-breadcrumbs-main] {position:fixed; top:0px; left:0; z-index:999999; width:100%; padding:10px !important; background-color:white !important; outline:1px solid black !important; font-size: 12pt !important; text-align: left !important;}', 'div[data-breadcrumbs-main] a {font-size: 12pt !important; text-decoration: underline !important;}', 'div[data-breadcrumbs-main] div {margin-top: 20px!important; color: silver !important;}', 'div[data-breadcrumbs-info-title] {background-repeat: no-repeat !important; background-position: left center !important; padding-left: 20px !important;}', ].join('\n'); url = l.protocol + (/:[/][/]/.test(l.href) ? '//' : ''); div.appendChild(d.createElement('span')).textContent = url; ar = l.hostname.split('.'); arl = ar.length; if(arl > 1) { ar.splice(arl - 2, 2, ar.slice(arl - 2).join('.')); arl--; } for (i = 0; i < arl; i++) { str = ar[i]; if (i > 0) {div.appendChild(d.createElement('span')).textContent = '.';} el = div.appendChild(d.createElement('a')); el.textContent = str; el.href = url + ar.slice(i).join('.'); } url += l.hostname; if(l.port && /:\d+$/.test(l.host)) { div.appendChild(d.createElement('span')).textContent = ':'; el = div.appendChild(d.createElement('a')); el.textContent = l.port; url += ':' + l.port; el.href = url; } div.appendChild(d.createElement('span')).textContent = '/'; url += '/'; if(l.pathname.length > 1){ ar = l.pathname.split('/'); ar.shift(); arl = ar.length; for (i = 0; i < arl; i++) { str = ar[i]; if (i < arl-1) { el = div.appendChild(d.createElement('a')); el.textContent = str; url += str; div.appendChild(d.createElement('span')).textContent = '/'; url += '/'; el.href = url; } else if (str !== '') { el = div.appendChild(d.createElement('a')); el.textContent = str; url += str; el.href = url; } } } if(l.search){ ar = l.search.split('&'); arl = ar.length; ar[0] = ar[0].substring(1); div.appendChild(d.createElement('span')).textContent = '?'; url += '?'; for (i = 0; i < arl; i++) { str = ar[i]; if (i > 0) { div.appendChild(d.createElement('span')).textContent = '&'; url += '&'; } el = div.appendChild(d.createElement('a')); el.textContent = str; url += str; el.href = url; } } if(l.hash){ div.appendChild(d.createElement('span')).textContent = '#'; el = div.appendChild(d.createElement('a')); el.textContent = l.hash.substring(1); url += l.hash; el.href = url; } if (div.querySelector('a:last-of-type').href == location.href) { div.querySelector('a:last-of-type').setAttribute('data-breadcrumbs-doc-title', d.title); } info_url = div.appendChild(d.createElement('div')); info_url.setAttribute('data-breadcrumbs-info-url', ''); info_url.textContent = 'URL'; info_title = div.appendChild(d.createElement('div')); info_title.setAttribute('data-breadcrumbs-info-title', ''); info_title.textContent = 'Title'; div.addEventListener('mouseover', function(evt, h, t){ if (h = evt.target.href) { info_url.textContent = h; if (t = evt.target.getAttribute('data-breadcrumbs-doc-title')) { info_title.textContent = t; } else{getTitle(evt.target);} } }); div.addEventListener('dblclick' /*mouseleave*/, function(){div.parentNode.removeChild(div);}); d.body.appendChild(div); function getTitle(lnk, xhr) { info_title.style.backgroundImage = 'url('+ throbber + ')'; xhr = new XMLHttpRequest(); try {xhr.open('GET', lnk.href, true);} catch (e) { info_title.style.backgroundImage = 'none'; info_title.textContent = e.name + ': ' + e.message; return; } xhr.setRequestHeader('Accept-Language', 'en-US,en;q=0.8,ru;q=0.6,uk;q=0.4,qya;q=0.001'); xhr.responseType = 'document'; xhr.timeout = 10000; xhr.withCredentials = true; xhr.onload = function() { info_title.style.backgroundImage = 'none'; if (this.response && this.response.title) { info_title.textContent = this.response.title; lnk.setAttribute('data-breadcrumbs-doc-title', this.response.title); } else { info_title.textContent = '?'; lnk.setAttribute('data-breadcrumbs-doc-title', '?'); } }; xhr.ontimeout = xhr.onerror = function(evt){ info_title.style.backgroundImage = 'none'; info_title.textContent = evt.type.charAt(0).toUpperCase() + evt.type.slice(1) + '.'; }; try {xhr.send(null);} catch (e) { info_title.style.backgroundImage = 'none'; info_title.textContent = e.name + ': ' + e.message; } } })(document) 


( , ) - , CORS . , . , MDN , :



.

IE11 (. ), , UglifyJS :

 javascript:(function(e,t,n,a,A,i,r,o,l,p,d,s){function h(e,t){s.style.backgroundImage='url('+n+')',t=new XMLHttpRequest;try{t.open('GET',e.href,!0)}catch(a){return s.style.backgroundImage='none',void(s.textContent=a.name+': '+a.message)}t.setRequestHeader('Accept-Language','en-US,en;q=0.8,ru;q=0.6,uk;q=0.4,qya;q=0.001'),t.responseType='document',t.timeout=1e4,t.withCredentials=!0,t.onload=function(){s.style.backgroundImage='none',this.response&&this.response.title?(s.textContent=this.response.title,e.setAttribute('data-breadcrumbs-doc-title',this.response.title)):(s.textContent='?',e.setAttribute('data-breadcrumbs-doc-title','?'))},t.ontimeout=t.onerror=function(e){s.style.backgroundImage='none',s.textContent=e.type.charAt(0).toUpperCase()+e.type.slice(1)+'.'};try{t.send(null)}catch(a){s.style.backgroundImage='none',s.textContent=a.name+': '+a.message}}for(t=e.activeElement.href?e.activeElement:location,n='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',a=e.createElement('div'),a.setAttribute('data-breadcrumbs-main',''),a.appendChild(e.createElement('style')).textContent='div[data-breadcrumbs-main] {position:fixed; top:0px; left:0; z-index:999999; width:100%; padding:10px !important; background-color:white !important; outline:1px solid black !important; font-size: 12pt !important; text-align: left !important;}\ndiv[data-breadcrumbs-main] a {font-size: 12pt !important; text-decoration: underline !important;}\ndiv[data-breadcrumbs-main] div {margin-top: 20px!important; color: silver !important;}\ndiv[data-breadcrumbs-info-title] {background-repeat: no-repeat !important; background-position: left center !important; padding-left: 20px !important;}',A=t.protocol+(/:[\/][\/]/.test(t.href)?'//':''),a.appendChild(e.createElement('span')).textContent=A,i=t.hostname.split('.'),r=i.length,r>1&&(i.splice(r-2,2,i.slice(r-2).join('.')),r--),o=0;r>o;o++)l=i[o],o>0&&(a.appendChild(e.createElement('span')).textContent='.'),p=a.appendChild(e.createElement('a')),p.textContent=l,p.href=A+i.slice(o).join('.');if(A+=t.hostname,t.port&&/:\d+$/.test(t.host)&&(a.appendChild(e.createElement('span')).textContent=':',p=a.appendChild(e.createElement('a')),p.textContent=t.port,A+=':'+t.port,p.href=A),a.appendChild(e.createElement('span')).textContent='/',A+='/',t.pathname.length>1)for(i=t.pathname.split('/'),i.shift(),r=i.length,o=0;r>o;o++)l=i[o],r-1>o?(p=a.appendChild(e.createElement('a')),p.textContent=l,A+=l,a.appendChild(e.createElement('span')).textContent='/',A+='/',p.href=A):''!==l&&(p=a.appendChild(e.createElement('a')),p.textContent=l,A+=l,p.href=A);if(t.search)for(i=t.search.split('&'),r=i.length,i[0]=i[0].substring(1),a.appendChild(e.createElement('span')).textContent='?',A+='?',o=0;r>o;o++)l=i[o],o>0&&(a.appendChild(e.createElement('span')).textContent='&',A+='&'),p=a.appendChild(e.createElement('a')),p.textContent=l,A+=l,p.href=A;t.hash&&(a.appendChild(e.createElement('span')).textContent='#',p=a.appendChild(e.createElement('a')),p.textContent=t.hash.substring(1),A+=t.hash,p.href=A),a.querySelector('a:last-of-type').href==location.href&&a.querySelector('a:last-of-type').setAttribute('data-breadcrumbs-doc-title',e.title),d=a.appendChild(e.createElement('div')),d.setAttribute('data-breadcrumbs-info-url',''),d.textContent='URL',s=a.appendChild(e.createElement('div')),s.setAttribute('data-breadcrumbs-info-title',''),s.textContent='Title',a.addEventListener('mouseover',function(e,t,n){(t=e.target.href)&&(d.textContent=t,(n=e.target.getAttribute('data-breadcrumbs-doc-title'))?s.textContent=n:h(e.target))}),a.addEventListener('dblclick',function(){a.parentNode.removeChild(a)}),e.body.appendChild(a)})(document) 


3. .

. sites , . : CSS- , ; : ( ) , ( — ).

( IE11 , DOM-).

, , .

, -. ( , ). , , , .

HTML- :



:

 <!doctype html> <html> <head> <meta charset='UTF-8'><title>User Data Aggregation</title> <style> body > div > a {display: inline-block; background-repeat: no-repeat;} body > div > pre, body > div > a {padding: 5px 5px 5px 20px; margin: 0px;} </style> <script> /******************************************************************************/ 'use strict'; /******************************************************************************/ var sites = { 'habrahabr.ru': { checkPageURL: 'http://habrahabr.ru/users/vmb/', keyElementSelectors: '#layout > div.inner > div.user_header > div.karma, #layout > div.inner > div.user_header > div.rating, #layout > div.inner > div.content_left > div.user_profile > div.rating-place, #layout > div.inner > div.sidebar_right > div.block.user_info > div.info', re: [ /(\d+) (\d+ .+)/, '$1 ($2)' ] }, 'geektimes.ru': { checkPageURL: 'http://geektimes.ru/users/vmb/', keyElementSelectors: '#layout > div.inner > div.user_header > div.karma, #layout > div.inner > div.user_header > div.rating, #layout > div.inner > div.content_left > div.user_profile > div.rating-place, #layout > div.inner > div.sidebar_right > div.block.user_info > div.info', re: [ /(\d+) (\d+ .+)/, '$1 ($2)' ] }, 'megamozg.ru': { checkPageURL: 'http://megamozg.ru/users/vmb/', keyElementSelectors: '#layout > div.inner > div.user_header > div.karma, #layout > div.inner > div.user_header > div.rating, #layout > div.inner > div.content_left > div.user_profile > div.rating-place, #layout > div.inner > div.sidebar_right > div.block.user_info > div.info', re: [ /(\d+) (\d+ .+)/, '$1 ($2)' ] }, 'toster.ru': { checkPageURL: 'https://toster.ru/user/vmb', keyElementSelectors: '#js-canvas > div.layout__body > section > div.column_main > main > header > div.page-header__stats > ul > li', }, }; /******************************************************************************/ var throbber = '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'; /******************************************************************************/ document.addEventListener('DOMContentLoaded', init); /******************************************************************************/ function init() { for (var site in sites) { var div = document.body.appendChild(document.createElement('div')); var querier = document.createElement('a'); querier.textContent = site; querier.target = '_blank'; querier.href = sites[site].openPageURL || sites[site].checkPageURL; div.appendChild(querier); } if(!location.hash){getAll();} } /******************************************************************************/ function getAll() { var queriers = document.querySelectorAll('a'); for (var i = 0, querier; querier = queriers[i]; i++) { window.setTimeout(getDoc, i*1000, querier); } } /******************************************************************************/ function getDoc(querier) { querier.style.backgroundImage = 'url('+ throbber + ')'; var site = querier.textContent; var xhr = new XMLHttpRequest(); xhr.open('GET', sites[site].checkPageURL, true); xhr.setRequestHeader('Accept-Language','en-US,en;q=0.8,ru;q=0.6,uk;q=0.4,qya;q=0.001'); xhr.responseType = 'document'; xhr.timeout = 10000; xhr.withCredentials = true; xhr.onload = function() { processDoc(this.response, querier); }; xhr.ontimeout = xhr.onerror = function(evt) { querier.parentNode.appendChild(document.createElement('pre')).textContent = evt.type.charAt(0).toUpperCase() + evt.type.slice(1) + '.'; querier.style.backgroundImage = 'none'; }; xhr.send(null); } /******************************************************************************/ function processDoc(doc, querier) { var site = querier.textContent; var keyElements = doc.querySelectorAll(sites[site].keyElementSelectors); if (keyElements.length) { querier.parentNode.appendChild(document.createElement('pre')).textContent = [].map.call(keyElements, function(el) { el = el.textContent.trim().replace(/\s+/g, ' '); if (sites[site].re) {el = el.replace(sites[site].re[0], sites[site].re[1]);} return el; }).join('\n'); } else { querier.parentNode.appendChild(document.createElement('pre')).textContent = 'Parsing error.'; } querier.style.backgroundImage = 'none'; } /******************************************************************************/ </script> </head> <body></body> </html> 


4.

. , ( ) , , .

IMDb ( ): 250 250 . : , , .

, : ( , ).

localStorage , , , indexedDB .

( , ; IMDb, , , — ; ).

, , ( ), .

( ) Chrome ( , — ( , — ), ).

but. :



b. :



. Chrome Firefox localStorage , IE11 — (, , , Edge). Fiddler . . , , IE11 localStorage . Fiddler-, «AutoResponder» ( , Fiddler , URL):



: localhost ( 127.0.0.1 ; , , ), IE11; . IE11. , , , , status quo:



, :

 <!doctype html> <html> <head> <meta charset='UTF-8'><title>IMDb Charts History</title> <style> body > div > * {padding: 5px 20px 5px 20px; margin: 2px;} body > div > a {display: inline-block; background-repeat: no-repeat; background-position: left center;} body > div > pre {border: 1px solid silver;} body > div > pre:not(:first-of-type) {background-color: gainsboro;} body > div > pre > button {margin: 0px 0px 0px 20px;} </style> <script> /******************************************************************************/ 'use strict'; /******************************************************************************/ var sites = { 'IMDb Top 250': { checkPageURL: 'http://www.imdb.com/chart/top', keyElementsSelector: '#main > div > span > div > div > div.lister > table > tbody > tr > td.titleColumn > a', }, 'IMDb Top 250 TV': { checkPageURL: 'http://www.imdb.com/chart/toptv', keyElementsSelector: '#main > div > span > div > div > div.lister > table > tbody > tr > td.titleColumn > a', }, }; /******************************************************************************/ var netData = JSON.parse(localStorage['IMDbChartsHistory.netData'] || '{}'); var throbber = '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'; /******************************************************************************/ document.addEventListener('DOMContentLoaded', init); /******************************************************************************/ function init() { for (var site in sites) { if(!netData[site]) { netData[site] = {lastEntries: [], lastEntriesTitles: [], history: [], saveHistory: true, lastCheck: ''}; } var div = document.body.appendChild(document.createElement('div')); var watcher = div.appendChild(document.createElement('a')); watcher.textContent = site; watcher.target = '_blank'; watcher.href = sites[site].openPageURL || sites[site].checkPageURL; } localStorage['IMDbChartsHistory.netData'] = JSON.stringify(netData); if(!location.hash){getAll();} } /******************************************************************************/ function getAll() { var watchers = document.querySelectorAll('a'); for (var i = 0, watcher; watcher = watchers[i]; i++) { window.setTimeout(getDoc, i*2000, watcher); } } /******************************************************************************/ function getDoc(watcher) { watcher.style.backgroundImage = 'url('+ throbber + ')'; var site = watcher.textContent; var xhr = new XMLHttpRequest(); xhr.open('GET', sites[site].checkPageURL, true); xhr.setRequestHeader('Accept-Language','en-US,en;q=0.8,ru;q=0.6,uk;q=0.4,qya;q=0.001'); xhr.responseType = 'document'; xhr.timeout = 10000; xhr.withCredentials = true; xhr.onload = function() { processDoc(this.response, watcher); }; xhr.ontimeout = xhr.onerror = function(evt) { watcher.parentNode.appendChild(document.createElement('pre')).textContent = evt.type.charAt(0).toUpperCase() + evt.type.slice(1) + '.'; watcher.style.backgroundImage = 'none'; }; xhr.send(null); } /******************************************************************************/ function processDoc(doc, watcher) { var site = watcher.textContent; var siteData = netData[site]; var keyElements = doc.querySelectorAll(sites[site].keyElementsSelector); var info, news; if (keyElements.length) { var now = new Date(Date.now()).toLocaleString(); var curEntries = [].map.call(keyElements, function(el) { el.search = ''; return el.href; }); var curEntriesTitles = [].map.call(keyElements, function(el) { return el.textContent; }); if (siteData.lastEntries.length) { var added = curEntries.filter(function(el) { return siteData.lastEntries.indexOf(el) == -1; }).map(function(el) { var ci = curEntries.indexOf(el); return '+ ' + (ci + 1) + '\t<a href="' + el + '">' + curEntriesTitles[ci] + '</a>'; }); var removed = siteData.lastEntries.filter(function(el) { return curEntries.indexOf(el) == -1; }).map(function(el) { var li = siteData.lastEntries.indexOf(el); return '- (' + (li + 1) + ')\t<a href="' + el + '">' + siteData.lastEntriesTitles[li] + '</a>'; }); var risen = siteData.lastEntries.filter(function(el, li) { var ci = curEntries.indexOf(el); return ci > -1 && ci < li; }).map(function(el) { var ci = curEntries.indexOf(el); return '↑ ' + (siteData.lastEntries.indexOf(el) + 1) + '\t→\t' + (ci + 1) + '\t<a href="' + el + '">' + curEntriesTitles[ci] + '</a>'; }); var fallen = siteData.lastEntries.filter(function(el, li) { return curEntries.indexOf(el) > li; }).map(function(el) { var ci = curEntries.indexOf(el); return '↓ ' + (siteData.lastEntries.indexOf(el) + 1) + '\t→\t' + (ci + 1) + '\t<a href="' + el + '">' + curEntriesTitles[ci] + '</a>'; }); news = added.length || removed.length || risen.length || fallen.length; info = now + ' (previous check: ' + siteData.lastCheck + ')<hr>' + (news? [added.join('\n'), removed.join('\n'), risen.join('\n'), fallen.join('\n')] .filter(function(el){return el.length;}).join('\n\n') : 'No news.' ); } else { info = now + '<hr>First launch. Data saved (' + curEntries.length + ' entries).'; } watcher.parentNode.appendChild(document.createElement('pre')).innerHTML = info; var historyHeader = watcher.parentNode.appendChild(document.createElement('pre')); var historyOptionLabel = historyHeader.appendChild(document.createElement('label')); var historyOption = historyOptionLabel.appendChild(document.createElement('input')); historyOption.type = 'checkbox'; historyOption.checked = siteData.saveHistory; historyOption.addEventListener('click', toggleHistory); historyOptionLabel.appendChild(document.createTextNode('Save history')); var clearer = historyHeader.appendChild(document.createElement('button')); clearer.type = 'button'; clearer.textContent = 'Clear history'; clearer.disabled = !siteData.history.length; clearer.addEventListener('click', clearHistory); watcher.parentNode.appendChild(document.createElement('pre')).innerHTML = siteData.history.length? siteData.history.join('\n<hr>\n') : 'No history.'; siteData.lastEntries = curEntries; siteData.lastEntriesTitles = curEntriesTitles; if (news && siteData.saveHistory) {siteData.history.unshift(info);} siteData.lastCheck = now; localStorage['IMDbChartsHistory.netData'] = JSON.stringify(netData); } else { watcher.parentNode.appendChild(document.createElement('pre')).textContent = 'Parsing error.'; } watcher.style.backgroundImage = 'none'; } /******************************************************************************/ function toggleHistory(evt) { var site = evt.target.parentNode.parentNode.parentNode.querySelector('a').textContent; var siteData = netData[site]; siteData.saveHistory = evt.target.checked; localStorage['IMDbChartsHistory.netData'] = JSON.stringify(netData); } /******************************************************************************/ function clearHistory(evt) { var site = evt.target.parentNode.parentNode.querySelector('a').textContent; var siteData = netData[site]; siteData.history = []; evt.target.parentNode.parentNode.querySelector('pre:last-of-type').innerHTML = 'History cleared.'; localStorage['IMDbChartsHistory.netData'] = JSON.stringify(netData); evt.target.disabled = true; } /******************************************************************************/ </script> </head> <body></body> </html> 


VII. application



1. IE — , HTML-. , IE , :

-
 <!doctype html> <html> <head><meta charset="UTF-8"><title>Bookmarklets</title></head> <body style="text-align: center; font-family: monospace; font-size: 12pt;"> <p><a href="javascript:(function(url, xhr) {if(!url) {return;}xhr = new XMLHttpRequest();try {xhr.open('HEAD', url, true);}catch (e) {alert(e.name + ': ' + e.message);return;}xhr.setRequestHeader('Accept-Language', 'en-US,en;q=0.8,ru;q=0.6,uk;q=0.4,qya;q=0.001');xhr.responseType = 'document';xhr.timeout = 10000;xhr.withCredentials = true;xhr.onload = function(evt, l) {l = this.getResponseHeader('Content-Length');alert([this.responseURL || '?',this.getResponseHeader('Content-Type') || '?',this.getResponseHeader('Last-Modified') || '?',l ?[l + ' B',(l / 1024) .toFixed(3) + ' kB',(l / 1048576) .toFixed(3) + ' MB',(l / 1073741824).toFixed(3) + ' GB'].join(' \u2248 '):'?'].join('\n\n') + '\n\n');};xhr.ontimeout = xhr.onerror = function(evt) {alert(evt.type.charAt(0).toUpperCase() + evt.type.slice(1) + '.');};try {xhr.send(null);}catch (e) {alert(e.name + ': ' + e.message); return;}})(document.activeElement.href || prompt('URL:'))"> </a></p> <p><a href="javascript:(function(e,t,n,a,A,i,r,o,l,p,d,s){function h(e,t){s.style.backgroundImage='url('+n+')',t=new XMLHttpRequest;try{t.open('GET',e.href,!0)}catch(a){return s.style.backgroundImage='none',void(s.textContent=a.name+': '+a.message)}t.setRequestHeader('Accept-Language','en-US,en;q=0.8,ru;q=0.6,uk;q=0.4,qya;q=0.001'),t.responseType='document',t.timeout=1e4,t.withCredentials=!0,t.onload=function(){s.style.backgroundImage='none',this.response&&this.response.title?(s.textContent=this.response.title,e.setAttribute('data-breadcrumbs-doc-title',this.response.title)):(s.textContent='?',e.setAttribute('data-breadcrumbs-doc-title','?'))},t.ontimeout=t.onerror=function(e){s.style.backgroundImage='none',s.textContent=e.type.charAt(0).toUpperCase()+e.type.slice(1)+'.'};try{t.send(null)}catch(a){s.style.backgroundImage='none',s.textContent=a.name+': '+a.message}}for(t=e.activeElement.href?e.activeElement:location,n='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',a=e.createElement('div'),a.setAttribute('data-breadcrumbs-main',''),a.appendChild(e.createElement('style')).textContent='div[data-breadcrumbs-main] {position:fixed; top:0px; left:0; z-index:999999; width:100%; padding:10px !important; background-color:white !important; outline:1px solid black !important; font-size: 12pt !important; text-align: left !important;}\ndiv[data-breadcrumbs-main] a {font-size: 12pt !important; text-decoration: underline !important;}\ndiv[data-breadcrumbs-main] div {margin-top: 20px!important; color: silver !important;}\ndiv[data-breadcrumbs-info-title] {background-repeat: no-repeat !important; background-position: left center !important; padding-left: 20px !important;}',A=t.protocol+(/:[\/][\/]/.test(t.href)?'//':''),a.appendChild(e.createElement('span')).textContent=A,i=t.hostname.split('.'),r=i.length,r>1&&(i.splice(r-2,2,i.slice(r-2).join('.')),r--),o=0;r>o;o++)l=i[o],o>0&&(a.appendChild(e.createElement('span')).textContent='.'),p=a.appendChild(e.createElement('a')),p.textContent=l,p.href=A+i.slice(o).join('.');if(A+=t.hostname,t.port&&/:\d+$/.test(t.host)&&(a.appendChild(e.createElement('span')).textContent=':',p=a.appendChild(e.createElement('a')),p.textContent=t.port,A+=':'+t.port,p.href=A),a.appendChild(e.createElement('span')).textContent='/',A+='/',t.pathname.length>1)for(i=t.pathname.split('/'),i.shift(),r=i.length,o=0;r>o;o++)l=i[o],r-1>o?(p=a.appendChild(e.createElement('a')),p.textContent=l,A+=l,a.appendChild(e.createElement('span')).textContent='/',A+='/',p.href=A):''!==l&&(p=a.appendChild(e.createElement('a')),p.textContent=l,A+=l,p.href=A);if(t.search)for(i=t.search.split('&'),r=i.length,i[0]=i[0].substring(1),a.appendChild(e.createElement('span')).textContent='?',A+='?',o=0;r>o;o++)l=i[o],o>0&&(a.appendChild(e.createElement('span')).textContent='&',A+='&'),p=a.appendChild(e.createElement('a')),p.textContent=l,A+=l,p.href=A;t.hash&&(a.appendChild(e.createElement('span')).textContent='#',p=a.appendChild(e.createElement('a')),p.textContent=t.hash.substring(1),A+=t.hash,p.href=A),a.querySelector('a:last-of-type').href==location.href&&a.querySelector('a:last-of-type').setAttribute('data-breadcrumbs-doc-title',e.title),d=a.appendChild(e.createElement('div')),d.setAttribute('data-breadcrumbs-info-url',''),d.textContent='URL',s=a.appendChild(e.createElement('div')),s.setAttribute('data-breadcrumbs-info-title',''),s.textContent='Title',a.addEventListener('mouseover',function(e,t,n){(t=e.target.href)&&(d.textContent=t,(n=e.target.getAttribute('data-breadcrumbs-doc-title'))?s.textContent=n:h(e.target))}),a.addEventListener('dblclick',function(){a.parentNode.removeChild(a)}),e.body.appendChild(a)})(document)"> </a></p> </body> </html> 


2. - , , , ( IE):

Internet Explorer 11: , ,

: XPath , DOM

localStorage

)




PS , MS Edge 11.00.10240.16397 7.22.2015 ( ), 20.10240.16384.0 ( ):

but. , XMLHttpRequest.withCredentials ;

b. null Access-Control-Allow-Origin : Origin null , .

at. navigator.languages ;

. XMLHttpRequest.responseURL ;

. localStorage .

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


All Articles