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