Gears

CSCE 242

University of South Carolina


José M. Vidal [1]
http://jmvidal.cse.sc.edu/talks/googlegears/ [2]

1 Goals

  1. Web apps = desktop apps (video [4])
  2. Eliminate pain points.
  3. Widespread deployment (baked-in, HTML 5, or add-on).

2 Getting Started

3 Local Server

  1. Write a manifest.json that lists files you want cached:
    {
      "betaManifestVersion": 1,
      "version": "1.0",
      "entries": [
          { "url": "go_offline.html"},
          { "url": "go_offline.js"},
          { "url": "gears_init.js"}
        ]
    }
    
  2. Your HTML:
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
    <script type="text/javascript" src="gears_init.js"></script>
    <script type="text/javascript" src="go_offline.js"></script>
    
    <title>Enable Offline Usage</title>
    <style type="text/css">
    <!--
    .style1 {
            color: #003399;
            font-style: italic;
            font-weight: bold;
            font-size: large;
    }
    .style3 {
            color: #009933;
            font-weight: bold;
            font-style: italic;
    }
    -->
    </style>
    </head>
    
    <body onload="init()">
    <h2>Getting offline-enabled documents with Gears </h2>
    <p>&nbsp;</p>
    <div>
      <p class="style1">Status Message: <span id="textOut" class="style3"></span></p>
    </div>
    
    
    <p><strong>Q:</strong> I want to see these documents when I'm not online! What must I do?<br />
    <strong>A:</strong> <a href="http://gears.google.com/">Install Gears</a> on your computer and then click &quot;Capture&quot; to store the documents to your computer. You can then access the URLs without a network connection. </p>
    <p>
      <button onclick="createStore()" > Capture </button>
     </p>
    
    
    <p><strong>Q:</strong> I want to remove my offline access to these documents. What must I do?<br />
    <strong>A: </strong>Click &quot;Erase&quot; below to removed the &quot;captured&quot; documents from your computer.  The documents will no longer be available without a network connection. </p>
    <p>
      <button onclick="removeStore()" > Erase </button>
    </p>
    </body>
    </html>
    
  3. The code:
    var STORE_NAME = "my_offline_docset";
    
    // Change this to set the URL of tha manifest file, which describe which
    // URLs to capture. It can be relative to the current page, or an
    // absolute URL.
    var MANIFEST_FILENAME = "manifest.json";
    
    var localServer;
    var store;
    
    // Called onload to initialize local server and store variables
    function init() {
      if (!window.google || !google.gears) {
        textOut("NOTE:  You must install Gears first.");
      } else {
        localServer = google.gears.factory.create("beta.localserver");
        store = localServer.createManagedStore(STORE_NAME);
        textOut("Yeay, Gears is already installed.");
      }
    }
    
    // Create the managed resource store
    function createStore() {
      if (!window.google || !google.gears) {
        alert("You must install Gears first.");
        return;
      }
    
      store.manifestUrl = MANIFEST_FILENAME;
      store.checkForUpdate();
    
      var timerId = window.setInterval(function() {
        // When the currentVersion property has a value, all of the resources
        // listed in the manifest file for that version are captured. There is
        // an open bug to surface this state change as an event.
        if (store.currentVersion) {
          window.clearInterval(timerId);
          textOut("The documents are now available offline.\n" + 
                  "With your browser offline, load the document at " +
                  "its normal online URL to see the locally stored " +
                                    "version. The version stored is: " + 
                  store.currentVersion);
        } else if (store.updateStatus == 3) {
          textOut("Error: " + store.lastErrorMessage);
        }
      }, 500);  
    }
    
    // Remove the managed resource store.
    function removeStore() {
      if (!window.google || !google.gears) {
        alert("You must install Gears first.");
        return;
      }
    
      localServer.removeManagedStore(STORE_NAME);
      textOut("Done. The local store has been removed." +
              "You will now see online versions of the documents.");
    }
    
    // Utility function to output some status text.
    function textOut(s) {
     var elm = document.getElementById("textOut");
      while (elm.firstChild) {
        elm.removeChild(elm.firstChild);
      } 
      elm.appendChild(document.createTextNode(s));
    }
    

3.1 ResourceStore

4 The Blob

