Loading...

Preflight

Press:
  • Space or / to move around
  • Ctrl/Command / or + to zoom in and out if slides don’t fit
  • N to show/hide speaker notes
  • H to highlight important elements in code snippets
  • 3 to toggle 3D

Utilizing HTML5 in
Google Chrome Extensions

Eric Bidelman < e.bidelman@google.com >
Add-On-Con 2010, Computer History Museum

Eric Bidelman ( @ebidel )
  • Developer Programs Engineer - Google
    • Docs, Sites, Health, OAuth, Chrome, HTML5
Agenda
  • Web Storage APIs
  • Database: Web SQL Database, IndexedDB
  • File Access: File/FileSystem APIs
  • XMLHttpRequest 2

Web Storage APIs

saving user preferences

localStorage

window.localStorage

  • Key / value pairs - hash table
  • Persistent on page reloads
  • Avoids HTTP overhead of cookies
  • Great for storing user preferences
Local Storage API
localStorage.setItem('someKey', someValue);
localStorage.getItem('someKey'); // value

// Can also access as a property, but not recommended.
localStorage.someKey = someValue;
Local Storage API
localStorage.setItem('dateOfBirth', '1984-07-22');
localStorage.getItem('dateOfBirth'); // '1984-07-22'
localStorage.length // num of items stored
localStorage.key(0); // 'dateOfBirth'
localStorage.removeItem('dateOfBirth');
localStorage.clear();
Using Native JSON
  • Web Storage APIs only store strings!

Solution:

localStorage.setItem('user', JSON.stringify({user: 'john', id: 10}));

var user = JSON.parse(localStorage.getItem('user'));
Example: Auto-save Text
<textarea id="ta" placeholder="Start typing..."></textarea>
document.querySelector('#ta').addEventListener('keyup', function(e) {
  localStorage.setItem('value', this.value);
  localStorage.setItem('timestamp', (new Date()).getTime());
}, false);

Extension: Screen Capture

  • <canvas>
  • localStorage
  • chrome.tabs.captureVisibleTab()
  • options page

Databases

Web SQL DB, IndexedDB

Web SQL Database
  • Client-side SQL database
  • Asynchronous & Synchronous* API
  • Basically wrapper around SQLite
  • Put processing (sorting, filtering, etc.) on client
openDatabase();
var db = window.openDatabase(
  'MyDB',           // dbName
  '1.0',            // version
  'test database',  // description
  2 * 1024 * 1024,  // estimatedSize in bytes
  function(db) {}   // optional creationCallback
);
openDatabase();
  • Size ( required )
    • Default: 5MB
    • Prompted after that: 5, 10, 50, 100, etc.
  • Versioning ( required )
    • Expected version - empty string means any version is fine.
    • Exception if version mismatch
  • Creation Callback ( optional )
    • Called if database is brand new
    • For updating version and/or schema:
      db.changeVersion('', '1.0', function (tx) { // opt
        // TODO: create schema
      }, opt_errorCallback, opt_successCallback);
  • Returns null if creation is unsuccessful
Transactions
  • readTransaction
    • read-only operations
    • db.readTransaction(function(tx) {
        // executeSql
      }, opt_errorCallback, opt_successCallback);
      
  • transaction
    • read/write operations
    • Locks the entire database
    • db.transaction(function(tx) {
        // executeSql
      }, opt_errorCallback, opt_successCallback);
      
executeSql();
tx.executeSql(sqlStatement, opt_arguments, opt_callback, opt_errorCb);

Create:

tx.executeSql('CREATE TABLE IF NOT EXISTS ' +
    'todo(id INTEGER PRIMARY KEY ASC, task TEXT, added_on DATETIME)');

Insert:

var taskText = 'buy groceries';
tx.executeSql(
  'INSERT INTO todo(task, added_on) VALUES (?,?)', [taskText, new Date()],
  renderFunc, onError);

Delete:

tx.executeSql('DELETE FROM todo WHERE id=?', [id], renderFunc, onError);
executeSql();
tx.executeSql(sqlStatement, opt_arguments, opt_callback, opt_errorCb);

Query:

function renderFunc(tx, results) {
  // results.insertId: last row inserted in db.
  // results.rowsAffected: num of rows changed by the SQL statement.
  var len = results.rows.length;
  for (var i = 0; i < len; ++i) {
    var row = results.rows.item(i);
    console.log(row.task, row.added_on.toJSON());
  }
}

