// ==UserScript==
// @name Gmail 2.0 fixes for Opera
// @author dbloom (special thanks: Ayush, diz^X, fearphage, xErath)
// @include http://mail.google.com/mail/*ui=2*
// @include https://mail.google.com/mail/*ui=2*
// @include http://mail.google.com/mail/*ui=1*
// @include https://mail.google.com/mail/*ui=1*
// @include http://mail.google.com/mail/contacts/ui/ContactManager*
// @include https://mail.google.com/mail/contacts/ui/ContactManager*
// @include http://mail.google.com/hosted/*.*/*ui=2*
// @include https://mail.google.com/hosted/*.*/*ui=2*
// @include http://mail.google.com/hosted/*.*/*ui=1*
// @include https://mail.google.com/hosted/*.*/*ui=1*
// @include http://mail.google.com/hosted/*.*/contacts/ui/ContactManager*
// @include https://mail.google.com/hosted/*.*/contacts/ui/ContactManager*
// @include http://mail.google.com/a/*.*/*ui=2*
// @include https://mail.google.com/a/*.*/*ui=2*
// @include http://mail.google.com/a/*.*/*ui=1*
// @include https://mail.google.com/a/*.*/*ui=1*
// @include http://mail.google.com/a/*.*/contacts/ui/ContactManager*
// @include https://mail.google.com/a/*.*/contacts/ui/ContactManager*
// @include https://www.google.com/*/ServiceLogin*
// ==/UserScript==

// 27-11-2007  5:50PM  Added version numbering,
//                     fixed overflow issue with chat conversations in Merlin
//             6:05PM  Actually fixed it :P
// 28-11-2007 10:23AM  Applied the getBoundingClientRect patch to all frames.
//                     This fixes the "Options" menu on chats...
//                     Also fixed rich text editor.
// 28-11-2007  2:00PM  Fixed font size in rich text editor.
//             5:13PM  Fixed a Merlin syntax error caused by regex_cm_1
// 30-11-2007  4:33PM  Fixed bug that broke chat popout windows
// 01-12-2007 10:38AM  Removed fix for top border on messages because
//                     Google fixed it. Code cleanup.
//                     Reduced UA string for Safari spoofing on contact mgr.
//            12:57PM  Fixed "sign out"/"older version" links
//             3:33PM  Prevent scrolling from canvas frame from scrolling
//                     parent frame
//                     "Next sender" thing at bottom right corner now updates
//                     as you scroll in conversations.
// 02-12-2007  7:44PM  Fixed regression that caused scrolling to not work on
//                     "show original", etc view.
//             9:54PM  Detect script source view/etc using document.compatMode,
//                     to prevent the script from running when viewing Gmail's
//                     JS and such.
// 04-12-2007  9:43AM  Fixed adding labels to messages
//            10:48AM  Fixed UNIX regression caused by previous fix
// 05-12-2007  7:10AM  The UNIX fix didn't actually work. Disabled <wbr>/&shy;
//                     in UNIX - unfortunately, this means long words won't
//                     wrap :-(
// 06-12-2007  9:45PM  Added "Use new version" checkbox to login page :-)
// 08-12-2007 11:25AM  Fixed "highlight" feature in rich text editor
//                     Improved "quote" feature in rich text editor
//                       (this fix has a few bugs...:-P)
//                     Fixed <wbr> in Kestrel on UNIX (still
//                       unfixed in Merlin on UNIX)
// 14-12-2007 10:13AM  Hide getBoundingClientRect on builds where it is broken
//                     Code cleanup
// 16-12-2007 11:15AM  Moved to Google Code
//                     Minor code cleanup and tweaks
// 16-12-2007 10:27PM  Fixed scroll to top when opening messages
//                     Fixed label display next to conversation subject heading
// 27-12-2007  8:26PM  Hello from Oslo :-)
//                     Used Greasemonkey API to improve scroll to top behavior
//                     Removed getBoundingClientRect fixes because they are no
//                     longer needed in the latest snapshots.
//                     Fixed bug that left a newline char in <textarea> after
//                     sending chat message
//                     Contact pictures now appear in contact manager
// 29-12-2007  9:03PM  No more horizontal scrollbar!!!
// 30-12-2007 10:15AM  Disabled getBoundingClientRect in some builds I forgot to
//                     disable it for...
// 25-01-2008  4:10PM  Updated to work with some changes Google made to IFRAMEs
// 26-01-2008  1:40PM  Fixed rich text editor, chat (again)
// 30-01-2008  8:30AM  Fixed duplicate label creation (again)
// 14-02-2008 10:02AM  Google has fixed some Opera bugs, script updated
//                     accordingly :-D
// 06-04-2008  7:04PM  Fixed disabled horizontal scrollbar
//                     Attempted to fix many random, tough to reproduce JS
//                       errors by blocking queued mouse events if their target
//                       is no longer in the same spot as before (this matches
//                       the behavior of other browsers)
//                     Optimized scroll to top behavior to prevent extra
//                       repaint
//                     Simplified selectors used in CSS patches for better
//                       performance
//                     Enabled "Newer version" link in ?ui=1
//                     Temporarily override String.prototype.indexOf to prevent
//                       actually needing "&nocheckbrowser" in the URL
//                     Track reflow count in the performance debugging log
//                     Hide text selections caused by focus
//                     Fix names on contact manager - Google fixed their code
//                       to make it standards compliant, which worked in Firefox
//                       3 but also revealed a new Opera bug :-P
//                     Fixed include's so that the user JS supports Google Apps
//                       For Your Domain's Gmail service
// 07-04-2008  5:35PM  Fixed back/forward navigation and bookmarking
//                     Force Gmail to show build information at bottom of
//                       pages.
// 10-04-2008  6:14PM  Fixed a ton of contact manager issues.