5 Database

  1. The HTML page:
    <html>
    <head>
    <title>Gears Database Demo</title>
    <link rel="stylesheet" type="text/css" href="sample.css">
    </head>
    
    <body>
    <h1>Gears Database Demo</h1>
    <div id="view-source">&nbsp;</div>
    
    <form onsubmit="handleSubmit(); return false;">
    
      <b>Enter a phrase to store in the database:</b>&nbsp;<br>
      <table>
        <tr>
          <td valign="middle"><input type="text" id="submitValue"
            style="width:20em;"></td>
          <td valign="middle"><input type="submit" value="OK"></td>
        </tr>
      </table>
    </form>
    
    <p><b>Your last three phrases were:</b>
    
    <p><span id="status">&nbsp;</span>
    
    <p><i>This page uses Gears to record your entries on the local disk.
       If you navigate away and revisit this page, all your data will still
       be here.  Try it!</i>
    
    <!-- ====================================== -->
    <!-- End HTML code.  Begin JavaScript code. -->
    
    <script type="text/javascript"  src="gears_init.js"></script>
    <script type="text/javascript" src="sample.js"></script>
    <script type="text/javascript" src="hello_world.js">
    </script>
    
    </body>
    </html>
    
  2. The hello_world.js:
    var db;
    init();
    
    // Open this page's local database.
    function init() {
      var success = false;
    
      if (window.google && google.gears) {
        try {
          db = google.gears.factory.create('beta.database');
    
          if (db) {
            db.open('database-demo');
            db.execute('create table if not exists Demo' +
                       ' (Phrase varchar(255), Timestamp int)');
    
            success = true;
            // Initialize the UI at startup.
            displayRecentPhrases();
          }
    
        } catch (ex) {
          setError('Could not create database: ' + ex.message);
        }
      }
    
      // Enable or disable UI elements
    
      var inputs = document.forms[0].elements;
      for (var i = 0, el; el = inputs[i]; i++) {
        el.disabled = !success;
      }
    
    }
    
    function handleSubmit() {
      if (!google.gears.factory || !db) {
        return;
      }
    
      var elm = getElementById('submitValue');
      var phrase = elm.value;
      var currTime = new Date().getTime();
    
      // Insert the new item.
      // The Gears database automatically escapes/unescapes inserted values.
      db.execute('insert into Demo values (?, ?)', [phrase, currTime]);
    
      // Update the UI.
      elm.value = '';
      displayRecentPhrases();
    }
    
    
    function displayRecentPhrases() {
      var recentPhrases = ['', '', ''];
    
      // Get the 3 most recent entries. Delete any others.
      var rs = db.execute('select * from Demo order by Timestamp desc');
      var index = 0;
      while (rs.isValidRow()) {
        if (index < 3) {
          recentPhrases[index] = rs.field(0);
        } else {
          db.execute('delete from Demo where Timestamp=?', [rs.field(1)]);
        }
        ++index;
        rs.next();
      }
      rs.close();
    
      var status = getElementById('status');
      status.innerHTML = '';
      for (var i = 0; i < recentPhrases.length; ++i) {
        var id = 'phrase' + i;
        status.innerHTML += '<span id="' + id + '"></span><br>';
        var bullet = '(' + (i + 1) + ') ';
        setTextContent(getElementById(id), bullet + recentPhrases[i]);
      }
    }
    
  3. The sample.js:
    function isDefined(type) {
      return (type != 'undefined' && type != 'unknown');
    }
    
    function childNodes(element) {
      if (isDefined(typeof element.childNodes)) {
        return element.childNodes;
      } else if (isDefined(typeof element.children)) {
        return element.children;
      }
    }
    
    function getElementById(element_name) {
      if (isDefined(typeof document.getElementById)) {
        return document.getElementById(element_name);
      } else if(typeof isDefined(document.all)) {
        return document.all[element_name];
      }
    }
    
    function setTextContent(elem, content) {
      if (isDefined(typeof elem.innerText)) {
        elem.innerText = content; 
      } else if (isDefined(typeof elem.textContent)) {
        elem.textContent = content;
      }
    }
    
    function setupSample() {
      // Make sure we have Gears. If not, tell the user.
      if (!window.google || !google.gears) {
        if (confirm("This demo requires Gears to be installed. Install now?")) {
          // Use an absolute URL to allow this to work when run from a local file.
          location.href = "http://code.google.com/apis/gears/install.html";
          return;
        }
      }
    
      var viewSourceElem = getElementById("view-source");
      if (!viewSourceElem) {
        return;
      }
      var elm;
      if (navigator.product == "Gecko") {
        // If we're gecko, we can show the source of the application with the
        // view-source protocol.
        elm = "<a href='view-source:" + location.href + "'>" +
              "View Demo Source" +
              "</a>";
      } else {
        // Otherwise, just tell users how to do it manually.
        elm = "<em>" +
              "To see how this works, use the <strong>view " +
              "source</strong> feature of your browser" +
              "</em>";
      }
      viewSourceElem.innerHTML += elm;
    }
    
    function checkProtocol() {
      if (location.protocol.indexOf('http') != 0) {
        setError('This sample must be hosted on an HTTP server');
        return false;
      } else {
        return true;
      }
    }
    
    function addStatus(message, opt_class) {
      var elm = getElementById('status');
      var id = 'statusEntry' + (childNodes(elm).length + 1);
      if (!elm) return;
      if (opt_class) {
        elm.innerHTML += '<span id="' + id + '" class="' + opt_class + '"></span>';
      } else {
        elm.innerHTML += '<span id="' + id + '"></span>';
      }
      elm.innerHTML += '<br>';
      setTextContent(getElementById(id), message);
    }
    
    function clearStatus() {
      var elm = getElementById('status');
      elm.innerHTML = '';
    }
    
    function setError(s) {
      clearStatus();
      addStatus(s, 'error');
    }
    
    setupSample();
    

