Bug 1025727 - [email/cronysync] Failsafe abort/window.close after failed cronsync(s)

This pull request introduces a failsafe close mechanism based on the cronsync mechanism.

The bulk of the work is done in wake_locks, since we ideally need the wake locks still active if some other UI data actions is occurring, like a mid-stream draft save or message send, and the wake lock timeout gives us a strong signal that something has gone wrong.

For the wake_lock timeout, it now uses a new function, closeIfNoDataOps, to decide if the app should be closed. If there are no in-process UI data actions, then it will proceed with closing the app if the app is currently in a hidden state. It was previously confirmed that the system will free any locks automatically for closed apps.

If the app is visible when the wake lock timeout occurs, then the locks are just cleared, and we wait for the next sync pass or regular other-app use to OOM email.

compose triggers uiDataOperationStart and uiDataOperationStop events to indicate in-process UI data actions: either saving a draft or sending a message.

If there are in-process UI data actions, there is a 5 second grace period to finish before the failsafe kills the app. This is done because one of those UI data actions could be contributing to the reason sync has gone bad, and it is more important to just shut down and reset. The odds of reaching that state should be very low, the UI data actions will likely complete in time, but we need an ultimate failsafe pathway just in case.

This will likely need to be revisited with background send. If it is an email send with a large attachment, that could also be a problem, but only if email is in the background when that happens. The most likely cause of getting into this state though, when sync fails so that this pathway is considered is if the network connections have gotten into a weird state, so sending is not likely to complete anyway.

Tests were performed by putting in an early return in sync.js at the top of its api.oncronsyncstop function, at line 108. This made it appear that syncing did not complete. Then the following things were tried, using a 60 second test interval:

* regular background sync with email closed. Saw the "force closing app" message and confirmed email was not running after that.
* regular background sync with email, but then tapping email icon so it is visible. Saw "app still visible" and email kept running.
* Changed compose to not emit the uiDataOperationStop event, then waited for a sync to start, opened a compose and saved draft, then went back to home screen. Saw the "waiting on email data operations" message with "saveDraft" mentioned, then 5 seconds later the app was closed.

I went ahead and gave each kind of action (saveDraft and emailSend) a unique name. This still means individual actions of a type are not unique, but should be good enough for these purposes. Full diff for this round of feedback is below. I went ahead and rebased to get to a travis endpoint sooner, which is why this diff is not in a separate commit:

```patch
diff --git a/apps/email/js/cards/compose.js b/apps/email/js/cards/compose.js
index 48f58c7..33bfd58 100755
--- a/apps/email/js/cards/compose.js
+++ b/apps/email/js/cards/compose.js
@@ -28,7 +28,6 @@ var templateNode = require('tmpl!./compose.html'),
     Cards = common.Cards,
     ConfirmDialog = common.ConfirmDialog,
     mimeToClass = common.mimeToClass,
-
     dataIdCounter = 0;
 
 /**
@@ -162,9 +161,11 @@ function ComposeCard(domNode, mode, args) {
   this.sentAudio.mozAudioChannelType = 'notification';
   this.playSoundOnSend = false;
 
-  // Sets up a unique data ID for sending out notifications about data-sensitive
-  // operations that are in progress.
-  this._dataId = module.id + '-' + (dataIdCounter += 1);
+  // Set up unique data IDs for data-sensitive operations that could be in
+  // progress. These IDs are unique per kind of action, not unique per instance
+  // of a kind of action. However, these IDs are just used to know if a hard
+  // shutdown should be delayed a bit, and are unique enough for those purposes.
+  var dataId = module.id + '-' + (dataIdCounter += 1);
+  this._dataIdSaveDraft = dataId + '-saveDraft';
+  this._dataIdSendEmail = dataId + '-sendEmail';
 }
 ComposeCard.prototype = {
   /**
@@ -409,9 +410,9 @@ ComposeCard.prototype = {
       return;
     }
     this._saveStateToComposer();
-    evt.emit('uiDataOperationStart', this._dataId, 'saveDraft');
+    evt.emit('uiDataOperationStart', this._dataIdSaveDraft);
     this.composer.saveDraft(function() {
-      evt.emit('uiDataOperationStop', this._dataId);
+      evt.emit('uiDataOperationStop', this._dataIdSaveDraft);
       if (callback) {
         callback();
       }
@@ -876,7 +877,7 @@ ComposeCard.prototype = {
 
     // Initiate the send.
     console.log('compose: initiating send');
-    evt.emit('uiDataOperationStart', this._dataId, 'sendEmail');
+    evt.emit('uiDataOperationStart', this._dataIdSendEmail);
     this.composer.finishCompositionSendMessage(
       function callback(error , badAddress, sentDate) {
         // Card could have been destroyed in the meantime,
@@ -922,7 +923,7 @@ ComposeCard.prototype = {
           self.sentAudio.play();
         }
 
-        evt.emit('uiDataOperationStop', this._dataId);
+        evt.emit('uiDataOperationStop', this._dataIdSendEmail);
         activityHandler();
         this._closeCard();
       }.bind(this)
diff --git a/apps/email/js/wake_locks.js b/apps/email/js/wake_locks.js
index 4d6b9d4..b09f39c 100644
--- a/apps/email/js/wake_locks.js
+++ b/apps/email/js/wake_locks.js
@@ -58,17 +58,12 @@ define(function(require) {
     var dataOpsKeys = Object.keys(dataOps);
 
     if (!dataOpsKeys.length) {
-      //All clear, no waiting data operations, shut it down.
+      // All clear, no waiting data operations, shut it down.
       return close();
     }
 
-    var waitingOps = [];
-    dataOpsKeys.forEach(function(key) {
-      waitingOps.push(dataOps[key]);
-    });
-
     console.log('email: cronsync wake lock force shutdown waiting on email ' +
-                'data operations: ' + waitingOps.join(', '));
+                'data operations: ' + dataOpsKeys.join(', '));
     // Allow data operations to complete, but also set a cap on that since
     // they could be the ones causing the sync to fail. Give it 5 seconds.
     dataOpsTimeoutId = setTimeout(close, dataOpsTimeout);
@@ -76,8 +71,8 @@ define(function(require) {
 
   // Listen for data operation events that might want to delay the failsafe
   // close switch.
-  evt.on('uiDataOperationStart', function(dataId, description) {
-    dataOps[dataId] = description;
+  evt.on('uiDataOperationStart', function(dataId) {
+    dataOps[dataId] = true;
   });
 
   evt.on('uiDataOperationStop', function(dataId) {
```