(function gmail2_user_js() {
	var opera = window.opera;
  
  if (location.search) {
  	var s = location.search + '&';
  	if (s.indexOf('?ui=1&') + s.indexOf('&ui=1&') > -2) {
  		// The best way to get the "Newer version" link is to mask as another
  		// browser inside of the JS frame on Gmail 1.0. There is no WebKit or
  		// Opera-specific code here, and masking as Gecko would require spoofing
  		// navigator.product, and masking as IE7 is risky. So we mask as WebKit.
  		if (window.name == 'js')
				navigator.userAgent = 'Mozilla/5.0 (' + navigator.platform + '; U; en-us) AppleWebKit/525.13 (¿pera, like KHTML) Version/3.1 Damer/525.13';
			return;
  	}
  }
  
  if (top == self) {
  	// Make Gmail 2.0 think that "nocheckbrowser" is in location.href
  	(function(indexOf) {
  		String.prototype.indexOf = function(k) {
  			if (k == 'nocheckbrowser' && this == location.href) {
  				// Restore the original indexOf for performance reasons
  				String.prototype.indexOf = indexOf;
  				return 1337;
  			}
  			return indexOf.apply(this, arguments);
  		};
  	})(String.prototype.indexOf);
  }
  
  // Make a <style> element to provide CSS fixes as needed
  var headEl = document.getElementsByTagName('head');
  headEl = headEl[0] || false;
  if (headEl) {
    var styleEl = document.createElement('style');
    headEl.appendChild(styleEl);
    headEl = null;
  }
  
  // True if the version number starts with 9.5, or has 2 or more digits before the first decimal point.
  // (parseFloat/etc is risky because Opera versions can have more than 1 decimal point...)
  var isKestrel = (opera.version().indexOf('.') > 1 || opera.version().indexOf('9.5') == 0);
  
  function patchUri(href) {
    var splitByHash = href.split('#');
    
    // Get the portion of the URL preceding the fragment identifier
    var base = splitByHash.shift();
    
    // Everything else is the fragment identifier
    var fragId = '#' + splitByHash.join('#');
    
    if (href.indexOf('ui=2') > -1) {
      //if (href.indexOf('nocheckbrowser') == -1) {
      //  return base + 'nocheckbrowser' + fragId;
      //}
      return base + fragId;
    } else {
      if (href.indexOf('disablechatbrowsercheck') == -1) {
				// Add a URI parameter delimiter
				base += (href.indexOf('?') > -1 ? '&' : '?');
        return base + 'disablechatbrowsercheck';
      }
    }
    return href;
  };
  
  if (location.pathname.indexOf('/ServiceLogin') > -1) {
    window.addEventListener('DOMContentLoaded', function() {
      var continueUri = document.getElementById('continue').value;
      
      // No new UI for Google Reader yet. ;-P
      if (continueUri.indexOf('/mail') == -1) return;
      
      var rememberTR = document.getElementById('PersistentCookie').parentNode.parentNode;
      
      var ui2TR = document.createElement('tr');
    
      var checkUi2 = document.createElement('input');
      checkUi2.type = 'checkbox';
      checkUi2.id = 'ui2';
      checkUi2.checked = (document.cookie.indexOf('ui2=yes') > -1);
      var tdCheckUi2 = document.createElement('td');
      tdCheckUi2.align = 'right';
      tdCheckUi2.appendChild(checkUi2);
      ui2TR.appendChild(tdCheckUi2);
      
      var labelUi2 = document.createElement('label');
      labelUi2.setAttribute('class', 'gaia le lbl');
      labelUi2.setAttribute('for', 'ui2');
      var labelUi2Text = document.createTextNode('Use new version');
      labelUi2.appendChild(labelUi2Text);
      var tdLabelUi2 = document.createElement('td');
      tdLabelUi2.appendChild(labelUi2);
      ui2TR.appendChild(tdLabelUi2);
      
      rememberTR.parentNode.insertBefore(ui2TR, rememberTR);
      
      var updateContinue = function() {
        var splitByHash = continueUri.split('#');
        var newUri = continueUri;
        if (checkUi2.checked) {
          // By 2027, Opera will be leading the browser market so this script will no longer be needed anyway =)
          document.cookie = 'ui2=yes; expires=Wed, Dec 1, 2027 3:25:48 PM; path=/';
          if (continueUri.indexOf('ui=1') == -1) {
            if (continueUri.indexOf('ui=2') == -1) {
              newUri = splitByHash.shift() + (continueUri.indexOf('?') > -1 ? '&' : '?') + 'ui=2#' + splitByHash.join('#');
            }
          } else {
            newUri = continueUri.replace('ui=1', 'ui=2');
          }
        } else {
          // We don't really need this cookie to last because "new version" is not checked by default.
          // So, we let this cookie expire immediately by using a date in the past for expires.
          document.cookie = 'ui2=no; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
          newUri = continueUri.replace('ui=2', 'ui=1');
        }
        document.getElementById('continue').value = patchUri(newUri);
      };
      checkUi2.addEventListener('click', updateContinue, false);
      updateContinue();
    }, false);
    return;
  }
  
  // Opera bug: getBoundingClientRect horribly broken in some builds
  switch (navigator.platform.substr(0,3) + opera.buildNumber()) {
    case 'Mac4579': case 'Win9694': case 'Lin1719':
    case 'Mac4591': case 'Win9716': case 'Lin1729':
    	delete Element.prototype.getBoundingClientRect;
      break;
    default:
      break;
  }

  // Make sure we aren't in a chat popout window (&view=cw)
	if (location.search.indexOf('&view=cw') == -1 &&
			window.top === window.self ||
			window.frameElement && window.frameElement.name == 'js') {
    /*
    // Opera bug: Script in parent frame called from an IFRAME terminates if IFRAME is removed (298005)
		// Workaround: delay removal of IFRAMEs.
		var delay = 10000;
		(function(removeChild) {
			// Removes scripts so that any unexecuted <script>s don't run after
			// the frame they are in is removed.
			var removeScripts = function(doc) {
				var elements, i;
				elements = doc.getElementsByTagName('iframe');
				i = elements.length;
				if (i--) do {
					removeScripts(elements[i].contentDocument);
				} while (i--);
				elements = doc.getElementsByTagName('script');
				i = elements.length;
				if (i--) do {
					removeChild.call(elements[i].parentNode, elements[i]);
				} while (i--);
			};
			Element.prototype.removeChild = function(child) {
				if (!(child instanceof HTMLIFrameElement))
					return removeChild.apply(this, arguments);
				var parent = this;
				var timeout;
				// If the iframe is reused, cancel the timeout!
				var reinsertListener = function() {
					clearTimeout(timeout);
					child.removeEventListener('DOMNodeInsertedIntoDocument', reinsertListener, false);          
				};
				child.addEventListener('DOMNodeInsertedIntoDocument', reinsertListener, false);
				timeout = setTimeout(function() {
					if (child && child.parentNode === parent) {
						child.removeEventListener('DOMNodeInsertedIntoDocument', reinsertListener, false);  
						removeChild.call(parent, child);
					}
				}, delay);
				removeScripts(child.contentDocument);
				child.contentDocument.close();
			};
		})(Element.prototype.removeChild);*/
	
		
    // Opera bug: XMLHttpRequest not continuously firing onreadystatechange and updating responseText on data receive (299926)
    // Special thanks to diz^X for debugging chat and finding this problem!
    // Workaround: Poll responseText and fire onreadystatechange as needed
    (function(send) {
      // We replace onreadystatechange with an instance of this wrapper.
      // This way, we can use instanceof later to determine if
      // onreadystatechange is already wrapped.
      var WrappedReadyStateChange = function(xhrInstance) {
        var o = xhrInstance.onreadystatechange;
        var rText = '';
        var interval = null;
        return function() {
          if (xhrInstance.readyState == 3 && xhrInstance.status == 200) {
            rText = xhrInstance.responseText;
            o.apply(this, arguments);
            if (interval === null) {
            	var t = this, a = arguments;
              interval = setInterval(function() {
                var success;
                try {
                  if (xhrInstance.readyState == 3) {
                    if (xhrInstance.responseText.length != rText.length) {
                      rText = xhrInstance.responseText;
                      o.apply(t, a);
                    }
                    success = true;
                  }
                } finally {
                  if (!success) {
                    clearInterval(interval);
                    interval = null;
                    rText = '';
                  }
                }
              }, 250);
            }
          } else {
            if (interval !== null) {
              clearInterval(interval);
              interval = null;
            }
            rText = '';
            if (xhrInstance.readyState == 4) {
              xhrInstance.onreadystatechange = o;
            }
            o.apply(this, arguments);
          }
        };
      };
      XMLHttpRequest.prototype.send = function() {
        if (this.onreadystatechange && !(this.onreadystatechange instanceof WrappedReadyStateChange)) {
          var o = this.onreadystatechange;
          this.onreadystatechange = new WrappedReadyStateChange(this);
          var success;
          try {
            var retVal = send.apply(this, arguments);
            success = true;
          } finally {
            if (!success) {
              this.onreadystatechange = o;
            }
          }
          return retVal;
        }
        if (interval !== null) clearInterval(interval);
        interval = null;
        rText = '';
        return send.apply(this, arguments);
      };
    })(XMLHttpRequest.prototype.send);
	
		
    opera.addEventListener('BeforeScript',function (e) {
    
      if (e.element.src.indexOf('&name=bjs&') > -1) {
        opera.removeEventListener('BeforeScript', arguments.callee, false);
        
        // Opera bug: IFRAME script thread changing top.location.href to a fragment identifier causes a reload (298627)
        // Workaround: change top.location.hash instead
        // (thanks xErath)
        //e.element.text = e.element.text.replace(/(\w+)\.href=(\w+)/g,'($1==top.location&&top.location.href.split("#")[0]==$2.split("#")[0])?($1.hash=$2.split("#").pop())&&$1.href:$1.href=$2');
      }
			
			// Google bug: Using &shy; in Opera breaks adding labels to messages (301574)
			/*
			var replacement;
			if (isKestrel) {
				replacement = '<wbr style=\\\"white-space: nowrap;\\\" \\\/>';
			} else if (navigator.platform.indexOf('Win') == navigator.platform.indexOf('Mac')) {
				// *nix
				// replacement = '"<wbr style=\\\"position:absolute;width:0px;height:0px;overflow:hidden;content:&quot;\\\\00200B&quot;\\\">"';
				replacement = ''; // TODO: Find a good <wbr> substitute that works on Linux...:-P
			} else {
				// Win/Mac
				replacement = '<wbr style=\\\"content:&quot;\\\\00200B&quot;\\\">';
			}
			e.element.text = e.element.text.replace('&shy;', replacement);
      */
    },false);
    
    // Better fix using GreaseMonkey API is now implemented.
    /*
    opera.addEventListener('BeforeScript',function (e) {
      if (e.element.src.indexOf('&name=js&') + 1) {
        opera.removeEventListener('BeforeScript', arguments.callee, false);
        // Google bug: Browser sniffing makes Gmail not scroll to top automatically (303234)
        // Workaround: Activate the Safari-specific code for this.
        //e.element.text = e.element.text.replace(/if\(([\d\w\s]+)\)\{if\(this\.([\d\w\s]+)\.([\d\w\s]+)\)\{this\.([\d\w\s]+)\.([\d\w\s]+)\.focus\(\);this\.([\d\w\s]+)\.([\d\w\s]+)\.blur\(\)\}\}/i, 'if (!$1){if(this.$2.$3 && "$2"=="$4" && "$4"=="$6" && "$3"=="$5" && "$5"=="$7"){this.$2.$3.focus();this.$2.$3.blur();}}');
      }
    }, false);
    */

    (function(originalEval) {
      
      // Some general-purpose code for unborking things
      function Fixes() {
        this.fixes = [];
      };
      Fixes.prototype.addFix = function(var_args) {
        var fixes = Array.prototype.slice.call(arguments);
        do { this.fixes.push(fixes.pop()) } while (fixes.length);
      };
      Fixes.prototype.unbork = function(str) {
        var fix, fixIndex = this.fixes.length;
        if (!fixIndex--) return str;
        do {
          fix = this.fixes[fixIndex];
          if (fix.canUnbork(str)) {
            str = fix.unbork(str);
            this.fixes.splice(fixIndex, 1);
            return str;
          }
        } while (fixIndex--);
        return str;
      };
      Fixes.prototype.isEmpty = function() {
        return this.fixes.length == 0;
      }
      
      function Fix(substr) {
        this.substr = substr;
        this.unborkers = [];
      };
      Fix.prototype.addUnborker = function(var_args) {
        var unborkers = Array.prototype.slice.call(arguments);
        do { this.unborkers.push(unborkers.pop()) } while (unborkers.length);
      };
      Fix.prototype.canUnbork = function(str) {
      	// without "str.indexOf && ", chat breaks because it likes to pass
      	// non-string things to eval for some reason.
        return str.indexOf && str.indexOf(this.substr) != -1;
      };
      Fix.prototype.unbork = function(str) {
        var unbork, unborkerIndex = this.unborkers.length;
        if (!unborkerIndex--) return str;
        do {
          unbork = this.unborkers[unborkerIndex];
          str = unbork(str);
        } while (unborkerIndex--);
        return str;
      };
      
      function Unborker(find, replace) {
        return function(txt) {
          return txt.replace(find, replace);
        };
      };
      
      // Override browser blocking on rich text editor
      // The regex matches a string like this:
      // return Cdp&&Cqp(IXa)>=0||Cep&&Cqp(JXa)>=0||Cmp&&Cqp(KXa)>=0}
      // (...which basically tests for Moz 1.8+, IE 6+, or WebKit 520+)
      var fixRichTextBrowserBlocking = new Unborker(/return [\d\w\s]*&&[\d\w\s]*\([\d\w\s]*\)[\s]?>=[\s]?0[\s]?\|\|[\d\w\s]*&&[\d\w\s]*\([\d\w]*\)[\s]?>=[\s]?0[\s]?\|\|[\d\w\s]*&&[\d\w\s]*\([\d\w\s]*\)[\s]?>=0[\s]?}/, '\nreturn true;}\n');
      
      // Google bug: Opera uses IE-compatible font-sizes in quirksmode
      // This workaround creates a rule that has more specificity than
      // the .tr-field rule, instead of using !important, because using
      // !important would override inline font styles on the editable text.
      var fixRichTextFontSize = new Unborker(/\.tr-field\{/, 'html > body.tr-field{font-size:x-small;}.tr-field{');
      
      // Google bug: WTF... getSelection().parentElement()???
      var fixGetSelectionParentElement = new Unborker(/(\w+)[\s]?\.[\s]?getSelection\(\)[\s]?\.[\s]?parentElement\(\)/, '\n($1.getSelection().getRangeAt(0).commonAncestorContainer instanceof $1.getSelection().getRangeAt(0).commonAncestorContainer.defaultView.Text)?($1.getSelection().getRangeAt(0).commonAncestorContainer.parentNode):($1.getSelection().getRangeAt(0).commonAncestorContainer)\n');

      // Google bug: highlight uses IE codepath...hiliteColor, not backColor...
      var fixHilite = new Unborker(/backColor/, 'hiliteColor');
      
      // Google bug: quote uses IE codepath
      // This workaround isn't "quite right" but it is usable...
      var fixQuote = new Unborker(/(\w+)[\s]?\.surroundContents[\s]?\((\w+)\)[\s]?/g, '(($2 instanceof $2.ownerDocument.defaultView.HTMLQuoteElement)?(function(){$2.ownerDocument.execCommand("FormatBlock",null,"address");var addresses=$2.ownerDocument.getElementsByTagName("address");var i = addresses.length;if (i--){$2.innerHTML="";do{$2.innerHTML = (i?"<br>":"") + addresses[i].innerHTML + $2.innerHTML;if (i) addresses[i].parentNode.removeChild(addresses[i]);}while(i--);addresses[0].parentNode.replaceChild($2,addresses[0]);} })():$1.surroundContents($2))');
      
      var cmFix = new Fix('_MD_Before(\'cm\')');
      cmFix.addUnborker(fixRichTextBrowserBlocking, fixRichTextFontSize);
      
      var eFix = new Fix('_MD_Before(\'e\')');
      eFix.addUnborker(fixGetSelectionParentElement, fixHilite, fixQuote);
      
      var evalFixes = new Fixes();
      evalFixes.addFix(cmFix, eFix);
      
      window.eval = function(str) {
        str = evalFixes.unbork(str);
        
        if (evalFixes.isEmpty()) {
          // Just in case window.eval was wrapped again by another user JS...
          if (window.eval === arguments.callee) {
            // Okay, we can safely restore eval.
            window.eval = originalEval;
          }
        }
        
        return originalEval.call(this, str);
      };
    })(window.eval);

    
    // Prevent Gmail from throwing errors on resize --
    // this bug needs further analysis.
    // (The IE codepath delays calls to dispatchEvent("sizechange"),
    // and that workaround seems to work for Opera too.)
    /*opera.addEventListener('BeforeScript', function(e) {
      e.element.text = e.element.text.replace(/;this\.dispatchEvent\((\w+)\)/g, ';($1=="sizechange"?(this.__timeout = setTimeout((function(o){clearTimeout(o.__timeout || 0);return function(){delete o.__timeout;o.dispatchEvent($1)};})(this),50)):(this.dispatchEvent($1)))');
    }, false);*/
    
    // Fix chat - Merlin doesn't support overflow-x, overflow-y
    if (!('overflowX' in document.documentElement.style)) {
      styleEl.text += 'div { overflow: auto !important; } div span {  margin-left: 0 !important; }';
    }
    if (window.top === window.self) {
			// === TOP FRAME ===
		 
			// Don't break viewing of script source, etc...
			if (document.compatMode == 'BackCompat') {
				return;
			}
			
			// Override browser blocking
			var href = location.href;
			//if (href.indexOf('nocheckbrowser') == -1)
				//location.replace(href.split('#')[0] + (href.indexOf('?') > -1 ? '&' : '?') + 'nocheckbrowser' + location.hash);
			
			// Hook into Gmail 2.0's GreaseMonkey APIs.
			var greaseReady = false;
			var greaseLoaded = false;
			var gmonkey, gmail;
			var initGrease = function(newGmonkey) {
				if (greaseReady || greaseLoaded) return;
				if (!newGmonkey.load) {
					setTimeout(function(){initGrease(newGmonkey)}, 100);
					return;
				}
				greaseReady = true;
				gmonkey = newGmonkey;
				gmonkey.load('1.0', function(GmailAPI) {
					greaseLoaded = true;
					gmail = GmailAPI;
					gmail.registerViewChangeCallback(function(view) {
						// Google bug: Browser sniffing makes Gmail not scroll to top automatically (303234)
						// Scrolling forces a repaint. Use a timeout to coalesce the repaint
						// with the paint for the new view.
						setTimeout(function() {
							var i = document.getElementById("canvas_frame").contentWindow.document.getElementsByTagName('input')[0];
							i.focus();
							i.blur();
						}, 0);
						//document.getElementById("canvas_frame").contentWindow.scrollTo(0,0);
					});
				});
			};
			window.addEventListener('load', function() {
				window.removeEventListener('load', arguments.callee, false);
				initGrease(js.gmonkey);
			}, false);
			
			// Opera bug: onunload should fire on top before firing on iframes (like FF/Safari) (301176)
			// Gmail reloads becuase it expects the other ordering...
			// Workaround: no reload
			//window.location.reload = function() { };
			
			// Google bug: documentElement doesn't fire scroll events in Kestrel...
			if (isKestrel) {
				window.addEventListener('DOMContentLoaded', function() {
					var canvasFrame = document.getElementById('canvas_frame');
					if (!canvasFrame || !canvasFrame.contentDocument) {
						setTimeout(arguments.callee, 10);
						return;
					}
					var contentDocument = canvasFrame.contentDocument;
					(function(addEventListener) {
						contentDocument.documentElement.addEventListener = function(evtType) {
							if (evtType == 'scroll') {
								return contentDocument.addEventListener.apply(contentDocument, arguments);
							}
							return addEventListener.apply(this, arguments);
						};
					})(contentDocument.documentElement.addEventListener);
					window.removeEventListener('DOMContentLoaded', arguments.callee, false);
				}, false);
			}
			
			// Opera bug: preventDefault doesn't work with keydown event. (234302)
			// This causes a line break to be left in the chat <textarea> after sending a message (by pressing [enter])
			// Workaround: clear out the <textarea> when it seems appropriate
			/*
			opera.addEventListener('AfterEvent.keydown', function(e) {
				var target = e.event.target;
				if (e.eventCancelled && target instanceof HTMLTextAreaElement && e.event.keyCode == 13) {
					if (target.value.replace(/\s/g,'') == '') {
						var timeout;
						var targetClear = function() {
							target.value = '';
							target.readOnly = false;
							target.removeEventListener('keyup', targetClear, false);
							clearTimeout(timeout);
						};
						target.readOnly = true;
						timeout = setTimeout(targetClear, 10);
						target.addEventListener('keyup', targetClear, false);
					}
				}
			},false);
			*/
			
      opera.addEventListener('AfterScript', function(e) {
      	if (window.globals) { 
      		globals.SHOW_BUILD_INFO = true;
      		opera.removeEventListener('AfterScript', arguments.callee, false);
      	}
 	    }, false);
		}
	} else { 
    // === CHILD FRAMES ===
    
    // Opera bug: Not possible to hide focus rect using CSS (298044)
    // Workaround: disable focus() on DIV :-P
    // (Disabled to test focus functionality once this is fixed...)
    // HTMLDivElement.prototype.focus = function() { };
    
    // Trying to fix scroll jumping when clicking things. Doesn't really work
    // for some reason.
    /*(function(focus) {
    	HTMLDivElement.prototype.focus = function() {
    		if (!this.ownerDocument || !this.ownerDocument.body)
    			return focus.apply(this, arguments);
    		var b = this.ownerDocument.body;
    		var x = b.scrollLeft, y = b.scrollTop;
    		var retVal = focus.apply(this, arguments);
    		x -= b.scrollLeft;
    		y -= b.scrollTop;
    		opera.postError(x + '\n' + y);
    		if (x || y || 1) this.ownerDocument.defaultView.scrollBy(x, y);
    		return retVal;
   	  };
   	})(HTMLDivElement.prototype.focus);*/
    
    // Make it possible to change links in the rich text editor.
    // Probably a timing issue with ordering of blur and mousedown events
    // across frames (needs analysis...)
    opera.addEventListener('BeforeEventListener.blur', function(e) {
      if (e.event.target instanceof HTMLDocument) {
        e.preventDefault();
      }
    }, false);
    opera.addEventListener('BeforeEventListener.keydown', function(e) {
    	if (e.event.ctrlKey && e.event.altKey) {
    		e.stopPropagation();
    	}
    }, false);
    if (window.frameElement && frameElement.id.indexOf('canvas_frame') > -1) {
      // === CANVAS FRAME ===
    
      // Opera bug: <html> or <body> with overflow:hidden too easy to scroll (300804)
     	top.document.body.style.position = 'fixed !important';
      if ('overflowY' in document.documentElement.style) {
        // Hide disabled horizontal scrollbar in Kestrel (Opera bug, 292597)
        styleEl.text += 'html { overflow-x: auto !important; overflow-y: auto !important; overflow: auto !important } body { overflow: visible !important; overflow-x: visible !important; overflow-y: visible !important }';
      } else {
        // Make scrollbars appear in Merlin
        // This makes a horizontal scrollbar too, but there's not much I can do to prevent it...:-(
        styleEl.text += 'html > body { overflow: auto !important; }';
      }
      
      
      // Opera bug: Fix page width if Gmail is loaded in an inactive tab
      // (TODO: Make some TCs and file this bug :-P)
      // Watch the loadingDiv to know when the canvas is completely rendered.
      var loadingDiv = top.document.getElementById('loading');
      var fixWidthTimeout = 0;
      var fixWidth = function() {
        // Don't fire the resize event if devtools are open,
        // because we don't want the resize event listener to reset
        // the entire DOM whenever the Gmail window is focused, making
        // the devtools DOM tree useless.
        if (!window.__registered) {
          var evt = document.createEvent('HTMLEvents');
          evt.initEvent('resize', true, true);
          window.dispatchEvent(evt);
        }
      };
      var focusListener = function() {
        top.removeEventListener('focus', focusListener, false);
        clearTimeout(fixWidthTimeout);
        fixWidthTimeout = setTimeout(fixWidth, 500);
      };
      var blurListener = function() {
        top.addEventListener('focus', focusListener, false);
        clearTimeout(fixWidthTimeout);
      };
      loadingDiv.addEventListener('DOMAttrModified', function(attrEvent) {
        if (attrEvent.attrName != 'style' || loadingDiv.style.display != 'none')
          return;
        loadingDiv.removeEventListener('DOMAttrModified', arguments.callee, false);
        fixWidth();
        top.addEventListener('focus', focusListener, false);
        top.addEventListener('blur', blurListener, false);
      }, false);
      
      // Google bug: Fix missing top border around messages (298593)
      // FIXED by Google at cl 5964091
      // styleEl.text += 'div[style="height:10px;display:"] { position: relative; }';
      
      // Opera bug: Incorrect documentElement.scrollHeight if height is specified for root element (300393 - Kestrel regression)
      //if (isKestrel) {
        //styleEl.text += 'html { width: auto !important; height: auto !important; }';
      //}
      
      if (isKestrel) {
      	// hide annoying focus highlighting
      	// this selector is complex, but the browser's selector engine can
      	// short circuit it out whenever ::selection doesn't match so
      	// it doesn't really slow anything down.
      	styleEl.text += '[hidefocus]:not([contenteditable]):focus::selection, [hidefocus]:not([contenteditable]):focus *::selection, [tabindex]:not([contenteditable]):focus::selection, [tabindex]:focus:not([contenteditable]) *::selection { background-color: transparent !important; color: currentColor !important }';
      }
      
      // Google bug: Fix label positioning. This is horribly broken on Safari 3
      // and Firefox 3 too...
      // Opera bug: Repainting after hovering over label name is messed up. Caused  by repaint bugs with inline-table (304347)
      // (Workaround: add a hidden outline. This workaround only works if scrolled all the way to top for some reason...)
      //styleEl.text += '\/*h1 > span + span { outline: 1px hidden white !important; }*\/ h1 > span + span > table { display: inline-table !important; vertical-align: top !important; }';
      // This selector is faster than the older one:
      styleEl.text += 'h1 table { vertical-align: top !important; display: inline-table !important }';
    } else if (location.pathname.indexOf('/ContactManager') > -1) {
      // === CONTACTS MANAGER ===
      
      // Google bug: Make names in contact manager visible (294911)
      // ...has been fixed by Google, revealing:
      // Opera bug: floats shouldn't provide opportunity for linebreaking 
      // (314479) 
      styleEl.text += '.checkable-list .row .check { display: inline !important; float: none !important }';
      
      // Opera bug: onload/onerror don't fire on images dynamically added to display:none parent (304786)
      // without this workaround, contact images won't appear in the contact manager.
      styleEl.text += '.contact-pane img#ContactPicture-present[style*="display: none"], .contact-pane img#ContactPicture-missing[style*="display: none"] { display: inline !important; }';
      
      // Opera uses a different offsetParent (XXX analyze)
      styleEl.text += '.contact-pane { position: relative; width: auto !important }';
      
      // Opera bug: repaint issue when hovering contact image (needs analysis)
      //styleEl.text += '#contact-picture > div > div { outline: 0px solid white; }';
      
      // Fix contact manager width (needs analysis...)
      // Google's standards-mode WebKit viewport size calculation seems to work fine in Opera 9.5 Quirks Mode. :-P
      /*navigator.userAgent = 'Opera, spoofing as AppleWebKit/523.12';
      opera.addEventListener('BeforeScript',function () {
        window.opera = undefined;
      }, false);
      document.compatMode = 'CSS1Compat';*/
      //styleEl.text += '#ContactManager { height: 100% !important; overflow: hidden !important; }';
      // Merlin doesn't support overflow-x, overflow-y
      if (!('overflowX' in document.documentElement.style)) {
        styleEl.text += '.checkable-list, .group-list, .contact-pane { overflow: auto !important; }';
      }
      
      // Google bug: Contact manager expects setting style.height to null to 
      // remove the height property. This does not follow the typical
      // type conversion behaviors of Javascript.
      // (invalid Opera bug: 318289)
      if (isKestrel) {
      	CSSStyleDeclaration.prototype.__defineGetter__('height', function() {
					var v = this.getPropertyValue('height');
					var i = this.getPropertyPriority('height');
					if (i) v += ' !' + i;
					return v;
      	});
      	CSSStyleDeclaration.prototype.__defineSetter__('height', function(v) {
      		if (v == null) v = '';
      		v = '' + v;
      		var i = '';
					if (v.indexOf('!important') + 1) {
						i = 'important';
						v = v.split('!')[0];
					}
					this.setProperty('height', v, i);
      	});
      }
    }
  }
})();