5.1 Security and Search

6 Desktop API

var desktop = google.gears.factory.create('beta.desktop');

desktop.createShortcut('Test Application',
                       'http://example.com/index.html',
                       {'128x128': 'http://example.com/icon128x128.png',
                          '48x48': 'http://example.com/icon48x48.png',
                          '32x32': 'http://example.com/icon32x32.png',
                          '16x16': 'http://example.com/icon16x16.png'},
                       'An application at http://example.com/index.html');

function openFilesCallback(files) {
    //Typically, here you would open the appropriate URL
  alert('User selected ' + files.length + ' files.');
}
desktop.openFiles(openFilesCallback);

7 Geolocation

var geo = google.gears.factory.create('beta.geolocation');

function updatePosition(position) {
  alert('Current lat/lon is: ' + position.latitude + ',' + position.longitude);
}

function handleError(positionError) {
  alert('Attempt to get location failed: ' + positionError.message);
}

geo.getCurrentPosition(updatePosition, handleError);

8 HttpRequest

var request = google.gears.factory.create('beta.httprequest');
request.open('GET', '/index.html');
request.onreadystatechange = function() {
  if (request.readyState == 4) {
    console.write(request.responseText);
  }
};
request.send();

9 Timer

var timer = google.gears.factory.create('beta.timer');
timer.setTimeout(function() { alert('Hello, from the future!'); },
                 1000);

