├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── changelog.html ├── css ├── bootstrap-treeview.min.css ├── bootstrap.min.css ├── ekill.css └── options.css ├── example.gif ├── fonts └── glyphicons-halflings-regular.woff2 ├── js ├── background.js ├── bootstrap-treeview.min.js ├── changelog.js ├── ekill.js ├── jquery.slim.min.js ├── options.js └── testable.js ├── manifest.json ├── options.html ├── package.sh ├── skull-and-bones-128.png ├── skull-and-bones-16-light.png ├── skull-and-bones-16.png ├── skull-and-bones-48-light.png ├── skull-and-bones-48.png ├── skull-and-bones.svg └── test ├── README.md ├── chai-shallow-deep-equal.js ├── index.html └── test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.gif filter=lfs diff=lfs merge=lfs -text 2 | *.png filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ekill.zip 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 René Hansen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ekill 2 | 3 | It's like [**xkill**](https://en.wikipedia.org/wiki/Xkill), but for annoying web pages instead. 4 | 5 | Chrome and Firefox plugin for quickly getting rid of elements on a web page. 6 | 7 | ## Installation 8 | 9 | - [Chrome web store](https://chrome.google.com/webstore/detail/ekill/lcgdpfaiipaelnpepigdafiogebaeedg?hl=en) 10 | - [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/ekill/) 11 | 12 | ![Example](https://media.githubusercontent.com/media/rhardih/ekill/master/example.gif) 13 | 14 | ## Keyboard shortcut 15 | 16 | By default **ekill** is toggled with *ctrl+shift+k*, but this can be modified at will. 17 | 18 | Go to [chrome://extensions/shortcuts](chrome://extensions/shortcuts), find the item labeled "ekill" and set it to whatever is most convenient. 19 | 20 | 21 | ## Options 22 | 23 | ### Grudge (Experimental) 24 | 25 | Turning this feature on, will let ekill hold a grudge against offending 26 | elements. 27 | 28 | By keeping a record of killed of elements on a per page basis, ekill will try 29 | it's best to remove these elements on subsequent visits to the same page. 30 | 31 | A rudimentary ui for toggling *Grudge*, as well as listing and editing the hit 32 | list, is included in the options page. 33 | 34 | ## License 35 | 36 | MIT: http://rhardih.mit-license.org 37 | 38 | ## Changelog 39 | 40 | **1.8** 41 | 42 | - Changes the default hot-key from Ctrl+k to Ctrl+Shift+k. 43 | 44 | **1.7** 45 | 46 | - Adds a kill count badge to the extension icon. 47 | 48 | **1.6** 49 | 50 | - Adds the Grudge feature 51 | - Adds options page to control Grudge 52 | - Adds changelog notifications on version upgrades 53 | 54 | **1.5** 55 | 56 | - Adds light icons for Firefox dark theme. 57 | 58 | **1.4** 59 | 60 | - Adds support for Firefox 61 | 62 | **1.1 - 1.3** 63 | 64 | - Introduces ability to toggle on/off, as well as dismiss with Esc key 65 | - Permissions narrowed to *activeTab* only 66 | - Targets anything with *role=button* 67 | 68 | **1.0** 69 | 70 | - Initial version 71 | -------------------------------------------------------------------------------- /changelog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ekill - changes 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 |

ekill

17 |

18 |
19 |
20 |

Changelog

21 | 22 |

1.9

23 |
    24 |
  • Adds support for killing content that is in iframes.
    This 25 | addition is primarily intended to target misc. chat widgets.
  • 26 |
27 | 28 |

1.8

29 | 45 | 46 |

1.7

47 |
    48 |
  • 49 | Adds a kill count badge to the extension icon. 50 |
  • 51 |
52 | 53 |

1.6

54 |
    55 |
  • 56 | Adds the Grudge feature. 57 |
  • 58 |
  • 59 | Adds options page to control Grudge. 60 |
  • 61 |
  • 62 | Adds changelog notifications on version upgrades. 63 |
  • 64 |
65 | 66 |

1.5

67 |
    68 |
  • 69 | Adds light icons for Firefox dark theme. 70 |
  • 71 |
72 | 73 |

1.4

74 |
    75 |
  • 76 | Adds support for Firefox. 77 |
  • 78 |
79 | 80 |

1.1 - 1.3

81 |
    82 |
  • 83 | Introduces ability to toggle on/off, as well as dismiss with Esc 84 | key. 85 |
  • 86 |
  • 87 | Permissions narrowed to *activeTab* only. 88 |
  • 89 |
  • 90 | Targets anything with *role=button*. 91 |
  • 92 |
93 | 94 |

1.0

95 |
    96 |
  • Initial version.
  • 97 |