// Performance profiling: Logs event handler executions that take more than 1 second. (thanks fearphage)
(function(opera) {
  var re = /(?:After|Before)EventListener/;
  currentEvents = {};
  opera.addEventListener('BeforeEventListener', function(e) {
    if (!re.test(e.event.type)) {
      currentEvents[e.event.type] = {
      	date: new Date(),
      	reflowCount: opera.reflowCount
      };
    }
  },false);
  var mouseEventFix = function(e) {
		var actualTarget = document.elementFromPoint(e.event.clientX + pageXOffset, e.event.clientY + pageYOffset);
		actualTarget = (actualTarget instanceof Text) ? actualTarget.parentNode : actualTarget;
		if (e.event.target != actualTarget) {
			e.preventDefault();
		}
	};
  opera.addEventListener('BeforeEvent.mousedown', mouseEventFix, false);
  opera.addEventListener('BeforeEvent.mouseup', mouseEventFix, false);
  opera.addEventListener('BeforeEvent.click', mouseEventFix, false);
  // opera.addEventListener('BeforeEventListener.mousemove', mouseEventFix, false);
	
  opera.addEventListener('AfterEventListener', function(e) {
    var type = e.event.type, time;
    //                        v-- assignment statement!
    if (!re.test(type) && (e2 = currentEvents[type])) {
      time = new Date() - e2.date;
      reflows = opera.reflowCount - e2.reflowCount;
      if (time > 1000 || reflows > 5) {
        opera.postError(type + ': ' + time + 'ms, ' + reflows + ' reflows');
      }
      delete currentEvents[type];
    }
  }, false);
})(window.opera);

// For debugging
if (top.location.search.indexOf('likeGecko') > -1) {
  window.opera = undefined; 
  navigator.product = 'Gecko';
  navigator.userAgent = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11';
}
if (top.location.search.indexOf('likeNothing') > -1) {
  window.opera = undefined;
}