diff --git a/lib/jquery-3.4.1.min.js b/lib/jquery-3.4.1.min.js deleted file mode 100644 index a1c07fd8..00000000 --- a/lib/jquery-3.4.1.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!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(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=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)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(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 de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){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[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.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},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.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},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===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]||se.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]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(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(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\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,"",""]};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&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],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=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.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"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.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,n){k.fn[n]=function(e,t){return 0 - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/background/background.js b/src/background/background.js index 650b704a..6d3dd34b 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -1,143 +1,167 @@ -/* globals chrome, bgapp, unescape, match */ -{ - bgapp.ruleDomains = {}; - bgapp.syncFunctions = []; +/* globals chrome, unescape, match */ +import {capabilities} from "./init.js" +import {simpleError} from "./util.js" +import {extractMimeType} from "./extractMime.js" +import {mainStorage} from "./mainStorage.js" +import {requestIdTracker} from "./requestIdTracker.js" +import {tabUrlTracker} from "./tabUrlTracker.js" +import {handleRequest} from "./requestHandling.js" +import {makeHeaderHandler} from "./headerHandling.js" - const simpleError = bgapp.util.simpleError; +export let ruleDomains = {}; +export let syncFunctions = []; - // Called when the user clicks on the browser action icon. - chrome.browserAction.onClicked.addListener(function() { - // open or focus options page. - const optionsUrl = chrome.runtime.getURL("src/ui/devtoolstab.html"); - chrome.tabs.query({}, function(extensionTabs) { - let found = false; - for (let i = 0, len = extensionTabs.length; i < len; i++) { - if (optionsUrl === extensionTabs[i].url) { - found = true; - chrome.tabs.update(extensionTabs[i].id, {selected: true}); - break; - } +// Called when the user clicks on the browser action icon. +chrome.browserAction.onClicked.addListener(function() { + // open or focus options page. + const optionsUrl = chrome.runtime.getURL("src/ui/devtoolstab.html"); + chrome.tabs.query({}, function(extensionTabs) { + let found = false; + for (let i = 0, len = extensionTabs.length; i < len; i++) { + if (optionsUrl === extensionTabs[i].url) { + found = true; + chrome.tabs.update(extensionTabs[i].id, {selected: true}); + break; } - if (found === false) { - chrome.tabs.create({url: optionsUrl}); - } - }); + } + if (found === false) { + chrome.tabs.create({url: optionsUrl}); + } }); +}); - const syncAllInstances = function() { - // Doing this weird dance because I cant figure out how to - // send data from this script to the dev tools script. - // Nothing seems to work (even the examples!). - bgapp.syncFunctions.forEach(function(fn) { - try { - fn(); - } catch (e) { /**/ } - }); - bgapp.syncFunctions = []; - }; +const syncAllInstances = function() { + // Doing this weird dance because I cant figure out how to + // send data from this script to the dev tools script. + // Nothing seems to work (even the examples!). + syncFunctions.forEach(function(fn) { + try { + fn(); + } catch (e) { /**/ } + }); + syncFunctions = []; +}; - chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { - if (request.action === "saveDomain") { - bgapp.mainStorage.put(request.data) - .then(syncAllInstances) - .catch(simpleError); - bgapp.ruleDomains[request.data.id] = request.data; - } else if (request.action === "getDomains") { - bgapp.mainStorage.getAll().then(function(domains) { - sendResponse(domains || []); - }).catch(simpleError); - } else if (request.action === "deleteDomain") { - bgapp.mainStorage.delete(request.id) - .then(syncAllInstances) - .catch(simpleError); - delete bgapp.ruleDomains[request.id]; - } else if (request.action === "import") { - let maxId = 0; - for (const id in bgapp.ruleDomains) { - maxId = Math.max(maxId, parseInt(id.substring(1))); - } - maxId++; - Promise.all(request.data.map(function(domainData) { - // dont overwrite any pre-existing domains. - domainData.id = "d" + maxId++; - bgapp.ruleDomains[domainData.id] = domainData; - return bgapp.mainStorage.put(domainData); - })) +const messageEventsProcessors = new Map([ + ["saveDomain", (request, sender) => { + mainStorage.put(request.data) .then(syncAllInstances) .catch(simpleError); - } else if (request.action === "makeGetRequest") { - const xhr = new XMLHttpRequest(); - xhr.open("GET", request.url, true); - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - sendResponse(xhr.responseText); - } - }; - xhr.send(); - } else if (request.action === "setSetting") { - localStorage[request.setting] = request.value; - } else if (request.action === "getSetting") { - sendResponse(localStorage[request.setting]); - } else if (request.action === "syncMe") { - bgapp.syncFunctions.push(sendResponse); - } else if (request.action === "match") { - sendResponse(match(request.domainUrl, request.windowUrl).matched); - } else if (request.action === "extractMimeType") { - sendResponse(bgapp.extractMimeType(request.fileName, request.file)); + ruleDomains[request.data.id] = request.data; + }], + ["getDomains", (request, sender) => { + return mainStorage.getAll().then(function(domains) { + return domains || []; + }); + }], + ["deleteDomain", (request, sender) => { + mainStorage.delete(request.id) + .then(syncAllInstances) + .catch(simpleError); + delete ruleDomains[request.id]; + }], + ["import", (request, sender) => { + let maxId = 0; + for (const id in ruleDomains) { + maxId = Math.max(maxId, parseInt(id.substring(1))); } + maxId++; + Promise.all(request.data.map(function(domainData) { + // dont overwrite any pre-existing domains. + domainData.id = "d" + maxId++; + ruleDomains[domainData.id] = domainData; + return mainStorage.put(domainData); + })) + .then(syncAllInstances) + .catch(simpleError); + }], + ["makeGetRequest", (request, sender) => { + return fetch(request.url, {"headers": {"Origin": null}, "referrer": "no-referrer"}).then(e=>e.text()); + }], + ["setSetting", (request, sender) => { + localStorage[request.setting] = request.value; + }], + ["getSetting", (request, sender) => { + return Promise.resolve(localStorage[request.setting]); + }], + ["syncMe", (request, sender) => { + return new Promise((resolve, reject) => { + syncFunctions.push(resolve); + }); + }], + ["match", (request, sender) => { + return Promise.resolve(match(request.domainUrl, request.windowUrl).matched); + }], + ["extractMimeType", (request, sender) => { + return Promise.resolve(extractMimeType(request.fileName, request.file)); + }], + ["getCapabilities", () => {return Promise.resolve(capabilities);}], +]); - // !!!Important!!! Need to return true for sendResponse to work. - return true; - }); +chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { + return messageEventsProcessors.get(request.action)(request, sender); +}); - chrome.webRequest.onBeforeRequest.addListener(function(details) { - if (!bgapp.requestIdTracker.has(details.requestId)) { - if (details.tabId > -1) { - let tabUrl = bgapp.tabUrlTracker.getUrlFromId(details.tabId); - if (details.type === "main_frame") { - // a new tab must have just been created. - tabUrl = details.url; - } - if (tabUrl) { - const result = bgapp.handleRequest(details.url, tabUrl, details.tabId, details.requestId); - if (result) { - // make sure we don't try to redirect again. - bgapp.requestIdTracker.push(details.requestId); - } - return result; - } - } + +function processOnlyTabTrackedURIs(details, callback){ + if (details.tabId > -1) { + let tabUrl = tabUrlTracker.getUrlFromId(details.tabId); + if (details.type === "main_frame") { + // a new tab must have just been created. + tabUrl = details.url; + } + if (tabUrl) { + return callback(details, tabUrl); } - }, { - urls: [""] - }, ["blocking"]); + } +} - chrome.webRequest.onHeadersReceived.addListener(bgapp.makeHeaderHandler("response"), { - urls: [""] - }, ["blocking", "responseHeaders"]); - chrome.webRequest.onBeforeSendHeaders.addListener(bgapp.makeHeaderHandler("request"), { - urls: [""] - }, ["blocking", "requestHeaders"]); +chrome.webRequest.onBeforeRequest.addListener(function(details) { + processOnlyTabTrackedURIs(details, (details, tabUrl) => { + if (!requestIdTracker.has(details.requestId)) { + const result = handleRequest(details, tabUrl); + if (result) { + // make sure we don't try to redirect again. + requestIdTracker.push(details.requestId); + } + return result; + } + }); +}, { + urls: [""] +}, ["blocking"]); - //init settings - if (localStorage.devTools === undefined) { - localStorage.devTools = "true"; - } - if (localStorage.showSuggestions === undefined) { - localStorage.showSuggestions = "true"; - } - if (localStorage.showLogs === undefined) { - localStorage.showLogs = "false"; - } +const responseHeadersHandler = makeHeaderHandler("response"); +chrome.webRequest.onHeadersReceived.addListener((details) => { + responseHeadersHandler(details); + //processOnlyTabTrackedURIs(details, handleBody); + handleBody(details, tabUrlTracker.getUrlFromId(details.tabId)); +}, { + urls: [""] +}, ["blocking", "responseHeaders"]); - // init domain storage - bgapp.mainStorage.getAll().then(function(domains) { - if (domains) { - domains.forEach(function(domainObj) { - bgapp.ruleDomains[domainObj.id] = domainObj; - }); - } - }).catch(simpleError); +chrome.webRequest.onBeforeSendHeaders.addListener(makeHeaderHandler("request"), { + urls: [""] +}, ["blocking", "requestHeaders"]); +//init settings +if (localStorage.devTools === undefined) { + localStorage.devTools = "true"; +} +if (localStorage.showSuggestions === undefined) { + localStorage.showSuggestions = "true"; } +if (localStorage.showLogs === undefined) { + localStorage.showLogs = "false"; +} + +// init domain storage +mainStorage.getAll().then(function(domains) { + if (domains) { + domains.forEach(function(domainObj) { + ruleDomains[domainObj.id] = domainObj; + }); + } +}).catch(simpleError); + diff --git a/src/background/extractMime.js b/src/background/extractMime.js index 9ca38321..097239de 100644 --- a/src/background/extractMime.js +++ b/src/background/extractMime.js @@ -1,45 +1,42 @@ -/* global bgapp */ -{ - // This function will try to guess the mime type. - // The goal is to use the highest ranking mime type that it gets from these sources (highest - // ranking sources first): The user provided mime type on the first line of the file, the url - // file extension, the file looks like html, the file looks like xml, the file looks like, - // JavaScript, the file looks like CSS, can't tell what the file is so default to text/plain. - bgapp.extractMimeType = function(requestUrl, file) { - file = file || ""; - const possibleExt = (requestUrl.match(/\.[A-Za-z]{2,4}$/) || [""])[0]; - const looksLikeCSSRegex = /[#.@][^\s\{]+\s*\{/; - const looksLikeJSRegex = /(var|const|let|function)\s+.+/; - const looksLikeXMLRegex = /<\?xml(\s+.+\s*)?\?>/i; - const looksLikeHTMLRegex = //i; - const mimeInFileRegex = /\/\* *mime: *([-\w\/]+) *\*\//i; - const firstLine = (file.match(/.*/) || [""])[0]; - let userMime = firstLine.match(mimeInFileRegex); - userMime = userMime ? userMime[1] : null; - const extToMime = { - ".js": "text/javascript", - ".html": "text/html", - ".css": "text/css", - ".xml": "text/xml" - }; - let mime = extToMime[possibleExt]; - if (!mime) { - if (looksLikeHTMLRegex.test(file)) { - mime = "text/html"; - } else if (looksLikeXMLRegex.test(file)) { - mime = "text/xml"; - } else if (looksLikeJSRegex.test(file)) { - mime = "text/javascript"; - } else if (looksLikeCSSRegex.test(file)) { - mime = "text/css"; - } else { - mime = "text/plain"; - } - } - if (userMime) { - mime = userMime; - file = file.replace(mimeInFileRegex, ""); - } - return {mime: mime, file: file}; +// This function will try to guess the mime type. +// The goal is to use the highest ranking mime type that it gets from these sources (highest +// ranking sources first): The user provided mime type on the first line of the file, the url +// file extension, the file looks like html, the file looks like xml, the file looks like, +// JavaScript, the file looks like CSS, can't tell what the file is so default to text/plain. +export function extractMimeType(requestUrl, file) { + file = file || ""; + const possibleExt = (requestUrl.match(/\.[A-Za-z]{2,4}$/) || [""])[0]; + const looksLikeCSSRegex = /[#.@][^\s\{]+\s*\{/; + const looksLikeJSRegex = /(var|const|let|function)\s+.+/; + const looksLikeXMLRegex = /<\?xml(\s+.+\s*)?\?>/i; + const looksLikeHTMLRegex = //i; + const mimeInFileRegex = /\/\* *mime: *([-\w\/]+) *\*\//i; + const firstLine = (file.match(/.*/) || [""])[0]; + let userMime = firstLine.match(mimeInFileRegex); + userMime = userMime ? userMime[1] : null; + const extToMime = { + ".js": "text/javascript", + ".html": "text/html", + ".css": "text/css", + ".xml": "text/xml" }; -} + let mime = extToMime[possibleExt]; + if (!mime) { + if (looksLikeHTMLRegex.test(file)) { + mime = "text/html"; + } else if (looksLikeXMLRegex.test(file)) { + mime = "text/xml"; + } else if (looksLikeJSRegex.test(file)) { + mime = "text/javascript"; + } else if (looksLikeCSSRegex.test(file)) { + mime = "text/css"; + } else { + mime = "text/plain"; + } + } + if (userMime) { + mime = userMime; + file = file.replace(mimeInFileRegex, ""); + } + return {mime: mime, file: file}; +}; diff --git a/src/background/headerHandling.js b/src/background/headerHandling.js index 7bb7708e..b083d98d 100644 --- a/src/background/headerHandling.js +++ b/src/background/headerHandling.js @@ -1,117 +1,120 @@ -/* global bgapp, match */ -{ - bgapp.makeHeaderHandler = function(type) { - return function(details) { - const headers = details[type + "Headers"]; - if (details.tabId > -1 && headers) { - let tabUrl = bgapp.tabUrlTracker.getUrlFromId(details.tabId); - if (details.type === "main_frame") { - // a new tab must have just been created. - tabUrl = details.url; - } - if (tabUrl) { - return handleHeaders(type, details.url, tabUrl, headers, details.tabId); - } +/* global match */ + +import {tabUrlTracker} from "./tabUrlTracker.js" +import {logOnTab} from "./util.js" +import {ruleDomains} from "./background.js" + +export function makeHeaderHandler(type) { + return function(details) { + const headers = details[type + "Headers"]; + if (details.tabId > -1 && headers) { + let tabUrl = tabUrlTracker.getUrlFromId(details.tabId); + if (details.type === "main_frame") { + // a new tab must have just been created. + tabUrl = details.url; + } + if (tabUrl) { + return handleHeaders(type, details.url, tabUrl, headers, details.tabId); } - }; + } }; +}; - const parseHeaderDataStr = function(headerDataStr) { - const ans = []; - const rules = headerDataStr.split(";"); - const len = rules.length; - for (let x = 0; x < len; x++) { - const rule = rules[x]; - const ruleParts = rule.split(": "); - if (ruleParts[0].indexOf("set") === 0) { - if (ruleParts.length === 2) { - ans.push({ - operation: "set", - name: decodeURIComponent(ruleParts[0].substring(4)).toLowerCase(), - value: decodeURIComponent(ruleParts[1]) - }); - } - } else if (ruleParts[0].indexOf("remove") === 0) { +const parseHeaderDataStr = function(headerDataStr) { + const ans = []; + const rules = headerDataStr.split(";"); + const len = rules.length; + for (let x = 0; x < len; x++) { + const rule = rules[x]; + const ruleParts = rule.split(": "); + if (ruleParts[0].indexOf("set") === 0) { + if (ruleParts.length === 2) { ans.push({ - operation: "remove", - name: decodeURIComponent(ruleParts[0].substring(7)).toLowerCase() + operation: "set", + name: decodeURIComponent(ruleParts[0].substring(4)).toLowerCase(), + value: decodeURIComponent(ruleParts[1]) }); } + } else if (ruleParts[0].indexOf("remove") === 0) { + ans.push({ + operation: "remove", + name: decodeURIComponent(ruleParts[0].substring(7)).toLowerCase() + }); } - return ans; - }; + } + return ans; +}; - const makeHeadersObject = function(headers) { - const ans = {}; - const len = headers.length; - for (let x = 0; x < len; x++) { - const header = headers[x]; - ans[header.name.toLowerCase()] = header; - } - return ans; - }; +const makeHeadersObject = function(headers) { + const ans = {}; + const len = headers.length; + for (let x = 0; x < len; x++) { + const header = headers[x]; + ans[header.name.toLowerCase()] = header; + } + return ans; +}; - const processRule = function(ruleObj, type, requestUrl, tabUrl, headers, tabId) { - const headerObjToReturn = {}; - const headerObjToReturnKey = type + "Headers"; - headerObjToReturn[headerObjToReturnKey] = headers; - if (ruleObj.on && ruleObj.type === "headerRule" && match(ruleObj.match, requestUrl).matched) { - const rulesStr = ruleObj[type + "Rules"]; - bgapp.util.logOnTab(tabId, "Header Rule Matched: " + requestUrl + - " applying rules: " + rulesStr, true); - const headerRules = parseHeaderDataStr(rulesStr); - const headersObj = makeHeadersObject(headers); - const numRules = headerRules.length; - for (let t = 0; t < numRules; t++) { - const rule = headerRules[t]; - if (rule.operation === "set") { - headersObj[rule.name] = { - name: rule.name, - value: rule.value - }; - } else { - if (headersObj[rule.name]) { - headersObj[rule.name] = undefined; - } +const processRule = function(ruleObj, type, requestUrl, tabUrl, headers, tabId) { + const headerObjToReturn = {}; + const headerObjToReturnKey = type + "Headers"; + headerObjToReturn[headerObjToReturnKey] = headers; + if (ruleObj.on && ruleObj.type === "headerRule" && match(ruleObj.match, requestUrl).matched) { + const rulesStr = ruleObj[type + "Rules"]; + logOnTab(tabId, "Header Rule Matched: " + requestUrl + + " applying rules: " + rulesStr, true); + const headerRules = parseHeaderDataStr(rulesStr); + const headersObj = makeHeadersObject(headers); + const numRules = headerRules.length; + for (let t = 0; t < numRules; t++) { + const rule = headerRules[t]; + if (rule.operation === "set") { + headersObj[rule.name] = { + name: rule.name, + value: rule.value + }; + } else { + if (headersObj[rule.name]) { + headersObj[rule.name] = undefined; } } - const newHeaders = []; - for (const headerKey in headersObj) { - const header = headersObj[headerKey]; - if (header) { - newHeaders.push(header); - } - } - headerObjToReturn[headerObjToReturnKey] = newHeaders; - return headerObjToReturn; } - }; - - const processTabGroup = function(domainObj, type, requestUrl, tabUrl, headers, tabId) { - const headerObjToReturn = {}; - const headerObjToReturnKey = type + "Headers"; - headerObjToReturn[headerObjToReturnKey] = headers; - if (domainObj.on && match(domainObj.matchUrl, tabUrl).matched) { - const rules = domainObj.rules || []; - for (let x = 0, len = rules.length; x < len; ++x) { - const ruleObj = rules[x]; - const ruleResults = processRule(ruleObj, type, requestUrl, tabUrl, headers, tabId); - if (ruleResults) { - return ruleResults; - } + const newHeaders = []; + for (const headerKey in headersObj) { + const header = headersObj[headerKey]; + if (header) { + newHeaders.push(header); } } + headerObjToReturn[headerObjToReturnKey] = newHeaders; return headerObjToReturn; - }; + } +}; - const handleHeaders = function(type, requestUrl, tabUrl, headers, tabId) { - const headerObjToReturn = {}; - const headerObjToReturnKey = type + "Headers"; - headerObjToReturn[headerObjToReturnKey] = headers; - for (const key in bgapp.ruleDomains) { - const domainObj = bgapp.ruleDomains[key]; - return processTabGroup(domainObj, type, requestUrl, tabUrl, headers, tabId); +const processTabGroup = function(domainObj, type, requestUrl, tabUrl, headers, tabId) { + const headerObjToReturn = {}; + const headerObjToReturnKey = type + "Headers"; + headerObjToReturn[headerObjToReturnKey] = headers; + if (domainObj.on && match(domainObj.matchUrl, tabUrl).matched) { + const rules = domainObj.rules || []; + for (let x = 0, len = rules.length; x < len; ++x) { + const ruleObj = rules[x]; + const ruleResults = processRule(ruleObj, type, requestUrl, tabUrl, headers, tabId); + if (ruleResults) { + return ruleResults; + } } - return headerObjToReturn; - }; -} + } + return headerObjToReturn; +}; + +const handleHeaders = function(type, requestUrl, tabUrl, headers, tabId) { + const headerObjToReturn = {}; + const headerObjToReturnKey = type + "Headers"; + headerObjToReturn[headerObjToReturnKey] = headers; + for (const key in ruleDomains) { + const domainObj = ruleDomains[key]; + return processTabGroup(domainObj, type, requestUrl, tabUrl, headers, tabId); + } + return headerObjToReturn; +}; diff --git a/src/background/htmlWebRequestProcessor.js b/src/background/htmlWebRequestProcessor.js new file mode 100644 index 00000000..93ab962a --- /dev/null +++ b/src/background/htmlWebRequestProcessor.js @@ -0,0 +1,52 @@ +"use strict"; + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +function concatTypedArrays(...arrs) { + let c_sumLen = 0, a_pos; + for (a_pos of arrs) + c_sumLen += a_pos.length; + c_sumLen = new arrs[0].constructor(c_sumLen); + a_pos = 0; + for (let a of arrs) + c_sumLen.set(a, a_pos), a_pos += a.length; + return c_sumLen +} + +function htmlWebRequestProcessor(requestId){ + let filter = browser.webRequest.filterResponseData(requestId); + let buffers = []; + filter.ondata = (evt) => { + buffers.push(new Uint8Array(evt.data)); + }; + return new Promise((resolve, reject) => { + filter.onstop = (evt) => { + resolve({"data": concatTypedArrays(...buffers), "event": evt}); + }; + }); +} diff --git a/src/background/init.js b/src/background/init.js index 93a2860b..e3142ee8 100644 --- a/src/background/init.js +++ b/src/background/init.js @@ -1,4 +1,5 @@ -{ - window.bgapp = {}; - window.browser = window.browser ? window.browser : window.chrome; -} +window.browser = window.browser ? window.browser : window.chrome; + +export const capabilities = { + realtimeRewriteSupported: !!browser.webRequest.filterResponseData +}; \ No newline at end of file diff --git a/src/background/keyvalDB.js b/src/background/keyvalDB.js index f12e9332..059ea9c1 100644 --- a/src/background/keyvalDB.js +++ b/src/background/keyvalDB.js @@ -1,5 +1,6 @@ -var keyvalDB = function(dbName, schemaDef, version, options) { - "use strict"; +"use strict"; + +export function keyvalDB(dbName, schemaDef, version, options) { options = options || {}; var schema = {}; if (Object.prototype.toString.call(schemaDef) === "[object Array]") { @@ -203,6 +204,3 @@ var keyvalDB = function(dbName, schemaDef, version, options) { }; }; -if (typeof module !== 'undefined' && module.exports) { - module.exports = keyvalDB; -} diff --git a/src/background/mainStorage.js b/src/background/mainStorage.js index 514b4607..e9a3a57c 100644 --- a/src/background/mainStorage.js +++ b/src/background/mainStorage.js @@ -1,73 +1,74 @@ -/* global bgapp, keyvalDB */ -{ - bgapp.mainStorage = (function() { - const db = keyvalDB("OverrideDB", [{store: "domains", key: "id"}], 1); - const domainStore = db.usingStore("domains"); +/* global keyvalDB */ - const put = function(domainData) { - return new Promise(function(res, rej) { - db.open(function(err) { - if (err) { - console.error(err); - rej(err); - } else { - domainStore.upsert(domainData.id, domainData, function(err) { - if (err) { - console.error(err); - rej(err); - } else { - res(); - } - }); - } - }); +import {keyvalDB} from "./keyvalDB.js" + +export let mainStorage = (function() { + const db = keyvalDB("OverrideDB", [{store: "domains", key: "id"}], 1); + const domainStore = db.usingStore("domains"); + + const put = function(domainData) { + return new Promise(function(res, rej) { + db.open(function(err) { + if (err) { + console.error(err); + rej(err); + } else { + domainStore.upsert(domainData.id, domainData, function(err) { + if (err) { + console.error(err); + rej(err); + } else { + res(); + } + }); + } }); - }; + }); + }; - const getDomains = function() { - return new Promise(function(res, rej) { - db.open(function(err) { - if (err) { - console.error(err); - rej(err); - } else { - domainStore.getAll(function(err, ans) { - if (err) { - console.error(err); - rej(err); - } else { - res(ans); - } - }); - } - }); + const getDomains = function() { + return new Promise(function(res, rej) { + db.open(function(err) { + if (err) { + console.error(err); + rej(err); + } else { + domainStore.getAll(function(err, ans) { + if (err) { + console.error(err); + rej(err); + } else { + res(ans); + } + }); + } }); - }; + }); + }; - const deleteDomain = function(id) { - return new Promise(function(res, rej) { - db.open(function(err) { - if (err) { - console.error(err); - rej(err); - } else { - domainStore.delete(id, function(err) { - if (err) { - console.error(err); - rej(err); - } else { - res(); - } - }); - } - }); + const deleteDomain = function(id) { + return new Promise(function(res, rej) { + db.open(function(err) { + if (err) { + console.error(err); + rej(err); + } else { + domainStore.delete(id, function(err) { + if (err) { + console.error(err); + rej(err); + } else { + res(); + } + }); + } }); - }; + }); + }; - return { - put: put, - getAll: getDomains, - delete: deleteDomain - }; - })(); -} + return { + put: put, + getAll: getDomains, + delete: deleteDomain + }; +})(); diff --git a/src/background/match.js b/src/background/match.js index b9056c9e..249ab9de 100644 --- a/src/background/match.js +++ b/src/background/match.js @@ -72,7 +72,7 @@ function replaceAfter(str, idx, match, replace) { return str.substring(0, idx) + str.substring(idx).replace(match, replace); } -function matchReplace(pattern, replacePattern, str) { +export function matchReplace(pattern, replacePattern, str) { "use strict"; var matchData; if (pattern.matched !== undefined && pattern.freeVars !== undefined) { @@ -102,7 +102,3 @@ function matchReplace(pattern, replacePattern, str) { return replacePattern; } - -if (typeof module === "object" && module.exports) { - module.exports = matchReplace; -} diff --git a/src/background/parseHTML.js b/src/background/parseHTML.js new file mode 100644 index 00000000..bf0c5e8d --- /dev/null +++ b/src/background/parseHTML.js @@ -0,0 +1,65 @@ +"use strict"; + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +export const thCT = "text/html"; + +export function _parseHTML(data, encoding, fatality) { + let decoder = new TextDecoder(encoding, { + fatal: fatality + }); + let dp = new DOMParser(); + let doc = dp.parseFromString(decoder.decode(data), thCT); + return doc; +} + +export function detectCharset(doc) { + let charsetEls = doc.querySelectorAll("meta[charset]"); + + let detectedCharset = null; + if (charsetEls.length) { + detectedCharset = charsetEls[charsetEls.length - 1].getAttribute("charset"); + } + + if (!detectedCharset) { + detectedCharset = "utf-8"; + } + return detectedCharset; +} + +export function parseHTML(data) { + const initialCharset = "ascii"; + let doc = _parseHTML(data, initialCharset, false); + let actualCharset = detectCharset(doc); + + if (initialCharset === actualCharset) { + return doc; + } else { + return _parseHTML(data, actualCharset, true); + } +} diff --git a/src/background/removeIntegrity.js b/src/background/removeIntegrity.js new file mode 100644 index 00000000..c20bcb1f --- /dev/null +++ b/src/background/removeIntegrity.js @@ -0,0 +1,67 @@ +"use strict"; + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +import {parseHTML} from "./parseHTML.js" + +export function removeIntegrityFromHTMLDoc(doc){ + doc.querySelectorAll("[integrity]").forEach(el => {el.removeAttribute("integrity");}); +} + +export function removeIntegrityFromHTMLDocBytes(sourceBytes){ + let doc = parseHTML(sourceBytes); + if(doc){ + removeIntegrityFromHTMLDoc(doc); + let encoder = new TextEncoder(); + return encoder.encode(doc.documentElement.outerHTML) + } +} + +export function removeIntegrity(details){ + let ct = null; + let headers = details.responseHeaders; + if(headers){ + for(let h of headers){ + if(h.name == "content-type"){ + ct = h.value; + } + } + + if(ct.substring(0, thCT.length) == thCT){ + htmlWebRequestProcessor(details.requestId).then((obj) => { + let res = removeIntegrityFromHTMLDoc(obj.data) + if(res){ + obj.event.target.write(res); + obj.event.target.disconnect(); + } + }); + } + } + + return {}; +} diff --git a/src/background/requestHandling.js b/src/background/requestHandling.js index 1d25fc1a..7b3757a2 100644 --- a/src/background/requestHandling.js +++ b/src/background/requestHandling.js @@ -1,69 +1,97 @@ -/* global bgapp, match, matchReplace, browser */ -{ - const logOnTab = bgapp.util.logOnTab; +/* global match, matchReplace, browser */ - const replaceContent = (requestId, mimeAndFile) => { - if (browser.webRequest.filterResponseData) { - // browsers that support filterResponseData - browser.webRequest.filterResponseData(requestId).onstart = e => { - const encoder = new TextEncoder(); - e.target.write(encoder.encode(mimeAndFile.file)); - e.target.disconnect(); - }; - return { - responseHeaders: [{ - name: "Content-Type", - value: mimeAndFile.mime - }] - }; - } +import {capabilities} from "./init.js" +import {ruleDomains} from "./background.js" +import {logOnTab} from "./util.js" +import {extractMimeType} from "./extractMime.js" +import {removeIntegrity} from "./removeIntegrity.js" - // browsers that dont support filterResponseData +const replaceContent = (requestId, mimeAndFile) => { + console.log("requestId, mimeAndFile", requestId, mimeAndFile); + if (browser.webRequest.filterResponseData) { + // browsers that support filterResponseData + browser.webRequest.filterResponseData(requestId).onstart = e => { + console.log("Replacing content", requestId); + const encoder = new TextEncoder(); + e.target.write(encoder.encode(mimeAndFile.file)); + e.target.disconnect(); + }; return { - // unescape is a easy solution to the utf-8 problem: - // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa#Unicode_Strings - redirectUrl: "data:" + mimeAndFile.mime + ";charset=UTF-8;base64," + - btoa(unescape(encodeURIComponent(mimeAndFile.file))) + responseHeaders: [{ + name: "Content-Type", + value: mimeAndFile.mime + }] }; + } + + // browsers that dont support filterResponseData + return { + // unescape is a easy solution to the utf-8 problem: + // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa#Unicode_Strings + redirectUrl: "data:" + mimeAndFile.mime + ";charset=UTF-8;base64," + + btoa(unescape(encodeURIComponent(mimeAndFile.file))) }; +}; - bgapp.handleRequest = function(requestUrl, tabUrl, tabId, requestId) { - for (const key in bgapp.ruleDomains) { - const domainObj = bgapp.ruleDomains[key]; - if (domainObj.on && match(domainObj.matchUrl, tabUrl).matched) { - const rules = domainObj.rules || []; - for (let x = 0, len = rules.length; x < len; ++x) { - const ruleObj = rules[x]; - if (ruleObj.on) { - if (ruleObj.type === "normalOverride") { - const matchedObj = match(ruleObj.match, requestUrl); - const newUrl = matchReplace(matchedObj, ruleObj.replace, requestUrl); - if (matchedObj.matched) { - logOnTab(tabId, "URL Override Matched: " + requestUrl + - " to: " + newUrl + " match url: " + ruleObj.match, true); - if (requestUrl !== newUrl) { - return {redirectUrl: newUrl}; - } else { - // allow redirections to the original url (aka do nothing). - // This allows for "redirect all of these except this." - return; - } - } - } else if (ruleObj.type === "fileOverride" && - match(ruleObj.match, requestUrl).matched) { +function walkRules(details, tabUrl, callback){ + console.log("walkRules details, tabUrl", details, tabUrl); + for (const key in ruleDomains) { + const domainObj = ruleDomains[key]; + if (domainObj.on && match(domainObj.matchUrl, tabUrl).matched) { + const rules = domainObj.rules || []; + for (let x = 0, len = rules.length; x < len; ++x) { + const ruleObj = rules[x]; + if (ruleObj.on) { + return callback(ruleObj, details); + } + } + logOnTab(details.tabId, "No override match for: " + details.url); + } else { + logOnTab(details.tabId, "Rule is off or tab URL does not match: " + domainObj.matchUrl); + } + } +} - logOnTab(tabId, "File Override Matched: " + requestUrl + " match url: " + - ruleObj.match, true); +function bodyCallback(ruleObj, details){ + if (ruleObj.type === "removeIntegrity" && match(ruleObj.match, details.url).matched) { + return removeIntegrity(details); + } +} - const mimeAndFile = bgapp.extractMimeType(requestUrl, ruleObj.file); - return replaceContent(requestId, mimeAndFile); - } - } - } - logOnTab(tabId, "No override match for: " + requestUrl); +export function handleBody(details, tabUrl) { + walkRules(details, tabUrl, bodyCallback); +} + +function headersCallback(ruleObj, details){ + console.log("headersCallback ruleObj, details", ruleObj, details); + const requestUrl = details.url, tabId = details.tabId; + if (ruleObj.type === "normalOverride") { + const matchedObj = match(ruleObj.match, requestUrl); + const newUrl = matchReplace(matchedObj, ruleObj.replace, requestUrl); + if (matchedObj.matched) { + logOnTab(tabId, "URL Override Matched: " + requestUrl + + " to: " + newUrl + " match url: " + ruleObj.match, true); + if (requestUrl !== newUrl) { + return {redirectUrl: newUrl}; } else { - logOnTab(tabId, "Rule is off or tab URL does not match: " + domainObj.matchUrl); + // allow redirections to the original url (aka do nothing). + // This allows for "redirect all of these except this." + return; } } - }; + } else if (ruleObj.type === "fileOverride" && + match(ruleObj.match, requestUrl).matched) { + + logOnTab(tabId, "File Override Matched: " + requestUrl + " match url: " + + ruleObj.match, true); + + const mimeAndFile = extractMimeType(requestUrl, ruleObj.file); + return replaceContent(details.requestId, mimeAndFile); + } else if (ruleObj.type === "removeIntegrity" && match(ruleObj.match, requestUrl).matched) { + removeIntegrity(details); + } } + +export function handleRequest(details, tabUrl) { + walkRules(details, tabUrl, headersCallback); +}; diff --git a/src/background/requestIdTracker.js b/src/background/requestIdTracker.js index be286982..a9e25144 100644 --- a/src/background/requestIdTracker.js +++ b/src/background/requestIdTracker.js @@ -1,45 +1,44 @@ -/* global bgapp */ -{ - bgapp.requestIdTracker = (function() { - let head; - let tail; - let length = 0; - const tracker = {}; - const maxSize = 1000; +"use strict"; - function pop() { - const val = head.val; - head = head.next; - length--; - delete tracker[val]; - } +export let requestIdTracker = (function() { + let head; + let tail; + let length = 0; + const tracker = {}; + const maxSize = 1000; - function push(obj) { - const newNode = { - val: obj, - next: undefined - }; - if (length > 0) { - tail.next = newNode; - tail = newNode; - } else { - head = newNode; - tail = newNode; - } - length++; - tracker[obj] = true; - while (length > maxSize) { - pop(); - } - } + function pop() { + const val = head.val; + head = head.next; + length--; + delete tracker[val]; + } - function has(id) { - return tracker[id]; + function push(obj) { + const newNode = { + val: obj, + next: undefined + }; + if (length > 0) { + tail.next = newNode; + tail = newNode; + } else { + head = newNode; + tail = newNode; } + length++; + tracker[obj] = true; + while (length > maxSize) { + pop(); + } + } - return { - push: push, - has: has - }; - })(); -} + function has(id) { + return tracker[id]; + } + + return { + push: push, + has: has + }; +})(); diff --git a/src/background/tabUrlTracker.js b/src/background/tabUrlTracker.js index fcd2477e..c3e18b83 100644 --- a/src/background/tabUrlTracker.js +++ b/src/background/tabUrlTracker.js @@ -1,49 +1,49 @@ -/* globals chrome, bgapp */ -{ - // http://stackoverflow.com/questions/15124995/how-to-wait-for-an-asynchronous-methods-callback-return-value - bgapp.tabUrlTracker = (function() { - // All opened urls - const urls = {}; - const closeListeners = []; +"use strict"; +/* globals chrome */ - const queryTabsCallback = function(allTabs) { - if (allTabs) { - allTabs.forEach(function(tab) { - urls[tab.id] = tab.url; - }); - } - }; +// http://stackoverflow.com/questions/15124995/how-to-wait-for-an-asynchronous-methods-callback-return-value +export let tabUrlTracker = (function() { + // All opened urls + const urls = {}; + const closeListeners = []; - const updateTabCallback = function(tabId, changeinfo, tab) { - urls[tabId] = tab.url; - }; - - // Not all tabs will fire an update event. If the page is pre-rendered, - // a replace will happen instead. - const tabReplacedCallback = function(newTabId, oldTabId) { - delete urls[oldTabId]; - chrome.tabs.get(newTabId, function(tab) { + const queryTabsCallback = function(allTabs) { + if (allTabs) { + allTabs.forEach(function(tab) { urls[tab.id] = tab.url; }); - }; + } + }; - const removeTabCallback = function(tabId) { - closeListeners.forEach(function(fn) { - fn(urls[tabId]); - }); - delete urls[tabId]; - }; + const updateTabCallback = function(tabId, changeinfo, tab) { + urls[tabId] = tab.url; + }; + + // Not all tabs will fire an update event. If the page is pre-rendered, + // a replace will happen instead. + const tabReplacedCallback = function(newTabId, oldTabId) { + delete urls[oldTabId]; + chrome.tabs.get(newTabId, function(tab) { + urls[tab.id] = tab.url; + }); + }; + + const removeTabCallback = function(tabId) { + closeListeners.forEach(function(fn) { + fn(urls[tabId]); + }); + delete urls[tabId]; + }; - // init - chrome.tabs.query({}, queryTabsCallback); - chrome.tabs.onUpdated.addListener(updateTabCallback); - chrome.tabs.onRemoved.addListener(removeTabCallback); - chrome.tabs.onReplaced.addListener(tabReplacedCallback); + // init + chrome.tabs.query({}, queryTabsCallback); + chrome.tabs.onUpdated.addListener(updateTabCallback); + chrome.tabs.onRemoved.addListener(removeTabCallback); + chrome.tabs.onReplaced.addListener(tabReplacedCallback); - return { - getUrlFromId: function(id) { - return urls[id]; - } - }; - })(); -} + return { + getUrlFromId: function(id) { + return urls[id]; + } + }; +})(); diff --git a/src/background/util.js b/src/background/util.js index 3a19c906..e95c37e2 100644 --- a/src/background/util.js +++ b/src/background/util.js @@ -1,23 +1,21 @@ -/* global bgapp, chrome */ -{ - bgapp.util = {}; +"use strict"; - bgapp.util.logOnTab = function(tabId, message, important) { - if (localStorage.showLogs === "true") { - important = !!important; - chrome.tabs.sendMessage(tabId, { - action: "log", - message: message, - important: important - }); - } - }; +/* global chrome */ +export let logOnTab = function(tabId, message, important) { + if (localStorage.showLogs === "true") { + important = !!important; + chrome.tabs.sendMessage(tabId, { + action: "log", + message: message, + important: important + }); + } +}; - bgapp.util.simpleError = function(err) { - if (err.stack) { - console.error("=== Printing Stack ==="); - console.error(err.stack); - } - console.error(err); - }; -} +export let simpleError = function(err) { + if (err.stack) { + console.error("=== Printing Stack ==="); + console.error(err.stack); + } + console.error(err); +}; diff --git a/src/subresourceIntegrity/subresourceIntegrity.js b/src/subresourceIntegrity/subresourceIntegrity.js new file mode 100644 index 00000000..90982dde --- /dev/null +++ b/src/subresourceIntegrity/subresourceIntegrity.js @@ -0,0 +1,70 @@ +"use strict"; + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +function removeIntegrityIfNeeded(node) { + if (node.integrity) { + console.log("Removed integrity from", node); + node.integrity = ""; // or maybe recalculate + } +} + +if (typeof MutationEvent != "undefined") { + console.log("Mutation events are supported in this browser. They are slow, deprecated, may be removed in future, but blocking! That's what we need."); + + function mutEventCallback(e) { + removeIntegrityIfNeeded(e.target) + } + window.document.addEventListener("DOMNodeInserted", mutEventCallback, true); + window.document.addEventListener("DOMNodeInserted", mutEventCallback, true); +} else { + console.log("Mutation events are not supported in this browser. Maybe they have been dropped. Using mutations observers that by design have no capturing mode. It won't work, the request will be done before we undo the mutation. Need some cooperation to WebRequest to drop the first request fast, and then do the second request after the mutation"); + + const mutationsProcessors = new Map([ + ['childList', (m) => m.addedNodes.forEach(removeIntegrityIfNeeded)], + ['attributes', (m) => removeIntegrityIfNeeded(m.target)], + ]) + + function observe(mutationsList, observer) { + for (let mutation of mutationsList) { + mutationsProcessors.get(mutation.type)(mutation); + } + }; + + const observer = new window.MutationObserver(observe); + + observer.observe(window.document.documentElement, { + attributes: true, + attributeFilter: ["integrity"], + attributeOldValue: false, + childList: true, + subtree: true, + characterData: false, + characterDataOldValue: false, + }); +} diff --git a/src/ui/devtools.html b/src/ui/devtools.html index d7bc422f..d8b7224a 100644 --- a/src/ui/devtools.html +++ b/src/ui/devtools.html @@ -4,6 +4,6 @@ - + diff --git a/src/ui/devtools.js b/src/ui/devtools.js index 715f06b0..700411e2 100644 --- a/src/ui/devtools.js +++ b/src/ui/devtools.js @@ -1,9 +1,11 @@ +"use strict"; + chrome.runtime.sendMessage({action: "getSetting", setting: "devTools"}, function(data) { if (data === "true") { chrome.devtools.panels.create("Overrides", "", //image file "/src/ui/devtoolstab.html", - function(panel) {} + function(panel) { } ); } }); diff --git a/src/ui/devtoolstab.html b/src/ui/devtoolstab.html index 66b75b11..9316abcb 100644 --- a/src/ui/devtoolstab.html +++ b/src/ui/devtoolstab.html @@ -184,6 +184,24 @@ + +

