HTML5 Web Workers

Why Web Workers

Single threaded UI

  • setTimeout
  • setInterval
  • requestAnimationFrame (prefixed: IE10, FF8, Chrome 16)

Useful for heavy JS loads, such as...

  • calculate prime numbers
  • client-side database operations
  • large JSON string parsing
  • image / video processing

Introduction To Web Workers

Access to:

  • navigator
  • location object
  • Object, Array, Date, Math, String objects
  • XMLHttpRequest object
  • setTimeout() and setInterval() methods

but not the DOM or caller

  • Chrome 4
  • Safari 4
  • Firefox 3.5
  • Opera 10.6
  • IE 10
  • iOS5+, BB6+, MeeGoN9, OpMob. Not on android or opera mini

Create a worker

var workerName = new Worker('javascriptFile.js');

Communicate with worker

workerName.postMessage(message_for_worker);

Listen to worker

self.onmessage = function (event) { 
	var theMessage = event.data;
}

Error Handling

self.onerror = function(error){
	var caller = error.filename;
	var errorLineNumber = error.lineno;
    var errorMsg = error.message;
}

Web Workers: Code Lab

In this lab, we are going to create a worker that calculate a fibonacci number sequence and display the result to the page that has spawned the worker

Currently, the fibonacci sequence is calculated in the same thread as the UI thread. For small sequences, it works fine but if you ask for bigger sequences, it starts to become problematic and the browser thinks that the script is unresponsive

In your labs folder open index.html in a browser. The button to calculate with web worker is not implemented yet. Try to calculate a fibonacci sequence of 10. Then try with 100

  1. In your labs folder, find the web_worker folder and edit the script.js file
  2. In the code, look for a // TODO 1. Here we want to create a new Worker called fib_worker using the worker file named worker.js (one line)
  3. Look for a // TODO 2. Here we are in the event listener for the message event. We want to display the message from the worker. Replace xxxxxx by the actual message from the worker
  4. Look for a // TODO 3. Here we want to send the num variable as a message to the worker (one line)
  5. Now we are done with the main UI "thread", it is time to edit the worker, worker.js
  6. Look for a // TODO 4. Here we want to send the number from the suite to the main window (one line)
    • The message to send is: fibonacci(i)
  7. Look for a // TODO 5. Here we need to use the show method defined above and pass the message sent from the main window as a parameter (one line)
  8. You can now try the final result by opening the index.html file in Safari, Chrome, Opera and Firefox

Note: Because of Chrome security restrictions, you cannot run your app from the file:// scheme unless you start Chrome with the --allow-file-access-from-files flag

HTML5 Messaging APIs

Same Origin Policy: Security

URL Same Origin Issue
http://www.a.com/ Yes  
http://www.a.com/folder/page.php Yes  
https://www.a.com/ No Different Protocol
http://blog.a.com No Different Host
http://www.a.com:1234 No Different Port

OLD: Solutions: JSON, Proxy, PHP CURL, iFrames

New Solutions: CORS, Cross-document Messaging

CORS:
Cross-Origin Resource Sharing

CORS

Cross-Origin Resource Sharing

Method of performing XMLHttpRequests across domains

Need correct headers sent and received

Allows responses to permitted origin domains (or * if public)

IE8 - XDomainRequest Object

IE10 & modern browsers: XMLHttpRequest

  • Chrome 4
  • Safari 4
  • Firefox 3.5
  • Opera 12
  • IE 8 / 10
  • Not opera mini, BB, HP✆, OperaMini, Win✆

CORS Headers (examples)

Request Headers

Origin: http://requestingdomain.com
Access-Control-Request-Headers: PASSPHRASE
Access-Control-Request-Method:POST

Response Headers

Access-Control-Allow-Origin: * # or domain
Access-Control-Allow-Methods:POST, GET, OPTIONS
Access-Control-Allow-Headers: PASSPHRASE
Access-Control-Max-Age: 86400