10 WorkerPool

  1. Your HTML file:
    <!DOCTYPE HTML>
    <html><head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Google Gears WorkerPool Demo</title>
    <link rel="stylesheet" type="text/css" href="hello_world_workerpool_files/sample.css">
    <style>
    
    p.results {
      margin: 4px;
    }
    
    </style>
    </head><body onload="init()" id="theBody">
    <h1>Google Gears WorkerPool Demo</h1>
    <div id="view-source">&nbsp;<a href="view-source:http://code.google.com/apis/gears/samples/hello_world_workerpool/hello_world_workerpool.html">View Demo Source</a></div>
    
    <p id="status">
    
    </p><p>
    <table>
    <tbody><tr>
    <td valign="top">Interact with UI:</td>
    <td><input value="Interact" onclick="interact();" type="button">
    </td>
    <td></td>
    </tr>
    <tr>
    <td valign="top">Run expensive computation:</td>
    <td>
        <input id="syncButton" value="Synchronous" onclick="syncComputation();" type="button">
    </td>
    <td><i>Note the synchronous computation blocks UI interaction
           (and may even cause an 'unresponsive script' warning).</i>
    </td>
    </tr>
    <tr>
    <td>
    </td>
    <td>
        <input id="asyncButton" value="Asynchronous" onclick="asyncComputation();" type="button">
    </td>
    <td>
        <i>But you can still interact while the asynchronous computation
           runs in a JavaScript worker.</i>
    </td>
    </tr>
    </tbody></table>
    
    </p><div id="results" style="font-size: 120%;" ;="">
    <p style="font-weight: bold;" class="results">Results
    </p><p class="results" id="message3">Hello, threaded JavaScript.</p><p class="results" id="message4">Parent initialized.</p></div>
    
    <!-- ====================================== -->
    <!-- End HTML code.  Begin JavaScript code. -->
    
    <script src="gears_init.js"></script>
    <script src="sample.js"></script>
    <script src="wpool.js"></script>
    </body><div FirebugVersion="1.3.3" style="display: none;" id="_firebugConsole"></div></html>
    
  2. The wpool.js
    
    function init() {
      insertRow('Hello, threaded JavaScript.');
      parentInit();
    }
    
    function insertRow(message) {
      var results = getElementById('results');
      var id = 'message' + (childNodes(results).length + 1);
      results.innerHTML += '<p class="results" id="' + id + '">';
      setTextContent(getElementById(id), message);
    }
    
    //
    // WorkerPool code
    //
    
    var workerPool = null;
    var childId;
    
    function parentInit() {
      if (!window.google || !google.gears) {
        return;
      }
    
      try {
        workerPool = google.gears.factory.create('beta.workerpool');
      } catch (e) {
        document.getElementById('asyncButton').disabled = true;
        setError('Could not create workerpool: ' + e.message);
        return;
      }
    
      // set the parent's message handler
      workerPool.onmessage = parentHandler;
    
      // create the worker
      try {
       childId = workerPool.createWorkerFromUrl('worker.js');
      } catch (e) {
       setError('Could not create worker: ' + e.message);
      }
    
      // Child workers will always set onmessage before createWorker() returns.
      insertRow('Parent initialized.');
    }
    
    function parentHandler(messageText, sender, message) {
      insertRow('Asynchronous result: ' + message.body);
    }
    
    
    //
    // UI-related functions
    //
    
    var uiToggleState = 0;
    function interact() {
      if (uiToggleState) {
        getElementById('theBody').style.backgroundColor = '#FFFFFF';
        uiToggleState = 0;
      } else {
        getElementById('theBody').style.backgroundColor = '#CCCCCC';
        uiToggleState = 1;
      }
    }
    
    function asyncComputation() {
      if (workerPool) {
        workerPool.sendMessage(112501234, childId);
      }
    }
    
    function syncComputation() {
      var result = identity(112501234);
      insertRow('Synchronous result: ' + result);
    
      function identity(n) {
        var result = 0;
        while (n > 0) {
          result += 1;
          var tempString = 'abc' + '123';  // for slowdown
          n--;
        }
        return result;
      }
    }
    
  3. The worker.js:
    // Copyright 2007, Google Inc.
    //
    // Redistribution and use in source and binary forms, with or without 
    // modification, are permitted provided that the following conditions are met:
    //
    //  1. Redistributions of source code must retain the above copyright notice, 
    //     this list of conditions and the following disclaimer.
    //  2. Redistributions in binary form must reproduce the above copyright notice,
    //     this list of conditions and the following disclaimer in the documentation
    //     and/or other materials provided with the distribution.
    //  3. Neither the name of Google Inc. nor the names of its contributors may be
    //     used to endorse or promote products derived from this software without
    //     specific prior written permission.
    //
    // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
    // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
    // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
    // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
    // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
    // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
    // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
    // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    google.gears.workerPool.onmessage = function(messageText, senderId, message) {
      google.gears.workerPool.sendMessage(identity(message.body), senderId);
    };
    
    function identity(n) {
      var result = 0;
      while (n > 0) {
        result += 1;
        var tempString = 'abc' + '123';  // for slowdown
        n--;
      }
      return result;
    }
    
  4. The sample.js:
    // Copyright 2007, Google Inc.
    //
    // Redistribution and use in source and binary forms, with or without 
    // modification, are permitted provided that the following conditions are met:
    //
    //  1. Redistributions of source code must retain the above copyright notice, 
    //     this list of conditions and the following disclaimer.
    //  2. Redistributions in binary form must reproduce the above copyright notice,
    //     this list of conditions and the following disclaimer in the documentation
    //     and/or other materials provided with the distribution.
    //  3. Neither the name of Google Inc. nor the names of its contributors may be
    //     used to endorse or promote products derived from this software without
    //     specific prior written permission.
    //
    // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
    // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
    // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
    // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
    // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
    // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
    // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
    // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    // On some WinCE devices, IE Mobile uses 'unknown' rather than 'undefined'. If
    // an element has type 'unknown', we can not pass it to a function, so we must
    // pass its type instead.
    function isDefined(type) {
      return (type != 'undefined' && type != 'unknown');
    }
    
    function childNodes(element) {
      if (isDefined(typeof element.childNodes)) {
        return element.childNodes;
      } else if (isDefined(typeof element.children)) {
        return element.children;
      }
    }
    
    function getElementById(element_name) {
      if (isDefined(typeof document.getElementById)) {
        return document.getElementById(element_name);
      } else if(typeof isDefined(document.all)) {
        return document.all[element_name];
      }
    }
    
    function setTextContent(elem, content) {
      if (isDefined(typeof elem.innerText)) {
        elem.innerText = content; 
      } else if (isDefined(typeof elem.textContent)) {
        elem.textContent = content;
      }
    }
    
    function setupSample() {
      // Make sure we have Gears. If not, tell the user.
      if (!window.google || !google.gears) {
        if (confirm("This demo requires Gears to be installed. Install now?")) {
          // Use an absolute URL to allow this to work when run from a local file.
          location.href = "http://code.google.com/apis/gears/install.html";
          return;
        }
      }
    
      var viewSourceElem = getElementById("view-source");
      if (!viewSourceElem) {
        return;
      }
      var elm;
      if (navigator.product == "Gecko") {
        // If we're gecko, we can show the source of the application with the
        // view-source protocol.
        elm = "<a href='view-source:" + location.href + "'>" +
              "View Demo Source" +
              "</a>";
      } else {
        // Otherwise, just tell users how to do it manually.
        elm = "<em>" +
              "To see how this works, use the <strong>view " +
              "source</strong> feature of your browser" +
              "</em>";
      }
      viewSourceElem.innerHTML += elm;
    }
    
    function checkProtocol() {
      if (location.protocol.indexOf('http') != 0) {
        setError('This sample must be hosted on an HTTP server');
        return false;
      } else {
        return true;
      }
    }
    
    function addStatus(message, opt_class) {
      var elm = getElementById('status');
      var id = 'statusEntry' + (childNodes(elm).length + 1);
      if (!elm) return;
      if (opt_class) {
        elm.innerHTML += '<span id="' + id + '" class="' + opt_class + '"></span>';
      } else {
        elm.innerHTML += '<span id="' + id + '"></span>';
      }
      elm.innerHTML += '<br>';
      setTextContent(getElementById(id), message);
    }
    
    function clearStatus() {
      var elm = getElementById('status');
      elm.innerHTML = '';
    }
    
    function setError(s) {
      clearStatus();
      addStatus(s, 'error');
    }
    
    setupSample();
    