Resource Override

@@ -225,6 +243,7 @@

Resource Override

+
@@ -346,7 +365,7 @@

Tips and Tricks

- + @@ -362,24 +381,35 @@

Tips and Tricks

- - - + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - + - + diff --git a/src/ui/devtoolstab.js b/src/ui/devtoolstab.js index 004d666f..1d91e3e9 100644 --- a/src/ui/devtoolstab.js +++ b/src/ui/devtoolstab.js @@ -1,128 +1,132 @@ -(function() { - "use strict"; - - /* globals chrome */ - - const app = window.app; - const ui = app.ui; - const util = app.util; - - app.mainSuggest = app.suggest(); - app.requestHeadersSuggest = app.suggest(); - app.responseHeadersSuggest = app.suggest(); - app.files = {}; - app.skipNextSync = false; - - function renderData() { - app.files = {}; - ui.domainDefs.children().remove(); - chrome.runtime.sendMessage({action: "getDomains"}, function(domains) { - if (domains.length) { - domains.forEach(function(domain) { - const domainMarkup = app.createDomainMarkup(domain); - ui.domainDefs.append(domainMarkup); - }); - } else { - const newDomain = app.createDomainMarkup({rules: [{type: "normalOverride"}]}); - ui.domainDefs.append(newDomain); - newDomain.find(".domainMatchInput").val("*"); - chrome.runtime.sendMessage({ - action: "saveDomain", - data: app.getDomainData(newDomain) - }); - app.skipNextSync = true; - } - util.getTabResources(function(res) { - app.mainSuggest.fillOptions(res); +"use strict"; + +/* globals chrome */ + +import {removeEl} from "./microJQuery.js"; + +import {app, ui} from './init.js'; +import {suggest} from './suggest.js'; +import {createDomainMarkup, getDomainData} from './tabGroup.js'; +import {headersLists} from './headers.js'; +import {getTabResources} from './util.js'; + +export const mainSuggest = suggest(); +export const requestHeadersSuggest = suggest(); +export const responseHeadersSuggest = suggest(); +export let files = {}; +export let skipNextSync = false; + +function renderData() { + files = {}; + [...ui.domainDefs.children].forEach(removeEl); + chrome.runtime.sendMessage({action: "getDomains"}, function(domains) { + if (domains.length) { + domains.forEach(function(domain) { + const domainMarkup = createDomainMarkup(domain); + ui.domainDefs.appendChild(domainMarkup); + }); + } else { + const newDomain = createDomainMarkup({rules: [{type: "normalOverride"}]}); + ui.domainDefs.appendChild(newDomain); + newDomain.getElementsByClassName("domainMatchInput")[0].value= "*"; + chrome.runtime.sendMessage({ + action: "saveDomain", + data: getDomainData(newDomain) }); - }); - } - - function setupSynchronizeConnection() { - chrome.runtime.sendMessage({action: "syncMe"}, function() { - if (!app.skipNextSync) { - renderData(); - } - app.skipNextSync = false; - setupSynchronizeConnection(); - }); - } - - function init() { - app.mainSuggest.init(); - app.requestHeadersSuggest.init(); - app.responseHeadersSuggest.init(); - app.requestHeadersSuggest.fillOptions(app.headersLists.requestHeaders); - app.responseHeadersSuggest.fillOptions(app.headersLists.responseHeaders); - - setupSynchronizeConnection(); - - renderData(); - - ui.addDomainBtn.on("click", function() { - const newDomain = app.createDomainMarkup(); - newDomain.find(".domainMatchInput").val("*"); - ui.domainDefs.append(newDomain); - chrome.runtime.sendMessage({action: "saveDomain", data: app.getDomainData(newDomain)}); app.skipNextSync = true; + } + getTabResources(function(res) { + mainSuggest.fillOptions(res); }); + }); +} - ui.helpBtn.on("click", function() { - ui.helpOverlay.toggle(); - }); - - ui.helpCloseBtn.on("click", function() { - ui.helpOverlay.hide(); - }); - - if (!chrome.devtools) { - ui.showSuggestions.hide(); - ui.showSuggestionsText.hide(); - chrome.runtime.sendMessage({ - action: "getSetting", - setting: "tabPageNotice" - }, function(data) { - - if (data !== "true") { - ui.tabPageNotice.find("a").on("click", function(e) { +function setupSynchronizeConnection() { + chrome.runtime.sendMessage({action: "syncMe"}, function() { + if (!app.skipNextSync) { + renderData(); + } + app.skipNextSync = false; + setupSynchronizeConnection(); + }); +} + + +function init() { + mainSuggest.init(); + requestHeadersSuggest.init(); + responseHeadersSuggest.init(); + requestHeadersSuggest.fillOptions(headersLists.requestHeaders); + responseHeadersSuggest.fillOptions(headersLists.responseHeaders); + + setupSynchronizeConnection(); + + renderData(); + + ui.addDomainBtn.addEventListener("click", function() { + const newDomain = createDomainMarkup(); + newDomain.getElementsByClassName("domainMatchInput")[0].value= "*"; + ui.domainDefs.appendChild(newDomain); + chrome.runtime.sendMessage({action: "saveDomain", data: getDomainData(newDomain)}); + app.skipNextSync = true; + }); + + ui.helpBtn.addEventListener("click", function() { + ui.helpOverlay.style.display = (ui.helpOverlay.style.display == "none" ? "block" : "none"); + }); + + ui.helpCloseBtn.addEventListener("click", function() { + ui.helpOverlay.style.display = "none"; + }); + + if (!chrome.devtools) { + ui.showSuggestions.style.display = "none"; + ui.showSuggestionsText.style.display = "none"; + chrome.runtime.sendMessage({ + action: "getSetting", + setting: "tabPageNotice" + }, function(data) { + + if (data !== "true") { + [...ui.tabPageNotice.getElementsByTagName("A")].forEach(e => { + e.addEventListener("click", function(e) { e.preventDefault(); chrome.runtime.sendMessage({ action: "setSetting", setting: "tabPageNotice", value: "true" }); - ui.tabPageNotice.fadeOut(); + ui.tabPageNotice.style.display = "none"; }); - ui.tabPageNotice.fadeIn(); - setTimeout(function() { - ui.tabPageNotice.fadeOut(); - }, 6000); - } - }); - } - - if (navigator.userAgent.indexOf("Firefox") > -1 && !!chrome.devtools) { - // Firefox is really broken with the "/" and "'" keys. They just dont work. - // So try to fix them here.. wow.. just wow. I can't believe I'm fixing the ability to type. - const brokenKeys = { "/": 1, "?": 1, "'": 1, '"': 1 }; - window.addEventListener("keydown", e => { - const brokenKey = brokenKeys[e.key]; - const activeEl = document.activeElement; - if (brokenKey && (activeEl.nodeName === "INPUT" || activeEl.nodeName === "TEXTAREA") && - activeEl.className !== "ace_text-input") { - - e.preventDefault(); - const start = activeEl.selectionStart; - const end = activeEl.selectionEnd; - activeEl.value = activeEl.value.substring(0, start) + e.key + - activeEl.value.substring(end, activeEl.value.length); - activeEl.selectionStart = start + 1; - activeEl.selectionEnd = start + 1; - } - }); - } + }); + ui.tabPageNotice.style.display = "block"; + setTimeout(function() { + ui.tabPageNotice.style.display = "none"; + }, 6000); + } + }); } - init(); + if (navigator.userAgent.indexOf("Firefox") > -1 && !!chrome.devtools) { + // Firefox is really broken with the "/" and "'" keys. They just dont work. + // So try to fix them here.. wow.. just wow. I can't believe I'm fixing the ability to type. + const brokenKeys = { "/": 1, "?": 1, "'": 1, '"': 1 }; + window.addEventListener("keydown", e => { + const brokenKey = brokenKeys[e.key]; + const activeEl = document.activeElement; + if (brokenKey && (activeEl.nodeName === "INPUT" || activeEl.nodeName === "TEXTAREA") && + activeEl.className !== "ace_text-input") { + + e.preventDefault(); + const start = activeEl.selectionStart; + const end = activeEl.selectionEnd; + activeEl.value = activeEl.value.substring(0, start) + e.key + + activeEl.value.substring(end, activeEl.value.length); + activeEl.selectionStart = start + 1; + activeEl.selectionEnd = start + 1; + } + }); + } +} -})(); +init(); diff --git a/src/ui/editor.js b/src/ui/editor.js index 4f4bf713..762a1366 100644 --- a/src/ui/editor.js +++ b/src/ui/editor.js @@ -1,190 +1,192 @@ -(function() { - "use strict"; - - /* globals $, chrome, ace, js_beautify */ - - const app = window.app; - const ui = app.ui; - const util = app.util; - - let editor; - let editingFile; - let saveFunc; - - function updateSaveButtons(edited) { - if (edited) { - ui.fileSaveAndCloseBtn.css("color", "#ff0000"); - ui.fileSaveBtn.css("color", "#ff0000"); - } else { - ui.fileSaveAndCloseBtn.css("color", "#000000"); - ui.fileSaveBtn.css("color", "#000000"); - } - } +"use strict"; - function setEditorVal(str) { - editor.off("change", updateSaveButtons); - editor.setValue(str); - editor.gotoLine(0, 0, false); - editor.on("change", updateSaveButtons); - } +/* globals $, chrome, ace, js_beautify */ - function editorGuessMode(fileName, file) { - chrome.runtime.sendMessage({ - action: "extractMimeType", - file: file, - fileName: fileName - }, function(data) { - const mimeToEditorSyntax = { - "text/javascript": "javascript", - "text/html": "html", - "text/css": "css", - "text/xml": "xml" - }; - const mode = mimeToEditorSyntax[data.mime] || "javascript"; - ui.syntaxSelect.val(mode); - editor.getSession().setMode("ace/mode/" + mode); - }); - } - function saveFile() { - updateSaveButtons(); - app.files[editingFile] = editor.getValue(); - saveFunc(); - } +import {app, ui} from './init.js'; +import {files} from './devtoolstab.js'; +import {isChrome, getTabResources, shortenString} from './util.js'; - function saveFileAndClose() { - saveFile(); - ui.editorOverlay.hide(); - ui.body.css("overflow", "auto"); - } +let editor; +let editingFile; +let saveFunc; - function setupEditor() { - editor = ace.edit("editor"); - editor.setTheme("ace/theme/monokai"); - editor.setShowPrintMargin(false); - - editor.on("change", updateSaveButtons); - editor.commands.addCommand({ - name: "multiEdit", - bindKey: { - win: "Ctrl-D", - mac: "Command-D" - }, - exec: function(editor, line) { - editor.selectMore(1); - }, - readOnly: true - }); - editor.commands.addCommand({ - name: "save", - bindKey: { - win: "Ctrl-S", - mac: "Command-S" - }, - exec: function(editor, line) { - saveFile(); - }, - readOnly: true - }); - editor.commands.addCommand({ - name: "saveAndClose", - bindKey: { - win: "Ctrl-Shift-S", - mac: "Command-Shift-S" - }, - exec: function(editor, line) { - saveFileAndClose(); - }, - readOnly: true - }); +function updateSaveButtons(edited) { + if (edited) { + ui.fileSaveAndCloseBtn.style.color = "#f00"; + ui.fileSaveBtn.style.color = "#f00"; + } else { + ui.fileSaveAndCloseBtn.style.color = "#000"; + ui.fileSaveBtn.style.color = "#000"; } - - function openEditor(fileId, match, isInjectFile, saveFunction) { - saveFunc = saveFunction; - editingFile = fileId; - updateSaveButtons(); - ui.editorOverlay.css("display", "flex"); - ui.body.css("overflow", "hidden"); - if (!editor) { - setupEditor(); - } - match = match || ""; - ui.editLabel.text(isInjectFile ? "Editing file:" : "Editing file for match:"); - ui.matchContainer.text(match); - - editorGuessMode(match, app.files[fileId]); - - if (chrome.devtools && util.isChrome()) { - ui.loadSelect.show(); - util.getTabResources(function(filteredList) { - ui.loadSelect.html(""); - filteredList.forEach(function(url) { - const $newOpt = $("