GET or POST simple request

CORS feature detection withCredentials Property

if (XMLHttpRequest) {
var request = new XMLHttpRequest();
var url = http://otherserver.com/resource;

// FEATURE DETECTION
if (request.withCredentials !== undefined) {

    
    request.open('GET', url, true);
    request.onreadystatechange = handleEvent;
    request.send();
}
}

Read the http headers

Credentialed Requests

Similar, but sends http cookie header with request (not the default)

if (XMLHttpRequest) {
    var request = new XMLHttpRequest();
    var url = http://otherserver.com/resource;
    
    // FEATURE DETECTION
    if (request.withCredentials !== undefined) {
         request.open('GET', url, true);
         request.withCredentials = "true";
         request.onreadystatechange = handler;
         request.send();
      }
}

Read the http headers

Preflighted Request

get "preapproval" for request

  1. Send OPTIONS header from requestor
    is reqest safe to send
  2. Other domain responds:
  3. Requestor makes request

Code snippet →

Read the http headers

var request = new XMLHttpRequest();
var url = 'http://otherdomain.com/file';
var requestHistoryText;
var body = 'some text';
function callOtherDomain(){
    if(request)  {
        request.open('POST', url, true);
        request.setRequestHeader('X-MADEUP', 'pingpong');
        request.setRequestHeader('Content-Type', 'application/xml');
        request.onreadystatechange = handleEvent;
        request.send(body); 
    } else { console.log("Request Failed"); }
}
function handleEvent(evtXHR) {
    if (request.readyState == 4) {
        if (request.status == 200)  {
            var response = request.responseText;
            requestHistoryText = document.createTextNode(response);
            var textDiv = document.getElementById("textDiv");
            textDiv.appendChild(requestHistoryText);
        } else {
            console.log("request Errors Occured " + request.readyState + " and the status is " + request.status);
        }
    } else {
        dump("currently the application is at" + request.readyState);
    }
}

Cross-document Messaging

Code Lab Files

Cross-document Messaging

otherWindow.postMessage(message, targetOrigin);
otherWindow
reference to other window via iframe contentWindow property, window.open return object or index value of window.frames
message
Data to be sent to otherWindow
targetOrigin
Security feature: Specifies what the origin of otherWindow must be for the event to be dispatched

otherWindow needs to listen for message

  • Chrome
  • Safari 4
  • Firefox 3
  • Opera 9.5
  • IE 8
  • Supported Android, iOS, Opera Mini

otherWindow snippet

window.addEventListener("message", handleMessage, false);  
  
function handleMessage(event) {  
  if (event.origin !== "http://messageOriginator.com")  
    return;  
  
  // safe ... handle message
  var receivedMsg = event.data;
  var receivedFromWindow = event.source;
    
}
origin
data
Data received from message
source
Reference to sending window, enables 2-way communication

Security
 

ALWAYS VERIFY origin AND source TO
AVOID MALICIOUS CODE

Also, dont' include message listener if none expected

And, always verify the syntax of the received message.

HTML5 Web Sockets

Real-time, full duplex communication using a single socket

Previous Web Communication Options

Half duplex solutions that are immediately old, and require http requests (including sending headers (with) cookies back and forth)

  • HTTP requests & Response
  • AJAX - asynchronous requests & responses, including polling

Web Sockets

  • two-way communication with a remote host
  • Full-duplex communication channel
  • Operates through a single socket
  • Reduced network traffic & latency
  • Handle proxies and firewalls
  • upstream and downstream over a single connection
  • Less server stress → more concurrent connections
  • Chrome 4 (old)
    14 (new)
  • Safari 5 (old)

  • Firefox 4 (old)
    6 (prefix)
    11
  • Opera 11 (old)

  • IE 10

  • Old syntax supported iOS & OperaMobile. BB. Not Android, AMZ or O-Mini

Server / Client Handshake

