PageRenderTime 42ms CodeModel.GetById 11ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/fix-gmail2.js

http://gmail2-for-opera.googlecode.com/
JavaScript | 765 lines | 386 code | 64 blank | 315 comment | 99 complexity | 59480364123a69f10a7cbc8a7a0d7728 MD5 | raw file
  1// ==UserScript==
  2// @name Gmail 2.0 fixes for Opera
  3// @author dbloom (special thanks: Ayush, diz^X, fearphage, xErath)
  4// @include http://mail.google.com/mail/*ui=2*
  5// @include https://mail.google.com/mail/*ui=2*
  6// @include http://mail.google.com/mail/*ui=1*
  7// @include https://mail.google.com/mail/*ui=1*
  8// @include http://mail.google.com/mail/contacts/ui/ContactManager*
  9// @include https://mail.google.com/mail/contacts/ui/ContactManager*
 10// @include http://mail.google.com/hosted/*.*/*ui=2*
 11// @include https://mail.google.com/hosted/*.*/*ui=2*
 12// @include http://mail.google.com/hosted/*.*/*ui=1*
 13// @include https://mail.google.com/hosted/*.*/*ui=1*
 14// @include http://mail.google.com/hosted/*.*/contacts/ui/ContactManager*
 15// @include https://mail.google.com/hosted/*.*/contacts/ui/ContactManager*
 16// @include http://mail.google.com/a/*.*/*ui=2*
 17// @include https://mail.google.com/a/*.*/*ui=2*
 18// @include http://mail.google.com/a/*.*/*ui=1*
 19// @include https://mail.google.com/a/*.*/*ui=1*
 20// @include http://mail.google.com/a/*.*/contacts/ui/ContactManager*
 21// @include https://mail.google.com/a/*.*/contacts/ui/ContactManager*
 22// @include https://www.google.com/*/ServiceLogin*
 23// ==/UserScript==
 24
 25// 27-11-2007  5:50PM  Added version numbering,
 26//                     fixed overflow issue with chat conversations in Merlin
 27//             6:05PM  Actually fixed it :P
 28// 28-11-2007 10:23AM  Applied the getBoundingClientRect patch to all frames.
 29//                     This fixes the "Options" menu on chats...
 30//                     Also fixed rich text editor.
 31// 28-11-2007  2:00PM  Fixed font size in rich text editor.
 32//             5:13PM  Fixed a Merlin syntax error caused by regex_cm_1
 33// 30-11-2007  4:33PM  Fixed bug that broke chat popout windows
 34// 01-12-2007 10:38AM  Removed fix for top border on messages because
 35//                     Google fixed it. Code cleanup.
 36//                     Reduced UA string for Safari spoofing on contact mgr.
 37//            12:57PM  Fixed "sign out"/"older version" links
 38//             3:33PM  Prevent scrolling from canvas frame from scrolling
 39//                     parent frame
 40//                     "Next sender" thing at bottom right corner now updates
 41//                     as you scroll in conversations.
 42// 02-12-2007  7:44PM  Fixed regression that caused scrolling to not work on
 43//                     "show original", etc view.
 44//             9:54PM  Detect script source view/etc using document.compatMode,
 45//                     to prevent the script from running when viewing Gmail's
 46//                     JS and such.
 47// 04-12-2007  9:43AM  Fixed adding labels to messages
 48//            10:48AM  Fixed UNIX regression caused by previous fix
 49// 05-12-2007  7:10AM  The UNIX fix didn't actually work. Disabled <wbr>/&shy;
 50//                     in UNIX - unfortunately, this means long words won't
 51//                     wrap :-(
 52// 06-12-2007  9:45PM  Added "Use new version" checkbox to login page :-)
 53// 08-12-2007 11:25AM  Fixed "highlight" feature in rich text editor
 54//                     Improved "quote" feature in rich text editor
 55//                       (this fix has a few bugs...:-P)
 56//                     Fixed <wbr> in Kestrel on UNIX (still
 57//                       unfixed in Merlin on UNIX)
 58// 14-12-2007 10:13AM  Hide getBoundingClientRect on builds where it is broken
 59//                     Code cleanup
 60// 16-12-2007 11:15AM  Moved to Google Code
 61//                     Minor code cleanup and tweaks
 62// 16-12-2007 10:27PM  Fixed scroll to top when opening messages
 63//                     Fixed label display next to conversation subject heading
 64// 27-12-2007  8:26PM  Hello from Oslo :-)
 65//                     Used Greasemonkey API to improve scroll to top behavior
 66//                     Removed getBoundingClientRect fixes because they are no
 67//                     longer needed in the latest snapshots.
 68//                     Fixed bug that left a newline char in <textarea> after
 69//                     sending chat message
 70//                     Contact pictures now appear in contact manager
 71// 29-12-2007  9:03PM  No more horizontal scrollbar!!!
 72// 30-12-2007 10:15AM  Disabled getBoundingClientRect in some builds I forgot to
 73//                     disable it for...
 74// 25-01-2008  4:10PM  Updated to work with some changes Google made to IFRAMEs
 75// 26-01-2008  1:40PM  Fixed rich text editor, chat (again)
 76// 30-01-2008  8:30AM  Fixed duplicate label creation (again)
 77// 14-02-2008 10:02AM  Google has fixed some Opera bugs, script updated
 78//                     accordingly :-D
 79// 06-04-2008  7:04PM  Fixed disabled horizontal scrollbar
 80//                     Attempted to fix many random, tough to reproduce JS
 81//                       errors by blocking queued mouse events if their target
 82//                       is no longer in the same spot as before (this matches
 83//                       the behavior of other browsers)
 84//                     Optimized scroll to top behavior to prevent extra
 85//                       repaint
 86//                     Simplified selectors used in CSS patches for better
 87//                       performance
 88//                     Enabled "Newer version" link in ?ui=1
 89//                     Temporarily override String.prototype.indexOf to prevent
 90//                       actually needing "&nocheckbrowser" in the URL
 91//                     Track reflow count in the performance debugging log
 92//                     Hide text selections caused by focus
 93//                     Fix names on contact manager - Google fixed their code
 94//                       to make it standards compliant, which worked in Firefox
 95//                       3 but also revealed a new Opera bug :-P
 96//                     Fixed include's so that the user JS supports Google Apps
 97//                       For Your Domain's Gmail service
 98
 99(function gmail2_user_js(opera, doc) {
100  
101  if (location.search) {
102    var s = location.search + '&';
103    if (s.indexOf('?ui=1&') + s.indexOf('&ui=1&') > -2) {
104      // The best way to get the "Newer version" link is to mask as another
105      // browser inside of the JS frame on Gmail 1.0. There is no WebKit or
106      // Opera-specific code here, and masking as Gecko would require spoofing
107      // navigator.product, and masking as IE7 is risky. So we mask as WebKit.
108      if (window.name == 'js')
109        navigator.userAgent = 'Mozilla/5.0 (' + navigator.platform + '; U; en-us) AppleWebKit/525.13 (┼╝pera, like KHTML) Version/3.1 Damer/525.13';
110      return;
111    }
112  }
113  
114  if (top == self) {
115    // Make Gmail 2.0 think that "nocheckbrowser" is in location.href
116    (function(indexOf) {
117      String.prototype.indexOf = function(k) {
118        if (k == 'nocheckbrowser' && this == location.href) {
119          // Restore the original indexOf for performance reasons
120          String.prototype.indexOf = indexOf;
121          return 1337;
122        }
123        return indexOf.apply(this, arguments);
124      };
125    })(String.prototype.indexOf);
126  }
127  
128  // Make a <style> element to provide CSS fixes as needed
129  var headEl = document.getElementsByTagName('head')[0];
130  if (headEl) {
131    var styleEl = document.createElement('style');
132    headEl.appendChild(styleEl);
133    headEl = null;
134  }
135  
136  // True if the version number starts with 9.5, or has 2 or more digits before the first decimal point.
137  // (parseFloat/etc is risky because Opera versions can have more than 1 decimal point...)
138  var isKestrel = (opera.version().indexOf('.') > 1 || opera.version().indexOf('9.5') == 0);
139  
140  if (location.pathname.indexOf('/ServiceLogin') > -1) {
141    document.addEventListener('DOMContentLoaded'
142      ,function() {
143        var doc = document, continueUri;
144        
145        // No new UI for Google Reader yet. ;-P
146        if (!doc.getElementById('PersistentCookie') || !doc.getElementById('continue') || 
147           ((continueUri = doc.getElementById('continue').value).indexOf('/mail') == -1))
148          return;
149        
150        var referenceRow = doc.getElementById('PersistentCookie').parentNode.parentNode
151            ,ui2TR = doc.createElement('tr')
152            ,tdCheckUi2 = ui2TR.appendChild(doc.createElement('td'));
153        tdCheckUi2.setAttribute('align', 'right');
154        
155        var checkUi2 = tdCheckUi2.appendChild(doc.createElement('input'));
156        checkUi2.setAttribute('type', 'checkbox');
157        checkUi2.setAttribute('id', 'ui2');
158        checkUi2.checked = (doc.cookie.indexOf('ui2=yes') > -1);
159        
160        var tdLabelUi2 = ui2TR.appendChild(doc.createElement('td'))
161            ,labelUi2 = tdLabelUi2.appendChild(doc.createElement('label'));
162        labelUi2.setAttribute('class', 'gaia le lbl');
163        labelUi2.setAttribute('for', 'ui2');
164        labelUi2.appendChild(doc.createTextNode('Use new version'));
165        
166        referenceRow.parentNode.insertBefore(ui2TR, referenceRow);
167        
168        checkUi2.form.addEventListener('submit'
169          ,function() {
170            var newUri;
171            if (document.getElementById('ui2').checked) {
172              // By 2027, Opera will be leading the browser market so this script will no longer be needed anyway =)
173              document.cookie = 'ui2=yes; expires=Wed, Dec 1, 2027 3:25:48 PM; path=/';
174              newUri = continueUri.replace('ui=1', 'ui=2');
175              if (newUri.indexOf('ui=2') == -1) {
176                var splitByHash = newUri.split('#'), base = splitByHash.shift() + (newUri.indexOf('?') > -1 ? '&' : '?') + 'ui=2';
177                splitByHash.unshift(base);
178                newUri = splitByHash.join('#');
179              }
180              document.getElementById('continue').value = newUri;
181            }
182            else {
183              // We don't really need this cookie to last because "new version" is not checked by default.
184              // So, we let this cookie expire immediately by using a date in the past for expires.
185              document.cookie = 'ui2=no; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
186              if (continueUri.indexOf('disablechatbrowsercheck') == -1)
187                document.getElementById('continue').value += (continueUri.indexOf('?') > -1 ? '&' : '?') + 'disablechatbrowsercheck';
188            }
189          }
190          ,false
191        );
192      }
193      ,false
194    );
195    return;
196  }
197  
198  // Opera bug: getBoundingClientRect horribly broken in some builds
199  switch (navigator.platform.substr(0,3) + opera.buildNumber()) {
200    case 'Mac4579': case 'Win9694': case 'Lin1719':
201    case 'Mac4591': case 'Win9716': case 'Lin1729':
202      delete Element.prototype.getBoundingClientRect;
203  }
204
205  // Make sure we aren't in a chat popout window (&view=cw)
206  if ((location.search.indexOf('&view=cw') == -1) &&
207      (window.top === window.self) ||
208      window.frameElement && (window.frameElement.name == 'js')) {
209    /*
210    // Opera bug: Script in parent frame called from an IFRAME terminates if IFRAME is removed (298005)
211    // Workaround: delay removal of IFRAMEs.
212    var delay = 10000;
213    (function(removeChild) {
214      // Removes scripts so that any unexecuted <script>s don't run after
215      // the frame they are in is removed.
216      var removeScripts = function(doc) {
217        var elements, i;
218        elements = doc.getElementsByTagName('iframe');
219        i = elements.length;
220        if (i--) do {
221          removeScripts(elements[i].contentDocument);
222        } while (i--);
223        elements = doc.getElementsByTagName('script');
224        i = elements.length;
225        if (i--) do {
226          removeChild.call(elements[i].parentNode, elements[i]);
227        } while (i--);
228      };
229      Element.prototype.removeChild = function(child) {
230        if (!(child instanceof HTMLIFrameElement))
231          return removeChild.apply(this, arguments);
232        var parent = this;
233        var timeout;
234        // If the iframe is reused, cancel the timeout!
235        var reinsertListener = function() {
236          clearTimeout(timeout);
237          child.removeEventListener('DOMNodeInsertedIntoDocument', reinsertListener, false);          
238        };
239        child.addEventListener('DOMNodeInsertedIntoDocument', reinsertListener, false);
240        timeout = setTimeout(function() {
241          if (child && child.parentNode === parent) {
242            child.removeEventListener('DOMNodeInsertedIntoDocument', reinsertListener, false);  
243            removeChild.call(parent, child);
244          }
245        }, delay);
246        removeScripts(child.contentDocument);
247        child.contentDocument.close();
248      };
249    })(Element.prototype.removeChild);*/
250  
251    
252    // Opera bug: XMLHttpRequest not continuously firing onreadystatechange and updating responseText on data receive (299926)
253    // Special thanks to diz^X for debugging chat and finding this problem!
254    // Workaround: Poll responseText and fire onreadystatechange as needed
255    (function(send) {
256      // We replace onreadystatechange with an instance of this wrapper.
257      // This way, we can use instanceof later to determine if
258      // onreadystatechange is already wrapped.
259      var WrappedReadyStateChange = function(xhrInstance) {
260        var o = xhrInstance.onreadystatechange;
261        var rText = '';
262        var interval = null;
263        return function() {
264          if (xhrInstance.readyState == 3 && xhrInstance.status == 200) {
265            rText = xhrInstance.responseText;
266            o.apply(this, arguments);
267            if (interval === null) {
268              var t = this, a = arguments;
269              interval = setInterval(function() {
270                var success;
271                try {
272                  if (xhrInstance.readyState == 3) {
273                    if (xhrInstance.responseText.length != rText.length) {
274                      rText = xhrInstance.responseText;
275                      o.apply(t, a);
276                    }
277                    success = true;
278                  }
279                } finally {
280                  if (!success) {
281                    clearInterval(interval);
282                    interval = null;
283                    rText = '';
284                  }
285                }
286              }, 250);
287            }
288          } else {
289            if (interval !== null) {
290              clearInterval(interval);
291              interval = null;
292            }
293            rText = '';
294            if (xhrInstance.readyState == 4) {
295              xhrInstance.onreadystatechange = o;
296            }
297            o.apply(this, arguments);
298          }
299        };
300      };
301      XMLHttpRequest.prototype.send = function() {
302        if (this.onreadystatechange && !(this.onreadystatechange instanceof WrappedReadyStateChange)) {
303          var o = this.onreadystatechange;
304          this.onreadystatechange = new WrappedReadyStateChange(this);
305          var success;
306          try {
307            var retVal = send.apply(this, arguments);
308            success = true;
309          } finally {
310            if (!success) {
311              this.onreadystatechange = o;
312            }
313          }
314          return retVal;
315        }
316        if (interval !== null) clearInterval(interval);
317        interval = null;
318        rText = '';
319        return send.apply(this, arguments);
320      };
321    })(XMLHttpRequest.prototype.send);
322    
323    opera.addEventListener('BeforeScript',function (e) {
324      if (e.element.src.indexOf('&name=bjs&') > -1) {
325        opera.removeEventListener('BeforeScript', arguments.callee, false);
326        
327        // Opera bug: IFRAME script thread changing top.location.href to a fragment identifier causes a reload (298627)
328        // Workaround: change top.location.hash instead
329        // (thanks xErath)
330        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');
331      }
332      
333      // Google bug: Using &shy; in Opera breaks adding labels to messages (301574)
334      /*
335      var replacement;
336      if (isKestrel) {
337        replacement = '<wbr style=\\\"white-space: nowrap;\\\" \\\/>';
338      } else if (navigator.platform.indexOf('Win') == navigator.platform.indexOf('Mac')) {
339        // *nix
340        // replacement = '"<wbr style=\\\"position:absolute;width:0px;height:0px;overflow:hidden;content:&quot;\\\\00200B&quot;\\\">"';
341        replacement = ''; // TODO: Find a good <wbr> substitute that works on Linux...:-P
342      } else {
343        // Win/Mac
344        replacement = '<wbr style=\\\"content:&quot;\\\\00200B&quot;\\\">';
345      }
346      e.element.text = e.element.text.replace('&shy;', replacement);
347      */
348    },false);
349    
350    // Better fix using GreaseMonkey API is now implemented.
351    /*
352    opera.addEventListener('BeforeScript',function (e) {
353      if (e.element.src.indexOf('&name=js&') + 1) {
354        opera.removeEventListener('BeforeScript', arguments.callee, false);
355        // Google bug: Browser sniffing makes Gmail not scroll to top automatically (303234)
356        // Workaround: Activate the Safari-specific code for this.
357        //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();}}');
358      }
359    }, false);
360    */
361
362    (function(originalEval) {
363      
364      // Some general-purpose code for unborking things
365      function Fixes() {
366        this.fixes = [];
367      };
368      Fixes.prototype.addFix = function(var_args) {
369        var fixes = Array.prototype.slice.call(arguments);
370        do { this.fixes.push(fixes.pop()) } while (fixes.length);
371      };
372      Fixes.prototype.unbork = function(str) {
373        var fix, fixIndex = this.fixes.length;
374        if (!fixIndex--) return str;
375        do {
376          fix = this.fixes[fixIndex];
377          if (fix.canUnbork(str)) {
378            str = fix.unbork(str);
379            this.fixes.splice(fixIndex, 1);
380            return str;
381          }
382        } while (fixIndex--);
383        return str;
384      };
385      Fixes.prototype.isEmpty = function() {
386        return this.fixes.length == 0;
387      }
388      
389      function Fix(substr) {
390        this.substr = substr;
391        this.unborkers = [];
392      };
393      Fix.prototype.addUnborker = function(var_args) {
394        var unborkers = Array.prototype.slice.call(arguments);
395        do { this.unborkers.push(unborkers.pop()) } while (unborkers.length);
396      };
397      Fix.prototype.canUnbork = function(str) {
398        // without "str.indexOf && ", chat breaks because it likes to pass
399        // non-string things to eval for some reason.
400        return str.indexOf && str.indexOf(this.substr) != -1;
401      };
402      Fix.prototype.unbork = function(str) {
403        var unbork, unborkerIndex = this.unborkers.length;
404        if (!unborkerIndex--) return str;
405        do {
406          unbork = this.unborkers[unborkerIndex];
407          str = unbork(str);
408        } while (unborkerIndex--);
409        return str;
410      };
411      
412      function Unborker(find, replace) {
413        return function(txt) {
414          return txt.replace(find, replace);
415        };
416      };
417      
418      // Override browser blocking on rich text editor
419      // The regex matches a string like this:
420      // return Cdp&&Cqp(IXa)>=0||Cep&&Cqp(JXa)>=0||Cmp&&Cqp(KXa)>=0}
421      // (...which basically tests for Moz 1.8+, IE 6+, or WebKit 520+)
422      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');
423      
424      // Google bug: Opera uses IE-compatible font-sizes in quirksmode
425      // This workaround creates a rule that has more specificity than
426      // the .tr-field rule, instead of using !important, because using
427      // !important would override inline font styles on the editable text.
428      var fixRichTextFontSize = new Unborker(/\.tr-field\{/, 'html > body.tr-field{font-size:x-small;}.tr-field{');
429      
430      // Google bug: WTF... getSelection().parentElement()???
431      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');
432
433      // Google bug: highlight uses IE codepath...hiliteColor, not backColor...
434      var fixHilite = new Unborker(/backColor/, 'hiliteColor');
435      
436      // Google bug: quote uses IE codepath
437      // This workaround isn't "quite right" but it is usable...
438      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))');
439      
440      var cmFix = new Fix('_MD_Before(\'cm\')');
441      cmFix.addUnborker(fixRichTextBrowserBlocking, fixRichTextFontSize);
442      
443      var eFix = new Fix('_MD_Before(\'e\')');
444      eFix.addUnborker(fixGetSelectionParentElement, fixHilite, fixQuote);
445      
446      var evalFixes = new Fixes();
447      evalFixes.addFix(cmFix, eFix);
448      
449      window.eval = function(str) {
450        str = evalFixes.unbork(str);
451        
452        if (evalFixes.isEmpty()) {
453          // Just in case window.eval was wrapped again by another user JS...
454          if (window.eval === arguments.callee) {
455            // Okay, we can safely restore eval.
456            window.eval = originalEval;
457          }
458        }
459        
460        return originalEval.call(this, str);
461      };
462    })(window.eval);
463
464    
465    // Prevent Gmail from throwing errors on resize --
466    // this bug needs further analysis.
467    // (The IE codepath delays calls to dispatchEvent("sizechange"),
468    // and that workaround seems to work for Opera too.)
469    /*opera.addEventListener('BeforeScript', function(e) {
470      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)))');
471    }, false);*/
472    
473    // Fix chat - Merlin doesn't support overflow-x, overflow-y
474    if (!('overflowX' in document.documentElement.style)) {
475      styleEl.text += 'div { overflow: auto !important; } div span {  margin-left: 0 !important; }';
476    }
477    if (window.top === window.self) {
478      // === TOP FRAME ===
479     
480      // Don't break viewing of script source, etc...
481      if (document.compatMode == 'BackCompat') {
482        return;
483      }
484      
485      // Override browser blocking
486      var href = location.href;
487      //if (href.indexOf('nocheckbrowser') == -1)
488        //location.replace(href.split('#')[0] + (href.indexOf('?') > -1 ? '&' : '?') + 'nocheckbrowser' + location.hash);
489      
490      // Hook into Gmail 2.0's GreaseMonkey APIs.
491      var greaseReady = false;
492      var greaseLoaded = false;
493      var gmonkey, gmail;
494      var initGrease = function(newGmonkey) {
495        if (greaseReady || greaseLoaded) return;
496        if (!newGmonkey.load) {
497          setTimeout(function(){initGrease(newGmonkey)}, 100);
498          return;
499        }
500        greaseReady = true;
501        gmonkey = newGmonkey;
502        gmonkey.load('1.0', function(GmailAPI) {
503          greaseLoaded = true;
504          gmail = GmailAPI;
505          gmail.registerViewChangeCallback(function(view) {
506            // Google bug: Browser sniffing makes Gmail not scroll to top automatically (303234)
507            // Scrolling forces a repaint. Use a timeout to coalesce the repaint
508            // with the paint for the new view.
509            setTimeout(function() {
510              var i = document.getElementById("canvas_frame").contentWindow.document.getElementsByTagName('input')[0];
511              i.focus();
512              i.blur();
513            }, 0);
514            //document.getElementById("canvas_frame").contentWindow.scrollTo(0,0);
515          });
516        });
517      };
518      window.addEventListener('load', function() {
519        window.removeEventListener('load', arguments.callee, false);
520        initGrease(js.gmonkey);
521      }, false);
522      
523      // Opera bug: onunload should fire on top before firing on iframes (like FF/Safari) (301176)
524      // Gmail reloads becuase it expects the other ordering...
525      // Workaround: no reload
526      //window.location.reload = function() { };
527      
528      // Google bug: documentElement doesn't fire scroll events in Kestrel...
529      if (isKestrel) {
530        window.addEventListener('DOMContentLoaded', function() {
531          var canvasFrame = document.getElementById('canvas_frame');
532          if (!canvasFrame || !canvasFrame.contentDocument) {
533            setTimeout(arguments.callee, 10);
534            return;
535          }
536          var contentDocument = canvasFrame.contentDocument;
537          (function(addEventListener) {
538            contentDocument.documentElement.addEventListener = function(evtType) {
539              if (evtType == 'scroll') {
540                return contentDocument.addEventListener.apply(contentDocument, arguments);
541              }
542              return addEventListener.apply(this, arguments);
543            };
544          })(contentDocument.documentElement.addEventListener);
545          window.removeEventListener('DOMContentLoaded', arguments.callee, false);
546        }, false);
547      }
548      
549      // Opera bug: preventDefault doesn't work with keydown event. (234302)
550      // This causes a line break to be left in the chat <textarea> after sending a message (by pressing [enter])
551      // Workaround: clear out the <textarea> when it seems appropriate
552      /*
553      opera.addEventListener('AfterEvent.keydown', function(e) {
554        var target = e.event.target;
555        if (e.eventCancelled && target instanceof HTMLTextAreaElement && e.event.keyCode == 13) {
556          if (target.value.replace(/\s/g,'') == '') {
557            var timeout;
558            var targetClear = function() {
559              target.value = '';
560              target.readOnly = false;
561              target.removeEventListener('keyup', targetClear, false);
562              clearTimeout(timeout);
563            };
564            target.readOnly = true;
565            timeout = setTimeout(targetClear, 10);
566            target.addEventListener('keyup', targetClear, false);
567          }
568        }
569      },false);
570      */
571    }
572  } else {  
573    // === CHILD FRAMES ===
574    
575    // Opera bug: Not possible to hide focus rect using CSS (298044)
576    // Workaround: disable focus() on DIV :-P
577    // (Disabled to test focus functionality once this is fixed...)
578    // HTMLDivElement.prototype.focus = function() { };
579    
580    // Trying to fix scroll jumping when clicking things. Doesn't really work
581    // for some reason.
582    /*(function(focus) {
583      HTMLDivElement.prototype.focus = function() {
584        if (!this.ownerDocument || !this.ownerDocument.body)
585          return focus.apply(this, arguments);
586        var b = this.ownerDocument.body;
587        var x = b.scrollLeft, y = b.scrollTop;
588        var retVal = focus.apply(this, arguments);
589        x -= b.scrollLeft;
590        y -= b.scrollTop;
591        opera.postError(x + '\n' + y);
592        if (x || y || 1) this.ownerDocument.defaultView.scrollBy(x, y);
593        return retVal;
594       };
595     })(HTMLDivElement.prototype.focus);*/
596    
597    // Make it possible to change links in the rich text editor.
598    // Probably a timing issue with ordering of blur and mousedown events
599    // across frames (needs analysis...)
600    opera.addEventListener('BeforeEventListener.blur', function(e) {
601      if (e.event.target instanceof HTMLDocument) {
602        e.preventDefault();
603      }
604    }, false);
605    opera.addEventListener('BeforeEventListener.keydown', function(e) {
606      if (e.event.ctrlKey && e.event.altKey) {
607        e.stopPropagation();
608      }
609    }, false);
610    if (window.frameElement && frameElement.id.indexOf('canvas_frame') > -1) {
611      // === CANVAS FRAME ===
612    
613      // Opera bug: <html> or <body> with overflow:hidden too easy to scroll (300804)
614       top.document.body.style.position = 'fixed !important';
615      
616      if ('overflowY' in document.documentElement.style) {
617        // Hide disabled horizontal scrollbar in Kestrel (Opera bug, 292597)
618        styleEl.text += 'html { overflow-x: auto !important; overflow-y: auto !important } body { overflow: auto !important }';
619      } else {
620        // Make scrollbars appear in Merlin
621        // This makes a horizontal scrollbar too, but there's not much I can do to prevent it...:-(
622        styleEl.text += 'html > body { overflow: auto !important; }';
623      }
624      
625      
626      // Opera bug: Fix page width if Gmail is loaded in an inactive tab
627      // (TODO: Make some TCs and file this bug :-P)
628      // Watch the loadingDiv to know when the canvas is completely rendered.
629      var loadingDiv = top.document.getElementById('loading');
630      var fixWidthTimeout = 0;
631      var fixWidth = function() {
632        // Don't fire the resize event if devtools are open,
633        // because we don't want the resize event listener to reset
634        // the entire DOM whenever the Gmail window is focused, making
635        // the devtools DOM tree useless.
636        if (!window.__registered) {
637          var evt = document.createEvent('HTMLEvents');
638          evt.initEvent('resize', true, true);
639          window.dispatchEvent(evt);
640        }
641      };
642      var focusListener = function() {
643        top.removeEventListener('focus', focusListener, false);
644        clearTimeout(fixWidthTimeout);
645        fixWidthTimeout = setTimeout(fixWidth, 500);
646      };
647      var blurListener = function() {
648        top.addEventListener('focus', focusListener, false);
649        clearTimeout(fixWidthTimeout);
650      };
651      loadingDiv.addEventListener('DOMAttrModified', function(attrEvent) {
652        if (attrEvent.attrName != 'style' || loadingDiv.style.display != 'none')
653          return;
654        loadingDiv.removeEventListener('DOMAttrModified', arguments.callee, false);
655        fixWidth();
656        top.addEventListener('focus', focusListener, false);
657        top.addEventListener('blur', blurListener, false);
658      }, false);
659      
660      // Google bug: Fix missing top border around messages (298593)
661      // FIXED by Google at cl 5964091
662      // styleEl.text += 'div[style="height:10px;display:"] { position: relative; }';
663      
664      // Opera bug: Incorrect documentElement.scrollHeight if height is specified for root element (300393 - Kestrel regression)
665      if (isKestrel) {
666        styleEl.text += 'html { width: auto !important; height: auto !important; }';
667      }
668      
669      if (isKestrel) {
670        // hide annoying focus highlighting
671        // this selector is complex, but the browser's selector engine can
672        // short circuit it out whenever ::selection doesn't match so
673        // it doesn't really slow anything down.
674        styleEl.text += 'div[hidefocus]:not([contenteditable]):focus::selection, div[hidefocus]:not([contenteditable]):focus *::selection, div[tabindex]:not([contenteditable]):focus::selection, div[tabindex]:focus:not([contenteditable]) *::selection { background-color: transparent !important }';
675      }
676      
677      // Google bug: Fix label positioning. This is horribly broken on Safari 3
678      // and Firefox 3 too...
679      // Opera bug: Repainting after hovering over label name is messed up. Caused  by repaint bugs with inline-table (304347)
680      // (Workaround: add a hidden outline. This workaround only works if scrolled all the way to top for some reason...)
681      //styleEl.text += '\/*h1 > span + span { outline: 1px hidden white !important; }*\/ h1 > span + span > table { display: inline-table !important; vertical-align: top !important; }';
682      // This selector is faster than the older one:
683      styleEl.text += 'h1 table { vertical-align: top !important; display: inline-table !important }';
684    } else if (location.pathname.indexOf('/ContactManager') > -1) {
685      // === CONTACTS MANAGER ===
686      
687      // Google bug: Make names in contact manager visible (294911)
688      // ...has been fixed by Google, revealing:
689      // Opera bug: floats shouldn't provide opportunity for linebreaking 
690      // (314479) 
691      styleEl.text += '.checkable-list .row .check { display: inline !important; float: none !important }';
692      
693      // Opera bug: onload/onerror don't fire on images dynamically added to display:none parent (304786)
694      // without this workaround, contact images won't appear in the contact manager.
695      styleEl.text += '.contact-pane img[onload][style*="display: none"] { display: inline !important; }';
696      
697      // horizontal scrolling issue when hovering contact image (needs analysis)
698      styleEl.text += '.contact-pane { overflow-x: hidden !important; }';
699      
700      // Opera bug: repaint issue when hovering contact image (needs analysis)
701      styleEl.text += '#contact-picture > div > div { outline: 0px solid white; }';
702      
703      // Fix contact manager width (needs analysis...)
704      // Google's standards-mode WebKit viewport size calculation seems to work fine in Opera 9.5 Quirks Mode. :-P
705      navigator.userAgent = 'Opera, spoofing as AppleWebKit/523.12';
706      opera.addEventListener('BeforeScript',function () {
707        window.opera = undefined;
708      }, false);
709      document.compatMode = 'CSS1Compat';
710      styleEl.text += '#ContactManager { height: 100% !important; overflow: hidden !important; }';
711      // Merlin doesn't support overflow-x, overflow-y
712      if (!('overflowX' in document.documentElement.style)) {
713        styleEl.text += '.checkable-list, .group-list, .contact-pane { overflow: auto !important; }';
714      }
715    }
716  }
717})(window.opera, window.document);
718
719// Performance profiling: Logs event handler executions that take more than 1 second. (thanks fearphage)
720(function(opera) {
721  var re = /(?:After|Before)EventListener/;
722  currentEvents = {};
723  opera.addEventListener('BeforeEventListener', function(e) {
724    if (!re.test(e.event.type)) {
725      currentEvents[e.event.type] = {
726        date: new Date(),
727        reflowCount: opera.reflowCount
728      };
729    }
730  },false);
731  var mouseEventFix = function(e) {
732    var actualTarget = document.elementFromPoint(e.event.clientX + pageXOffset, e.event.clientY + pageYOffset);
733    actualTarget = (actualTarget instanceof Text) ? actualTarget.parentNode : actualTarget;
734    if (e.event.target != actualTarget) {
735      e.preventDefault();
736    }
737  };
738  opera.addEventListener('BeforeEvent.mousedown', mouseEventFix, false);
739  opera.addEventListener('BeforeEvent.mouseup', mouseEventFix, false);
740  opera.addEventListener('BeforeEvent.click', mouseEventFix, false);
741  // opera.addEventListener('BeforeEventListener.mousemove', mouseEventFix, false);
742  
743  opera.addEventListener('AfterEventListener', function(e) {
744    var type = e.event.type, time;
745    //                        v-- assignment statement!
746    if (!re.test(type) && (e2 = currentEvents[type])) {
747      time = new Date() - e2.date;
748      reflows = opera.reflowCount - e2.reflowCount;
749      if (time > 1000 || reflows > 5) {
750        opera.postError(type + ': ' + time + 'ms, ' + reflows + ' reflows');
751      }
752      delete currentEvents[type];
753    }
754  }, false);
755})(window.opera);
756
757// For debugging
758if (top.location.search.indexOf('likeGecko') > -1) {
759  window.opera = undefined; 
760  navigator.product = 'Gecko';
761  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';
762}
763if (top.location.search.indexOf('likeNothing') > -1) {
764  window.opera = undefined;
765}