11 Architecture

No data layer

11.1 Data Layer

Data Layer

11.2 Data Switch Layer

Data Switch Layer

11.3 Local Data Layer

Local Data Layer

11.4 Modality

11.5 Synchronization

11.6 Background Sync

Background Sync

12 Conclusion

URLs

  1. José M. Vidal, http://jmvidal.cse.sc.edu
  2. http://jmvidal.cse.sc.edu/talks/googlegears/, http://jmvidal.cse.sc.edu/talks/javascriptinbrowser/
  3. Gears API, http://code.google.com/apis/gears/
  4. video, http://www.youtube.com/watch?v=hapkRYxCU_8
  5. gears_init.js, http://code.google.com/apis/gears/tools.html#gears_init
  6. tutorial, http://code.google.com/apis/gears/tutorial.html
  7. ResourceStore, http://code.google.com/apis/gears/api_localserver.html#ResourceStore
  8. database, http://code.google.com/apis/gears/api_database.html
  9. demo, http://code.google.com/apis/gears/samples/hello_world_database.html
  10. full text search, http://code.google.com/apis/gears/api_database.html#sqlite_fts
  11. demo, http://code.google.com/apis/gears/samples/hello_world_geolocation.html
  12. demo, http://code.google.com/apis/gears/samples/hello_world_workerpool/hello_world_workerpool.html
  13. my copy, http://jmvidal.cse.sc.edu/talks/googlegears/wpdemo/
  14. architecture, http://code.google.com/apis/gears/architecture.html

This talk available at http://jmvidal.cse.sc.edu/talks/googlegears/
Copyright © 2009 José M. Vidal . All rights reserved.

24 March 2009, 08:11AM