GET /chat HTTP/1.1
Connection: Upgrade 
Host: example.com
Origin: http://example.com 
Sec-WebSocket-Key1: 284 ^rI 2 447 8 Me1*V 8*
Sec-WebSocket-Key2: 30]8N763$84 12>*
Upgrade: WebSocket 
64:6E:AC:0C:FD:90:8A:51*   *(Random tokens for 16 byte token)
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Location: ws://example.com/chat
79:C5:C1:29:4A:60:8B:34:66:D5:61:10:C2:0C:4F:AA *The token

Request headers from the client plus the 8 byte token to say that the client handshake has been read. Once handshake established, the server can send messages to client

Web Socket API Features

// ready state
CONNECTING = 0
OPEN = 1
CLOSED = 2
readyState
bufferedAmount

// networking
onopen
onmessage
onclose
send(data)
close()

Methods

Create Socket

var socket=new WebSocket("ws://127.0.0.1:8080/")

Event Listeners

socket.onopen = function(){
  console.log("Connection to the socket is now open");
}
socket.onmessage = function(e){
  console.log("Message received: " + e.data);
}
socket.onclose = function(e){
  console.log("Connection to the socket is closed");
}

Send Message

socket.send("This is the message sent by the client")

Libraries

ChatWebSocket Library

Jetty Library

History API

Load data & change browser address bar (without reloading)

window.history.pushState(data, title, url) 
/* Pushes data into history, with title, and, if provided, the URL.*/
window.history.replaceState(data, title, url) /* Updates current history entry to given data, with title, and, if provided, URL */
window.history.length /* number of entries in session history */

window.history.state /* current state */

window.history.go( Δ ) /*  back or forward Δ steps */

window.history.back() /* Goes back one step Δ== -1*/

window.history.forward() /* Goes forward one step Δ = 1*/

Drag and Drop

document.addEventListener('dragstart', function(event) {
  event.dataTransfer.setData('text', 'Customized text');
  event.dataTransfer.effectAllowed = 'copy';
}, false);
  • HTML5 & CSS3 for the Real World
  • Drag this text or the image above to the right. You can even change this text.

Drop Area
  • Chrome 8
  • Safari
  • Firefox 3.5
  • Opera 12?
  • IE
  • mobile: only Android

FullScreen API

var goFullScreen = function(){
    var doc = document.documentElement;
    if (doc.requestFullscreen) {
        doc.requestFullscreen();
    }
    else if (docElm.mozRequestFullScreen) {
        doc.mozRequestFullScreen();
    }
    else if (docElm.webkitRequestFullScreen) {
        doc.webkitRequestFullScreen();
    }
}
var exitFullScreen = function(){
    if (document.exitFullscreen) {
        document.exitFullscreen();
    }
    else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
    }
    else if (document.webkitCancelFullScreen) {
        document.webkitCancelFullScreen();
    } 
}