98 |
99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 |
107 | 108 | 109 | -------------------------------------------------------------------------------- /css/bootstrap-treeview.min.css: -------------------------------------------------------------------------------- 1 | .treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed} -------------------------------------------------------------------------------- /css/ekill.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ekill-icon-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15'%3E%3Cpath d='M14 14.7c-.1.2-.3.3-.5.3h-.2l-5.8-2.4L1.7 15c-.3.1-.5 0-.6-.3a.5.5 0 0 1 .2-.7l4.9-2-4.9-2a.5.5 0 1 1 .4-1l5.8 2.5L13.3 9a.5.5 0 1 1 .4.9l-4.9 2 4.8 2a.5.5 0 0 1 .3.7zM12 4.2v.5c0 .2 0 .4-.2.6L10 7v1.2c0 .2-.1.4-.3.4l-2.2.9-2.2-.9a.5.5 0 0 1-.3-.4V7L3.2 5.3a1 1 0 0 1-.2-.6v-.5C3.2 2 4.9.2 7.1 0H8c2.2.2 4 2 4.1 4.2zM6 4a1 1 0 1 0-2 0 1 1 0 0 0 2 0zm1 3a.5.5 0 0 0-1 0v.5a.5.5 0 0 0 1 0V7zm2 0a.5.5 0 0 0-1 0v.5a.5.5 0 0 0 1 0V7zm2-3a1 1 0 1 0-2 0 1 1 0 0 0 2 0z'/%3E%3C/svg%3E") 3 | } 4 | 5 | .ekill-cursor, 6 | .ekill-cursor a, 7 | .ekill-cursor input, 8 | .ekill-cursor select, 9 | .ekill-cursor button, 10 | .ekill-cursor div[role=button] { 11 | cursor: var(--ekill-icon-url), crosshair !important; 12 | } 13 | 14 | .ekill { 15 | filter: opacity(0.2); 16 | box-shadow: inset 0px 0px 25px rgba(255,0,0,.5); 17 | } 18 | 19 | .ekill-overlay { 20 | position: absolute; 21 | z-index: 2147483647; 22 | background: 23 | no-repeat center/70% var(--ekill-icon-url), 24 | linear-gradient( 25 | rgba(255, 255, 255, 0.85), 26 | rgba(255, 255, 255, 0.85) 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /css/options.css: -------------------------------------------------------------------------------- 1 | /* used in both options.html and changelog.html */ 2 | 3 | #logo { 4 | margin: 20px auto; /* Same as h1 */ 5 | width: 100%; 6 | max-width: 200px; 7 | } 8 | 9 | .badge { 10 | vertical-align: super; 11 | } 12 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9e1cdbb8daeedefb7a2163fe43ef3af7658648305c82b6ae3457f788fc56d668 3 | size 2322538 4 | -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhardih/ekill/fa1c474cced431e41ad998bd396219e6b7ac2707/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /js/background.js: -------------------------------------------------------------------------------- 1 | (c => { 2 | c.browserAction.setBadgeBackgroundColor({ color: "#000000" }); 3 | 4 | c.storage.sync.get({ 5 | "ekillVersion": { 6 | shownChangesFor: "0.0" 7 | }, 8 | "ekillSettings": { 9 | holdsGrudge: "false" 10 | } 11 | }, item => { 12 | if (c.runtime.lastError) { 13 | console.error(c.runtime.lastError); 14 | } else { 15 | let ekillVersion = item.ekillVersion; 16 | let ekillSettings = item.ekillSettings; 17 | 18 | let showChanges = ekill.isNewerVersion( 19 | c.runtime.getManifest().version, 20 | ekillVersion.shownChangesFor); 21 | 22 | if (showChanges) { 23 | c.browserAction.setBadgeText({text: "New"}); 24 | } 25 | 26 | c.browserAction.onClicked.addListener(tab => { 27 | if (showChanges) { 28 | c.tabs.create({ url: c.extension.getURL("changelog.html") }); 29 | 30 | showChanges = false; 31 | c.browserAction.setBadgeText({text: ""}); 32 | 33 | } else { 34 | c.tabs.sendMessage(tab.id, "toggle"); 35 | } 36 | }); 37 | 38 | if (ekillSettings.holdsGrudge === "true" && !showChanges) { 39 | let updateKillCounter = tabId => { 40 | c.tabs.sendMessage(tabId, "queryKillCount", {}, response => { 41 | if (c.runtime.lastError) { 42 | console.error(c.runtime.lastError); 43 | } else { 44 | if (response === undefined || response === 0) { 45 | c.browserAction.setBadgeText({text: ""}); 46 | } else { 47 | c.browserAction.setBadgeText({text: response.toString()}); 48 | } 49 | } 50 | }); 51 | } 52 | 53 | // Check if a kill count was set when switching to a tab, and either 54 | // clear the badge or re-display the count 55 | c.tabs.onActivated.addListener((activeInfo) => { 56 | updateKillCounter(activeInfo.tabId); 57 | }); 58 | 59 | let msgHandler = (message, sender, sendResponse) => { 60 | if (message === "killCountUpdated") { 61 | updateKillCounter(sender.tab.id); 62 | } else if (message === "pageLoading") { 63 | // Clear badge on page reloads 64 | c.browserAction.setBadgeText({text: ""}); 65 | } 66 | }; 67 | 68 | c.runtime.onMessage.addListener(msgHandler); 69 | } 70 | } 71 | }); 72 | })(chrome); 73 | -------------------------------------------------------------------------------- /js/bootstrap-treeview.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b,c,d){"use strict";var e="treeview",f={};f.settings={injectStyle:!0,levels:2,expandIcon:"glyphicon glyphicon-plus",collapseIcon:"glyphicon glyphicon-minus",emptyIcon:"glyphicon",nodeIcon:"",selectedIcon:"",checkedIcon:"glyphicon glyphicon-check",uncheckedIcon:"glyphicon glyphicon-unchecked",color:d,backColor:d,borderColor:d,onhoverColor:"#F5F5F5",selectedColor:"#FFFFFF",selectedBackColor:"#428bca",searchResultColor:"#D9534F",searchResultBackColor:d,enableLinks:!1,highlightSelected:!0,highlightSearchResults:!0,showBorder:!0,showIcon:!0,showCheckbox:!1,showTags:!1,multiSelect:!1,onNodeChecked:d,onNodeCollapsed:d,onNodeDisabled:d,onNodeEnabled:d,onNodeExpanded:d,onNodeSelected:d,onNodeUnchecked:d,onNodeUnselected:d,onSearchComplete:d,onSearchCleared:d},f.options={silent:!1,ignoreChildren:!1},f.searchOptions={ignoreCase:!0,exactMatch:!1,revealResults:!0};var g=function(b,c){return this.$element=a(b),this.elementId=b.id,this.styleId=this.elementId+"-style",this.init(c),{options:this.options,init:a.proxy(this.init,this),remove:a.proxy(this.remove,this),getNode:a.proxy(this.getNode,this),getParent:a.proxy(this.getParent,this),getSiblings:a.proxy(this.getSiblings,this),getSelected:a.proxy(this.getSelected,this),getUnselected:a.proxy(this.getUnselected,this),getExpanded:a.proxy(this.getExpanded,this),getCollapsed:a.proxy(this.getCollapsed,this),getChecked:a.proxy(this.getChecked,this),getUnchecked:a.proxy(this.getUnchecked,this),getDisabled:a.proxy(this.getDisabled,this),getEnabled:a.proxy(this.getEnabled,this),selectNode:a.proxy(this.selectNode,this),unselectNode:a.proxy(this.unselectNode,this),toggleNodeSelected:a.proxy(this.toggleNodeSelected,this),collapseAll:a.proxy(this.collapseAll,this),collapseNode:a.proxy(this.collapseNode,this),expandAll:a.proxy(this.expandAll,this),expandNode:a.proxy(this.expandNode,this),toggleNodeExpanded:a.proxy(this.toggleNodeExpanded,this),revealNode:a.proxy(this.revealNode,this),checkAll:a.proxy(this.checkAll,this),checkNode:a.proxy(this.checkNode,this),uncheckAll:a.proxy(this.uncheckAll,this),uncheckNode:a.proxy(this.uncheckNode,this),toggleNodeChecked:a.proxy(this.toggleNodeChecked,this),disableAll:a.proxy(this.disableAll,this),disableNode:a.proxy(this.disableNode,this),enableAll:a.proxy(this.enableAll,this),enableNode:a.proxy(this.enableNode,this),toggleNodeDisabled:a.proxy(this.toggleNodeDisabled,this),search:a.proxy(this.search,this),clearSearch:a.proxy(this.clearSearch,this)}};g.prototype.init=function(b){this.tree=[],this.nodes=[],b.data&&("string"==typeof b.data&&(b.data=a.parseJSON(b.data)),this.tree=a.extend(!0,[],b.data),delete b.data),this.options=a.extend({},f.settings,b),this.destroy(),this.subscribeEvents(),this.setInitialStates({nodes:this.tree},0),this.render()},g.prototype.remove=function(){this.destroy(),a.removeData(this,e),a("#"+this.styleId).remove()},g.prototype.destroy=function(){this.initialized&&(this.$wrapper.remove(),this.$wrapper=null,this.unsubscribeEvents(),this.initialized=!1)},g.prototype.unsubscribeEvents=function(){this.$element.off("click"),this.$element.off("nodeChecked"),this.$element.off("nodeCollapsed"),this.$element.off("nodeDisabled"),this.$element.off("nodeEnabled"),this.$element.off("nodeExpanded"),this.$element.off("nodeSelected"),this.$element.off("nodeUnchecked"),this.$element.off("nodeUnselected"),this.$element.off("searchComplete"),this.$element.off("searchCleared")},g.prototype.subscribeEvents=function(){this.unsubscribeEvents(),this.$element.on("click",a.proxy(this.clickHandler,this)),"function"==typeof this.options.onNodeChecked&&this.$element.on("nodeChecked",this.options.onNodeChecked),"function"==typeof this.options.onNodeCollapsed&&this.$element.on("nodeCollapsed",this.options.onNodeCollapsed),"function"==typeof this.options.onNodeDisabled&&this.$element.on("nodeDisabled",this.options.onNodeDisabled),"function"==typeof this.options.onNodeEnabled&&this.$element.on("nodeEnabled",this.options.onNodeEnabled),"function"==typeof this.options.onNodeExpanded&&this.$element.on("nodeExpanded",this.options.onNodeExpanded),"function"==typeof this.options.onNodeSelected&&this.$element.on("nodeSelected",this.options.onNodeSelected),"function"==typeof this.options.onNodeUnchecked&&this.$element.on("nodeUnchecked",this.options.onNodeUnchecked),"function"==typeof this.options.onNodeUnselected&&this.$element.on("nodeUnselected",this.options.onNodeUnselected),"function"==typeof this.options.onSearchComplete&&this.$element.on("searchComplete",this.options.onSearchComplete),"function"==typeof this.options.onSearchCleared&&this.$element.on("searchCleared",this.options.onSearchCleared)},g.prototype.setInitialStates=function(b,c){if(b.nodes){c+=1;var d=b,e=this;a.each(b.nodes,function(a,b){b.nodeId=e.nodes.length,b.parentId=d.nodeId,b.hasOwnProperty("selectable")||(b.selectable=!0),b.state=b.state||{},b.state.hasOwnProperty("checked")||(b.state.checked=!1),b.state.hasOwnProperty("disabled")||(b.state.disabled=!1),b.state.hasOwnProperty("expanded")||(!b.state.disabled&&c0?b.state.expanded=!0:b.state.expanded=!1),b.state.hasOwnProperty("selected")||(b.state.selected=!1),e.nodes.push(b),b.nodes&&e.setInitialStates(b,c)})}},g.prototype.clickHandler=function(b){this.options.enableLinks||b.preventDefault();var c=a(b.target),d=this.findNode(c);if(d&&!d.state.disabled){var e=c.attr("class")?c.attr("class").split(" "):[];-1!==e.indexOf("expand-icon")?(this.toggleExpandedState(d,f.options),this.render()):-1!==e.indexOf("check-icon")?(this.toggleCheckedState(d,f.options),this.render()):(d.selectable?this.toggleSelectedState(d,f.options):this.toggleExpandedState(d,f.options),this.render())}},g.prototype.findNode=function(a){var b=a.closest("li.list-group-item").attr("data-nodeid"),c=this.nodes[b];return c||console.log("Error: node does not exist"),c},g.prototype.toggleExpandedState=function(a,b){a&&this.setExpandedState(a,!a.state.expanded,b)},g.prototype.setExpandedState=function(b,c,d){c!==b.state.expanded&&(c&&b.nodes?(b.state.expanded=!0,d.silent||this.$element.trigger("nodeExpanded",a.extend(!0,{},b))):c||(b.state.expanded=!1,d.silent||this.$element.trigger("nodeCollapsed",a.extend(!0,{},b)),b.nodes&&!d.ignoreChildren&&a.each(b.nodes,a.proxy(function(a,b){this.setExpandedState(b,!1,d)},this))))},g.prototype.toggleSelectedState=function(a,b){a&&this.setSelectedState(a,!a.state.selected,b)},g.prototype.setSelectedState=function(b,c,d){c!==b.state.selected&&(c?(this.options.multiSelect||a.each(this.findNodes("true","g","state.selected"),a.proxy(function(a,b){this.setSelectedState(b,!1,d)},this)),b.state.selected=!0,d.silent||this.$element.trigger("nodeSelected",a.extend(!0,{},b))):(b.state.selected=!1,d.silent||this.$element.trigger("nodeUnselected",a.extend(!0,{},b))))},g.prototype.toggleCheckedState=function(a,b){a&&this.setCheckedState(a,!a.state.checked,b)},g.prototype.setCheckedState=function(b,c,d){c!==b.state.checked&&(c?(b.state.checked=!0,d.silent||this.$element.trigger("nodeChecked",a.extend(!0,{},b))):(b.state.checked=!1,d.silent||this.$element.trigger("nodeUnchecked",a.extend(!0,{},b))))},g.prototype.setDisabledState=function(b,c,d){c!==b.state.disabled&&(c?(b.state.disabled=!0,this.setExpandedState(b,!1,d),this.setSelectedState(b,!1,d),this.setCheckedState(b,!1,d),d.silent||this.$element.trigger("nodeDisabled",a.extend(!0,{},b))):(b.state.disabled=!1,d.silent||this.$element.trigger("nodeEnabled",a.extend(!0,{},b))))},g.prototype.render=function(){this.initialized||(this.$element.addClass(e),this.$wrapper=a(this.template.list),this.injectStyle(),this.initialized=!0),this.$element.empty().append(this.$wrapper.empty()),this.buildTree(this.tree,0)},g.prototype.buildTree=function(b,c){if(b){c+=1;var d=this;a.each(b,function(b,e){for(var f=a(d.template.item).addClass("node-"+d.elementId).addClass(e.state.checked?"node-checked":"").addClass(e.state.disabled?"node-disabled":"").addClass(e.state.selected?"node-selected":"").addClass(e.searchResult?"search-result":"").attr("data-nodeid",e.nodeId).attr("style",d.buildStyleOverride(e)),g=0;c-1>g;g++)f.append(d.template.indent);var h=[];if(e.nodes?(h.push("expand-icon"),h.push(e.state.expanded?d.options.collapseIcon:d.options.expandIcon)):h.push(d.options.emptyIcon),f.append(a(d.template.icon).addClass(h.join(" "))),d.options.showIcon){var h=["node-icon"];h.push(e.icon||d.options.nodeIcon),e.state.selected&&(h.pop(),h.push(e.selectedIcon||d.options.selectedIcon||e.icon||d.options.nodeIcon)),f.append(a(d.template.icon).addClass(h.join(" ")))}if(d.options.showCheckbox){var h=["check-icon"];h.push(e.state.checked?d.options.checkedIcon:d.options.uncheckedIcon),f.append(a(d.template.icon).addClass(h.join(" ")))}return f.append(d.options.enableLinks?a(d.template.link).attr("href",e.href).append(e.text):e.text),d.options.showTags&&e.tags&&a.each(e.tags,function(b,c){f.append(a(d.template.badge).append(c))}),d.$wrapper.append(f),e.nodes&&e.state.expanded&&!e.state.disabled?d.buildTree(e.nodes,c):void 0})}},g.prototype.buildStyleOverride=function(a){if(a.state.disabled)return"";var b=a.color,c=a.backColor;return this.options.highlightSelected&&a.state.selected&&(this.options.selectedColor&&(b=this.options.selectedColor),this.options.selectedBackColor&&(c=this.options.selectedBackColor)),this.options.highlightSearchResults&&a.searchResult&&!a.state.disabled&&(this.options.searchResultColor&&(b=this.options.searchResultColor),this.options.searchResultBackColor&&(c=this.options.searchResultBackColor)),"color:"+b+";background-color:"+c+";"},g.prototype.injectStyle=function(){this.options.injectStyle&&!c.getElementById(this.styleId)&&a('").appendTo("head")},g.prototype.buildStyle=function(){var a=".node-"+this.elementId+"{";return this.options.color&&(a+="color:"+this.options.color+";"),this.options.backColor&&(a+="background-color:"+this.options.backColor+";"),this.options.showBorder?this.options.borderColor&&(a+="border:1px solid "+this.options.borderColor+";"):a+="border:none;",a+="}",this.options.onhoverColor&&(a+=".node-"+this.elementId+":not(.node-disabled):hover{background-color:"+this.options.onhoverColor+";}"),this.css+a},g.prototype.template={list:'',item:'
  • ',indent:'',icon:'',link:'',badge:''},g.prototype.css=".treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}",g.prototype.getNode=function(a){return this.nodes[a]},g.prototype.getParent=function(a){var b=this.identifyNode(a);return this.nodes[b.parentId]},g.prototype.getSiblings=function(a){var b=this.identifyNode(a),c=this.getParent(b),d=c?c.nodes:this.tree;return d.filter(function(a){return a.nodeId!==b.nodeId})},g.prototype.getSelected=function(){return this.findNodes("true","g","state.selected")},g.prototype.getUnselected=function(){return this.findNodes("false","g","state.selected")},g.prototype.getExpanded=function(){return this.findNodes("true","g","state.expanded")},g.prototype.getCollapsed=function(){return this.findNodes("false","g","state.expanded")},g.prototype.getChecked=function(){return this.findNodes("true","g","state.checked")},g.prototype.getUnchecked=function(){return this.findNodes("false","g","state.checked")},g.prototype.getDisabled=function(){return this.findNodes("true","g","state.disabled")},g.prototype.getEnabled=function(){return this.findNodes("false","g","state.disabled")},g.prototype.selectNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setSelectedState(a,!0,b)},this)),this.render()},g.prototype.unselectNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setSelectedState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeSelected=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleSelectedState(a,b)},this)),this.render()},g.prototype.collapseAll=function(b){var c=this.findNodes("true","g","state.expanded");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setExpandedState(a,!1,b)},this)),this.render()},g.prototype.collapseNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setExpandedState(a,!1,b)},this)),this.render()},g.prototype.expandAll=function(b){if(b=a.extend({},f.options,b),b&&b.levels)this.expandLevels(this.tree,b.levels,b);else{var c=this.findNodes("false","g","state.expanded");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setExpandedState(a,!0,b)},this))}this.render()},g.prototype.expandNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setExpandedState(a,!0,b),a.nodes&&b&&b.levels&&this.expandLevels(a.nodes,b.levels-1,b)},this)),this.render()},g.prototype.expandLevels=function(b,c,d){d=a.extend({},f.options,d),a.each(b,a.proxy(function(a,b){this.setExpandedState(b,c>0?!0:!1,d),b.nodes&&this.expandLevels(b.nodes,c-1,d)},this))},g.prototype.revealNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){for(var c=this.getParent(a);c;)this.setExpandedState(c,!0,b),c=this.getParent(c)},this)),this.render()},g.prototype.toggleNodeExpanded=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleExpandedState(a,b)},this)),this.render()},g.prototype.checkAll=function(b){var c=this.findNodes("false","g","state.checked");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setCheckedState(a,!0,b)},this)),this.render()},g.prototype.checkNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setCheckedState(a,!0,b)},this)),this.render()},g.prototype.uncheckAll=function(b){var c=this.findNodes("true","g","state.checked");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setCheckedState(a,!1,b)},this)),this.render()},g.prototype.uncheckNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setCheckedState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeChecked=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleCheckedState(a,b)},this)),this.render()},g.prototype.disableAll=function(b){var c=this.findNodes("false","g","state.disabled");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setDisabledState(a,!0,b)},this)),this.render()},g.prototype.disableNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!0,b)},this)),this.render()},g.prototype.enableAll=function(b){var c=this.findNodes("true","g","state.disabled");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setDisabledState(a,!1,b)},this)),this.render()},g.prototype.enableNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeDisabled=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!a.state.disabled,b)},this)),this.render()},g.prototype.forEachIdentifier=function(b,c,d){c=a.extend({},f.options,c),b instanceof Array||(b=[b]),a.each(b,a.proxy(function(a,b){d(this.identifyNode(b),c)},this))},g.prototype.identifyNode=function(a){return"number"==typeof a?this.nodes[a]:a},g.prototype.search=function(b,c){c=a.extend({},f.searchOptions,c),this.clearSearch({render:!1});var d=[];if(b&&b.length>0){c.exactMatch&&(b="^"+b+"$");var e="g";c.ignoreCase&&(e+="i"),d=this.findNodes(b,e),a.each(d,function(a,b){b.searchResult=!0})}return c.revealResults?this.revealNode(d):this.render(),this.$element.trigger("searchComplete",a.extend(!0,{},d)),d},g.prototype.clearSearch=function(b){b=a.extend({},{render:!0},b);var c=a.each(this.findNodes("true","g","searchResult"),function(a,b){b.searchResult=!1});b.render&&this.render(),this.$element.trigger("searchCleared",a.extend(!0,{},c))},g.prototype.findNodes=function(b,c,d){c=c||"g",d=d||"text";var e=this;return a.grep(this.nodes,function(a){var f=e.getNodeValue(a,d);return"string"==typeof f?f.match(new RegExp(b,c)):void 0})},g.prototype.getNodeValue=function(a,b){var c=b.indexOf(".");if(c>0){var e=a[b.substring(0,c)],f=b.substring(c+1,b.length);return this.getNodeValue(e,f)}return a.hasOwnProperty(b)?a[b].toString():d};var h=function(a){b.console&&b.console.error(a)};a.fn[e]=function(b,c){var d;return this.each(function(){var f=a.data(this,e);"string"==typeof b?f?a.isFunction(f[b])&&"_"!==b.charAt(0)?(c instanceof Array||(c=[c]),d=f[b].apply(f,c)):h("No such method : "+b):h("Not initialized, can not call method : "+b):"boolean"==typeof b?d=f:a.data(this,e,new g(this,a.extend(!0,{},b)))}),d||this}}(jQuery,window,document); -------------------------------------------------------------------------------- /js/changelog.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', _ => { 2 | let stringVersion = chrome.runtime.getManifest().version.toString(); 3 | $("#version").text(stringVersion); 4 | 5 | chrome.storage.sync.get({ 6 | "ekillVersion": { 7 | shownChangesFor: "0.0" 8 | } 9 | }, item => { 10 | if (chrome.runtime.lastError) { 11 | console.error(chrome.runtime.lastError); 12 | } else { 13 | let ekillVersion = item.ekillVersion; 14 | 15 | // Mark each version header not previously show with a "New" badge 16 | $("h2.version-header").each((index, header) => { 17 | let showBadge = ekill.isNewerVersion( 18 | $(header).data("version").toString(), 19 | ekillVersion.shownChangesFor); 20 | 21 | if (showBadge) { 22 | $(header).html(`${$(header).text()} New`); 23 | } 24 | }); 25 | 26 | // Update 'shownChangesFor' to indicate newly shown version 27 | item.ekillVersion.shownChangesFor = stringVersion; 28 | 29 | chrome.storage.sync.set(item, _ => { 30 | if (chrome.runtime.lastError) { 31 | console.error(chrome.runtime.lastError); 32 | } 33 | }); 34 | } 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /js/ekill.js: -------------------------------------------------------------------------------- 1 | ((c, d, l, w) => { 2 | c.runtime.sendMessage('pageLoading'); 3 | let ekill = w.ekill; 4 | 5 | let contentAction = settings => { 6 | let killCount = 0; 7 | 8 | // No need to wait for a 'DOMContentLoaded' event since the manifest 9 | // specifies: 10 | // 11 | // "run_at": "document_end" 12 | // 13 | if (settings.holdsGrudge === "true") { 14 | c.storage.local.get({ 15 | "ekillHitlistV2": "{}" 16 | }, item => { 17 | if (c.runtime.lastError) { 18 | console.error(c.runtime.lastError); 19 | } else { 20 | // Remove all elements ekill currently holds a grudge against on the 21 | // current page 22 | 23 | let hitList = JSON.parse(item.ekillHitlistV2); 24 | let paths = hitList[l.hostname]; 25 | 26 | if (paths !== undefined) { 27 | let selectors = []; 28 | 29 | if (paths[l.pathname] !== undefined) { 30 | paths[l.pathname].forEach(item => { 31 | selectors.push(item.selector); 32 | }); 33 | } 34 | 35 | if (paths["*"] !== undefined) { 36 | paths["*"].forEach(item => { 37 | selectors.push(item.selector); 38 | }); 39 | } 40 | 41 | if (selectors.length !== 0) { 42 | killCount = 0; 43 | 44 | selectors.forEach(s => { 45 | // We have to consider, that the hit might not be present in the 46 | // page until later, so we null guard it here 47 | let target = document.querySelector(s); 48 | if (target !== null) { 49 | target.remove() 50 | killCount++; 51 | } 52 | }); 53 | 54 | c.runtime.sendMessage('killCountUpdated'); 55 | 56 | // If not all selectors had a match, maybe some of them targets 57 | // elements that load onto the page later. 58 | // 59 | // In that case we monitor page changes 60 | // 61 | // (Obviously it can simply be that the page changed as well.) 62 | if (killCount !== selectors.length) { 63 | console.info("ekill still holds a grudge and will lie in wait..."); 64 | 65 | let body = d.querySelector('body'); 66 | let config = { attributes: true, childList: true, subtree: true }; 67 | let observer = new MutationObserver((mutationsList, observer) => { 68 | selectors.forEach(s => { 69 | let target = document.querySelector(s); 70 | if (target !== null) { 71 | target.remove() 72 | killCount++; 73 | } 74 | 75 | // When all targets have been found and killed, we can be at 76 | // peace 77 | if (killCount === selectors.length) { 78 | observer.disconnect(); 79 | console.info("ekill satisfied"); 80 | } 81 | }); 82 | 83 | c.runtime.sendMessage('killCountUpdated'); 84 | }); 85 | 86 | observer.observe(body, config); 87 | } 88 | } 89 | } 90 | } 91 | }); 92 | 93 | let msgHandler = (message, sender, sendResponse) => { 94 | if (message === "queryKillCount") 95 | sendResponse(killCount); 96 | }; 97 | 98 | c.runtime.onMessage.addListener(msgHandler); 99 | } 100 | 101 | let active = false; 102 | let clickable = [ 103 | d.getElementsByTagName("a"), 104 | d.getElementsByTagName("button"), 105 | d.querySelectorAll('[role=button]'), 106 | ]; 107 | let iframeOverlays = []; 108 | 109 | let overHandler = e => { 110 | e.target.classList.add("ekill"); 111 | 112 | if (e.target.classList.contains("ekill-overlay")) { 113 | let iframe = iframeOverlays[e.target.dataset.index].iframe; 114 | iframe.classList.add("ekill"); 115 | } 116 | 117 | e.stopPropagation(); 118 | }; 119 | let outHandler = e => { 120 | e.target.classList.remove("ekill"); 121 | e.stopPropagation(); 122 | }; 123 | 124 | let saveRemovedElement = (element, callback) => { 125 | c.storage.local.get({ 126 | "ekillHitlistV2": "{}" 127 | }, item => { 128 | if (c.runtime.lastError) { 129 | console.error(c.runtime.lastError); 130 | } else { 131 | let hitList = JSON.parse(item.ekillHitlistV2); 132 | let hierarchy = ekill.generateElementHierarchy(element); 133 | 134 | try { 135 | let selector = ekill.elementHierarchyToDOMString(hierarchy); 136 | 137 | ekill.addHit(hitList, l.hostname, l.pathname, selector); 138 | 139 | c.storage.local.set({ 140 | "ekillHitlistV2": JSON.stringify(hitList) 141 | }, _ => { 142 | if (c.runtime.lastError) { 143 | console.error(c.runtime.lastError); 144 | } else { 145 | callback(); 146 | } 147 | }); 148 | } catch (e) { 149 | console.log("ekill: removed element not saved"); 150 | console.error(e); 151 | } 152 | } 153 | }); 154 | } 155 | 156 | let clickHandler = e => { 157 | disable(); 158 | 159 | let target = e.target; 160 | 161 | if (target.classList.contains("ekill-overlay")) { 162 | target = iframeOverlays[target.dataset.index].iframe; 163 | } 164 | 165 | if (settings.holdsGrudge === "true") { 166 | saveRemovedElement(target, () => target.remove()); 167 | } else { 168 | target.remove(); 169 | } 170 | 171 | e.preventDefault(); 172 | e.stopPropagation(); 173 | }; 174 | 175 | let keyHandler = e => { 176 | if (e.key === "Escape") { 177 | disable(); 178 | } 179 | } 180 | 181 | let enable = _ => { 182 | active = true; 183 | 184 | // override click handlers on any clickable element 185 | clickable.forEach(c => { 186 | for (var i = 0; i < c.length; i++) { 187 | c[i].onclickBackup = c[i].onclick; 188 | c[i].addEventListener("click", clickHandler); 189 | } 190 | }); 191 | 192 | let iframes = d.querySelectorAll('iframe'); 193 | 194 | iframes.forEach(i => { 195 | let overlay = d.createElement('div'); 196 | let iframeClientRect = i.getBoundingClientRect(); 197 | let offsetX = iframeClientRect.left + w.scrollX; 198 | let offsetY = iframeClientRect.top + w.scrollY; 199 | 200 | overlay.classList.add("ekill-overlay"); 201 | overlay.style.top = `${offsetY}px`; 202 | overlay.style.left = `${offsetX}px`; 203 | overlay.style.width = `${iframeClientRect.width}px`; 204 | overlay.style.height = `${iframeClientRect.height}px`; 205 | 206 | overlay.dataset.index = iframeOverlays.length; 207 | 208 | iframeOverlays.push({ 209 | iframe: i, 210 | overlay: overlay 211 | }); 212 | 213 | d.body.appendChild(overlay); 214 | }); 215 | 216 | d.documentElement.classList.add("ekill-cursor"); 217 | d.addEventListener("mouseover", overHandler); 218 | d.addEventListener("mouseout", outHandler); 219 | d.addEventListener("click", clickHandler); 220 | d.addEventListener("keydown", keyHandler, true); 221 | }; 222 | 223 | let disable = _ => { 224 | active = false; 225 | 226 | clickable.forEach(c => { 227 | for (var i = 0; i < c.length; i++) { 228 | c[i].removeEventListener("click", clickHandler); 229 | c[i].addEventListener("click", c[i].onclickBackup); 230 | delete c[i].onclickBackup; 231 | } 232 | }); 233 | 234 | iframeOverlays.forEach(o => o.overlay.remove()); 235 | 236 | d.documentElement.classList.remove("ekill-cursor"); 237 | 238 | // clean any orphaned hover applied class 239 | let orphan = d.querySelector('.ekill'); 240 | if (orphan !== null) { 241 | orphan.classList.remove("ekill"); 242 | } 243 | 244 | d.removeEventListener("mouseover", overHandler); 245 | d.removeEventListener("mouseout", outHandler); 246 | d.removeEventListener("click", clickHandler); 247 | d.removeEventListener("keydown", keyHandler, true); 248 | }; 249 | 250 | let msgHandler = (message, sender, sendResponse) => { 251 | if (message === "toggle") { 252 | if (active) { 253 | disable(); 254 | } else { 255 | enable(); 256 | } 257 | } 258 | }; 259 | 260 | let updateOverlayPositions = e => { 261 | iframeOverlays.forEach(o => { 262 | let iframe = o.iframe; 263 | let iframeClientRect = iframe.getBoundingClientRect(); 264 | let overlay = o.overlay; 265 | let offsetX = iframeClientRect.left + w.scrollX; 266 | let offsetY = iframeClientRect.top + w.scrollY; 267 | 268 | overlay.style.top = `${offsetY}px`; 269 | overlay.style.left = `${offsetX}px`; 270 | }); 271 | }; 272 | 273 | c.runtime.onMessage.addListener(msgHandler); 274 | w.addEventListener('scroll', updateOverlayPositions); 275 | w.addEventListener('resize', updateOverlayPositions); 276 | } 277 | 278 | // Note even though Firefox uses promises, it supports the 'chrome' object and 279 | // callbacks as well: 280 | // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities#Firefox_supports_both_chrome_and_browser_namespaces 281 | c.storage.sync.get({ 282 | "ekillSettings": { 283 | holdsGrudge: "false" 284 | } 285 | }, item => { 286 | if (c.runtime.lastError) { 287 | console.error(c.runtime.lastError); 288 | } else { 289 | contentAction(item.ekillSettings); 290 | } 291 | }); 292 | })(chrome, document, location, window); 293 | -------------------------------------------------------------------------------- /js/jquery.slim.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.3.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,u=n.push,s=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,d=f.toString,p=d.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},v=function e(t){return null!=t&&t===t.window},y={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in y)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function b(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var x="3.3.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector",w=function(e,t){return new w.fn.init(e,t)},C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:x,constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,u,s,l,c,f,d,p,h,g,v,y,m,b,x="sizzle"+1*new Date,w=e.document,C=0,T=0,E=ae(),N=ae(),k=ae(),A=function(e,t){return e===t&&(f=!0),0},D={}.hasOwnProperty,S=[],L=S.pop,j=S.push,q=S.push,O=S.slice,P=function(e,t){for(var n=0,r=e.length;n+~]|"+I+")"+I+"*"),_=new RegExp("="+I+"*([^\\]'\"]*?)"+I+"*\\]","g"),U=new RegExp(M),V=new RegExp("^"+R+"$"),X={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+B),PSEUDO:new RegExp("^"+M),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+I+"*(even|odd|(([+-]|)(\\d*)n|)"+I+"*(?:([+-]|)"+I+"*(\\d+)|))"+I+"*\\)|)","i"),bool:new RegExp("^(?:"+H+")$","i"),needsContext:new RegExp("^"+I+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+I+"*((?:-\\d)?\\d*)"+I+"*\\)|)(?=[^-]|$)","i")},Q=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,G=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,J=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+I+"?|("+I+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){d()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{q.apply(S=O.call(w.childNodes),w.childNodes),S[w.childNodes.length].nodeType}catch(e){q={apply:S.length?function(e,t){j.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,u,l,c,f,h,y,m=t&&t.ownerDocument,C=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==C&&9!==C&&11!==C)return r;if(!i&&((t?t.ownerDocument||t:w)!==p&&d(t),t=t||p,g)){if(11!==C&&(f=K.exec(e)))if(o=f[1]){if(9===C){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&b(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return q.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return q.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!k[e+" "]&&(!v||!v.test(e))){if(1!==C)m=t,y=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=x),u=(h=a(e)).length;while(u--)h[u]="#"+c+" "+ye(h[u]);y=h.join(","),m=J.test(e)&&ge(t.parentNode)||t}if(y)try{return q.apply(r,m.querySelectorAll(y)),r}catch(e){}finally{c===x&&t.removeAttribute("id")}}}return s(e.replace($,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function ue(e){return e[x]=!0,e}function se(e){var t=p.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function de(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pe(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return ue(function(t){return t=+t,ue(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},d=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==p&&9===a.nodeType&&a.documentElement?(p=a,h=p.documentElement,g=!o(p),w!==p&&(i=p.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=se(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=se(function(e){return e.appendChild(p.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=G.test(p.getElementsByClassName),n.getById=se(function(e){return h.appendChild(e).id=x,!p.getElementsByName||!p.getElementsByName(x).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},y=[],v=[],(n.qsa=G.test(p.querySelectorAll))&&(se(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+I+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+I+"*(?:value|"+H+")"),e.querySelectorAll("[id~="+x+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+x+"+*").length||v.push(".#.+[+~]")}),se(function(e){e.innerHTML="";var t=p.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+I+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(n.matchesSelector=G.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&se(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),y.push("!=",M)}),v=v.length&&new RegExp(v.join("|")),y=y.length&&new RegExp(y.join("|")),t=G.test(h.compareDocumentPosition),b=t||G.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===p||e.ownerDocument===w&&b(w,e)?-1:t===p||t.ownerDocument===w&&b(w,t)?1:c?P(c,e)-P(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],u=[t];if(!i||!o)return e===p?-1:t===p?1:i?-1:o?1:c?P(c,e)-P(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)u.unshift(n);while(a[r]===u[r])r++;return r?ce(a[r],u[r]):a[r]===w?-1:u[r]===w?1:0},p):p},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&d(e),t=t.replace(_,"='$1']"),n.matchesSelector&&g&&!k[t+" "]&&(!y||!y.test(t))&&(!v||!v.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,p,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==p&&d(e),b(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==p&&d(e);var i=r.attrHandle[t.toLowerCase()],o=i&&D.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(A),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:ue,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return X.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&U.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+I+")"+e+"("+I+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace(W," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),u="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,s){var l,c,f,d,p,h,g=o!==a?"nextSibling":"previousSibling",v=t.parentNode,y=u&&t.nodeName.toLowerCase(),m=!s&&!u,b=!1;if(v){if(o){while(g){d=t;while(d=d[g])if(u?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?v.firstChild:v.lastChild],a&&m){b=(p=(l=(c=(f=(d=v)[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===C&&l[1])&&l[2],d=p&&v.childNodes[p];while(d=++p&&d&&d[g]||(b=p=0)||h.pop())if(1===d.nodeType&&++b&&d===t){c[e]=[C,p,b];break}}else if(m&&(b=p=(l=(c=(f=(d=t)[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===C&&l[1]),!1===b)while(d=++p&&d&&d[g]||(b=p=0)||h.pop())if((u?d.nodeName.toLowerCase()===y:1===d.nodeType)&&++b&&(m&&((c=(f=d[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]=[C,b]),d===t))break;return(b-=i)===r||b%r==0&&b/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[x]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?ue(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=P(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:ue(function(e){var t=[],n=[],r=u(e.replace($,"$1"));return r[x]?ue(function(e,t,n,i){var o,a=r(e,null,i,[]),u=e.length;while(u--)(o=a[u])&&(e[u]=!(t[u]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:ue(function(e){return function(t){return oe(e,t).length>0}}),contains:ue(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:ue(function(e){return V.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:pe(!1),disabled:pe(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xe(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else y=we(y===a?y.splice(h,y.length):y),i?i(null,a,y,s):q.apply(a,y)})}function Te(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],u=a||r.relative[" "],s=a?1:0,c=me(function(e){return e===t},u,!0),f=me(function(e){return P(t,e)>-1},u,!0),d=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];s1&&be(d),s>1&&ye(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),n,s0,i=e.length>0,o=function(o,a,u,s,c){var f,h,v,y=0,m="0",b=o&&[],x=[],w=l,T=o||i&&r.find.TAG("*",c),E=C+=null==w?1:Math.random()||.1,N=T.length;for(c&&(l=a===p||a||c);m!==N&&null!=(f=T[m]);m++){if(i&&f){h=0,a||f.ownerDocument===p||(d(f),u=!g);while(v=e[h++])if(v(f,a||p,u)){s.push(f);break}c&&(C=E)}n&&((f=!v&&f)&&y--,o&&b.push(f))}if(y+=m,n&&m!==y){h=0;while(v=t[h++])v(b,x,a,u);if(o){if(y>0)while(m--)b[m]||x[m]||(x[m]=L.call(s));x=we(x)}q.apply(s,x),c&&!o&&x.length>0&&y+t.length>1&&oe.uniqueSort(s)}return c&&(C=E,l=w),b};return n?ue(o):o}return u=oe.compile=function(e,t){var n,r=[],i=[],o=k[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Te(t[n]))[x]?r.push(o):i.push(o);(o=k(e,Ee(i,r))).selector=e}return o},s=oe.select=function(e,t,n,i){var o,s,l,c,f,d="function"==typeof e&&e,p=!i&&a(e=d.selector||e);if(n=n||[],1===p.length){if((s=p[0]=p[0].slice(0)).length>2&&"ID"===(l=s[0]).type&&9===t.nodeType&&g&&r.relative[s[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;d&&(t=t.parentNode),e=e.slice(s.shift().value.length)}o=X.needsContext.test(e)?0:s.length;while(o--){if(l=s[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),J.test(s[0].type)&&ge(t.parentNode)||t))){if(s.splice(o,1),!(e=i.length&&ye(s)))return q.apply(n,i),n;break}}}return(d||u(e,p))(i,t,!g,n,!t||J.test(e)&&ge(t.parentNode)||t),n},n.sortStable=x.split("").sort(A).join("")===x,n.detectDuplicates=!!f,d(),n.sortDetached=se(function(e){return 1&e.compareDocumentPosition(p.createElement("fieldset"))}),se(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&se(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),se(function(e){return null==e.getAttribute("disabled")})||le(H,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var N=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},k=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},A=w.expr.match.needsContext;function D(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var S=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function L(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return s.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(L(this,e||[],!1))},not:function(e){return this.pushStack(L(this,e||[],!0))},is:function(e){return!!L(this,"string"==typeof e&&A.test(e)?w(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:q.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),S.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,j=w(r);var O=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?s.call(w(e),this[0]):s.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function H(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return N(e,"parentNode")},parentsUntil:function(e,t,n){return N(e,"parentNode",n)},next:function(e){return H(e,"nextSibling")},prev:function(e){return H(e,"previousSibling")},nextAll:function(e){return N(e,"nextSibling")},prevAll:function(e){return N(e,"previousSibling")},nextUntil:function(e,t,n){return N(e,"nextSibling",n)},prevUntil:function(e,t,n){return N(e,"previousSibling",n)},siblings:function(e){return k((e.parentNode||{}).firstChild,e)},children:function(e){return k(e.firstChild)},contents:function(e){return D(e,"iframe")?e.contentDocument:(D(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(P[e]||w.uniqueSort(i),O.test(e)&&i.reverse()),this.pushStack(i)}});var I=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(I)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],u=-1,s=function(){for(i=i||e.once,r=t=!0;a.length;u=-1){n=a.shift();while(++u-1)o.splice(n,1),n<=u&&u--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||s()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function B(e){return e}function M(e){throw e}function W(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var u=this,s=arguments,l=function(){var e,l;if(!(t=o&&(r!==M&&(u=void 0,s=[e]),n.rejectWith(u,s))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:B,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:B)),n[2][3].add(a(0,e,g(r)?r:M))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],u=t[5];i[t[1]]=a.add,u&&a.add(function(){r=u},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),u=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&(W(e,a.done(u(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)W(i[n],u(n),a.reject);return a.promise()}});var $=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&$.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function z(){r.removeEventListener("DOMContentLoaded",z),e.removeEventListener("load",z),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",z),e.addEventListener("load",z));var _=function(e,t,n,r,i,o,a){var u=0,s=e.length,l=null==n;if("object"===b(n)){i=!0;for(u in n)_(e,t,u,n[u],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;u1,null,!0)},removeData:function(e){return this.each(function(){J.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=K.get(e,t),n&&(!r||Array.isArray(n)?r=K.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return K.get(e,n)||K.access(e,n,{empty:w.Callbacks("once memory").add(function(){K.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&D(e,t)?w.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var xe=r.documentElement,we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ne(){return!1}function ke(){try{return r.activeElement}catch(e){}}function Ae(e,t,n,r,i,o){var a,u;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(u in t)Ae(e,u,n,r,t[u],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ne;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,u,s,l,c,f,d,p,h,g,v=K.get(e);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(xe,i),n.guid||(n.guid=w.guid++),(s=v.events)||(s=v.events={}),(a=v.handle)||(a=v.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(I)||[""]).length;while(l--)p=g=(u=Te.exec(t[l])||[])[1],h=(u[2]||"").split(".").sort(),p&&(f=w.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=w.event.special[p]||{},c=w.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=s[p])||((d=s[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(p,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),w.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,u,s,l,c,f,d,p,h,g,v=K.hasData(e)&&K.get(e);if(v&&(s=v.events)){l=(t=(t||"").match(I)||[""]).length;while(l--)if(u=Te.exec(t[l])||[],p=g=u[1],h=(u[2]||"").split(".").sort(),p){f=w.event.special[p]||{},d=s[p=(r?f.delegateType:f.bindType)||p]||[],u=u[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||u&&!u.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||w.removeEvent(e,p,v.handle),delete s[p])}else for(p in s)w.event.remove(e,p+t[l],n,r,!0);w.isEmptyObject(s)&&K.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,u,s=new Array(arguments.length),l=(K.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(s[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&u.push({elem:l,handlers:o})}return l=this,s\x20\t\r\n\f]*)[^>]*)\/>/gi,Se=/\s*$/g;function qe(e,t){return D(e,"table")&&D(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function Oe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Pe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function He(e,t){var n,r,i,o,a,u,s,l;if(1===t.nodeType){if(K.hasData(e)&&(o=K.access(e),a=K.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof v&&!h.checkClone&&Le.test(v))return e.each(function(i){var o=e.eq(i);y&&(t[0]=v.call(this,i,o.html())),Re(o,t,n,r)});if(d&&(i=be(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(u=w.map(ve(i,"script"),Oe)).length;f")},clone:function(e,t,n){var r,i,o,a,u=e.cloneNode(!0),s=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ve(u),r=0,i=(o=ve(e)).length;r0&&ye(a,!s&&ve(e,"script")),u},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[K.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[K.expando]=void 0}n[J.expando]&&(n[J.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Be(this,e,!0)},remove:function(e){return Be(this,e)},text:function(e){return _(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return _(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Se.test(e)&&!ge[(pe.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(s+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-s-u-.5))),s}function et(e,t,n){var r=We(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(Me.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,u=Q(t),s=Ue.test(t),l=e.style;if(s||(t=Ke(u)),a=w.cssHooks[t]||w.cssHooks[u],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[u]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(s?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,u=Q(t);return Ue.test(t)||(t=Ke(u)),(a=w.cssHooks[t]||w.cssHooks[u])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Xe&&(i=Xe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!_e.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):ue(e,Ve,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=We(e),a="border-box"===w.css(e,"boxSizing",!1,o),u=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(u-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),u&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Je(e,n,u)}}}),w.cssHooks.marginLeft=ze(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Je)}),w.fn.extend({css:function(e,t){return _(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=We(e),i=t.length;a1)}}),w.fn.delay=function(t,n){return t=w.fx?w.fx.speeds[t]||t:t,n=n||"fx",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=r.createElement("input"),t=r.createElement("select").appendChild(r.createElement("option"));e.type="checkbox",h.checkOn=""!==e.value,h.optSelected=t.selected,(e=r.createElement("input")).value="t",e.type="radio",h.radioValue="t"===e.value}();var tt,nt=w.expr.attrHandle;w.fn.extend({attr:function(e,t){return _(this,w.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?tt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&D(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(I);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),tt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=nt[t]||w.find.attr;nt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=nt[a],nt[a]=i,i=null!=n(e,t,r)?a:null,nt[a]=o),i}});var rt=/^(?:input|select|textarea|button)$/i,it=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return _(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):rt.test(e.nodeName)||it.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function ot(e){return(e.match(I)||[]).join(" ")}function at(e){return e.getAttribute&&e.getAttribute("class")||""}function ut(e){return Array.isArray(e)?e:"string"==typeof e?e.match(I)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,u,s=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,at(this)))});if((t=ut(e)).length)while(n=this[s++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(u=ot(r))&&n.setAttribute("class",u)}return this},removeClass:function(e){var t,n,r,i,o,a,u,s=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,at(this)))});if(!arguments.length)return this.attr("class","");if((t=ut(e)).length)while(n=this[s++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(u=ot(r))&&n.setAttribute("class",u)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,at(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=ut(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=at(this))&&K.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":K.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+ot(at(n))+" ").indexOf(t)>-1)return!0;return!1}});var st=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(st,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:ot(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,u=a?null:[],s=a?o+1:i.length;for(r=o<0?s:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var lt=/^(?:focusinfocus|focusoutblur)$/,ct=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,u,s,l,c,d,p,h,y=[i||r],m=f.call(t,"type")?t.type:t,b=f.call(t,"namespace")?t.namespace.split("."):[];if(u=h=s=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!lt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(b=m.split(".")).shift(),b.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=b.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),p=w.event.special[m]||{},o||!p.trigger||!1!==p.trigger.apply(i,n))){if(!o&&!p.noBubble&&!v(i)){for(l=p.delegateType||m,lt.test(l+m)||(u=u.parentNode);u;u=u.parentNode)y.push(u),s=u;s===(i.ownerDocument||r)&&y.push(s.defaultView||s.parentWindow||e)}a=0;while((u=y[a++])&&!t.isPropagationStopped())h=u,t.type=a>1?l:p.bindType||m,(d=(K.get(u,"events")||{})[t.type]&&K.get(u,"handle"))&&d.apply(u,n),(d=c&&u[c])&&d.apply&&Y(u)&&(t.result=d.apply(u,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(y.pop(),n)||!Y(i)||c&&g(i[m])&&!v(i)&&((s=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,ct),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,ct),w.event.triggered=void 0,s&&(i[c]=s)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=K.access(r,t);i||r.addEventListener(e,n,!0),K.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=K.access(r,t)-1;i?K.access(r,t,i):(r.removeEventListener(e,n,!0),K.remove(r,t))}}});var ft=/\[\]$/,dt=/\r?\n/g,pt=/^(?:submit|button|image|reset|file)$/i,ht=/^(?:input|select|textarea|keygen)/i;function gt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||ft.test(e)?r(e,i):gt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==b(t))r(e,t);else for(i in t)gt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)gt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&ht.test(this.nodeName)&&!pt.test(e)&&(this.checked||!de.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(dt,"\r\n")}}):{name:t.name,value:n.replace(dt,"\r\n")}}).get()}}),w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},h.createHTMLDocument=function(){var e=r.implementation.createHTMLDocument("").body;return e.innerHTML="
    ",2===e.childNodes.length}(),w.parseHTML=function(e,t,n){if("string"!=typeof e)return[];"boolean"==typeof t&&(n=t,t=!1);var i,o,a;return t||(h.createHTMLDocument?((i=(t=r.implementation.createHTMLDocument("")).createElement("base")).href=r.location.href,t.head.appendChild(i)):t=r),o=S.exec(e),a=!n&&[],o?[t.createElement(o[1])]:(o=be([e],t,a),a&&a.length&&w(a).remove(),w.merge([],o.childNodes))},w.offset={setOffset:function(e,t,n){var r,i,o,a,u,s,l,c=w.css(e,"position"),f=w(e),d={};"static"===c&&(e.style.position="relative"),u=f.offset(),o=w.css(e,"top"),s=w.css(e,"left"),(l=("absolute"===c||"fixed"===c)&&(o+s).indexOf("auto")>-1)?(a=(r=f.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(s)||0),g(t)&&(t=t.call(e,n,w.extend({},u))),null!=t.top&&(d.top=t.top-u.top+a),null!=t.left&&(d.left=t.left-u.left+i),"using"in t?t.using.call(e,d):f.css(d)}},w.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){w.offset.setOffset(this,e,t)});var t,n,r=this[0];if(r)return r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===w.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===w.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=w(e).offset()).top+=w.css(e,"borderTopWidth",!0),i.left+=w.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-w.css(r,"marginTop",!0),left:t.left-i.left-w.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===w.css(e,"position"))e=e.offsetParent;return e||xe})}}),w.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n="pageYOffset"===t;w.fn[e]=function(r){return _(this,function(e,r,i){var o;if(v(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),w.each(["top","left"],function(e,t){w.cssHooks[t]=ze(h.pixelPosition,function(e,n){if(n)return n=Fe(e,t),Me.test(n)?w(e).position()[t]+"px":n})}),w.each({Height:"height",Width:"width"},function(e,t){w.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){w.fn[r]=function(i,o){var a=arguments.length&&(n||"boolean"!=typeof i),u=n||(!0===i||!0===o?"margin":"border");return _(this,function(t,n,i){var o;return v(t)?0===r.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body["scroll"+e],o["scroll"+e],t.body["offset"+e],o["offset"+e],o["client"+e])):void 0===i?w.css(t,n,u):w.style(t,n,i,u)},t,a?i:void 0,a)}})}),w.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){w.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),w.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=D,w.isFunction=g,w.isWindow=v,w.camelCase=Q,w.type=b,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},"function"==typeof define&&define.amd&&define("jquery",[],function(){return w});var vt=e.jQuery,yt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=yt),t&&e.jQuery===w&&(e.jQuery=vt),w},t||(e.jQuery=e.$=w),w}); 3 | -------------------------------------------------------------------------------- /js/options.js: -------------------------------------------------------------------------------- 1 | ((c, d, l, ekill) => { 2 | let getSettings = callback => { 3 | c.storage.sync.get({ 4 | "ekillSettings": { 5 | holdsGrudge: "false" 6 | } 7 | }, item => { 8 | if (c.runtime.lastError) { 9 | console.error(c.runtime.lastError); 10 | } else { 11 | callback(item.ekillSettings); 12 | } 13 | }); 14 | } 15 | 16 | let onLoad = _ => { 17 | let convertToTreeData = hitListData => { 18 | var d = []; 19 | 20 | for (let hostname in hitListData) { 21 | let hostnameNode = { 22 | text: hostname, 23 | selectable: false, 24 | nodes: [] 25 | } 26 | 27 | let hostnameObject = hitListData[hostname]; 28 | 29 | for (let pathname in hostnameObject) { 30 | let pathnameNode = { 31 | text: pathname, 32 | selectable: false, 33 | nodes: [] 34 | } 35 | 36 | let pathnameObject = hostnameObject[pathname]; 37 | 38 | pathnameObject.forEach(item => pathnameNode.nodes.push({ 39 | text: item.selector, 40 | hostname: hostname, 41 | pathname: pathname 42 | })); 43 | 44 | hostnameNode.nodes.push(pathnameNode); 45 | } 46 | 47 | d.push(hostnameNode); 48 | } 49 | 50 | return d; 51 | } 52 | 53 | c.storage.local.get({ 54 | "ekillHitlistV2": "{}" 55 | }, item => { 56 | if (c.runtime.lastError) { 57 | console.error(c.runtime.lastError); 58 | } else { 59 | let hitList = JSON.parse(item.ekillHitlistV2); 60 | let treeData = convertToTreeData(hitList); 61 | 62 | $('#hit-list').treeview({ 63 | data: treeData, 64 | levels: 0, 65 | multiSelect: true 66 | }); 67 | 68 | let searchInput = $('#search-input'); 69 | searchInput.on('input', e => { 70 | let value = e.target.value; 71 | 72 | // Min three characters typed before searching 73 | if (value.length < 3) return; 74 | 75 | $('#hit-list').treeview('clearSearch'); 76 | $('#hit-list').treeview('search', [ value, { 77 | ignoreCase: true, 78 | exactMatch: false, 79 | revealResults: true, 80 | }]); 81 | }); 82 | searchInput.focusout(e => { 83 | $('#hit-list').treeview('clearSearch'); 84 | }); 85 | 86 | 87 | $('#hit-list').on('nodeSelected', (event, data) => { 88 | let queue = [data]; 89 | 90 | $('button#remove-button').prop("disabled", false); 91 | 92 | // select every node in sub-tree 93 | while (queue.length !== 0) { 94 | let currentNode = queue.pop(); 95 | $('#hit-list').treeview('selectNode', [ currentNode.nodeId, { silent: true } ]); 96 | if (currentNode.nodes !== undefined) { 97 | queue.push(...currentNode.nodes); 98 | } 99 | } 100 | }); 101 | 102 | $('#hit-list').on('nodeUnselected', (event, data) => { 103 | let queue = [data]; 104 | 105 | // unselect every node in sub-tree 106 | while (queue.length !== 0) { 107 | let currentNode = queue.pop(); 108 | $('#hit-list').treeview('unselectNode', [ currentNode.nodeId, { silent: true } ]); 109 | if (currentNode.nodes !== undefined) { 110 | queue.push(...currentNode.nodes); 111 | } 112 | } 113 | 114 | if($('#hit-list').treeview('getSelected').length === 0) { 115 | $('button#remove-button').prop("disabled", true); 116 | } 117 | }); 118 | 119 | $('input[type=radio][name=grudge]').change(e => { 120 | let holdsGrudge; 121 | 122 | if (e.target.value === "on") { 123 | holdsGrudge = "true"; 124 | 125 | $('#hit-list').treeview('enableAll', { silent: true }); 126 | $("#search-input").prop("disabled", false); 127 | } else { 128 | holdsGrudge = "false"; 129 | $('#hit-list').treeview('disableAll', { silent: true }); 130 | $("#search-input").prop("disabled", true); 131 | } 132 | 133 | let settings = { 134 | ekillSettings: { 135 | holdsGrudge: holdsGrudge 136 | } 137 | } 138 | 139 | c.storage.sync.set(settings, _ => { 140 | if (c.runtime.lastError) { 141 | console.error(c.runtime.lastError); 142 | } 143 | }); 144 | }); 145 | 146 | $('button#remove-button').click(_ => { 147 | if (window.confirm("Remove selected Hit List targets?")) { 148 | $('#hit-list').treeview('getSelected').forEach(s => { 149 | ekill.removeHit(hitList, s.hostname, s.pathname, s.text); 150 | 151 | let settings = { 152 | "ekillHitlistV2": JSON.stringify(hitList) 153 | }; 154 | 155 | c.storage.local.set(settings, _ => { 156 | if (c.runtime.lastError) { 157 | console.error(c.runtime.lastError); 158 | } else { 159 | l.reload(); 160 | } 161 | }); 162 | }); 163 | } 164 | }); 165 | } 166 | 167 | getSettings(settings => { 168 | if (settings.holdsGrudge === "true") { 169 | $("input#optionsOn").prop("checked", true); 170 | $('#hit-list').treeview('enableAll', { silent: true }); 171 | $("input#optionsOff").prop("checked", false); 172 | $("#search-input").prop("disabled", false); 173 | } else { 174 | $("input#optionsOn").prop("checked", false); 175 | $('#hit-list').treeview('disableAll', { silent: true }); 176 | $("input#optionsOff").prop("checked", true); 177 | $("#search-input").prop("disabled", true); 178 | } 179 | }); 180 | }); 181 | 182 | $("#version").text(c.runtime.getManifest().version); 183 | } 184 | 185 | d.addEventListener('DOMContentLoaded', onLoad); 186 | })(chrome, document, location, ekill); 187 | -------------------------------------------------------------------------------- /js/testable.js: -------------------------------------------------------------------------------- 1 | window.ekill = window.ekill || {}; 2 | 3 | (function(ekill) { 4 | /** 5 | * Converts various collections, lists etc. into a pure array. 6 | * 7 | * @param {Object} notArray - Any convertible non-array type 8 | * @returns {Array} 9 | */ 10 | ekill.toArray = notArray => Array.prototype.slice.call(notArray); 11 | 12 | /** 13 | * Generates a recursive object structure representing the subtree of the 14 | * DOM containing the specified element. 15 | * 16 | * The outhermost layer of the object will be for the 'body' element. Each 17 | * further node on the path down to 'element' will be attached via a 'child' 18 | * property. 19 | * 20 | * @param {Element} element - target DOM element 21 | * @returns {Object} Representation of the subtree in the following 22 | * format: 23 | * 24 | * Stucture = { 25 | * localName: {String} 26 | * id: {String} 27 | * classes: {String} 28 | * el: {Element} 29 | * child: {Structure|undefined} 30 | * } 31 | */ 32 | ekill.generateElementHierarchy = element => { 33 | let newObject = () => { 34 | return { 35 | localName: "", 36 | id: "", 37 | classes: "", 38 | el: undefined, 39 | child: undefined 40 | }; 41 | } 42 | 43 | let currentElement = element; 44 | let currentObject = undefined; 45 | let lastObject = undefined; 46 | while (currentElement.localName !== "html") { 47 | currentObject = newObject(); 48 | currentObject.el = currentElement; 49 | currentObject.localName = currentElement.localName; 50 | currentObject.id = currentElement.id; 51 | currentObject.classes = ekill.toArray(currentElement.classList). 52 | reduce((acc, val) => `${acc}.${val}`, ""); 53 | currentObject.child = lastObject; 54 | 55 | lastObject = currentObject; 56 | currentElement = currentElement.parentElement; 57 | } 58 | 59 | return currentObject; 60 | }; 61 | 62 | 63 | /** 64 | * Generates a selector string from a element hierarchy produced by 65 | * generateElementHierarchy. The selector string will uniquely return the 66 | * bottom most element in the hierarchy, when passed to .querySelector(). 67 | * 68 | * The selector is fully qualified. This means each node in the path from 69 | * 'element' up the DOM tree, to the first uniquely identifiable node, is 70 | * referenced within the selector. 71 | * 72 | * E.g. this snippet: 73 | * 74 | * 75 | *
    76 | *
    77 | *
    78 | *
    79 | *
    80 | * 81 | * 82 | * Will yield the following DOMString: 83 | * 84 | * #foo > ul > li > p 85 | * 86 | * To determine uniqueness, id, classes and sibling index is considered, 87 | * according this order of precedence: 88 | * 89 | * 1. id 90 | * 2. classes 91 | * 3. index 92 | * 93 | * @param {Object} hierarchy - Hierarchy object returned by 94 | * generateElementHierarchy 95 | * @returns {String} Selector DOMString 96 | */ 97 | ekill.elementHierarchyToDOMString = hierarchy => { 98 | let selectorParts = []; 99 | let currentObject = hierarchy; 100 | let parentElement = document; // document will always be a parent 101 | while (currentObject !== undefined) { 102 | let selectorPart = ""; 103 | let uniqueByIdInDocument = false; 104 | let uniqueByIdInParent = false; 105 | let idSelector = `#${currentObject.id}`; 106 | 107 | if (currentObject.id !== "") { 108 | uniqueByIdInDocument = document.querySelectorAll(idSelector). 109 | length === 1; 110 | // Will be the same in first iteration since d === parentElement 111 | uniqueByIdInParent = parentElement.querySelectorAll(idSelector). 112 | length === 1; 113 | } 114 | 115 | if (uniqueByIdInDocument) { 116 | selectorPart = idSelector; 117 | 118 | // Since this element is *globally* unique by id, any parts of the 119 | // selector created until now can be disregarded 120 | selectorParts = []; 121 | } else if (uniqueByIdInParent) { 122 | selectorPart = idSelector; 123 | } else { 124 | // Try with classes when id fails 125 | let uniqueByClassesInDocument = false; 126 | let uniqueByClassesInParent = false; 127 | 128 | if (currentObject.classes !== "") { 129 | uniqueByClassesInDocument = document. 130 | querySelectorAll(currentObject.classes).length === 1; 131 | uniqueByClassesInParent = parentElement. 132 | querySelectorAll(currentObject.classes).length === 1; 133 | } 134 | 135 | if (uniqueByClassesInDocument) { 136 | selectorPart = currentObject.classes; 137 | 138 | // Same as above; if classes can uniquely target the element, 139 | // previous parts of the selector can be thrown away 140 | selectorParts = []; 141 | } else if (uniqueByClassesInParent) { 142 | selectorPart = currentObject.classes; 143 | } else { 144 | // In case both id and class based selectors are non-viable, use 145 | // nth-of-type instead where needed 146 | 147 | // Find all siblings of the same tag 148 | let childrenArray = ekill.toArray(parentElement.children); 149 | let sameTagChildren = childrenArray. 150 | filter(child => child.localName === currentObject.localName); 151 | 152 | if (sameTagChildren.length > 1) { 153 | let currentObjectIndex = sameTagChildren.indexOf(currentObject.el); 154 | 155 | selectorPart = `${currentObject.localName}:nth-of-type(${currentObjectIndex + 1})` 156 | } else { 157 | selectorPart = currentObject.localName; 158 | } 159 | } 160 | } 161 | 162 | selectorParts.push(selectorPart); 163 | parentElement = currentObject.el; 164 | currentObject = currentObject.child; 165 | } 166 | 167 | return selectorParts.join(" > "); 168 | }; 169 | 170 | /** 171 | * Modifies the hit list in place, by adding a new hit 172 | * 173 | * @param {Object} hitList - existing hit list 174 | * @param {String} hostname 175 | * @param {String} pathname 176 | * @param {String} selector - DOMString as returned by 177 | * elementHierarchyToDOMString 178 | */ 179 | ekill.addHit = (hitList, hostname, pathname, selector) => { 180 | hitList[hostname] = hitList[hostname] || {}; 181 | let paths = hitList[hostname]; 182 | 183 | /** 184 | * Checks if a DOMString might be a pseudo specific version of another 185 | * 186 | * @param {String} s0 - DOMString 187 | * @param {String} s1 - DOMString 188 | * @returns {Bool} 189 | */ 190 | let pseudo = (s0, s1) => s0.length > s1.length && s0[s1.length] === ":"; 191 | 192 | // 1. Check if this selector is a parent of a wildcard 193 | // 194 | // E.g. of the following two distinct selectors: 195 | // 196 | // A: lorem > ipsum > dolor 197 | // B: lorem > ipsum 198 | // 199 | // B points to a parent of A, so we only need to store B. 200 | let hostHasWildcards = paths.hasOwnProperty("*"); 201 | if (hostHasWildcards) { 202 | let wildcards = paths["*"]; 203 | 204 | for (let i = 0; i < wildcards.length; ++i) { 205 | let item = wildcards[i]; 206 | 207 | // Note that startsWith returns true for equality and not just substring 208 | // match. In both cases nothing further needs to be done though, since 209 | // the selector is either pre-existing or being updated. 210 | if (item.selector.startsWith(selector)) { 211 | if(!pseudo(item.selector, selector)) { 212 | wildcards[i] = { 213 | selector: selector, 214 | lastUsed: (new Date()).getTime() 215 | }; 216 | } 217 | return; 218 | } 219 | } 220 | } 221 | 222 | // 2. Do the same as in 1. but for path specific selectors 223 | let hostHasPath = paths.hasOwnProperty(pathname); 224 | if (hostHasPath) { 225 | let selectors = paths[pathname]; 226 | 227 | for (let i = 0; i < selectors.length; ++i) { 228 | let item = selectors[i]; 229 | if (item.selector.startsWith(selector)) { 230 | if(!pseudo(item.selector, selector)) { 231 | selectors[i] = { 232 | selector: selector, 233 | lastUsed: (new Date()).getTime() 234 | }; 235 | } 236 | return; 237 | } 238 | } 239 | } 240 | 241 | // 3. Selector is neither for a parent, nor pre-existing so we go ahead and 242 | // add it 243 | paths[pathname] = paths[pathname] || []; 244 | 245 | // If the same element has been killed under the same domain but for 246 | // different pathnames, hoist it to be a wildcard match 247 | let selectors = paths[pathname]; 248 | 249 | let addToWildcard = false; 250 | for (let p in paths) { 251 | if (p === "*") continue; 252 | if (p === pathname) continue; 253 | 254 | if (paths.hasOwnProperty(p)) { 255 | let selectorIndex = paths[p].findIndex(e => { 256 | return e.selector === selector; 257 | }); 258 | 259 | if (selectorIndex !== -1) { 260 | addToWildcard = true; 261 | 262 | // Remove from all other paths, since it's now a wildcard match 263 | paths[p].splice(selectorIndex, 1); 264 | } 265 | } 266 | } 267 | 268 | if (addToWildcard) { 269 | if (!hostHasWildcards) 270 | paths["*"] = []; 271 | paths["*"].push({ 272 | selector: selector, 273 | lastUsed: (new Date()).getTime() 274 | }); 275 | } else { 276 | // Only append selector if it's not already there 277 | let selectorIndex = selectors.findIndex(e => { 278 | return e.selector === selector; 279 | }); 280 | if (selectorIndex === -1) { 281 | selectors.push({ 282 | selector: selector, 283 | lastUsed: (new Date()).getTime() 284 | }); 285 | } 286 | } 287 | 288 | // Clean up potentially empty paths 289 | for (let p in paths) { 290 | if (paths.hasOwnProperty(p)) { 291 | if (paths[p].length === 0) 292 | delete paths[p]; 293 | } 294 | } 295 | } 296 | 297 | /** 298 | * Modifies the hit list in place, by removing new hit 299 | * 300 | * @param {Object} hitList - existing hit list 301 | * @param {String} hostname 302 | * @param {String} pathname 303 | * @param {String} selector - DOMString as returned by 304 | * elementHierarchyToDOMString 305 | */ 306 | ekill.removeHit = (hitList, hostname, pathname, selector) => { 307 | let paths = hitList[hostname]; 308 | let selectorIndex = paths[pathname].findIndex(item => { 309 | return item.selector = selector; 310 | }); 311 | paths[pathname].splice(selectorIndex, 1); 312 | 313 | // Cleanup empty paths 314 | if (paths[pathname].length === 0) 315 | delete paths[pathname]; 316 | 317 | // Cleanup empty hosts 318 | if (Object.keys(paths).length === 0) 319 | delete hitList[hostname]; 320 | }; 321 | 322 | ekill.migrateHitList = (h0, h1) => { 323 | for (let hostname in h0) { 324 | if (h0.hasOwnProperty(hostname)) { 325 | h1[hostname] = {}; 326 | 327 | for (let pathname in h0[hostname]) { 328 | if (h0[hostname].hasOwnProperty(pathname)) { 329 | h1[hostname][pathname] = []; 330 | 331 | for (let i = 0; i < h0[hostname][pathname].length; ++i) { 332 | h1[hostname][pathname].push({ 333 | selector: h0[hostname][pathname][i], 334 | lastUsed: (new Date()).getTime() 335 | }); 336 | } 337 | } 338 | } 339 | } 340 | } 341 | }; 342 | 343 | /** 344 | * Tests whether one "major.minor.patch" version string represents a newer 345 | * version than another, but only considering major and minor versions. 346 | * 347 | * @param {String} v0 - Canditate version string 348 | * @param {String} v1 - Version string to test against 349 | * @returns {Bool} 350 | */ 351 | ekill.isNewerVersion = (v0, v1) => { 352 | let t0 = v0.split(".").map(n => parseInt(n, 10)); 353 | let t1 = v1.split(".").map(n => parseInt(n, 10)); 354 | 355 | return t0[0] > t1[0] || (t0[0] === t1[0] && t0[1] > t1[1]); 356 | }; 357 | 358 | })(window.ekill) 359 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ekill", 3 | "version": "1.9.0", 4 | "description": "Remove unwanted elements from a page quickly!", 5 | "manifest_version": 2, 6 | "browser_action": { 7 | "default_icon": { 8 | "16": "skull-and-bones-16.png", 9 | "48": "skull-and-bones-48.png", 10 | "128": "skull-and-bones-128.png" 11 | }, 12 | "theme_icons": [{ 13 | "light": "skull-and-bones-16-light.png", 14 | "dark": "skull-and-bones-16.png", 15 | "size": 16 16 | }, { 17 | "light": "skull-and-bones-48-light.png", 18 | "dark": "skull-and-bones-48.png", 19 | "size": 48 20 | }] 21 | }, 22 | "options_ui": { 23 | "page": "options.html", 24 | "open_in_tab": true, 25 | "browser_style": false, 26 | "chrome_style": false 27 | }, 28 | "applications": { 29 | "gecko": { 30 | "id": "{e4326fc5-909b-47e0-a0af-dd3ec180dea5}", 31 | "strict_min_version": "42.0" 32 | } 33 | }, 34 | "permissions": [ 35 | "activeTab", 36 | "storage", 37 | "unlimitedStorage" 38 | ], 39 | "background": { 40 | "scripts": ["js/testable.js", "js/background.js"] 41 | }, 42 | "content_scripts": [{ 43 | "matches": [""], 44 | "js": ["js/testable.js", "js/ekill.js"], 45 | "css": ["css/ekill.css"], 46 | "run_at": "document_end" 47 | }], 48 | "icons": { 49 | "16": "skull-and-bones-16.png", 50 | "48": "skull-and-bones-48.png", 51 | "128": "skull-and-bones-128.png" 52 | }, 53 | "web_accessible_resources": [ 54 | "changelog.html" 55 | ], 56 | "commands": { 57 | "_execute_browser_action": { 58 | "suggested_key": { 59 | "default": "Ctrl+Shift+K", 60 | "mac": "MacCtrl+Shift+K" 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ekill 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 |
    14 |
    15 | 16 |

    ekill

    17 |

    18 |
    19 |
    20 |

    Settings

    21 |

    22 |

    Experimental

    23 | 24 |

    Grudge

    25 |
    26 |
    27 |

    Turning this setting on lets ekill hold a grudge against any 28 | element you kill.

    29 |

    On subsequent visits to the same page, ekill will 30 | spot repeat offenders and carry out sweet vengeance.

    31 |

    FYI: This setting is strictly local. The 32 | Hit List is not shared across installations of ekill.

    33 | 34 |
    35 |
    36 | 40 |
    41 |
    42 | 47 |
    48 |
    49 |
    50 |
    51 | 52 |

    Hit List

    53 |
    54 |
    55 |
    56 | 57 |
    58 |
    59 |
    60 |
    61 |
    62 |
    63 |
    64 | 66 |
    67 |
    68 |
    69 |
    70 |
    71 |
    72 | 73 | 74 | 75 | 76 | 77 |
    78 | 79 | 80 | -------------------------------------------------------------------------------- /package.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir tmp 4 | git archive HEAD --format=zip > tmp/ekill.zip 5 | 6 | pushd tmp 7 | 8 | zip -d ekill.zip .gitignore 9 | zip -d ekill.zip README.md 10 | zip -d ekill.zip package.sh 11 | zip -d ekill.zip example.gif 12 | zip -d ekill.zip test/* 13 | 14 | popd 15 | 16 | if [ -f ekill.zip ]; 17 | then 18 | rm ekill.zip 19 | fi 20 | 21 | mv tmp/ekill.zip . 22 | rm -rf tmp 23 | 24 | zipinfo ekill.zip 25 | -------------------------------------------------------------------------------- /skull-and-bones-128.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6177d47f763455ce1a436581edf8ab42c15e331626f5813016d995226abc55ef 3 | size 2233 4 | -------------------------------------------------------------------------------- /skull-and-bones-16-light.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:216f4c4593408e8fbab1be9b6663d80a45370a77dc82a7f621a78ed6996a1391 3 | size 646 4 | -------------------------------------------------------------------------------- /skull-and-bones-16.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:552eca194dfabbfe3bd234407c197808c1ab87c6860222da3f7ad995393a23ab 3 | size 296 4 | -------------------------------------------------------------------------------- /skull-and-bones-48-light.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:aa6d816b879cfc9e596da64ae660bc3cb116718f8b23ef37471c4f6b27c91cb6 3 | size 1983 4 | -------------------------------------------------------------------------------- /skull-and-bones-48.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d4306f1f142bcfe074d75563eda2701bc889d8b12981875351747ed11faf012a 3 | size 878 4 | -------------------------------------------------------------------------------- /skull-and-bones.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | Tests are written using [Mocha.js](https://mochajs.org/) and 4 | [Chai](https://www.chaijs.com/). 5 | 6 | Run the tests in a browser by serving up the root folder contents with e.g.: 7 | 8 | ```bash 9 | python -m SimpleHTTPServer 10 | ``` 11 | 12 | Or any other local web server. (Ahem; [https://github.com/rhardih/serve](https://github.com/rhardih/serve)). 13 | 14 | Then go to [http://localhost:8000/test](http://localhost:8080/test) to run the 15 | tests. 16 | -------------------------------------------------------------------------------- /test/chai-shallow-deep-equal.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | (function (plugin) { 4 | if ( 5 | typeof require === "function" && 6 | typeof exports === "object" && 7 | typeof module === "object" 8 | ) { 9 | // NodeJS 10 | module.exports = plugin; 11 | } else if ( 12 | typeof define === "function" && 13 | define.amd 14 | ) { 15 | // AMD 16 | define(function () { 17 | return plugin; 18 | }); 19 | } else { 20 | // Other environment (usually 10 | 11 | 12 | 13 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 | 21 |
    22 |
    23 |

    24 |
    25 |
    26 | 27 |
    28 |
    29 |
    30 | 31 |
    32 | 33 |
    34 |
    35 |
    36 |
    37 |
    38 | 39 |
    40 |
    41 |
    42 |
    43 |
    44 | 45 |
    46 |
    47 |
    48 |
    49 |
    50 | 51 |
    52 |
    53 |
    54 |
    55 |
    56 | 57 |
    58 |
    59 |
    60 |
    61 |
    62 | 63 | 64 |

    65 |
    66 |
    67 |
    68 |
    69 |

    70 |

    71 |
    72 |
    73 | 74 |
    75 |
    76 |
    77 |
    78 | 79 | 80 | 81 | 82 | 83 | 84 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | let assert = chai.assert; 2 | let expect = chai.expect; 3 | 4 | describe("ekill", function() { 5 | describe("#toArray", function() { 6 | it("should convert NodeList to array", function() { 7 | let list = document.querySelectorAll("#toArrayTest div"); 8 | let arr = ekill.toArray(list); 9 | 10 | expect(arr).to.be.an.instanceOf(Array); 11 | expect(arr).to.have.a.lengthOf(3); 12 | expect(arr).to.satisfy(arr => arr.every( 13 | node => node.nodeType === Node.ELEMENT_NODE && 14 | node.localName === "div")); 15 | }); 16 | 17 | it("should convert DOMTokenList to array", function() { 18 | let list = document.querySelector("#toArrayTest").classList; 19 | let arr = ekill.toArray(list); 20 | 21 | expect(arr).to.be.an.instanceOf(Array); 22 | expect(arr).to.have.a.lengthOf(3); 23 | expect(arr).to.have.members(["lorem", "ipsum", "dolor"]); 24 | }); 25 | }); 26 | 27 | describe("#generateElementHierarchy", function() { 28 | it("should return an object hierarchy", function() { 29 | let target = document.querySelector("[data-target=geh-00-target]"); 30 | let hierarchy = ekill.generateElementHierarchy(target); 31 | let expected = { 32 | el: {}, 33 | id: "", 34 | child: { 35 | child: { 36 | id: "", 37 | el: {}, 38 | localName: "div", 39 | classes: "", 40 | child: { 41 | el: {}, 42 | id: "geh-00", 43 | classes: "", 44 | child: { 45 | el: {}, 46 | id: "", 47 | classes: "", 48 | localName: "p" 49 | }, 50 | localName: "div" 51 | } 52 | }, 53 | classes: "", 54 | localName: "div", 55 | el: {}, 56 | id: "fixtures" 57 | }, 58 | classes: "", 59 | localName: "body" 60 | }; 61 | 62 | expect(hierarchy).to.shallowDeepEqual(expected); 63 | }); 64 | }); 65 | 66 | describe("#elementHierarchyToDOMString", function() { 67 | it("should use id as root if unique in document", function() { 68 | let target = document.querySelector("[data-target=ehtd-00-target]"); 69 | let hierarchy = ekill.generateElementHierarchy(target); 70 | let ds = ekill.elementHierarchyToDOMString(hierarchy); 71 | let selected = document.querySelector(ds); 72 | 73 | expect(ds).to.match(/^#ehtd-00/); 74 | expect(selected).to.equal(target); 75 | }); 76 | 77 | it("should use id as sub-selector if unique in parent", function() { 78 | let target = document.querySelector("[data-target=ehtd-01-target]"); 79 | let hierarchy = ekill.generateElementHierarchy(target); 80 | let ds = ekill.elementHierarchyToDOMString(hierarchy); 81 | let selected = document.querySelector(ds); 82 | 83 | expect(ds).to.equal("#ehtd-01 > #ehtd-01-not-unique"); 84 | expect(selected).to.equal(target); 85 | }); 86 | 87 | it("should use classes as as root if unique in document", function() { 88 | let target = document.querySelector("[data-target=ehtd-02-target]"); 89 | let hierarchy = ekill.generateElementHierarchy(target); 90 | let ds = ekill.elementHierarchyToDOMString(hierarchy); 91 | let selected = document.querySelector(ds); 92 | 93 | expect(ds).to.equal(".ehtd-02"); 94 | expect(selected).to.equal(target); 95 | }); 96 | 97 | it("should use classes as sub-selector if unique in parent", function() { 98 | let target = document.querySelector("[data-target=ehtd-03-target]"); 99 | let hierarchy = ekill.generateElementHierarchy(target); 100 | let ds = ekill.elementHierarchyToDOMString(hierarchy); 101 | let selected = document.querySelector(ds); 102 | 103 | expect(ds).to.equal("#ehtd-03 > .ehtd-03-not-unique"); 104 | expect(selected).to.equal(target); 105 | }); 106 | 107 | it("should use nth-of-type if neither id or class is viable", function() { 108 | let target = document.querySelector("[data-target=ehtd-04-target]"); 109 | let hierarchy = ekill.generateElementHierarchy(target); 110 | let ds = ekill.elementHierarchyToDOMString(hierarchy); 111 | let selected = document.querySelector(ds); 112 | 113 | expect(ds).to.equal("#ehtd-04 > div:nth-of-type(3)"); 114 | expect(selected).to.equal(target); 115 | }); 116 | 117 | it("should use plain tag name if no other siblings of same type", function() { 118 | let target = document.querySelector("[data-target=ehtd-05-target]"); 119 | let hierarchy = ekill.generateElementHierarchy(target); 120 | let ds = ekill.elementHierarchyToDOMString(hierarchy); 121 | let selected = document.querySelector(ds); 122 | 123 | expect(ds).to.equal("#ehtd-05 > div"); 124 | expect(selected).to.equal(target); 125 | }); 126 | 127 | it("should use nth-of-type correctly", function() { 128 | let target = document.querySelector("[data-target=ehtd-06-target]"); 129 | let hierarchy = ekill.generateElementHierarchy(target); 130 | let ds = ekill.elementHierarchyToDOMString(hierarchy); 131 | 132 | let selected = document.querySelector(ds); 133 | 134 | expect(ds).to.equal("#ehtd-06 > p:nth-of-type(1)"); 135 | expect(selected).to.equal(target); 136 | }); 137 | 138 | it("should throw an error for invalid css id", function() { 139 | let target = document.querySelector("[data-target=ehtd-07-target]"); 140 | let hierarchy = ekill.generateElementHierarchy(target); 141 | let proc = _ => ekill.elementHierarchyToDOMString(hierarchy); 142 | 143 | expect(proc).to.throw(); 144 | }); 145 | }); 146 | 147 | describe("#addHit", function() { 148 | before(function() { 149 | this.clock = sinon.useFakeTimers(42); 150 | }); 151 | 152 | after(function() { 153 | this.clock.restore(); 154 | }); 155 | 156 | it("should add new hits", function() { 157 | let hitList = {}; 158 | let expected = { 159 | "example.com": { 160 | "/foo": [{ 161 | selector: "body > div#annoying-popup", 162 | lastUsed: 42 163 | }] 164 | } 165 | } 166 | 167 | ekill.addHit( 168 | hitList, 169 | "example.com", 170 | "/foo", 171 | "body > div#annoying-popup" 172 | ) 173 | 174 | expect(hitList).to.shallowDeepEqual(expected); 175 | }); 176 | 177 | it("should append hits on the same page and only once", function() { 178 | let hitList = { 179 | "example.com": { 180 | "/foo": [{ 181 | selector: "body > div#annoying-popup-0", 182 | lastUsed: 123 183 | }] 184 | } 185 | } 186 | let expected = { 187 | "example.com": { 188 | "/foo": [{ 189 | selector: "body > div#annoying-popup-0", 190 | lastUsed: 123 191 | }, { 192 | selector: "body > div#annoying-popup-1", 193 | lastUsed: 42 194 | }] 195 | } 196 | } 197 | 198 | ekill.addHit( 199 | hitList, 200 | "example.com", 201 | "/foo", 202 | "body > div#annoying-popup-1" 203 | ) 204 | 205 | ekill.addHit( 206 | hitList, 207 | "example.com", 208 | "/foo", 209 | "body > div#annoying-popup-1" 210 | ) 211 | 212 | expect(hitList).to.shallowDeepEqual(expected); 213 | }); 214 | 215 | it("should hoist equal hits on different pages as a single wildcard", function() { 216 | let hitList = {} 217 | let expected = { 218 | "example.com": { 219 | "*": [{ 220 | selector: "body > div#annoying-popup-0", 221 | lastUsed: 42 222 | }] 223 | } 224 | } 225 | 226 | ekill.addHit( 227 | hitList, 228 | "example.com", 229 | "/foo", 230 | "body > div#annoying-popup-0" 231 | ) 232 | 233 | ekill.addHit( 234 | hitList, 235 | "example.com", 236 | "/bar", 237 | "body > div#annoying-popup-0" 238 | ) 239 | 240 | ekill.addHit( 241 | hitList, 242 | "example.com", 243 | "/baz", 244 | "body > div#annoying-popup-0" 245 | ) 246 | 247 | expect(hitList).to.shallowDeepEqual(expected); 248 | }); 249 | 250 | it("should collapse selectors if matching a parent of previous killed element", function() { 251 | let hitList = { 252 | "example.com": { 253 | "/foo": [{ 254 | selector: "body > div#annoying-popup-0 > div.popup-content", 255 | lastUsed: 123 256 | }] 257 | } 258 | } 259 | 260 | let expected = { 261 | "example.com": { 262 | "/foo": [{ 263 | selector: "body > div#annoying-popup-0", 264 | lastUsed: 42 265 | }] 266 | } 267 | } 268 | 269 | ekill.addHit( 270 | hitList, 271 | "example.com", 272 | "/foo", 273 | "body > div#annoying-popup-0" 274 | ) 275 | 276 | expect(hitList).to.shallowDeepEqual(expected); 277 | }); 278 | 279 | it("should not collapse selectors if matching isn't a full parent", function() { 280 | let hitList = { 281 | "example.com": { 282 | "/foo": [{ 283 | selector: "div > p:nth-of-type(1)", 284 | lastUsed: 42 285 | }] 286 | } 287 | } 288 | 289 | let expected = { 290 | "example.com": { 291 | "/foo": [{ 292 | selector: "div > p:nth-of-type(1)", 293 | lastUsed: 42 294 | }] 295 | } 296 | } 297 | 298 | ekill.addHit( 299 | hitList, 300 | "example.com", 301 | "/foo", 302 | "div > p" 303 | ) 304 | 305 | expect(hitList).to.shallowDeepEqual(expected); 306 | }); 307 | }); 308 | 309 | describe("#removeHit", function() { 310 | it("should remove a hit", function() { 311 | let hitList = { 312 | "example.com": { 313 | "/foo": [{ 314 | selector: "body > div#annoying-popup-0", 315 | lastUsed: 123 316 | }, { 317 | selector: "body > div#annoying-popup-1", 318 | lastUsed: 42 319 | }] 320 | } 321 | } 322 | let expected = { 323 | "example.com": { 324 | "/foo": [{ 325 | selector: "body > div#annoying-popup-1", 326 | lastUsed: 42 327 | }] 328 | } 329 | } 330 | 331 | ekill.removeHit( 332 | hitList, 333 | "example.com", 334 | "/foo", 335 | "body > div#annoying-popup-0" 336 | ) 337 | 338 | expect(hitList).to.shallowDeepEqual(expected); 339 | }); 340 | 341 | it("should remove a hit and path if there's no other hits for path", function() { 342 | let hitList = { 343 | "example.com": { 344 | "/foo": [{ 345 | selector: "body > div#annoying-popup-0", 346 | lastUsed: 123 347 | }], 348 | "/bar": [{ 349 | selector: "body > div#annoying-popup-1", 350 | lastUsed: 42 351 | }] 352 | } 353 | } 354 | let expected = { 355 | "example.com": { 356 | "/bar": [{ 357 | selector: "body > div#annoying-popup-1", 358 | lastUsed: 42 359 | }] 360 | } 361 | } 362 | 363 | ekill.removeHit( 364 | hitList, 365 | "example.com", 366 | "/foo", 367 | "body > div#annoying-popup-0" 368 | ) 369 | 370 | expect(hitList).to.shallowDeepEqual(expected); 371 | expect(hitList["example.com"]).to.not.have.any.keys("/foo"); 372 | }); 373 | 374 | it("should remove a hit, path and host if no other...", function() { 375 | let hitList = { 376 | "example.com": { 377 | "/foo": [{ 378 | selector: "body > div#annoying-popup-0", 379 | lastUsed: 123 380 | }] 381 | }, 382 | "lorem.com": { 383 | "/ipsum": [{ 384 | selector: "body > div#annoying-popup-1", 385 | lastUsed: 42 386 | }] 387 | } 388 | } 389 | let expected = { 390 | "example.com": { 391 | "/foo": [{ 392 | selector: "body > div#annoying-popup-0", 393 | lastUsed: 123 394 | }] 395 | } 396 | } 397 | 398 | ekill.removeHit( 399 | hitList, 400 | "lorem.com", 401 | "/ipsum", 402 | "body > div#annoying-popup-0" 403 | ) 404 | 405 | expect(hitList).to.shallowDeepEqual(expected); 406 | expect(hitList).to.not.have.any.keys("lorem.com"); 407 | }); 408 | }); 409 | 410 | describe("#migrateHitList", function() { 411 | before(function() { 412 | this.clock = sinon.useFakeTimers(42); 413 | }); 414 | 415 | after(function() { 416 | this.clock.restore(); 417 | }); 418 | 419 | it("Migrates a hitList to V2 structure", function() { 420 | let hitList = { 421 | "example.com": { 422 | "/foo": [ 423 | "body > div#bar" 424 | ] 425 | } 426 | }; 427 | let hitListV2 = {}; 428 | let expected = { 429 | "example.com": { 430 | "/foo": [ 431 | { 432 | "selector": "body > div#bar", 433 | "lastUsed": 42 434 | } 435 | ] 436 | } 437 | }; 438 | 439 | ekill.migrateHitList(hitList, hitListV2); 440 | 441 | expect(hitListV2).to.shallowDeepEqual(expected); 442 | }); 443 | }); 444 | 445 | describe("#isNewerVersion", function() { 446 | it("returns true for greater major", function() { 447 | var v0 = "1.0.0" 448 | var v1 = "2.0.0" 449 | 450 | expect(ekill.isNewerVersion(v1, v0)).to.be.true; 451 | }); 452 | 453 | it("returns true for greater minor", function() { 454 | var v0 = "1.0.0" 455 | var v1 = "1.1.0" 456 | 457 | expect(ekill.isNewerVersion(v1, v0)).to.be.true; 458 | }); 459 | 460 | it("returns false for greater patch", function() { 461 | var v0 = "1.0.0" 462 | var v1 = "1.0.1" 463 | 464 | expect(ekill.isNewerVersion(v1, v0)).to.be.false; 465 | }); 466 | 467 | it("returns false for equal major", function() { 468 | var v0 = "1.0.0" 469 | var v1 = "1.0.0" 470 | 471 | expect(ekill.isNewerVersion(v1, v0)).to.be.false; 472 | }); 473 | 474 | it("returns false for equal minor", function() { 475 | var v0 = "1.1.0" 476 | var v1 = "1.1.0" 477 | 478 | expect(ekill.isNewerVersion(v1, v0)).to.be.false; 479 | }); 480 | 481 | it("returns false for equal patch", function() { 482 | var v0 = "1.0.1" 483 | var v1 = "1.0.1" 484 | 485 | expect(ekill.isNewerVersion(v1, v0)).to.be.false; 486 | }); 487 | 488 | it("returns false for lesser major", function() { 489 | var v0 = "2.0.0" 490 | var v1 = "1.0.0" 491 | 492 | expect(ekill.isNewerVersion(v1, v0)).to.be.false; 493 | }); 494 | 495 | it("returns false for lesser minor", function() { 496 | var v0 = "1.2.0" 497 | var v1 = "1.1.0" 498 | 499 | expect(ekill.isNewerVersion(v1, v0)).to.be.false; 500 | }); 501 | 502 | it("returns false for lesser patch", function() { 503 | var v0 = "1.0.2" 504 | var v1 = "1.0.1" 505 | 506 | expect(ekill.isNewerVersion(v1, v0)).to.be.false; 507 | }); 508 | }); 509 | }); 510 | --------------------------------------------------------------------------------