Support Statistics
¥.00 ·
0times
Text Preview (First 20 pages)
Registered users can read the full content for free
Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.
Page
1
Additional articles Part 3 Ilya Kantor
Page
2
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● Built at July 10, 2019 The last version of the tutorial is at https://javascript.info. We constantly work to improve the tutorial. If you find any mistakes, please write at our github. Frames and windows Popups and window methods Cross-window communication The clickjacking attack Binary data, files ArrayBuffer, binary arrays TextDecoder and TextEncoder Blob File and FileReader Network requests Fetch FormData Fetch: Download progress Fetch: Abort Fetch: Cross-Origin Requests Fetch API URL objects XMLHttpRequest Resumable file upload Long polling WebSocket Server Sent Events Storing data in the browser Cookies, document.cookie LocalStorage, sessionStorage IndexedDB Animation Bezier curve CSS-animations JavaScript animations Web components From the orbital height
Page
3
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● Custom elements Shadow DOM Template element Shadow DOM slots, composition Shadow DOM styling Shadow DOM and events Regular expressions Patterns and flags Methods of RegExp and String Character classes Escaping, special characters Sets and ranges [...] Quantifiers +, *, ? and {n} Greedy and lazy quantifiers Capturing groups Backreferences in pattern: \n and \k Alternation (OR) | String start ^ and finish $ Multiline mode, flag "m" Lookahead and lookbehind Infinite backtracking problem Unicode: flag "u" Unicode character properties \p Sticky flag "y", searching at position
Page
4
A popup window is one of the oldest methods to show additional document to user. Basically, you just run: …And it will open a new window with given URL. Most modern browsers are configured to open new tabs instead of separate windows. Popups exist from really ancient times. The initial idea was to show another content without closing the main window. As of now, there are other ways to do that: we can load content dynamically with fetch and show it in a dynamically generated <div> . So, popups is not something we use everyday. Also, popups are tricky on mobile devices, that don’t show multiple windows simultaneously. Still, there are tasks where popups are still used, e.g. for OAuth authorization (login with Google/Facebook/…), because: 1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe. 2. It’s very easy to open a popup. 3. A popup can navigate (change URL) and send messages to the opener window. In the past, evil sites abused popups a lot. A bad page could open tons of popup windows with ads. So now most browsers try to block popups and protect the user. Most browsers block popups if they are called outside of user-triggered event handlers like onclick . For example: Frames and windows Popups and window methods window.open('https://javascript.info/') Popup blocking // popup blocked window.open('https://javascript.info'); // popup allowed button.onclick = () => { window.open('https://javascript.info'); };
Page
5
● This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally. What if the popup opens from onclick , but after setTimeout ? That’s a bit tricky. Try this code: The popup opens in Chrome, but gets blocked in Firefox. …If we decrease the delay, the popup works in Firefox too: The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it – removes the “trust”, assuming that now it’s “outside of the user action”. So the first one is blocked, and the second one is not. The syntax to open a popup is: window.open(url, name, params) : url An URL to load into the new window. name A name of the new window. Each window has a window.name , and here we can specify which window to use for the popup. If there’s already a window with such name – the given URL opens in it, otherwise a new window is opened. params The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: width:200,height=100 . Settings for params : Position: // open after 3 seconds setTimeout(() => window.open('http://google.com'), 3000); // open after 1 seconds setTimeout(() => window.open('http://google.com'), 1000); window.open
Page
6
● ● ● ● ● ● ● ● ● left/top (numeric) – coordinates of the window top-left corner on the screen. There is a limitation: a new window cannot be positioned offscreen. width/height (numeric) – width and height of a new window. There is a limit on minimal width/height, so it’s impossible to create an invisible window. Window features: menubar (yes/no) – shows or hides the browser menu on the new window. toolbar (yes/no) – shows or hides the browser navigation bar (back, forward, reload etc) on the new window. location (yes/no) – shows or hides the URL field in the new window. FF and IE don’t allow to hide it by default. status (yes/no) – shows or hides the status bar. Again, most browsers force it to show. resizable (yes/no) – allows to disable the resize for the new window. Not recommended. scrollbars (yes/no) – allows to disable the scrollbars for the new window. Not recommended. There is also a number of less supported browser-specific features, which are usually not used. Check window.open in MDN for examples. Let’s open a window with minimal set of features just to see which of them browser allows to disable: Here most “window features” are disabled and window is positioned offscreen. Run it and see what really happens. Most browsers “fix” odd things like zero width/height and offscreen left/top . For instance, Chrome open such a window with full width/height, so that it occupies the full screen. Let’s add normal positioning options and reasonable width , height , left , top coordinates: Example: a minimalistic window let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no width=0,height=0,left=-1000,top=-1000`; open('/', 'test', params); let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no width=600,height=300,left=100,top=100`; open('/', 'test', params);
Page
7
● ● ● ● Most browsers show the example above as required. Rules for omitted settings: If there is no 3rd argument in the open call, or it is empty, then the default window parameters are used. If there is a string of params, but some yes/no features are omitted, then the omitted features assumed to have no value. So if you specify params, make sure you explicitly set all required features to yes. If there is no left/top in params, then the browser tries to open a new window near the last opened window. If there is no width/height , then the new window will be the same size as the last opened. The open call returns a reference to the new window. It can be used to manipulate it’s properties, change location and even more. In this example, we generate popup content from JavaScript: And here we modify the contents after loading: Please note: immediately after window.open , the new window isn’t loaded yet. That’s demonstrated by alert in line (*) . So we wait for onload to modify it. We could also use DOMContentLoaded handler for newWin.document . Accessing popup from window let newWin = window.open("about:blank", "hello", "width=200,height=200"); newWin.document.write("Hello, world!"); let newWindow = open('/', 'example', 'width=300,height=300') newWindow.focus(); alert(newWin.location.href); // (*) about:blank, loading hasn't started yet newWindow.onload = function() { let html = `<div style="font-size:30px">Welcome!</div>`; newWindow.document.body.insertAdjacentHTML('afterbegin', html); };
Page
8
⚠ Same origin policy Windows may freely access content of each other only if they come from the same origin (the same protocol://domain:port). Otherwise, e.g. if the main window is from site.com , and the popup from gmail.com , that’s impossible for user safety reasons. For the details, see chapter Cross-window communication. A popup may access the “opener” window as well using window.opener reference. It is null for all windows except popups. If you run the code below, it replaces the opener (current) window content with “Test”: So the connection between the windows is bidirectional: the main window and the popup have a reference to each other. To close a window: win.close() . To check if a window is closed: win.closed . Technically, the close() method is available for any window , but window.close() is ignored by most browsers if window is not created with window.open() . So it’ll only work on a popup. The closed property is true if the window is closed. That’s useful to check if the popup (or the main window) is still open or not. A user can close it anytime, and our code should take that possibility into account. This code loads and then closes the window: Accessing window from popup let newWin = window.open("about:blank", "hello", "width=200,height=200"); newWin.document.write( "<script>window.opener.document.body.innerHTML = 'Test'<\/script>" ); Closing a popup let newWindow = open('/', 'example', 'width=300,height=300'); newWindow.onload = function() { newWindow.close();
Page
9
There are methods to move/resize a window: win.moveBy(x,y) Move the window relative to current position x pixels to the right and y pixels down. Negative values are allowed (to move left/up). win.moveTo(x,y) Move the window to coordinates (x,y) on the screen. win.resizeBy(width,height) Resize the window by given width/height relative to the current size. Negative values are allowed. win.resizeTo(width,height) Resize the window to the given size. There’s also window.onresize event. ⚠ Only popups To prevent abuse, the browser usually blocks these methods. They only work reliably on popups that we opened, that have no additional tabs. ⚠ No minification/maximization JavaScript has no way to minify or maximize a window. These OS-level functions are hidden from Frontend-developers. Move/resize methods do not work for maximized/minimized windows. We already talked about scrolling a window in the chapter Window sizes and scrolling. win.scrollBy(x,y) Scroll the window x pixels right and y down relative the current scroll. Negative values are allowed. alert(newWindow.closed); // true }; Scrolling and resizing Scrolling a window
Page
10
● ● win.scrollTo(x,y) Scroll the window to the given coordinates (x,y) . elem.scrollIntoView(top = true) Scroll the window to make elem show up at the top (the default) or at the bottom for elem.scrollIntoView(false) . There’s also window.onscroll event. Theoretically, there are window.focus() and window.blur() methods to focus/unfocus on a window. Also there are focus/blur events that allow to focus a window and catch the moment when the visitor switches elsewhere. In the past evil pages abused those. For instance, look at this code: When a user attempts to switch out of the window ( blur ), it brings it back to focus. The intention is to “lock” the user within the window . So, there are limitations that forbid the code like that. There are many limitations to protect the user from ads and evils pages. They depend on the browser. For instance, a mobile browser usually ignores that call completely. Also focusing doesn’t work when a popup opens in a separate tab rather than a new window. Still, there are some things that can be done. For instance: When we open a popup, it’s might be a good idea to run a newWindow.focus() on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now. If we want to track when a visitor actually uses our web-app, we can track window.onfocus/onblur . That allows us to suspend/resume in-page activities, animations etc. But please note that the blur event means that the visitor switched out from the window, but they still may observe it. The window is in the background, but still may be visible. Popup windows are used rarely, as there are alternatives: loading and displaying information in-page, or in iframe. Focus/blur on a window window.onblur = () => window.focus(); Summary
Page
11
● ● ● ● ● ● ● ● ● ● If we’re going to open a popup, a good practice is to inform the user about it. An “opening window” icon near a link or button would allow the visitor to survive the focus shift and keep both windows in mind. A popup can be opened by the open(url, name, params) call. It returns the reference to the newly opened window. Browsers block open calls from the code outside of user actions. Usually a notification appears, so that a user may allow them. Browsers open a new tab by default, but if sizes are provided, then it’ll be a popup window. The popup may access the opener window using the window.opener property. The main window and the popup can freely read and modify each other if they havee the same origin. Otherwise, they can change location of each other and [exchange messages. To close the popup: use close() call. Also the user may close them (just like any other windows). The window.closed is true after that. Methods focus() and blur() allow to focus/unfocus a window. But they don’t work all the time. Events focus and blur allow to track switching in and out of the window. But please note that a window may still be visible even in the background state, after blur . The “Same Origin” (same site) policy limits access of windows and frames to each other. The idea is that if a user has two pages open: one from john-smith.com , and another one is gmail.com , then they wouldn’t want a script from john- smith.com to read our mail from gmail.com . So, the purpose of the “Same Origin” policy is to protect users from information theft. Two URLs are said to have the “same origin” if they have the same protocol, domain and port. These URLs all share the same origin: http://site.com http://site.com/ http://site.com/my/page.html Cross-window communication Same Origin
Page
12
● ● ● ● ● ● ● ● These ones do not: http://www.site.com (another domain: www. matters) http://site.org (another domain: .org matters) https://site.com (another protocol: https ) http://site.com:8080 (another port: 8080 ) The “Same Origin” policy states that: if we have a reference to another window, e.g. a popup created by window.open or a window inside <iframe> , and that window comes from the same origin, then we have full access to that window. otherwise, if it comes from another origin, then we can’t access the content of that window: variables, document, anything. The only exception is location : we can change it (thus redirecting the user). But we cannot read location (so we can’t see where the user is now, no information leak). In action: iframe An <iframe> tag hosts a separate embedded window, with its own separate document and window objects. We can access them using properties: iframe.contentWindow to get the window inside the <iframe> . iframe.contentDocument to get the document inside the <iframe> , короткий аналог iframe.contentWindow.document . When we access something inside the embedded window, the browser checks if the iframe has the same origin. If that’s not so then the access is denied (writing to location is an exception, it’s still permitted). For instance, let’s try reading and writing to <iframe> from another origin: <iframe src="https://example.com" id="iframe"></iframe> <script> iframe.onload = function() { // we can get the reference to the inner window let iframeWindow = iframe.contentWindow; // OK try { // ...but not to the document inside it let doc = iframe.contentDocument; // ERROR } catch(e) { alert(e); // Security Error (another origin) } // also we can't READ the URL of the page in iframe
Page
13
● ● The code above shows errors for any operations except: Getting the reference to the inner window iframe.contentWindow – that’s allowed. Writing to location . Contrary to that, if the <iframe> has the same origin, we can do anything with it: iframe.onload vs iframe.contentWindow.onload The iframe.onload event (on the <iframe> tag) is essentially the same as iframe.contentWindow.onload (on the embedded window object). It triggers when the embedded window fully loads with all resources. …But we can’t access iframe.contentWindow.onload for an iframe from another origin, so using iframe.onload . By definition, two URLs with different domains have different origins. try { // Can't read URL from the Location object let href = iframe.contentWindow.location.href; // ERROR } catch(e) { alert(e); // Security Error } // ...we can WRITE into location (and thus load something else into the iframe) iframe.contentWindow.location = '/'; // OK iframe.onload = null; // clear the handler, not to run it after the location cha }; </script> <!-- iframe from the same site --> <iframe src="/" id="iframe"></iframe> <script> iframe.onload = function() { // just do anything iframe.contentDocument.body.prepend("Hello, world!"); }; </script> Windows on subdomains: document.domain
Page
14
But if windows share the same second-level domain, for instance john.site.com , peter.site.com and site.com (so that their common second-level domain is site.com ), we can make the browser ignore that difference, so that they can be treated as coming from the “same origin” for the purposes of cross-window communication. To make it work, each such window should run the code: That’s all. Now they can interact without limitations. Again, that’s only possible for pages with the same second-level domain. When an iframe comes from the same origin, and we may access its document , there’s a pitfall. It’s not related to cross-domain things, but important to know. Upon its creation an iframe immediately has a document. But that document is different from the one that loads into it! So if we do something with the document immediately, that will probably be lost. Here, look: We shouldn’t work with the document of a not-yet-loaded iframe, because that’s the wrong document. If we set any event handlers on it, they will be ignored. How to detect the moment when the document is there? The right document is definitely at place when iframe.onload triggers. But it only triggers when the whole iframe with all resources is loaded. We can try to catch the moment earlier using checks in setInterval : document.domain = 'site.com'; Iframe: wrong document pitfall <iframe src="/" id="iframe"></iframe> <script> let oldDoc = iframe.contentDocument; iframe.onload = function() { let newDoc = iframe.contentDocument; // the loaded document is not the same as initial! alert(oldDoc == newDoc); // false }; </script>
Page
15
● ● ● ● ● An alternative way to get a window object for <iframe> – is to get it from the named collection window.frames : By number: window.frames[0] – the window object for the first frame in the document. By name: window.frames.iframeName – the window object for the frame with name="iframeName" . For instance: An iframe may have other iframes inside. The corresponding window objects form a hierarchy. Navigation links are: window.frames – the collection of “children” windows (for nested frames). window.parent – the reference to the “parent” (outer) window. window.top – the reference to the topmost parent window. For instance: <iframe src="/" id="iframe"></iframe> <script> let oldDoc = iframe.contentDocument; // every 100 ms check if the document is the new one let timer = setInterval(() => { let newDoc = iframe.contentDocument; if (newDoc == oldDoc) return; alert("New document is here!"); clearInterval(timer); // cancel setInterval, don't need it any more }, 100); </script> Collection: window.frames <iframe src="/" style="height:80px" name="win" id="iframe"></iframe> <script> alert(iframe.contentWindow == frames[0]); // true alert(iframe.contentWindow == frames.win); // true </script>
Page
16
We can use the top property to check if the current document is open inside a frame or not: The sandbox attribute allows for the exclusion of certain actions inside an <iframe> in order to prevent it executing untrusted code. It “sandboxes” the iframe by treating it as coming from another origin and/or applying other limitations. There’s a “default set” of restrictions applied for <iframe sandbox src="..."> . But it can be relaxed if we provide a space-separated list of restrictions that should not be applied as a value of the attribute, like this: <iframe sandbox="allow-forms allow-popups"> . In other words, an empty "sandbox" attribute puts the strictest limitations possible, but we can put a space-delimited list of those that we want to lift. Here’s a list of limitations: allow-same-origin By default "sandbox" forces the “different origin” policy for the iframe. In other words, it makes the browser to treat the iframe as coming from another origin, even if its src points to the same site. With all implied restrictions for scripts. This option removes that feature. allow-top-navigation Allows the iframe to change parent.location . allow-forms Allows to submit forms from iframe . allow-scripts Allows to run scripts from the iframe . allow-popups window.frames[0].parent === window; // true if (window == top) { // current window == window.top? alert('The script is in the topmost window, not in a frame'); } else { alert('The script runs in a frame!'); } The “sandbox” iframe attribute
Page
17
Allows to window.open popups from the iframe See the manual for more. The example below demonstrates a sandboxed iframe with the default set of restrictions: <iframe sandbox src="..."> . It has some JavaScript and a form. Please note that nothing works. So the default set is really harsh: https://plnkr.co/edit/GAhzx0j3JwAB1TMzwyxL?p=preview Please note: The purpose of the "sandbox" attribute is only to add more restrictions. It cannot remove them. In particular, it can’t relax same-origin restrictions if the iframe comes from another origin. The postMessage interface allows windows to talk to each other no matter which origin they are from. So, it’s a way around the “Same Origin” policy. It allows a window from john- smith.com to talk to gmail.com and exchange information, but only if they both agree and call corresponding JavaScript functions. That makes it safe for users. The interface has two parts. postMessage The window that wants to send a message calls postMessage method of the receiving window. In other words, if we want to send the message to win , we should call win.postMessage(data, targetOrigin) . Arguments: data The data to send. Can be any object, the data is cloned using the “structured cloning algorithm”. IE supports only strings, so we should JSON.stringify complex objects to support that browser. targetOrigin Specifies the origin for the target window, so that only a window from the given origin will get the message. The targetOrigin is a safety measure. Remember, if the target window comes from another origin, we can’t read it’s location in the sender window. So we can’t Cross-window messaging
Page
18
be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it. Specifying targetOrigin ensures that the window only receives the data if it’s still at the right site. Important when the data is sensitive. For instance, here win will only receive the message if it has a document from the origin http://example.com : If we don’t want that check, we can set targetOrigin to * . onmessage To receive a message, the target window should have a handler on the message event. It triggers when postMessage is called (and targetOrigin check is successful). The event object has special properties: data The data from postMessage . origin The origin of the sender, for instance http://javascript.info . source The reference to the sender window. We can immediately source.postMessage(...) back if we want. <iframe src="http://example.com" name="example"> <script> let win = window.frames.example; win.postMessage("message", "http://example.com"); </script> <iframe src="http://example.com" name="example"> <script> let win = window.frames.example; win.postMessage("message", "*"); </script>
Page
19
● ● ● ● ● To assign that handler, we should use addEventListener , a short syntax window.onmessage does not work. Here’s an example: The full example: https://plnkr.co/edit/ltrzlGvN8UPdpMtyxlI9?p=preview There’s no delay There’s totally no delay between postMessage and the message event. The event triggers synchronously, faster than setTimeout(...,0) . To call methods and access the content of another window, we should first have a reference to it. For popups we have these references: From the opener window: window.open – opens a new window and returns a reference to it, From the popup: window.opener – is a reference to the opener window from a popup. For iframes, we can access parent/children windows using: window.frames – a collection of nested window objects, window.parent , window.top are the references to parent and top windows, iframe.contentWindow is the window inside an <iframe> tag. If windows share the same origin (host, port, protocol), then windows can do whatever they want with each other. window.addEventListener("message", function(event) { if (event.origin != 'http://javascript.info') { // something from an unknown domain, let's ignore it return; } alert( "received: " + event.data ); // can message back using event.source.postMessage(...) }); Summary
Page
20
● ● ● ● ● ● ● Otherwise, only possible actions are: Change the location of another window (write-only access). Post a message to it. Exceptions are: Windows that share the same second-level domain: a.site.com and b.site.com . Then setting document.domain='site.com' in both of them puts them into the “same origin” state. If an iframe has a sandbox attribute, it is forcefully put into the “different origin” state, unless the allow-same-origin is specified in the attribute value. That can be used to run untrusted code in iframes from the same site. The postMessage interface allows two windows with any origins to talk: 1. The sender calls targetWin.postMessage(data, targetOrigin) . 2. If targetOrigin is not '*' , then the browser checks if window targetWin has the origin targetOrigin . 3. If it is so, then targetWin triggers the message event with special properties: origin – the origin of the sender window (like http://my.site.com ) source – the reference to the sender window. data – the data, any object in everywhere except IE that supports only strings. We should use addEventListener to set the handler for this event inside the target window. The “clickjacking” attack allows an evil page to click on a “victim site” on behalf of the visitor. Many sites were hacked this way, including Twitter, Facebook, Paypal and other sites. They have all been fixed, of course. The idea is very simple. Here’s how clickjacking was done with Facebook: 1. A visitor is lured to the evil page. It doesn’t matter how. The clickjacking attack The idea
Comments 0
Loading comments...
Reply to Comment
Edit Comment