var getFullScreenState = function(){
	if(document.exitFullscreen) {
    	return (document.fullscreen) ? true : false;
    }
	if (document.mozCancelFullScreen) { 
    	return (document.mozFullScreen) ? true : false;
    }
	if (document.webkitCancelFullScreen) {
    	return (document.webkitIsFullScreen) ? true : false;
    }
}
document.addEventListener("fullscreenchange"...
document.addEventListener("mozfullscreenchange"...
document.addEventListener("webkitfullscreenchange"...
:fullscreen & :fullscreen-ancestor
  • Chrome
  • Safari
  • Firefox 10
  • Opera 11.1
  • IE 10
  • mobile: only Android 4+ (partial)

File API

  • Import from the filesystem or the web.
  • Create new files from scratch.
  • Manipulate existing file data.
  • Store file data on the client.
  • Publish files back to the web.
  • Chrome 6
  • Safari
  • Firefox 3.6
  • Opera 11.1
  • IE 10
  • mobile: Android, BB 2+(partial), FF 11

Tutorial: Talk from Google I/O

File API

  <input type="file" id="files" accept="image/*" multiple>
document.querySelector('#files').onchange = function(e) {
  var files = e.target.files; // FileList of File objects.

  for (var i = 0, f; f = files[i]; ++i) {
    console.log(f.name, f.type, f.size,
                f.lastModifiedDate.toLocaleDateString());
  }
}; 

JS Events and Methods

var elements = document.getElementsByClassName(className)
  • Chrome 6
  • Safari
  • Firefox 3
  • Opera
  • IE 9
  • mobile: all
var element = document.querySelector(selectors);
var element = $(selectors)[0];

var elements = document.querySelectorAll(selectors);
var elements = $(selectors);

Where 'selectors' is a comma separated list of CSS selectors

  • Chrome 6
  • Safari
  • Firefox 3
  • Opera
  • IE 8
  • mobile: all

classList

<article id="main" class="blogpost popup"></article>
var el = document.getElementById('main').classList;
var el = document.querySelector('#main').classList;
el.add('bgimage');
el.remove('blogpost'); el.toggle('popup'); console.log(el.contains('popup')); // false console.log(el.contains('blogpost')); // false console.log(el.classList.toString() == el.className); // true

Result:

<article id="main" class="bgimage"></article>
  • Chrome
  • Safari 5.1
  • Firefox
  • Opera
  • IE 10
  • mobile: Android 3, Opera 11.1

Page Visibility API

Determine if your media is visible or not:

document.addEventListener('visibilitychange', function(e) {
  console.log('hidden:' + document.hidden,
              'state:' + document.visibilityState)
}, false);

Demo

Page Visibility

// set name of hidden property and visibility change event
var hidden, visibilityChange; 

if (typeof document.hidden !== "undefined") {
	hidden = "hidden";
	visibilityChange = "visibilitychange";
} else if (typeof document.mozHidden !== "undefined") {
	hidden = "mozHidden";
	visibilityChange = "mozvisibilitychange";
} else if (typeof document.msHidden !== "undefined") {
	hidden = "msHidden";
	visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
	hidden = "webkitHidden";
	visibilityChange = "webkitvisibilitychange";
}
 
var videoElement = document.getElementById("videoElement");

// if the page is hidden, pause the video
// if the page is shown, play the video
function handleVisibilityChange() {
	if (document[hidden]) {
		videoElement.pause();
	} else {
		videoElement.play();
	}
}

// warn if the browser doesn't support addEventListener or the Page Visibility API
if (typeof document.addEventListener === "undefined" || 
	typeof hidden === "undefined") {
	alert("This demo requires a browser such as Google Chrome that supports the Page Visibility API.");
} else {

    // handle page visibility change
    // see https://developer.mozilla.org/en/API/PageVisibility/Page_Visibility_API
    document.addEventListener(visibilityChange, handleVisibilityChange, false);
    
    // revert to existing favicon for site when the page is closed
    // otherwise the favicon will remain as paused.png
    window.addEventListener("unload", function(){
		favicon.change("/favicon.ico");
	}, false);
    
    // when the video pauses, set the favicon
    videoElement.addEventListener("pause", function(){
        favicon.change("images/paused.png");
	}, false);
    
    // when the video plays, set the favicon
    videoElement.addEventListener("play", function(){
		favicon.change("images/playing.png");
	}, false);
    
    // set the document (tab) title from the current video time
    videoElement.addEventListener("timeupdate", function(){
		document.title = Math.floor(videoElement.currentTime) + " second(s)";
	}, false);

getUserMedia

Device API

navigator.getUserMedia({audio: true, video: true}, function(s) {
  var video = document.querySelector('video');
  video.src = window.URL.createObjectURL(s);
}, function(e) {
  console.log(e);
});

HTML5 Demos

HTML5-demos.com

Notifications

if (window.webkitNotifications.checkPermission() == 0) {
  window.webkitNotifications.createNotification(picture, title, 
      content).show();
} else {
  window.webkitNotifications.requestPermission();
}

That's all folks