tx.executeSql('SELECT * FROM todo', [], renderFunc, onError);
Example: Todo List

    See the generated database:
    Developer > Developer Tools > Resources

    Indexed Database

    http://www.flickr.com/photos/31408547@N06/4671916278
    IndexedDB

    window.indexedDB

    • Best of localStorage/sessionStorage and Web SQL DB:
      • Object based data store
      • In-order retrieval by index or locate by key
    • Asynchronous & Synchronous API
      • For the browser and for Web Workers
    Creating an Object Store
    var db = window.indexedDB.open('FriendDB', 'My Friends!');
    
    if (db.version != '1') {
      // User's first visit, initialize database.
      db.createObjectStore('Friends', // name of new object store
                            'id',      // key path
                            true);     // auto increment?
      db.setVersion('1');
    } else {
      // DB already initialized.
    }
    
    • Key path must be the name of an enumerated property of all objects being stored in the object store
    • DB versioning up to caller
    Stocking the Store
    var store = db.openObjectStore('Friends');
    
    var user = store.put({name: 'Eric',
                           gender: 'male',
                           likes: 'html5'});
    
    console.log(user.id); // Expect 1
    
    • Auto-increment keys get assigned automatically
    • Schema flexible, store anything
    Finding things

    Retrieving by key ( indexes ):

    db.createIndex(indexName, keyPath, unique?);
    // db.createObjectStore("Friend", "id", true);
    db.createIndex("FriendNames", "name", false);
    var index = db.openIndex('FriendNames');
    var id = index.get('Eric');
    

    Querying ( cursors ):

    // Restrict to names beginning A-E
    var range = new KeyRange.bound('A', 'E');
    var cursor = index.openObjectCursor(range);
    
    while (cursor.continue()) {
      console.log(cursor.value.name);
    }
    

    File Access

    File/FileSystem APIs

    File APIs

    File APIs

    http://www.flickr.com/photos/daddo83/3406962115/
    Getting File Handles: File / FileList
    <input type="file" id="files" name="files[]" multiple />
    <output id="list"></output>
    
    document.querySelector('#files').onchange = function(e) {
      var files = e.target.files; // FileList
    
      var output = [];
      for (var i = 0, f; f = files[i]; ++i) {
        output.push('<li><b>', f.name, '</b> (', f.type || 'n/a',
            ') - ', f.size, ' bytes, last modified: ',
            f.lastModifiedDate.toLocaleDateString(), '</li>');
      }
      document.querySelector('#list').innerHTML =
          '<ul>' + output.join('') + '</ul>';
    };
    

    Getting File Handles: Directory Upload
    <input type="file" webkitdirectory />
    • Files in the FileList will have a webkitRelativePath property
    FileReader

    Asynchronously read binary data into memory: Demo

    var reader = new FileReader();
    
    reader.readAsBinaryString(File|Blob);
    
    reader.readAsText(File|Blob, opt_encoding); // default UTF-8
    
    reader.readAsDataURL(Blob|File);
    
    reader.readAsArrayBuffer(Blob|File);

    Reading byte ranges: Demo

    var blob = file.slice(startingByte, length); // Blob
    reader.readAsBinaryString(blob);
    Example: Drag-in Files from Desktop
    var reader = new FileReader();
    reader.onload = function(e) {
      document.querySelector('img').src = e.target.result;
    };
     
    function onDrop(e) {
      reader.readAsDataURL(e.dataTransfer.files[0]);
    };
    Drop image files from your desktop
    Example: Drag-out Files from Desktop
    <a href="src/star.mp3" draggable="true" class="dragout"
       data-downloadurl="MIMETYPE:FILENAME:ABSOLUTE_URI_TO_FILE">download</a>
    
    var files = document.querySelectorAll('.dragout');
    for (var i = 0, file; file = files[i]; ++i) {
      file.addEventListener('dragstart', function(e) {
        e.dataTransfer.setData('DownloadURL', this.dataset.downloadurl);
      }, false);
    }
    

    Drag these files onto your desktop: .pdf file .mp3 file

    Monitoring Read Progress
    reader.onerror = errorHandler;
    
    reader.onprogress = updateProgress;
    
    reader.onabort = cancelRead;
    
    reader.onloadstart = updateProgress;
    
    reader.onload = readFile;
    
    reader.onloadend = function(e) {
      // e.target.readyState == FileReader.DONE == 2
    };
    

    Demo

    Blob URLs
    A unique (temporary) handle describing a resource:
    if (file.type.match(/image.*/)) {
      var blobURL = window.URL.createObjectURL(file);
    
      var img = document.createElement('img');
      img.src = blobURL; // blob:example.com:///7a6fa-5496-42d4-9bbb-40f9a0
      thumbnails.appendChild(img);
    
      // Clean-up: window.URL.revokeObjectURL(img.src);
    }
    
    var bb = new BlobBuilder();
    bb.append('<marquee>Flashy Lights!!!</marquee>');
    
    var iframe = document.createElement('iframe');
    iframe.src = window.URL.createObjectURL(bb.getBlob('text/html'));
    document.body.appendChild(iframe);
    
    Example: Downloads without a Server
    <iframe id="myframe" style="display:none"></iframe>
    
    document.querySelector('#download-button').onclick = function(e) {
      var bb = new BlobBuilder();
      bb.append('Lorem ipsum');
    
      var blob = bb.getBlob()
      document.querySelector('#myframe').src =
          window.URL.createObjectURL(blob);
    };
    
    Download with a Server: Canvas Exporter
    <form action="downloadFile.php" method="POST" name="myform" id="myform">
      <input type="hidden" name="dataUrl" />
      <input type="hidden" name="filename" />
      <input type="hidden" name="mimetype" />
    </form>
    
    function downloadImage(name, format) {
      var form = document.forms['myform'];
      form.filename.value = name;
      form.mimetype.value = format;
      form.dataUrl.value = document.querySelector('#mycanvas').toDataURL(format);
      form.submit();
    }
    
    <?php
      header("Content-type: {$_REQUEST['mimetype']}");
      header("Content-Disposition: attachment; filename={$_REQUEST['filename']}");
    
      $uri = substr($_REQUEST['dataUrl'], strpos($_REQUEST['dataUrl'], ',') + 1);
      echo base64_decode($uri);
    ?>
    
    Demo
    Example: Inline Worker
    <!DOCTYPE html>
    <html>
    <body>
      <script id="worker1" type="text/plain">
        onmessage = function(e) {
          ...
        };
      </script>
      <script>
        var bb = new BlobBuilder();
        bb.append(document.querySelector('#worker1').textContent);
    
        var worker = new Worker(
            window.URL.createObjectURL(bb.getBlob()));
        worker.onmessage = function(e) {
          ...
        };
        worker.postMessage(); // Start the worker.
      </script>
    </body>
    </html>
    

    FileSystem APIs

    http://www.flickr.com/photos/christosnyc/87637818/
    requestFileSystem();
    window.requestFileSystem(
      TEMPORARY,        // persistent vs. temporary storage
      1024 * 1024,      // size (bytes) of needed space
      initFs,           // success callback
      opt_errorHandler  // opt. error callback, denial of access
    );
    • TEMPORARY storage
      • Meant for caching
      • Data can be deleted at the UA's convenience
      • (e.g. to deal with a shortage of disk space)
    • PERSISTENT storage
      • UA should require permission to use
      • Data should not be deleted by UA w/o user intervention
        • ...but app can delete at will
    Fetching a file by name
    function initFs(fs) {
    
      fs.root.getFile('logFile.txt', {create: true}, function(fileEntry) {
    
        // fileEntry.isFile == true
        // fileEntry.name == 'logFile.txt'
        // fileEntry.fullPath == '/logFile.txt'
    
        // Get a File obj
        fileEntry.file(function(file) { /* TODO: Use FileReader */ }, errorHandler);
    
        // fileEntry.remove(function() {}, errorHandler);
        // fileEntry.moveTo(...);
        // fileEntry.copyTo(...);
        // fileEntry.getParent(function(dirEntry) {}, errorHandler);
    
      }, errorHandler);
    
    }
    
    Fetching a directory by name
    function initFs(fs) {
    
      fs.root.getDirectory('myFolder', {create: true, exclusive: true},
        function (dirEntry) {
    
          // dirEntry.isDirectory == true
    
          // Remove the directory
          dirEntry.removeRecursively(function() {
            console.log('Well, that was pointless!');
          }, errorHandler);
    
          // dirEntry.getFile(...);
          // dirEntry.getDirectory(...);
    
        }, errorHandler);
    
    }
    
    Reading directory contents
    function initFs(fs) {
    
      var root = fs.root;  // A DirectoryEntry itself
    
      var dirReader = root.createReader(); // DirectoryReader
      dirReader.readEntries(function(entries) {
        for (var i = 0, entry; entry = entries[i]; ++i) {
          console.log(entry.name, entry.isFile, entry.isDirectory);
        }
      }, errorHandler);
    
    }
    
    Writing a file ( FileWriter )
    function initFs(fs) {
    
      fs.root.getFile('logFile.txt', {create: true}, function(fileEntry) {
    
        fileEntry.createWriter(function(writer) {  // FileWriter
    
            writer.onwrite = function(e) {
              console.log('Write completed.');
            };
    
            writer.onerror = function(e) {
              console.log('Write failed: ' + e);
            };
    
            var bb = new BlobBuilder();
            bb.append('Lorem ipsum');
            writer.write(bb.getBlob('text/plain'));
    
        }, errorHandler);
      }
    
    }
    

    XMLHttpRequest 2

    http://www.flickr.com/photos/carrick/8247817/
    xhr.send(string)

    Sending txt is not new:

    function sendText() {
      var xhr = new XMLHttpRequest();
      xhr.open('POST', '/server', true);
      xhr.onload = function(e) {
        if (this.status == 200) {
          console.log(this, this.responseText);
        }
      };
      xhr.send('test string');
    }
    
    xhr.send(FormData)
    function sendFormData() {
      var formData = new FormData(); // or FormData(window.myform)
      formData.append("username", "Groucho");
      formData.append("accountnum", 123456);
    
      var file = document.querySelector('#afile').files[0];
      if (file) {
        formData.append('afile', file);
      }
    
      var xhr = new XMLHttpRequest();
      xhr.open('POST', '/sever', true);
      xhr.onload = onLoad;
      xhr.send(formData);
    }
    
    xhr.send(Blob|File)
    function sendBlob() {
      var builder = new BlobBuilder();
      builder.append('12345');
    
      var xhr = new XMLHttpRequest();
      xhr.open('POST', '/server', true);
      xhr.onload = onLoad;
      xhr.send(builder.getBlob('text/plain'));
    }
    
    xhr.send(ArrayBuffer)
    function sendArrayBuffer() {
      var data = [1, 2, 3];
      var buffer = new ArrayBuffer(data.length);
      var uInt8Array = new Uint8Array(buffer);
      for (var i = 0, l = uInt8Array.length; i < l; ++i) {
        uInt8Array[i] = data[i];
      }
    
      var xhr = new XMLHttpRequest();
      xhr.open('POST', '/server', true);
      xhr.onload = onLoad;
      xhr.send(buffer);
    }
    
    Example: Fetch + Write Binary Data with XHR1

    BTW, Chrome Extensions can make XD XHRs.

    function getRequest(data) {
      var xhr = new XMLHttpRequest();
      xhr.open('GET', data.url, true);
    
      // Fetch as binary data.
      xhr.overrideMimeType('text/plain; charset=x-user-defined');
    
      xhr.onload = function(e) {
        writeToFS(this.responseText, data.filename, data.type);
      };
      xhr.send();
    }
    
    getRequest({filename: 'google_logo.png', type: 'image/png',
                url: 'http://www.google.com/images/logos/ps_logo2.png'});
    
    Example: Fetch + Write Binary Data with XHR1
    function writeToFS(data, filename, type) {
      window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
        fs.root.getFile(filename, {create: true}, function(fileEntry) {
          fileEntry.createWriter(function(writer) {
            writer.onwrite = function(e) { ... };
    
            var bb = new BlobBuilder();
            var ui8a = new Uint8Array(data.length);
            for (var i = 0; i < ui8a.length; ++i) {
              ui8a[i] = data.charCodeAt(i);
            }
            bb.append(ui8a.buffer);
    
            writer.write(bb.getBlob(type));
          }, onError);
        }, onError);
      }, onError);
    }
    
    Example: Fetch + Write Binary Data with XHR2
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/path/to/image.png', true);
    
    xhr.responseType = 'arraybuffer'; // OR, xhr.responseType = 'blob';
    
    xhr.onload = function(e) {
    
      var bb = new BlobBuilder();
      bb.append(this.response); // Note: not xhr.responseText
    
      window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
        fs.root.getFile('image.png', {create: true}, function(fileEntry) {
          fileEntry.createWriter(function(writer) {
    
            writer.onwrite = function(e) { ... };
    
            writer.write(bb.getBlob('image/png'));
    
          }, onError);
        }, onError);
      }, onError);
    };
    
    xhr.send();
    

    Thanks!

    Questions?