[
  {
    "path": "LICENSE",
    "content": "MIT License\r\n----\r\n\r\nCopyright (c) 2010-2018 Balazs Galambosi\r\n\r\nThe only restriction is to not publish any extension for browsers or \r\nnative application without getting a written permission first. Otherwise:\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE.\r\n"
  },
  {
    "path": "README",
    "content": "A Google Chrome extension for smooth scrolling with the mouse wheel and keyboard buttons.\n\nAlso available as a Mac app\n http://www.smoothscroll.net/mac/\n\nEmbedding it into your website\n  https://github.com/gblazex/smoothscroll-for-websites\n\nChrome extension\n  https://chrome.google.com/webstore/detail/smoothscroll/nbokbjkabcmbfdlbddjidfmibcpneigj\n\nOpera extension\n  https://addons.opera.com/extensions/details/smoothscroll-3/\n\nFeatures\n- Picasa-like smooth scrolling\n- Mouse wheel, middle mouse and keyboard support\n- Arrow keys, PgUp/PgDown, Spacebar, Home/End\n- Customizable step sizes, frames per second and more...\n- Works with embedded content (PDF, flash)\n- Full touchpad support\n- Excluded pages list\n\nPeople involved\n - Balazs Galambosi (maintainer)\n - Michael Herf     (pulse algorithm)\n"
  },
  {
    "path": "manifest.json",
    "content": "{\n   \"background\": {\n      \"scripts\": [ \"pages/background.js\" ],\n      \"persistent\": false\n   },\n   \"content_scripts\": [ {\n      \"all_frames\": true,\n      \"js\": [ \"src/sscr.js\", \"src/middlemouse.js\" ],\n      \"matches\": [ \"http://*/*\", \"https://*/*\", \"ftp://*/*\" ],\n      \"exclude_globs\": [\"*.pdf*\"],\n      \"run_at\": \"document_start\"\n   } ],\n   \"description\": \"Scroll smoothly on all websites with your mouse and keyboard.\",\n   \"icons\": {\n      \"128\": \"img/128n.png\",\n      \"16\": \"img/16n.png\",\n      \"32\": \"img/32n.png\",\n      \"48\": \"img/48n.png\"\n   },\n   \"key\": \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD1YCUodXSIZuDNa4PJGDCzSWnJAsPymJLQNj+fbxNHbE8BJ4x062dCB0rg0ovXXjNjGJW5FUX+aIEdhh1oNpouWkfu0GP6D6VXCrArXS1hKa7mV8jrBSuMLQo/aU3X7iieqkDzeSFRwUaAEp54C62J22sJ06EHI1QMLuCJ6C9lVQIDAQAB\",\n   \"name\": \"SmoothScroll\",\n   \"options_page\": \"pages/options.html\",\n   \"version\": \"1.5.10\",\n   \"manifest_version\": 2,\n   \"permissions\": [\"storage\", \"http://*/*\", \"https://*/*\"],\n   \"web_accessible_resources\": [ \"img/cursor.png\" ]\n}\n"
  },
  {
    "path": "pages/background.js",
    "content": "\nvar defaultOptions = {\n\n    // Plugin\n    middleMouse       : true, \n\n    // Scrolling Core\n    framerate         : 150, // [Hz]\n    animationTime     : 400, // [px]\n    stepSize          : 120, // [px]\n\n    // Pulse (less tweakable)\n    // ratio of \"tail\" to \"acceleration\"\n    pulseAlgorithm    : true,\n    pulseScale        : 4,\n    pulseNormalize    : 1,\n\n    // Acceleration\n    accelerationDelta : 20,  // 20\n    accelerationMax   : 1,   // 1\n\n    // Keyboard Settings\n    keyboardSupport   : true,  // option\n    arrowScroll       : 50,    // [px]\n\n    // Other\n    touchpadSupport   : true,\n    fixedBackground   : true, \n    excluded          : \"example.com, another.example.com\"    \n}\n\n\n// Fired when the extension is first installed, \n// when the extension is updated to a new version, \n// and when Chrome is updated to a new version.\nchrome.runtime.onInstalled.addListener(init);\n\nfunction init(details) {\n    if (details.reason == \"install\") {\n        chrome.storage.sync.set(defaultOptions);\n        var optionsPage = chrome.runtime.getURL(\"pages/options.html\");\n        chrome.tabs.create({ url: optionsPage });\n        chrome.tabs.query({}, function (tabs) {\n            tabs.forEach(addSmoothScrollToTab);\n        });\n    }\n}\n\nfunction addSmoothScrollToTab(tab) {\n    chrome.tabs.executeScript(tab.id, {\n        file: \"src/sscr.js\",\n        allFrames: true\n    });\n    chrome.tabs.executeScript(tab.id, {\n        file: \"src/middlemouse.js\",\n        allFrames: true\n    });\n}\n"
  },
  {
    "path": "pages/options.html",
    "content": "<!DOCTYPE html>\n<html>\n<head><title>SmoothScroll Options</title>\n<meta charset=utf-8 />\n<style type=\"text/css\">\n\nbody {\n    background:#fdfdfd;\n    font-size: 13.4px;\n    font-family: Verdana, sans-serif;\n    color: #333333;\n    margin: 0;\n    zoom:1.35;\n\n}\n\na       { text-decoration: none;  color: #333333; border-bottom:1px dotted #ccc; }\na:hover { text-decoration: none;  color: #0CF; border-bottom:1px solid #0CF; }\nhr      { margin:0; }\nh1      { text-shadow: transparent 0 0 1px; }\nh2      { font-size: 16px; color: #00CCFF; margin:20px 0 15px 0; }\nli      { padding: 2px 0; }\n\n#page {\n    /*border: 3px solid #28AEA8;*/ \n    background: #fff;\n    margin:0 auto 10px;\n    width: 800px; \n    text-align: left; \n    padding: 30px;\n    -webkit-border-radius: 20px; \n\n    /*-webkit-box-shadow: #BBB 0px 0px 20px;*/\n    -webkit-box-shadow: #ddd -2px 2px 10px;\n}\n\n#title {\n    margin: 30px auto;\n    font-size: 40px;\n    background-image:url(../img/128n.png);\n    width: 400px;\n    padding-left: 140px;\n    background-repeat: no-repeat;\n    background-position: 0% 50%;\n    text-align: left;\n    font-weight: bold;\n    text-shadow: transparent 0 0 1px; /*anit-alias*/\n}\n\n#title a { color: #30BCFF /*#3c78d7*/; border:0; }\n#title a:hover { color:#0CF; }\n\ninput[type=text] { \n    color: #010101;\n    background-color: transparent;\n    padding: 3px;\n    outline: none;\n    text-align: center;\n    border: 1px solid #bbb;\n    width: 38px;\n    margin-right:10px; \n    -webkit-border-radius: 4px;\n    -webkit-box-shadow: #CCC 0px 0px 2px;  \n}\n\ntextarea {\n  padding:10px;\n  width: 770px;\n  border: 1px solid #DDD;\n  -webkit-border-radius: 8px;\n  color: #777;\n}\n\ntextarea:focus { color:#222; }\n\n\n#save {\n    font-size:16px; \n    cursor:pointer; \n    width:160px;\n    margin:0 auto;\n    display:block;\n}\n\nbutton { \n    font-weight:bold; \n    border:1px solid #ccc; \n    background:#f6f6f6; \n    color:#777;\n    text-shadow:1px 1px 0 white;\n    padding:5px 10px; \n    border-radius:5px;\n    background-image:-webkit-gradient(linear,left top,left bottom,from(white),to(#EFEFEF)); \n    -webkit-transition: all 0.15s ease-out;\n}\n\nbutton:hover {  \n    color:#555; \n    box-shadow:0 0 3px #999;\n}\n\nbutton:active { \n    background: #DDD; \n    background-image: -webkit-gradient(linear,left top,left bottom,from(#CCC),to(white)); \n}\n\n\n/*.default_value { display:none; }*/\n\n.note { \n    font-size: 80%;\n    color: #666; \n    padding-left: 10px; \n    cursor:help; \n}\n\nlabel[title], .help  { cursor:help;  }\n.setting-name { width: 150px; }\n\n.dialog { \n    text-align:center;\n    display:none;\n    opacity: 0;\n    -webkit-transition: opacity 1s ease-in-out;\n    background-color: #111;\n    color: #fff;\n    position: fixed;\n    padding: 30px;\n    bottom: 0; \n    left: 50%; \n    margin-left:-260px;\n    width:460px;\n    border-radius: 8px 8px 0 0;\n}\n\n.dialog-header {\n    color: #999;\n    font: bold 20px 'arial black', arial;\n}\n\n.dialog a { \n    color: white;\n    font-weight: bold;\n    text-decoration: underline;\n    border:0;\n}\n\n#footer {\n  margin-top: 40px;\n  border-top: 20px solid #f6f6f6;\n}\n\n#footer li { \n  margin:5px 0;\n}\n\n#test  { width:800px; margin:0 auto; }\n#test div { margin:20px 0; }\n\n#version-container { \n    color: #222; \n    font-size: 22px;\n    font-weight: normal; \n    margin-top: 5px; font-style:italic;\n    width:300px;                     \n    height:10px;\n    background:#ececec; \n    text-align:center;\n}\n\n#version  { \n    background:#fdfcfc; \n    color:#999;\n    padding:0 8px;\n    position:relative;\n    top:-8px; \n}\n\n#thanks {\n  text-align: center;\n  margin: 30px 0 0;\n}\n</style>\n<script src=\"../src/sscr.js\"></script>\n<script src=\"../src/middlemouse.js\"></script>\n\n</head>\n<body>\n\n<div id=\"title\">\n  <a href=\"https://chrome.google.com/webstore/detail/smoothscroll/nbokbjkabcmbfdlbddjidfmibcpneigj\" target=\"_blank\">SmoothScroll</a> \n  <div id=\"version-container\"><span id=\"version\"></span></div>\n</div>\n\n<!-- Fork me on GitHub -->\n<a href=\"https://github.com/galambalazs/smoothscroll\"><img style=\"position: absolute; top: 0; left: 0; border: 0;\" src=\"../img/forkme.png\" alt=\"Fork me on GitHub\"></a>\n\n<div id=\"page\">\n\n<h2 style=\"margin-top:0;\">Scroll Settings</h2>\n\n<!-- -->\n<div id=\"profiles\">\n&nbsp;&nbsp;<i class=\"help \"title=\"Don't forget to hit 'Save' if you want to use them!\">Load profile:</i>\n<button id=\"custom\">Custom</button>\n<button id=\"default\">Default</button>\n<button id=\"iphone\">iPhone</button>\n<button id=\"opera\">Opera</button>\n<button id=\"ie9\">IE</button>\n</div>\n\n<br />\n\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"2\" style=\"font-size: 12px; margin-left: 6px\">\n <tr>\n  <td class=\"setting-name\"><label for=\"stepSize\" title=\"higher means more scrolling for each mouse event\">Step size [px]</label></td>\n  <td><input id=\"stepSize\" type=\"text\" size=\"1\" maxlength=\"3\"> </td>\n  <td class=\"default_value\"><span style=\"color: #00CCFF; font-size: 12px;\"> <i>Default value: 100</i></span></td>\n  <td class=\"note\" title=\"higher means more scrolling for each mouse event\">(<b>how</b> much)</td>\n </tr>\n <tr>\n  <td class=\"setting-name\"><label for=\"animationTime\" title=\"higher number stretches animation\">Animation time [ms]</label></td>\n  <td><input id=\"animationTime\" type=\"text\" size=\"1\" maxlength=\"3\"> </td>\n  <td class=\"default_value\"><span style=\"color: #00CCFF; font-size: 12px;\"> <i>Default value: 400</i></span></td>\n  <td class=\"note\" title=\"higher number stretches animation\">(<b>how</b> quick)</td>\n </tr>\n <tr>\n  <td class=\"setting-name\"><label for=\"accelerationMax\" title=\"how much scrolling can accelerate\">Acceleration scale</label></td>\n  <td><input id=\"accelerationMax\" type=\"text\" size=\"1\" maxlength=\"3\"></td>\n  <td class=\"default_value\"><span style=\"color: #00CCFF; font-size: 12px;\"> <i>Default value: 3</i></span></td>\n  <td class=\"note\" title=\"how much scrolling can accelerate\">(<b>how </b>swift)</td>\n</tr>\n <tr>\n  <td class=\"setting-name\"><label for=\"accelerationDelta\" title=\"lower means acceleration kicks in more frequently\">Acceleration delta [ms]</label></td>\n  <td><input id=\"accelerationDelta\" type=\"text\" size=\"1\" maxlength=\"2\"></td>\n  <td class=\"default_value\"><span style=\"color: #00CCFF; font-size: 12px;\"> <i>Default value: 50</i></span></td>\n  <td class=\"note\" title=\"lower means acceleration kicks in more frequently\">(<b>time</b> between scroll events)</td>\n</tr>\n <tr>\n  <td class=\"setting-name\"><label for=\"pulseScale\" title=\"it affects how long the decceleration part is\">Pulse Scale</label></td>\n  <td><input id=\"pulseScale\" type=\"text\" size=\"1\" maxlength=\"2\"></td>\n  <td class=\"default_value\"><span style=\"color: #00CCFF; font-size: 12px;\"> <i>Default value: 4</i></span></td>\n  <td class=\"note\" title=\"it affects how long the decceleration part is\">(<b>ratio </b>of \"tail\" to \"acceleration\")</td>\n</tr>\n <tr>\n  <td style=\"width:180px;\"><label for=\"arrowScroll\" title=\"higher means more scrolling for each arrow key press\">Arrow key step size [px]</label></td>\n  <td><input id=\"arrowScroll\" type=\"text\" size=\"1\" maxlength=\"3\"> </td>\n  <td class=\"default_value\" ><span style=\"color: #00CCFF; font-size: 12px;\"> <i>Default value: 50</i></span></td>\n </tr>\n</table>\n<br>\n\n<h2>Features</h2>\n\n<input type=\"checkbox\" id=\"keyboardSupport\"><label for=\"keyboardSupport\">Enable <b>keyboard support</b></label><br><br>\n\n<input type=\"checkbox\" id=\"touchpadSupport\"><label for=\"touchpadSupport\">Enable <b>touchpad support</b></label><br><br>\n\n<input type=\"checkbox\" id=\"pulseAlgorithm\"><label for=\"pulseAlgorithm\" title=\"adds easing to the animations\">Enable <b>pulse algorithm</b></label><br><br>\n\n<input type=\"checkbox\" id=\"middleMouse\"><label for=\"middleMouse\" title=\"scroll smooth with the middle mouse button\">Enable <b>middle mouse</b> scrolling</label><br><br>\n\n<input type=\"checkbox\" id=\"fixedBackground\"><label for=\"fixedBackground\" title=\"disabling it improves performance on sites like twitter.com\">Enable <b>fixed backgrounds</b></label><br><br>\n\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"2\" style=\"font-size: 12px; \">\n\n <!--\n <tr>\n  <td style=\"width:200px;\"><label for=\"pgscroll\">PgUp/PgDown step size [px]</label></td>\n  <td><input id=\"pgscroll\" type=\"text\" size=\"1\" maxlength=\"4\"> </td>\n  <td><span style=\"color: #00CCFF; font-size: 12px;\"> <i>Default value: 800</i></span></td>\n </tr>\n -->\n</table>\n\n<br>\n\n<h2><label for=\"excluded\">Excluded pages</label></h2>\n<textarea id=\"excluded\" cols=\"70\" rows=\"5\"></textarea> \n\n<br><br>\n\n<button id=\"save\">Save Settings</button>\n\n<div id=\"thanks\">If you <strong><i>love</i></strong> this extension please consider \n      <a href=\"https://chrome.google.com/webstore/detail/smoothscroll/nbokbjkabcmbfdlbddjidfmibcpneigj/reviews\" style=\"color:#00CCFF;font-weight:bold\" target=\"_blank\">writing a great review</a>\n</div>\n\n<!--\n<div style=\"text-align:center; height: 30px;margin:20px 0 5px;\">\n  <div id=\"saving\"  style=\"color: #00CCFF; font-size: 12px; font-weight: bold;\">&nbsp;</div>\n  <div id=\"message\" style=\"color: #00CCFF; font-size: 11px; font-style:italic;\"></div>\n</div>\n-->\n\n<div id=\"footer\">\n\n<div>\n  <h2>Links</h2>\n  <ul>\n   <li><a href=\"https://github.com/galambalazs/smoothscroll/\"\n          target=\"_blank\">Bug Report, Known Issues, Source Code...</a></li>\n   <li><a href=\"https://chrome.google.com/webstore/detail/smoothscroll/nbokbjkabcmbfdlbddjidfmibcpneigj\" \n          target=\"_blank\"> Extension Homepage</a></li>\n  </ul>\n</div>\n\n<div>\n  <h2>People involved</h2>\n  <ul>\n   <li><a href=\"mailto:galambalazs@yahoo.co.uk\">Balázs Galambosi <b>(</b>developer<b>)</b></a></li>\n   <li><a href=\"http://stereopsis.com/stopping/\">Michael Herf's pulse algorithm is used</a></li>\n  </ul>\n</div>\n\n</div>\n\n</div> <!-- /#page -->\n\n\n\n\n<br><br><br><br><br><br>\n\n<div class=\"dialog\">\n  <div class=\"dialog-header\">Settings saved</div>\n  <div class=\"dialog-content\">You can now test the new scroll settings!</div>\n</div>\n\n<div id=\"test\">\n\n<h1>Test your settings!</h1>\n\n<div>\n<h2>By scrolling on this long page.</h2>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum non orci ipsum, eu laoreet nisl. Suspendisse ut dictum neque. Pellentesque nulla ipsum, posuere ut mattis vel, dapibus fringilla arcu. Donec fermentum purus in risus convallis semper. Nullam tempor urna eu libero vulputate vitae pulvinar enim accumsan. Donec vitae leo a enim faucibus consectetur. Nullam lectus justo, eleifend quis condimentum ut, pharetra et risus. Ut luctus molestie nibh sit amet vestibulum. Vestibulum tempor luctus scelerisque. Duis posuere elementum mollis. Cras sit amet varius massa. Nulla condimentum feugiat aliquam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean id ante et diam lacinia convallis vel eu magna.</p>\n\n<p>In congue, risus sed commodo molestie, tortor sem dictum neque, in viverra sapien arcu vulputate velit. Pellentesque nec dignissim neque. Proin non felis quis lectus fermentum lobortis ut nec purus. Nam quam turpis, sodales in pellentesque vel, tempor gravida lacus. Pellentesque euismod aliquet pulvinar. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tempus velit in lacus tristique facilisis. Mauris commodo, nunc et tempor pharetra, lectus dui posuere odio, eu auctor lorem augue non neque. Aenean auctor velit ac erat lobortis iaculis. Nullam in nibh et sem porttitor malesuada non non urna. Nulla lacus est, aliquet vel egestas a, accumsan a ante. Sed semper molestie dictum. Morbi at quam non enim porttitor varius. Vivamus ut metus et enim egestas ornare malesuada at augue.</p>\n\n<p>Morbi laoreet velit sit amet metus tincidunt hendrerit. Aliquam augue ante, venenatis a rutrum sit amet, tristique non urna. Suspendisse et euismod nunc. Donec tempus luctus convallis. Proin blandit justo in nulla volutpat imperdiet. Integer dapibus lectus vitae libero hendrerit commodo. Praesent felis metus, pretium id tempus sed, porttitor ut justo. Praesent dapibus massa a erat congue malesuada. Fusce at erat in augue lobortis ultrices. Fusce vitae nunc sed arcu viverra convallis. Etiam nec magna turpis, id iaculis dui. Etiam at interdum est. Ut quis eros justo, vel consequat dolor. Proin pulvinar tristique congue. Mauris consectetur aliquam ipsum lobortis tempor. In non ligula orci, a commodo turpis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer non tempus lectus. Praesent tristique ante a purus lobortis viverra.</p>\n\n<p>Curabitur eget leo velit, ac sagittis arcu. Mauris pulvinar orci et libero ultricies non iaculis velit gravida. Nulla facilisi. Pellentesque vel tellus nisl, et eleifend turpis. Vestibulum accumsan tincidunt tellus ac posuere. Nunc et orci metus. Cras quis lacus at tortor semper sollicitudin. Sed mi velit, semper et eleifend eget, faucibus mattis velit. Quisque interdum, risus et vulputate cursus, mauris elit fermentum nunc, in imperdiet ipsum odio sed magna. Vivamus porttitor ornare lacus in congue. Morbi et justo nulla, non tincidunt eros. Nulla scelerisque, tortor eget pulvinar volutpat, ante justo dapibus arcu, eu lacinia quam ante ut libero. Integer porta libero eu est scelerisque malesuada. Sed risus diam, pellentesque eu euismod sit amet, rhoncus quis velit. Aliquam et vehicula odio. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque nec magna nec odio venenatis sollicitudin non id massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut in eleifend mauris.</p>\n\n<p>Sed ac scelerisque nisl. Vivamus ac diam et nibh pulvinar gravida vitae a magna. Fusce ipsum ligula, suscipit sit amet ullamcorper a, fringilla sed neque. Curabitur ipsum dolor, consectetur condimentum luctus nec, molestie eu nulla. Curabitur eros nunc, varius at vestibulum sed, tincidunt aliquam risus. Aenean ultricies iaculis augue. Sed tempor scelerisque velit, et dignissim erat hendrerit gravida. Mauris consectetur porta lacus vel posuere. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut diam diam, tempus mattis interdum nec, tincidunt in ante. Pellentesque lacus ipsum, eleifend non imperdiet id, dapibus vel metus. Proin egestas sagittis urna facilisis congue. Fusce nec dapibus nibh.</p>\n<div>\n</div>\n\n<script src=\"options.js\"></script>\n\n</body>\n</html>"
  },
  {
    "path": "pages/options.js",
    "content": "\n/**\n * Options page logic.\n */\n(function(window, undefined){\n\n/**\n * List of available options\n */\nvar optionsList = [\n  'animationTime',\n  'stepSize',\n  'arrowScroll',\n  'middleMouse',\n  'accelerationMax',\n  'accelerationDelta',\n  'pulseAlgorithm',\n  'pulseScale',\n  'keyboardSupport',\n  'touchpadSupport',\n  'excluded',\n  'fixedBackground'\n];\n\nvar options = {};\n\nfunction byId(id) { return document.getElementById(id); }\nfunction byClass(cname) { return document.getElementsByClassName(cname); }\nfunction byTag(tag,base) { return (base||document).getElementsByTagName(tag||'*'); }\n\nfunction isNodeName(el, tag) {\n    return el.nodeName.toLowerCase() === tag.toLowerCase();\n}\n\nfunction show(elem, newop) {\n    elem.style.display = \"block\";\n    elem.style.webkitTransition = \"opacity 0.2s ease-in-out\";\n    setTimeout(function(){\n        elem.style.opacity = (newop || 1);\n    }, 0);\n}\n\nfunction hide(elem, newop) {\n    elem.style.webkitTransition = \"opacity 1s ease-in-out\";\n    elem.style.opacity = (newop || 0);\n    setTimeout(function(){\n        elem.style.display = \"none\";\n    }, 1000);\n}\n\nfunction isCheckbox(key) {\n  var re = /^(?:keyboardSupport|touchpadSupport|middleMouse|pulseAlgorithm|fixedBackground)$/;\n  return re.test(key);\n}\n\nfunction init() {\n  chrome.storage.sync.get(optionsList, initWithOptions);\n}\n\n/**\n * Fills up the form with the saved values from local storage.\n */\nfunction initWithOptions(optionsSynced) {\n\n    options = optionsSynced;\n\n    // settings were updated -> show dialog\n    if (localStorage.saved == 'true') {\n        var dialog  = byClass('dialog')[0];\n        show(dialog, 0.9);\n        setTimeout(function () {\n            hide(dialog);\n        }, 3000);\n    }\n\n    // updated complete\n    localStorage.saved = 'false';\n        \n    // fill the form fields from storage\n    for (var key in options) {\n      if (isCheckbox(key)) {\n          byId(key).checked = options[key];\n      } else if (options[key]) {\n          byId(key).value = options[key];\n      }\n    }\n}\n\n/**\n * Saves the values from the form to local storage.\n */\nfunction save() {\n\n    var i, key, opt, elem, error, options = {};\n\n    // save options to the local storage\n    optionsList.forEach(function(key, i) {\n        // <input type=\"text\"> and <textarea>\n        if (!isCheckbox(key)) {\n            elem = byId(key);\n            opt = elem.value;\n            // every <input> \n            if (isNodeName(elem, \"input\")) {\n              // should be a number\n              opt = parseFloat(opt, 10);\n              if (isNaN(opt)) {\n                  error = \"Numeric Values Only!\";\n                  return; // stop iteration\n               }\n            }\n            options[key] = opt;\n        } else { // checkbox\n            options[key] = byId(key).checked;\n        }\n    });\n\n    // update message\n    if (!error) {\n        chrome.storage.sync.set(options, function(){\n            localStorage.saved = 'true';\n            reload();   \n        });\n    }\n    // error message\n    else {\n        alert(error);\n    } \n}\n\nfunction get_manifest(callback) {\n    var xhr = new XMLHttpRequest();\n    xhr.onload = function () {\n        callback(JSON.parse(xhr.responseText));\n    };\n    xhr.open('GET', '../manifest.json', true);\n    xhr.send(null);\n}\n\nget_manifest(function (manifest) {\n    version = manifest.version;\n    byId(\"version\").innerHTML = version;\n});\n\nfunction reload() {\n    window.location.reload();\n}\n\n\nvar profiles = {\n\n  '_custom': {\n    'animationTime': 160,\n    'stepSize': 120,\n    'pulseAlgorithm': 'true',\n    'pulseScale': 4\n  },\n\n  '_default': {\n    'animationTime': 400,\n    'stepSize': 100,\n    'pulseAlgorithm': 'true',\n    'pulseScale': 4\n  },\n\n  '_iphone': {\n    'animationTime': 600,\n    'stepSize': 120,\n    'pulseAlgorithm': 'true',\n    'pulseScale': 3\n  },\n  \n  '_opera': {\n    'animationTime': 120,\n    'stepSize': 120,\n    'pulseAlgorithm': 'false'\n  },\n  \n  '_ie9': {\n    'animationTime': 60,\n    'stepSize': 120,\n    'pulseAlgorithm': 'false'\n  }\n};\n\n// TODO: merge with init\nfunction setProfile(profile) {\n\n    if ('custom' == profile){\n      init();\n      return;\n    }  \n\n    profile = profiles['_'+profile];\n\n    // set\n    for (var key in profile) {\n        if (isCheckbox(key)) {\n            byId(key).checked = (profile[key] == \"true\");\n        } else if (options[key]) {\n            byId(key).value = profile[key];\n        }\n    };\n}\n\n// Restores select box state to saved value from storage.\n// function restore_options() {}\n\nfunction generateTest() {\n    var test = byId('test');\n    var el = byTag('div', test)[0];\n    for (var i = 5; i--;) {\n      test.appendChild(el.cloneNode(true));\n    }\n}\n\nbyId('profiles').onclick = function(e) {\n  if (e.target.id && e.target.nodeName == 'BUTTON') {\n    setProfile(e.target.id);\n  }\n};\n\nbyId('save').onclick = save;\n\n// public interface\ninit();\nwindow.addEventListener(\"DOMContentLoaded\", generateTest, false);\nwindow.reload = reload;\nwindow.save = save;\nwindow.setProfile = setProfile;\n\n})(window);\n"
  },
  {
    "path": "src/middlemouse.js",
    "content": "\n//\n// SmoothScroll (Balazs Galambosi)\n// Licensed under the terms of the MIT license.\n// The only restriction would be not to publish any  \n// extension for browsers or native application\n// without getting a written permission first.\n//\n\n/**\n * A module for middle mouse scrolling.\n */\n(function (window) {\n\nvar defaultOptions = {\n    middleMouse : false,\n    frameRate   : 200\n};\n\nvar options = defaultOptions;\n\nvar img = document.createElement(\"div\"); // img at the reference point\nvar scrolling = false; // guards one phase\n\n\n// we check the OS for default middle mouse behavior only!\nvar isLinux = (navigator.platform.indexOf(\"Linux\") != -1); \n\n// get global settings\nchrome.storage.sync.get(defaultOptions, function (syncedOptions) {\n    options = syncedOptions;\n    // leave time for the main script to check excluded pages\n    setTimeout(function() {\n        // if we shouldn't run, stop listening to events\n        if (isExcluded && !options.middleMouse) {\n            cleanup();\n        }\n    }, 10);\n});\n\n \n/**\n * Initializes the image at the reference point.\n */\nfunction init() {\n    var url = chrome.extension.getURL(\"../img/cursor.png\");\n    var style = img.style;\n    style.background = \"url(\"+url+\") no-repeat\";\n    style.position   = \"fixed\";\n    style.zIndex     = \"1000\";\n    style.width      = \"20px\";\n    style.height     = \"20px\";\n    new Image().src  = url; // force download\n}\n\n/**\n * Removes event listeners and other traces left on the page.\n */\nfunction cleanup() {\n    removeEvent(\"mousedown\", mousedown);\n}\n\n/**\n * Shows the reference image, and binds event listeners for scrolling.\n * It also manages the animation.\n * @param {Object} event\n */\nfunction mousedown(e) {\n\n    // use default action if we're disabled\n    // or it's not the midde mouse button\n    if (!options.middleMouse || e.button !== 1) {\n        return true;\n    }\n\n    var isLink = false;\n    var elem   = e.target;\n    \n    // linux middle mouse shouldn't be overwritten (paste)\n    var isLinuxInput = (isLinux && (/input|textarea/i.test(elem.nodeName) || \n                                    elem.isContentEditable));\n\n    do {\n        isLink = isNodeName(elem, \"a\");\n        if (isLink) break;\n    } while ((elem = elem.parentNode));\n        \n    elem = overflowingAncestor(e.target);\n    \n    // if it's being used on an <a> element\n    // take the default action\n    if (!elem || isLink || isLinuxInput) {\n        return true;\n    }\n    \n    // we don't want the default by now\n    e.preventDefault();\n    \n    // quit if there's an ongoing scrolling\n    if (scrolling) {\n        return false;\n    }\n    \n    // set up a new scrolling phase\n    scrolling = true;\n \n    // reference point\n    img.style.left = e.clientX - 10 + \"px\";\n    img.style.top  = e.clientY - 10 + \"px\";\n    document.body.appendChild(img);\n    \n    var refereceX = e.clientX;\n    var refereceY = e.clientY;\n\n    var speedX = 0;\n    var speedY = 0;\n    \n    // animation loop\n    var last = dateNow();\n    var delay = 1000 / options.frameRate;\n    var finished = false;\n    \n    window.requestAnimationFrame(function step(time) {\n        var now = dateNow();\n        var elapsed = now - last;\n        // NOTE: later can also use document.scrollingElement\n        if (elem == document.body) {\n            window.scrollBy(speedX * elapsed, speedY * elapsed);\n        } else {\n            elem.scrollLeft += (speedX * elapsed) >> 0;\n            elem.scrollTop  += (speedY * elapsed) >> 0;\n        }\n        last = now;\n        if (!finished) {\n            window.requestAnimationFrame(step);\n        }\n    }, elem, delay);\n    \n    var firstMove = true;\n\n    function mousemove(e) {\n        var deltaX = Math.abs(refereceX - e.clientX);\n        var deltaY = Math.abs(refereceY - e.clientY);\n        var movedEnough = Math.max(deltaX, deltaY) > 10; \n        if (firstMove && movedEnough) {\n            addEvent(\"mouseup\", remove);\n            firstMove = false;\n        }\n        speedX = (e.clientX - refereceX) * 10 / 1000;\n        speedY = (e.clientY - refereceY) * 10 / 1000;\n    }\n    \n    function remove(e) {\n        removeEvent(\"mousemove\", mousemove);\n        removeEvent(\"mousedown\", remove);\n        removeEvent(\"mouseup\", remove);\n        removeEvent(\"keydown\", remove);\n        document.body.removeChild(img);\n        scrolling = false;\n        finished  = true;\n    }\n    \n    addEvent(\"mousemove\", mousemove);\n    addEvent(\"mousedown\", remove);\n    addEvent(\"keydown\", remove);\n}\n\n/**\n * performance.now with fallback\n */\nvar dateNow = (function () {\n  return (window.performance && performance.now) \n        ? function () { return performance.now(); }\n        : function () { return Date.now(); };\n})();\n\naddEvent(\"mousedown\", mousedown);\naddEvent(\"DOMContentLoaded\", init);\n\n})(window);\n"
  },
  {
    "path": "src/sscr.js",
    "content": "\n//\n// SmoothScroll (Balazs Galambosi)\n// Licensed under the terms of the MIT license.\n// The only restriction would be not to publish any  \n// extension for browsers or native application\n// without getting a written permission first.\n//\n\n// Scroll Variables (tweakable)\nvar defaultOptions = {\n\n    // Scrolling Core\n    frameRate        : 150, // [Hz]\n    animationTime    : 400, // [px]\n    stepSize         : 100, // [px]\n\n    // Pulse (less tweakable)\n    // ratio of 'tail' to 'acceleration'\n    pulseAlgorithm   : true,\n    pulseScale       : 4,\n    pulseNormalize   : 1,\n\n    // Acceleration\n    accelerationDelta : 50,  // 20\n    accelerationMax   : 3,   // 1\n\n    // Keyboard Settings\n    keyboardSupport   : true,  // option\n    arrowScroll       : 50,     // [px]\n\n    // Other\n    touchpadSupport   : false,\n    fixedBackground   : true, \n    excluded          : ''    \n};\n\nvar options = defaultOptions;\n\n\n// Other Variables\nvar isExcluded = false;\nvar isFrame = false;\nvar direction = { x: 0, y: 0 };\nvar initDone  = false;\nvar root = document.documentElement;\nvar activeElement;\nvar observer;\nvar deltaBuffer = [];\nvar deltaBufferTimer;\nvar isMac = /^Mac/.test(navigator.platform);\nvar isWin = /Windows/i.test(navigator.userAgent);\n\nvar key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, \n            pageup: 33, pagedown: 34, end: 35, home: 36 };\nvar arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 };\n\n/***********************************************\n * SETTINGS\n ***********************************************/\n\nchrome.storage.sync.get(defaultOptions, function (syncedOptions) {\n\n    options = syncedOptions;\n\n    // it seems that sometimes settings come late\n    // and we need to test again for excluded pages\n    initTest();\n});\n\n\n/***********************************************\n * INITIALIZE\n ***********************************************/\n\n/**\n * Tests if smooth scrolling is allowed. Shuts down everything if not.\n */\nfunction initTest() {\n\n    // disable keyboard support if the user said so\n    if (!options.keyboardSupport) {\n        removeEvent('keydown', keydown);\n    }\n\n    // disable everything if the page is blacklisted\n    if (options.excluded) {\n        var domains = options.excluded.split(/[,\\n] ?/);\n        domains.push('mail.google.com'); // exclude Gmail for now\n        domains.push('play.google.com/music'); // problem with Polymer elements\n        for (var i = domains.length; i--;) {\n            if (document.URL.indexOf(domains[i]) > -1) {\n                isExcluded = true;\n                cleanup();\n                return;\n            }\n        }\n    }\n}\n\n/**\n * Sets up scrolls array, determines if frames are involved.\n */\nfunction init() {\n\n    if (initDone || isExcluded || !document.body) {\n        return;\n    }\n\n    initDone = true;\n \n    var body = document.body;\n    var html = document.documentElement;\n    var windowHeight = window.innerHeight; \n    var scrollHeight = body.scrollHeight;\n    \n    // check compat mode for root element\n    root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;\n    activeElement = body;\n\n    // Checks if this script is running in a frame\n    if (top != self) {\n        isFrame = true;\n    }\n\n    // TODO: check if clearfix is still needed\n    else if (scrollHeight > windowHeight &&\n            (body.clientHeight + 1 < body.scrollHeight &&\n             html.clientHeight + 1 < html.scrollHeight)) {\n        if (root.offsetHeight <= windowHeight) {\n            var clearfix = document.createElement('div');   \n            clearfix.style.clear = 'both';\n            body.appendChild(clearfix);\n        }\n    }\n\n    // disable fixed background\n    if (!options.fixedBackground && !isExcluded) {\n        body.style.backgroundAttachment = 'scroll';\n        html.style.backgroundAttachment = 'scroll';\n    }\n\n    if (!isFrame) {\n        addEvent('message', function (e) {\n            if (e.data.SS == 'SmoothScroll') {\n                var wheelEvent = e.data;\n                wheelEvent.target = getFrameByEvent(e);\n                wheel(wheelEvent);\n            }\n        });\n    }\n}\n\nfunction getFrameByEvent(event) {\n  var iframes = document.getElementsByTagName('iframe');\n  return [].filter.call(iframes, function(iframe) {\n    return iframe.contentWindow === event.source;\n  })[0];\n}\n\n/**\n * Removes event listeners and other traces left on the page.\n */\nfunction cleanup() {\n    observer && observer.disconnect();\n    removeEvent(wheelEvent, wheel);\n    removeEvent('mousedown', mousedown);\n    removeEvent('keydown', keydown);\n}\n\n/**\n * Make sure we are the last listener on the page so special \n * key event handlers (e.g for <video>) can come before us\n */\nfunction loaded() {\n    setTimeout(function () {\n        init();\n        if (options.keyboardSupport) {\n            removeEvent('keydown', keydown);\n            addEvent('keydown', keydown);\n        }\n    }, 1);\n}\n\n\n/************************************************\n * SCROLLING \n ************************************************/\n \nvar que = [];\nvar pending = null;\nvar lastScroll = Date.now();\n\n/**\n * Pushes scroll actions to the scrolling queue.\n */\nfunction scrollArray(elem, left, top) {\n    directionCheck(left, top);\n\n    if (options.accelerationMax != 1) {\n        var now = Date.now();\n        var elapsed = now - lastScroll;\n        if (elapsed < options.accelerationDelta) {\n            var factor = (1 + (50 / elapsed)) / 2;\n            if (factor > 1) {\n                factor = Math.min(factor, options.accelerationMax);\n                left *= factor;\n                top  *= factor;\n            }\n        }\n        lastScroll = Date.now();\n    }          \n    \n    // push a scroll command\n    que.push({\n        x: left, \n        y: top, \n        lastX: (left < 0) ? 0.99 : -0.99,\n        lastY: (top  < 0) ? 0.99 : -0.99, \n        start: Date.now()\n    });\n        \n    // don't act if there's a pending frame loop\n    if (pending) {\n        return;\n    }  \n\n    var scrollRoot = getScrollRoot();\n    var isWindowScroll = (elem === scrollRoot || elem === document.body);\n\n    // if we haven't already fixed the behavior, \n    // and it needs fixing for this sesh\n    if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) {\n        elem.$scrollBehavior = elem.style.scrollBehavior;\n        elem.style.scrollBehavior = 'auto';\n    }\n\n    var step = function (time) {\n        \n        var now = Date.now();\n        var scrollX = 0;\n        var scrollY = 0; \n    \n        for (var i = 0; i < que.length; i++) {\n            \n            var item = que[i];\n            var elapsed  = now - item.start;\n            var finished = (elapsed >= options.animationTime);\n            \n            // scroll position: [0, 1]\n            var position = (finished) ? 1 : elapsed / options.animationTime;\n            \n            // easing [optional]\n            if (options.pulseAlgorithm) {\n                position = pulse(position);\n            }\n            \n            // only need the difference\n            var x = (item.x * position - item.lastX) >> 0;\n            var y = (item.y * position - item.lastY) >> 0;\n            \n            // add this to the total scrolling\n            scrollX += x;\n            scrollY += y;            \n            \n            // update last values\n            item.lastX += x;\n            item.lastY += y;\n        \n            // delete and step back if it's over\n            if (finished) {\n                que.splice(i, 1); i--;\n            }           \n        }\n\n        if (window.devicePixelRatio) {\n            //scrollX /= (window.devicePixelRatio;\n            //scrollY /= window.devicePixelRatio;\n        }\n\n        // scroll left and top\n        if (isWindowScroll) {\n            window.scrollBy(scrollX, scrollY);\n        } \n        else {\n            if (scrollX) elem.scrollLeft += scrollX;\n            if (scrollY) elem.scrollTop  += scrollY;                    \n        }\n        \n        // clean up if there's nothing left to do\n        if (!left && !top) {\n            que = [];\n        }\n        \n        if (que.length) { \n            pending = window.requestAnimationFrame(step); \n        } else { \n            pending = null;\n            // restore default behavior at the end of scrolling sesh\n            if (elem.$scrollBehavior != null) {\n                elem.style.scrollBehavior = elem.$scrollBehavior;\n                elem.$scrollBehavior = null;\n            }\n        }\n    };\n    \n    // start a new queue of actions\n    pending = window.requestAnimationFrame(step);\n}\n\n\n/***********************************************\n * EVENTS\n ***********************************************/\n\n/**\n * Mouse wheel handler.\n * @param {Object} event\n */\nfunction wheel(event) {\n\n    if (!initDone) {\n        init();\n    }\n\n    var target = event.target;\n\n    // leave early if default action is prevented   \n    // or it's a zooming event with CTRL \n    if (event.defaultPrevented || event.ctrlKey) {\n        return true;\n    }\n    \n    // leave embedded content alone (flash & pdf)\n    if (isNodeName(activeElement, 'embed') || \n       (isNodeName(target, 'embed') && /\\.pdf/i.test(target.src)) ||\n        isNodeName(activeElement, 'object') ||\n        target.shadowRoot) {\n        return true;\n    }\n\n    var deltaX = -event.wheelDeltaX || event.deltaX || 0;\n    var deltaY = -event.wheelDeltaY || event.deltaY || 0;\n\n    if (isMac) {\n        if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {\n            deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));\n        }\n        if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {\n            deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));\n        }\n    }\n\n    // use wheelDelta if deltaX/Y is not available\n    if (!deltaX && !deltaY) {\n        deltaY = -event.wheelDelta || 0;\n    }\n\n    // line based scrolling\n    if (event.deltaMode === 1) {\n        deltaX *= 40;\n        deltaY *= 40;\n    }\n\n    // check if it's a touchpad scroll that should be ignored\n    if (!options.touchpadSupport && isTouchpad(deltaY)) {\n        return true;\n    }\n\n    var xOnly = (deltaX && !deltaY);\n    var overflowing = overflowingAncestor(target, xOnly);\n\n    // nothing to do if there's no element that's scrollable\n    if (!overflowing) {\n        // Chrome iframes seem to eat wheel events, which we need to \n        // propagate up if the iframe has nothing overflowing to scroll\n        if (isFrame) {\n            event.preventDefault();\n            postScrollToParent(deltaX, deltaY);\n            // change target to iframe element itself for the parent frame\n            //Object.defineProperty(event, \"target\", {value: window.frameElement});\n            //return parent.wheel(event);\n        }\n        return true;\n    }\n\n    // scale by step size\n    // delta is 120 most of the time\n    // synaptics seems to send 1 sometimes\n    if (Math.abs(deltaX) > 1.2) {\n        deltaX *= options.stepSize / 120;\n    }\n    if (Math.abs(deltaY) > 1.2) {\n        deltaY *= options.stepSize / 120;\n    }\n    \n    scrollArray(overflowing, deltaX, deltaY);\n    if (event.preventDefault) event.preventDefault();\n    scheduleClearCache();\n}\n\n/**\n * Keydown event handler.\n * @param {Object} event\n */\nfunction keydown(event) {\n\n    var target   = event.target;\n    var modifier = event.ctrlKey || event.altKey || event.metaKey || \n                  (event.shiftKey && event.keyCode !== key.spacebar);\n\n    // our own tracked active element could've been removed from the DOM\n    if (!document.contains(activeElement)) {\n        activeElement = document.activeElement;\n    }\n\n    // do nothing if user is editing text\n    // or using a modifier key (except shift)\n    // or in a dropdown\n    // or inside interactive elements\n    var inputNodeNames = /^(textarea|select|embed|object)$/i;\n    var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;\n    if ( event.defaultPrevented ||\n         inputNodeNames.test(target.nodeName) ||\n         isNodeName(target, 'input') && !buttonTypes.test(target.type) ||\n         isNodeName(activeElement, 'video') ||\n         isInsideYoutubeVideo(event) ||\n         target.isContentEditable || \n         modifier ) {\n      return true;\n    }\n\n    // [spacebar] should trigger button press, leave it alone\n    if ((isNodeName(target, 'button') ||\n         isNodeName(target, 'input') && buttonTypes.test(target.type)) &&\n        event.keyCode === key.spacebar) {\n      return true;\n    }\n\n    // [arrwow keys] on radio buttons should be left alone\n    if (isNodeName(target, 'input') && target.type == 'radio' &&\n        arrowKeys[event.keyCode])  {\n      return true;\n    }\n\n    var xOnly = (event.keyCode == key.left || event.keyCode == key.right);\n    var overflowing = overflowingAncestor(activeElement, xOnly);\n\n    if (!overflowing) {\n        // iframes seem to eat key events, which we need to propagate up\n        // if the iframe has nothing overflowing to scroll\n        return isFrame ? parent.keydown(event) : true;\n    }\n\n    var clientHeight = overflowing.clientHeight;\n    var shift, x = 0, y = 0;\n\n    if (overflowing == document.body) {\n        clientHeight = window.innerHeight;\n    }\n\n    switch (event.keyCode) {\n        case key.up:\n            y = -options.arrowScroll;\n            break;\n        case key.down:\n            y = options.arrowScroll;\n            break;         \n        case key.spacebar: // (+ shift)\n            shift = event.shiftKey ? 1 : -1;\n            y = -shift * clientHeight * 0.9;\n            break;\n        case key.pageup:\n            y = -clientHeight * 0.9;\n            break;\n        case key.pagedown:\n            y = clientHeight * 0.9;\n            break;\n        case key.home:\n            if (overflowing == document.body && document.scrollingElement)\n                overflowing = document.scrollingElement;\n            y = -overflowing.scrollTop;\n            break;\n        case key.end:\n            var scroll = overflowing.scrollHeight - overflowing.scrollTop;\n            var scrollRemaining = scroll - clientHeight;\n            y = (scrollRemaining > 0) ? scrollRemaining+10 : 0;\n            break;\n        case key.left:\n            x = -options.arrowScroll;\n            break;\n        case key.right:\n            x = options.arrowScroll;\n            break;            \n        default:\n            return true; // a key we don't care about\n    }\n\n    scrollArray(overflowing, x, y);\n    event.preventDefault();\n    scheduleClearCache();\n}\n\n/**\n * Mousedown event only for updating activeElement\n */\nfunction mousedown(event) {\n    activeElement = event.target;\n}\n\n\n/***********************************************\n * OVERFLOW\n ***********************************************/\n \nvar uniqueID = (function () {\n    var i = 0;\n    return function (el) {\n        return el.uniqueID || (el.uniqueID = i++);\n    };\n})();\n\nvar cacheX = {}; // cleared out after a scrolling session\nvar cacheY = {}; // cleared out after a scrolling session\nvar clearCacheTimer;\nvar smoothBehaviorForElement = {};\n\n//setInterval(function () { cache = {}; }, 10 * 1000);\n\nfunction scheduleClearCache() {\n    clearTimeout(clearCacheTimer);\n    clearCacheTimer = setInterval(function () { \n        cacheX = cacheY = smoothBehaviorForElement = {}; \n    }, 1*1000);\n}\n\nfunction setCache(elems, overflowing, x) {\n    var cache = x ? cacheX : cacheY;\n    for (var i = elems.length; i--;)\n        cache[uniqueID(elems[i])] = overflowing;\n    return overflowing;\n}\n\nfunction getCache(el, x) {\n    return (x ? cacheX : cacheY)[uniqueID(el)];\n}\n\n//  (body)                (root)\n//         | hidden | visible | scroll |  auto  |\n// hidden  |   no   |    no   |   YES  |   YES  |\n// visible |   no   |   YES   |   YES  |   YES  |\n// scroll  |   no   |   YES   |   YES  |   YES  |\n// auto    |   no   |   YES   |   YES  |   YES  |\n\nfunction overflowingAncestor(el, x) {\n    var elems = [];\n    var body = document.body;\n    var rootScrollHeight = root.scrollHeight;\n    var rootScrollWidth  = root.scrollWidth;\n    do {\n        var cached = getCache(el, x);\n        if (cached) {\n            return setCache(elems, cached, x);\n        }\n        elems.push(el);\n        if (x && rootScrollWidth  === el.scrollWidth ||\n           !x && rootScrollHeight === el.scrollHeight) {\n            var topOverflowsNotHidden = overflowNotHidden(root, x) && overflowNotHidden(body, x);\n            var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root, x);\n            if (isFrame && isContentOverflowing(root, x) || \n               !isFrame && isOverflowCSS) {\n                return setCache(elems, getScrollRoot(), x); \n            }\n        } else if (isContentOverflowing(el, x) && overflowAutoOrScroll(el, x)) {\n            return setCache(elems, el, x);\n        }\n    } while ((el = el.parentElement));\n}\n\nfunction isContentOverflowing(el, x) {\n    return x ? (el.clientWidth  + 10 < el.scrollWidth) \n             : (el.clientHeight + 10 < el.scrollHeight);\n}\n\nfunction computedOverflow(el, x) {\n    var property = x ? 'overflow-x' : 'overflow-y';\n    return getComputedStyle(el, '').getPropertyValue(property);\n}\n\n// typically for <body> and <html>\nfunction overflowNotHidden(el, x) {\n    return (computedOverflow(el, x) != 'hidden');\n}\n\n// for all other elements\nfunction overflowAutoOrScroll(el, x) {\n    return /^(scroll|auto)$/.test(computedOverflow(el, x));\n}\n\n// for all other elements\nfunction isScrollBehaviorSmooth(el) {\n    var id = uniqueID(el);\n    if (smoothBehaviorForElement[id] == null) {\n        var scrollBehavior = getComputedStyle(el, '')['scroll-behavior'];\n        smoothBehaviorForElement[id] = ('smooth' == scrollBehavior);\n    }\n    return smoothBehaviorForElement[id];\n}\n\nfunction postScrollToParent(deltaX, deltaY) {\n    parent.postMessage({\n        deltaX: deltaX,\n        deltaY: deltaY,\n        SS: 'SmoothScroll'\n    }, '*');\n}\n\n\n/***********************************************\n * HELPERS\n ***********************************************/\n\nfunction addEvent(type, fn) {\n    window.addEventListener(type, fn, false);\n}\n\nfunction removeEvent(type, fn) {\n    window.removeEventListener(type, fn, false);  \n}\n\nfunction isNodeName(el, tag) {\n    return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase();\n}\n\nfunction directionCheck(x, y) {\n    x = (x > 0) ? 1 : -1;\n    y = (y > 0) ? 1 : -1;\n    if (direction.x !== x || direction.y !== y) {\n        direction.x = x;\n        direction.y = y;\n        que = [];\n        lastScroll = 0;\n        window.cancelAnimationFrame(pending);\n        pending = null;\n    }\n}\n\nfunction isTouchpad(deltaY) {\n    if (!deltaY) return;\n    if (!deltaBuffer.length) {\n        deltaBuffer = [deltaY, deltaY, deltaY];\n    }\n    deltaY = Math.abs(deltaY);\n    deltaBuffer.push(deltaY);\n    deltaBuffer.shift();\n    clearTimeout(deltaBufferTimer);\n    deltaBufferTimer = setTimeout(function () {\n        chrome.storage.local.set({ deltaBuffer: deltaBuffer });\n    }, 1000);\n    var dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64 \n    return !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100) && !dpiScaledWheelDelta;\n} \n\nfunction isDivisible(n, divisor) {\n    return (Math.floor(n / divisor) == n / divisor);\n}\n\nfunction allDeltasDivisableBy(divisor) {\n    return (isDivisible(deltaBuffer[0], divisor) &&\n            isDivisible(deltaBuffer[1], divisor) &&\n            isDivisible(deltaBuffer[2], divisor));\n}\n\nchrome.storage.local.get('deltaBuffer', function (stored) {\n    if (stored.deltaBuffer) {\n        deltaBuffer = stored.deltaBuffer;\n    }\n});\n\nfunction isInsideYoutubeVideo(event) {\n    var elem = event.target;\n    var isControl = false;\n    if (document.URL.indexOf ('www.youtube.com/watch') != -1) {\n        do {\n            isControl = (elem.classList && \n                         elem.classList.contains('html5-video-controls'));\n            if (isControl) break;\n        } while ((elem = elem.parentNode));\n    }\n    return isControl;\n}\n\nfunction getScrollRoot() {\n    return document.scrollingElement || document.body; // scrolling root in WebKit\n}\n\n/***********************************************\n * PULSE (by Michael Herf)\n ***********************************************/\n \n/**\n * Viscous fluid with a pulse for part and decay for the rest.\n * - Applies a fixed force over an interval (a damped acceleration), and\n * - Lets the exponential bleed away the velocity over a longer interval\n * - Michael Herf, http://stereopsis.com/stopping/\n */\nfunction pulse_(x) {\n    var val, start, expx;\n    // test\n    x = x * options.pulseScale;\n    if (x < 1) { // acceleartion\n        val = x - (1 - Math.exp(-x));\n    } else {     // tail\n        // the previous animation ended here:\n        start = Math.exp(-1);\n        // simple viscous drag\n        x -= 1;\n        expx = 1 - Math.exp(-x);\n        val = start + (expx * (1 - start));\n    }\n    return val * options.pulseNormalize;\n}\n\nfunction pulse(x) {\n    if (x >= 1) return 1;\n    if (x <= 0) return 0;\n\n    if (options.pulseNormalize == 1) {\n        options.pulseNormalize /= pulse_(1);\n    }\n    return pulse_(x);\n}\n\n// new standard wheel event from Chrome 31+\nvar wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; \n\naddEvent(wheelEvent, wheel);\naddEvent('mousedown', mousedown);\naddEvent('keydown', keydown);\naddEvent('load', loaded);\n"
  }
]