// ==UserScript==
// @name Gmail Autosave
// @namespace http://www.cs.toronto.edu/~james/google
// @description Autosaves Gmail drafts whenever you switch away from the window
// @include http://gmail.google.com/gmail*
// @include https://gmail.google.com/gmail*
// @include http://mail.google.com/mail*
// @include https://mail.google.com/mail*
// ==/UserScript==

/* Created by David James
 
   Kudos to Vito Milano for coming up with the original idea for the 
   Gmail Autosave extension.

   More information will soon be posted at
     http://www.cs.toronto.edu/~james/greasemonkey/
   
   Version 0.1 -- Initial Version
   Version 0.2 -- Upgraded to be more robust to changes in the gmail code
   Version 0.3 -- Fixed crash bug when multiple reply windows were open at the same time
   Version 0.4 -- Fixed performance problems
   Version 0.5 -- Added fix to correct bug in Gmail where attachments are lost
   Version 0.6 -- Added mail.google.com/mail* to list of URLs 
   Version 0.7 -- Added fix to prevent overly frequent saves
*/

        

(function() {
    var JS = top.js;
    var saveOK = true;
    var justSaved = false;
    var saveTimeout;
    if (JS && JS._IF_GetVisibleWindow) {
                
        if (JS == this) {
            // Patch the Gmail code to use DOMCharacterDataModified and DOMNodeInserted events instead of "focus" events.
            // We keep this regular expression quite flexible so that we'll be able to adapt to changes in the Gmail code.
            if (this.document.documentElement.innerHTML.match(/function\s*(\w+)([^}]+;)([^};]+["'])focus(["'][^};]+)([^}]*\})/) ) {
              eval(RegExp.$1 + " = function " + RegExp.$2
                    + RegExp.$3 + 'DOMCharacterDataModified'  + RegExp.$4 + ';' 
                    + RegExp.$3 + 'DOMNodeInserted' + RegExp.$4
                    + RegExp.$5 );
            }
    
            // Patch the Gmail code to not require us to save drafts when we haven't
            // made any changes
            if (JS.document.documentElement.innerHTML.match(/function\s*(\w+)[^}]+["']Your draft has been modified/) ) {
                JS.confirmUnsavedChanges = JS[RegExp.$1];
                JS[RegExp.$1] = function(win, confirm) {
                    if (win.hasUnsavedChanges()) {
                        return JS.confirmUnsavedChanges(win, confirm);
                    }
                    return true;
                }
            }
    
            // Patch _CM_OnFocusField to use DOMCharacterDataModified and
            // DOMNodeInserted events instead of "focus" events.
            if (JS._CM_OnFocusField) {
                var oldOnFocusField = JS._CM_OnFocusField;    
                JS._CM_OnFocusField = function(win, item) {
                    var onModify = function() {
                        oldOnFocusField(win,item);
                    };
                    item.addEventListener("DOMCharacterDataModified", onModify, false);
                    item.addEventListener("DOMNodeInserted", onModify, false);
                    item.onfocus = "";
                };
            }
            
            // Patch _CM_OnFocus to use DOMCharacterDataModified and
            // DOMNodeInserted events instead of "focus" events.
            if (JS._CM_OnFocus) {
                var oldOnFocus = JS._CM_OnFocus;
                JS._CM_OnFocus = function(win, id) {
                    var item = win.document.getElementById("ta_" + id);
                    var onModify = function() {
                      oldOnFocus(win,id);
                    };
                    item.addEventListener("DOMCharacterDataModified", onModify, false);
                    item.addEventListener("DOMNodeInserted", onModify, false);
                    item.onfocus = "";            
                }
            }

            // Patch Gmail code to always show attachments
            if (this.document.documentElement.innerHTML.match(/function\s*(\w+)[^\n]+CM_form\s*\[[^\]]+\]\s*=\s*(\w+)[^}]*return[^;]*\2/) ) {
                var initializeComposeWindow = this[RegExp.$1];
                var modifiedInitializeComposeWindow = function() {
                    var newWindow = initializeComposeWindow.apply(this, modifiedInitializeComposeWindow.arguments);
                    newWindow.inc_orig_attach = true;
                    return newWindow;
                };
                this[RegExp.$1] = modifiedInitializeComposeWindow;
            }



        } 
        
        var win = JS._IF_GetVisibleWindow();
        var doc = win.document;
    
        if (win == this && win && win.document) {
           
            // If the user switches contexts, and its not within the page, 
            // then save the email
            win.addEventListener('blur', function(event) {
                // event.target is the element that was clicked
                if (saveOK && hasUnsavedChanges()) {
                    clearTimeout(saveTimeout);
                    saveOK = false;
                    saveTimeout = setTimeout(function() {
                        if (justSaved == false) {
                          justSaved = true;
                          saveAllDrafts();
                          saveTimeout = setTimeout(function() { 
                            saveOK = true;
                          }, 2000);
                          setTimeout(function() { 
                            justSaved = false;
                          }, 2000);
                        }
                    }, 0);
                }
            }, true);
    
            // If the user clicks or switches contexts within the page,
            // don't save
            var cancelPendingAutomaticSave = function(event) {
                saveOK = false;
                clearTimeout(saveTimeout);
                saveTimeout = setTimeout(function() { saveOK = true; } ,200);
            };
            doc.addEventListener('click', cancelPendingAutomaticSave, true);
            doc.addEventListener('focus', cancelPendingAutomaticSave, true);
    
            // Check whether we have unsaved changes
            function hasUnsavedChanges() {            
                // Look through draft buttons
                var buttons = doc.evaluate("//*[contains(@id, '_top')]//button[@id='d']", doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);            
                for (var i = 0; i < buttons.snapshotLength; i++) {                
                    var button = buttons.snapshotItem(i);
                    if (!button.disabled) {
                        // We have unsaved changes
                        return true;
                    }
                }
                return false;
            }
            this.hasUnsavedChanges = hasUnsavedChanges;            
            
            // Save all drafts, starting with draft "i"
            function saveDrafts(buttons, i) {
                for (; i < buttons.snapshotLength; i++) {                
                    var button = buttons.snapshotItem(i);
                    if (!button.disabled) {
                        // We have unsaved changes, so save the draft
                        var e = document.createEvent("MouseEvents");   
                        e.initEvent("click", true, false);
                        button.dispatchEvent(e);
                        var saveTheRestOfTheDrafts = function() {
                            var buttons = doc.evaluate("//*[contains(@id, '_top')]//button[@id='d']", doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);            
                            if (buttons.snapshotItem(i).disabled) {
                                saveDrafts(buttons, i + 1);
                            } else {
                                setTimeout(saveTheRestOfTheDrafts, 100);
                            }        
                        };
                        setTimeout(saveTheRestOfTheDrafts, 100);
                        break;
                    }
                }
        
            }
            
            // Save all of the drafts on the current page
            function saveAllDrafts() {
                
                // Look through draft buttons
                var buttons = document.evaluate("//*[contains(@id, '_top')]//button[@id='d']", doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);            
                saveDrafts(buttons,0);
                return false;
            }    
    
        }
        
        
    }
})();
