Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*global content,Components,addMessageListener,sendAsyncMessage*/
- var yass = {
- m_pref: {},
- m_prefservice: Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch),
- m_consoleservice: Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService),
- m_sobj: null,
- m_jumpto: 0,
- m_wheelactive: false,
- m_lastfunctime: 0,
- m_lastaccel: 1.0,
- m_lasteventtime: (new Date()).getTime(),
- m_lasteventtime_vtp: 0,
- m_speed: 0.0001,
- m_lastcheckdesignmodearg: null,
- m_keyenable: true,
- m_keyscrolltarget: null,
- m_clickedtarget: null,
- m_mousescrolltarget: null,
- m_mousemoved: [-100000, -100000],
- m_edgesize: 54,
- m_sumpixelscroll: 0,
- m_jumptoremain: 0,
- m_lastdocument: null,
- m_ticksstack: [],
- m_timer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),
- m_mousebuttonstate: false,
- oneshot: function(func, delay) {
- this.m_timer.initWithCallback(func, delay, 0);
- },
- range: function(t, min, max) {
- return (t < min) ? min : ((t > max) ? max : t);
- },
- dump: function(s) {
- if (this.m_pref.dolog) this.m_consoleservice.logStringMessage(s);
- },
- bouncyEdgeSize: function() {
- return Math.min(this.m_sobj.body.clientHeight * 0.2, this.m_edgesize);
- },
- m_virtualtrackpad: {
- m_timer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),
- m_running: false,
- m_step: 0,
- m_reset: false,
- m_lastinstep: 0,
- m_lastinstep_cur: 0,
- oneshot: function(func, delay) {
- this.m_timer.initWithCallback(func, delay, 0);
- },
- startvirtualtrackpad: function(step, time) {
- this.m_reset = true;
- //yass.dump("" + step + "/" + time + " ");
- if (time > 30) return; // too big gap of time
- var laststep = this.m_lastinstep;
- var absstep = Math.abs(step);
- if (laststep + 50 < absstep) return; // ignore big positive gap
- // calcurate average from remaining 3 items
- if (absstep < 30) {
- this.m_step = 0; // step too small to determine as flick
- }
- else if (laststep < absstep) {
- //yass.dump(" flick ");
- this.m_step = step;
- if (this.m_running == false) this.virtualtrackpad();
- }
- // save current step
- this.m_lastinstep = absstep;
- },
- virtualtrackpad: function() {
- this.m_running = true;
- if (!this.m_reset) {
- this.m_step *= 0.94;
- yass.handleEvent({
- type: "MozMousePixelScroll",
- detail: this.m_step,
- preventDefault: function() {},
- stopPropagation: function() {},
- generated: true
- });
- }
- //yass.dump("trackpad " + this.m_vtp_step + "\n");
- if (Math.abs(this.m_step) < 1 || (yass.m_sobj && yass.m_sobj.offset != 0)) {
- this.m_running = false;
- return;
- }
- this.m_reset = false;
- this.oneshot(function() {
- yass.m_virtualtrackpad.virtualtrackpad();
- }, 10);
- }
- },
- urgeRefreshTarget: function() {
- this.m_mousescrolltarget = null;
- this.m_keyscrolltarget = null;
- this.m_clickedtarget = null;
- },
- // check document consistency and commit full reset if false
- checkDocumentOfEvent: function(ev) {
- if (ev.originalTarget.ownerDocument != this.m_lastdocument) {
- // full reset
- this.refreshTarget(null, null);
- this.m_lastcheckdesignmodearg = null;
- this.urgeRefreshTarget();
- this.checkDesignMode(ev);
- this.checkBlackList(ev.originalTarget.baseURI);
- this.m_lastdocument = ev.originalTarget.ownerDocument;
- }
- },
- checkTickWheel: function(d) {
- if (this.m_ticksstack == null) return;
- if (this.m_sobj == null) return; // on no scrollable area and on xul(about:blank), ev.detail is not normal value
- this.m_ticksstack.push(Math.abs(d));
- if (this.m_ticksstack.length < 10) return;
- // pick lowest delta value
- var tickwheel = 99999;
- for (var i in this.m_ticksstack) {
- if (this.m_ticksstack[i] < tickwheel) tickwheel = this.m_ticksstack[i];
- }
- if (tickwheel < 28) tickwheel = 28;
- sendAsyncMessage("YASmoothScrolling@kataho:set_tickwheel", { tickwheel: tickwheel });
- this.dump("yass:tickwheel set to " + tickwheel);
- this.m_pref.tickwheel = tickwheel;
- this.m_ticksstack = null;
- },
- handleEvent: function(ev) {
- if (!this.m_pref.enabled) return;
- if (ev.altKey || ev.ctrlKey || (ev.keyCode != 32 && ev.shiftKey)) return;
- var fromkeyboard = false;
- var mozscrollstep = 0;
- var ev_detail = ev.detail;
- var ctm = (new Date()).getTime();
- switch (ev.type) {
- case "wheel":
- ev_detail = ev.deltaY * [1.0, 28.0, 500.0][ev.deltaMode];
- //this.dump("DX:" + ev.deltaX + " DY:" + ev.deltaY + " " + ev.deltaMode);
- var wheeldeltax = ev.deltaX * [1.0, 28.0, 500.0][ev.deltaMode];
- if (Math.abs(ev_detail) < Math.abs(wheeldeltax)) return;
- case "MozMousePixelScroll": // eslint-disable-line no-fallthrough
- if (this.m_mousebuttonstate) return;
- mozscrollstep = mozscrollstep || (ev_detail * (this.m_pref.wheelstep / this.m_pref.tickwheel));
- //case "DOMMouseScroll":
- // this makes iframe mouse cursor scrolled in immediatly a scroll target (chrome like behavior)
- // but this is firefox and it causes unexpected speed reset when passing over unscrollable iframe (ex. banners of ads)
- //this.checkDocumentOfEvent(ev);
- if (ev.axis && ev.axis == ev.HORIZONTAL_AXIS) return; // handle only vertical events
- this.m_keyscrolltarget = null;
- var mousex = ev.screenX - this.m_mousemoved[0];
- var mousey = ev.screenY - this.m_mousemoved[1];
- if ((mousex * mousex) + (mousey * mousey) >= 16 || this.m_mousescrolltarget == null) {
- this.m_mousescrolltarget = ev.originalTarget;
- this.m_mousemoved = [ev.screenX, ev.screenY];
- this.refreshTarget(ev.originalTarget, ev_detail);
- }
- this.checkTickWheel(ev_detail);
- //this.dump("MEV:" + ev_detail + " ");
- //if (wheeldeltax != 0 && this.m_sobj) {
- // this.m_sobj.body.scrollLeft += wheeldeltax;
- //}
- break;
- case "keydown":
- var spacekey = false;
- switch (ev.keyCode) {
- case 38:
- ev_detail = -1;
- break;
- case 40:
- ev_detail = 1;
- break;
- case 33:
- ev_detail = -2;
- break;
- case 34:
- ev_detail = 2;
- break;
- case 35:
- ev_detail = 3;
- break;
- case 36:
- ev_detail = -3;
- break;
- case 32:
- ev_detail = (ev.shiftKey) ? -2 : 2;
- spacekey = (ev.shiftKey) ? false : true;
- break;
- default:
- // mainly for google reader shortcut issue
- this.refreshTarget(null, null);
- this.m_mousescrolltarget = null;
- return;
- }
- var detailsq_ = ev_detail * ev_detail;
- if (this.m_pref.usekbd == false && detailsq_ == 1) return;
- if (this.m_pref.usepagejump == false && detailsq_ == 4) return;
- if (this.m_pref.usewholejump == false && detailsq_ == 9) return;
- this.checkDocumentOfEvent(ev);
- this.checkDesignMode(ev);
- if (this.m_keyenable == false) return;
- if (this.m_keyscrolltarget == null || this.m_sobj == null || this.m_clickedtarget != null) {
- var t = this.m_clickedtarget ? this.m_clickedtarget : ev.originalTarget;
- this.refreshTarget(t, ev_detail);
- this.m_keyscrolltarget = ev.originalTarget;
- }
- var tagname = ev.target.tagName.toLowerCase();
- yass.dump("key target tagname=" + tagname);
- if (tagname == "input") {
- if (spacekey) return;
- var typeattr = (ev.target.getAttribute("type")) ? ev.target.getAttribute("type").toLowerCase() : "text";
- if (/^(text|search|password|url|tel|email|datetime|date|month|week|time|datetime-local|number|color|range|radio)$/.test(typeattr)) return;
- }
- if (/^(select|textarea|embed|object|audio|video)$/.test(tagname)) return;
- fromkeyboard = true;
- this.m_mousescrolltarget = null;
- this.m_clickedtarget = null;
- break;
- case "mousedown":
- if (ev.button == 2) this.m_mousebuttonstate = true;
- this.m_clickedtarget = ev.target;
- if (this.m_sobj && this.m_sobj.offset / this.m_lastd < 0) return;
- this.m_jumpto = this.m_vpos;
- return;
- case "mouseup":
- this.m_mousebuttonstate = false;
- return;
- case "DOMContentLoaded":
- case "unload":
- case "resize":
- this.urgeRefreshTarget();
- return;
- default:
- return;
- }
- if (this.m_pref.blacklist_hit == true) return;
- if (!this.m_sobj) return;
- ev.preventDefault();
- if (ev_detail == 0) return; // maybe this fixes microsoft smooth wheel problem
- // ignore events in edge bounce animation
- // for MacOS native flick
- if (mozscrollstep != 0 && this.m_edgesize != 0) {
- if (ctm - this.m_lasteventtime <= 200) {
- if (((this.m_sobj.offset < 0 || this.m_vpos < 5) && mozscrollstep < 0) ||
- ((this.m_sobj.offset > 0 || this.m_vpos > this.m_sobj.maxscroll - 5) && mozscrollstep > 0)) {
- this.m_lasteventtime = ctm;
- return;
- }
- }
- }
- var detailsq = ev_detail * ev_detail;
- var pagescroll = (detailsq == 4);
- var wholepagescroll = (detailsq == 9);
- var evtime = this.range((ctm - this.m_lasteventtime), 1, 1500);
- this.m_sumpixelscroll += Math.abs(mozscrollstep);
- var speed = 0.24 * (this.m_sumpixelscroll / this.m_pref.wheelstep / evtime); // distance(ticks) / time
- this.m_speed *= 1.0 - this.range(((evtime - 100) / 800), 0, 0.9999); // speed loss
- this.m_speed = Math.max(this.m_speed, speed);
- //yass.dump("e deta:" + ev_detail + " time:" + evtime + " sum:" + this.m_sumpixelscroll +
- // " speed:" + Math.round(this.m_speed * 100000));
- var edgemargin = (0.64 / (fromkeyboard ? this.m_pref.kbddumping : this.m_pref.wheeldumping)) + ((this.m_edgesize == 0) ? 2 : 0);
- var edgelimit = this.bouncyEdgeSize() + edgemargin + 25;
- // branch for pixel scroller
- // set "step" and "this.m_lasteventtime"
- var step = 0;
- if (mozscrollstep != 0) {
- step = Math.abs(mozscrollstep);
- var p = 300;
- if (this.m_pref.useflickemulation) {
- if (!("generated" in ev)) {
- this.m_virtualtrackpad.startvirtualtrackpad(mozscrollstep, ctm - this.m_lasteventtime_vtp);
- this.m_lasteventtime_vtp = ctm;
- }
- }
- if (this.m_sumpixelscroll >= p) {
- this.m_sumpixelscroll = Math.round(this.m_sumpixelscroll / 11);
- this.m_lasteventtime = ctm - Math.round(evtime / 11);
- }
- }
- else {
- // branch of step
- step =
- (fromkeyboard) ?
- ((pagescroll) ?
- Math.max(0, this.m_sobj.body.clientHeight - this.m_pref.pagemargin) :
- ((wholepagescroll) ?
- (this.m_sobj.maxscroll + 100) :
- this.m_pref.kbdstep)) :
- this.m_pref.wheelstep;
- this.m_lasteventtime = ctm;
- }
- var delta = step * ((ev_detail < 0) ? -1 : 1);
- if (!this.m_wheelactive) {
- // actually we shold know scroll height change asap and refrect to m_sobj.maxscroll
- // changing scroll target's (most case it is body) "overflow" to hidden,fixed implesitly makes scrollHeight to 0
- // few pages (facebook) make scrolling target's "overflow" to fixed for preventing scrolling when showing modal dialog
- this.m_sobj.refreshmaxscroll();
- this.m_beginsmoothtime = ctm;
- this.m_resetsmooth = 0;
- this.m_lastd = 0;
- // something special power prevents scrollTop to be just 1pixel up so
- this.m_sobj.body.scrollTop = (this.m_sobj.body.scrollTop - ((ev_detail < 0) ? 1.0000001 : -1));
- this.m_jumpto = this.m_sobj.body.scrollTop - 0 + delta + ((ev_detail * this.m_jumptoremain > 0) ? this.m_jumptoremain : 0);
- this.m_jumpto = this.range(this.m_jumpto, -edgelimit, this.m_sobj.maxscroll + edgelimit);
- this.m_jumptoremain = 0;
- this.m_wheelactive = true;
- this.m_lastscrolltop = this.m_sobj.body.scrollTop;
- this.m_lastfunctime = (new Date()).getTime() - 17;
- this.m_vpos = this.m_sobj.body.scrollTop;
- this.funcwheel(fromkeyboard, delta, edgemargin);
- }
- else {
- this.m_resetsmooth = 1;
- var accel = 1.0;
- if (fromkeyboard) {
- if (!pagescroll) accel = this.m_pref.kbdaccel / 100;
- }
- else {
- if (this.m_sobj.offset == 0 && this.m_lastd * ev_detail < 0) {
- this.m_jumpto += (this.m_vpos - this.m_jumpto) * 0.92;
- return;
- }
- accel = this.range(this.m_pref.wheelaccel * this.m_speed, 1.0, 30.0);
- }
- this.m_jumpto += delta * accel;
- this.m_jumpto = this.range(this.m_jumpto, -edgelimit, this.m_sobj.maxscroll + edgelimit);
- }
- },
- // class scroller that have no edge element
- ScrollerNoEdge: (function() {
- var F = function(orig, b, type, w, h, log) {
- this.target = orig;
- this.body = b;
- this.scrolltype = type;
- this.width = w;
- this.height = h;
- this.log = log;
- this.offset = 0;
- };
- F.prototype = {
- activate: function() {
- this.etop = yass.edgetop;
- this.ebot = yass.edgebot;
- this.innerframe = (this.body.ownerDocument.defaultView.frameElement) ? true : false;
- this.maxscroll = this.body.scrollHeight - this.body.clientHeight;
- },
- adjust: function(newp, oldp) {
- // adjust maxscroll
- var scrollsize = this.body.scrollHeight - this.body.clientHeight;
- if (scrollsize != this.maxscroll) this.maxscroll = scrollsize;
- if (this.maxscroll == 0) return false;
- // on the edge - must be stopped explicitly
- if (this.body.scrollTop == 0 || this.body.scrollTop == this.maxscroll) return false;
- return true;
- },
- scrollovercheck: function(hint) {
- yass.dump("yass - must not be called.");
- return false;
- },
- stop: function() {},
- release: function() {},
- render: function(offsetscroll, pos) {
- this.body.scrollTop = pos;
- },
- restoreedges: function() {},
- refreshmaxscroll: function() {
- this.maxscroll = this.body.scrollHeight - this.body.clientHeight;
- }
- };
- return F;
- })(),
- // class scroller
- Scroller: (function() {
- var F = function(orig, b, type, w, h, log) {
- this.target = orig;
- this.body = b;
- this.scrolltype = type;
- this.width = w;
- this.height = h;
- this.log = log;
- this.offset = 0;
- };
- F.prototype = {
- activate: function() {
- this.etop = yass.edgetop;
- this.ebot = yass.edgebot;
- this.body._yass_ownedby = this;
- this.offset = 0;
- var h0 = this.ebot.setowner(this.body);
- var h1 = this.etop.setowner(this.body);
- this.innerframe = (this.body.ownerDocument.defaultView.frameElement) ? true : false;
- this.maxscroll = this.body.scrollHeight - this.body.clientHeight - h0 - h1;
- },
- refreshmaxscroll: function() {
- this.maxscroll = this.body.scrollHeight - this.body.clientHeight;
- },
- adjust: function(newp, oldp) {
- // adjust maxscroll
- var scrollsize = this.body.scrollHeight - this.body.clientHeight - this.ebot.e.clientHeight - this.etop.e.clientHeight;
- if (scrollsize != this.maxscroll) {
- // there are evil pages that have 100% height child or absolutely positioned child. scrollHeight not be changed by making botedge height changed
- // bot edge's height change changes maxscroll - emergency stop
- if (this._lastscrollheight == this.body.scrollHeight && newp > this.maxscroll) return false;
- this.maxscroll = scrollsize;
- }
- this._lastscrollheight = this.body.scrollHeight;
- if (this.maxscroll <= 0) return false; // fixed previous "==" which causes bouncing back/foreward forever bug
- // when entering top edge
- if (oldp >= 0 && newp < 0) {
- this.etop.adjust(this.ebot);
- }
- // when entering bottom edge
- else if (oldp <= this.maxscroll && newp > this.maxscroll) {
- this.ebot.adjust();
- }
- return true;
- },
- scrollovercheck: function(hint) {
- if (this.offset == 0) {
- return (
- (hint > 0 && this.body.scrollTop < this.maxscroll) ||
- (hint < 0 && this.body.scrollTop > 0));
- }
- else {
- return !((this.offset > 0 && hint > 0) || (this.offset < 0 && hint < 0));
- }
- },
- stop: function() {
- this.etop.e.style.height = "0px";
- this.ebot.e.style.height = "0px";
- this.etop.render_abs(0);
- if (this.offset < 0) this.body.scrollTop = 0;
- this.offset = 0;
- },
- release: function() {
- // if document is already closed, accessing dead object exception thrown (Firefox15beta)
- try {
- var x = this.etop.e.style; // eslint-disable-line no-unused-vars
- this.stop();
- }
- catch (e) {}
- try {
- if (this.body && this.body._yass_ownedby == this) this.body._yass_ownedby = null;
- }
- catch (e) {}
- this.etop.e = null;
- this.ebot.e = null;
- this.body = null;
- },
- // realize virtual offset
- render: function(offsetscroll, pos) {
- this.offset = offsetscroll;
- if (offsetscroll < 0) {
- offsetscroll = -offsetscroll; // invert
- var h = this.etop.e.clientHeight;
- if (h < offsetscroll) h += 64;
- this.etop.render_abs(h);
- this.etop.e.style.height = h + "px";
- this.body.scrollTop = h - offsetscroll;
- }
- else if (offsetscroll > 0) {
- if (this.ebot.e.clientHeight < offsetscroll) this.ebot.e.style.height = (offsetscroll + 50) + "px";
- this.body.scrollTop = this.maxscroll + offsetscroll;
- }
- else {
- this.etop.e.style.height =
- this.ebot.e.style.height = "0px";
- this.etop.render_abs(0);
- this.body.scrollTop = pos;
- }
- }
- };
- return F;
- })(),
- // edgetop object extends div element
- edgetop: {
- e: null,
- dummy: null,
- epadding: null,
- eorig: function(doc) {
- var e = null;
- if ((e = doc.getElementById("yass_top_edge"))) return e;
- e = doc.createElement("div");
- e.style.backgroundImage = "url(chrome://yass/content/edgebgtop.png)";
- e.style.backgroundAttachment =
- e.style.backgroundAttachment = "scroll";
- e.style.backgroundPosition = "bottom";
- e.style.height =
- e.style.borderWidth =
- e.style.margin =
- e.style.padding = "0px";
- e.style.display = "block";
- //e.style.postion = "absolute";
- //e.style.left = e.style.top = "0px";
- e.setAttribute("id", "yass_top_edge");
- return e;
- },
- dummyorig: function(doc) {
- var e = null;
- if ((e = doc.getElementById("yass_top_edge_dummy"))) return e;
- e = doc.createElement("div");
- e.style.height = e.style.width = "1px";
- e.style.borderWidth =
- e.style.margin =
- e.style.padding = "0px";
- e.style.display = "block";
- //e.style.postion = "absolute";
- //e.style.left = e.style.top = "0px";
- e.setAttribute("id", "yass_top_edge_dummy");
- return e;
- },
- epaddingorig: function(doc) {
- var e = null;
- if ((e = doc.getElementById("yass_top_edge_padding"))) return e;
- e = doc.createElement("div");
- e.style.height =
- e.style.borderWidth =
- e.style.margin =
- e.style.padding = "0px";
- e.style.display = "block";
- e.setAttribute("id", "yass_top_edge_padding");
- return e;
- },
- _owner: null,
- setowner: function(owner) {
- this.e = this.eorig(owner.ownerDocument);
- this.dummy = this.dummyorig(owner.ownerDocument);
- this.epadding = this.epaddingorig(owner.ownerDocument);
- // edges must be inserted inside of body not html even if html is scrollable
- // body may return frameset but owner must be scrollable something inside of frame, so it must not be, as far as here.
- owner = (owner == owner.ownerDocument.documentElement) ? owner.ownerDocument.body : owner;
- if (this._owner == owner || this.e == owner) return this.e.clientHeight;
- if (this._owner) {
- try {
- this._owner.removeChild(this.e);
- this._owner.removeChild(this.dummy);
- this._owner.removeChild(this.epadding);
- if (this._ancientnode) this._ancientnode.style.marginTop = this._ancientnode_marginback; // restore offset - must be tested
- }
- catch (ex) {}
- }
- this.abselms = [];
- this.e.style.height = "0px";
- this.e.style.marginBottom = "0px";
- try {
- this.epadding.style.padding = "0px";
- }
- catch (ex) {}
- this._owner = owner;
- this._ancientnode = null;
- this._paddingback = owner.style.paddingTop;
- // detect body margin size
- var bodymargintop = 0;
- var bodypaddingtop = 0;
- var bodymarginleft = 0;
- this._widthtarget = owner;
- var s; // local computed style
- var i; // index of elements
- var e; // an element
- if (owner == owner.ownerDocument.body) {
- s = owner.ownerDocument.defaultView.getComputedStyle(owner, null);
- bodymargintop = s.getPropertyCSSValue("margin-top").getFloatValue(5); // as pixels
- bodypaddingtop = s.getPropertyCSSValue("padding-top").getFloatValue(5); // as pixels
- bodymarginleft = Math.max(0, ((owner.parentNode.clientWidth - owner.offsetWidth) / 2)); // this way can get correct margin in case margin:0 auto
- this._widthtarget = owner.ownerDocument.documentElement;
- this._ancientnode = owner.ownerDocument.querySelector(
- "body>p:-moz-first-node,body>dl:-moz-first-node,body>multicol:-moz-first-node,body>blockquote:-moz-first-node," +
- "body>h1:-moz-first-node,body>h2:-moz-first-node,body>h3:-moz-first-node,body>h4:-moz-first-node,body>h5:-moz-first-node," +
- "body>h6:-moz-first-node,body>listing:-moz-first-node,body>plaintext:-moz-first-node,body>xmp:-moz-first-node," +
- "body>pre:-moz-first-node,body>ul:-moz-first-node,body>menu:-moz-first-node,body>dir:-moz-first-node,body>ol:-moz-first-node"
- ); // compatible only with 3.5 and later versions
- // put dummy div for negate first node offset hack
- if (this._ancientnode) {
- // check style for display:noe
- var astyle = owner.ownerDocument.defaultView.getComputedStyle(this._ancientnode, null);
- if (astyle.getPropertyCSSValue("display").cssText == "none") this._ancientnode = null;
- if (astyle.getPropertyCSSValue("margin-top").getFloatValue(5) != 0) this._ancientnode = null;
- }
- if (this._ancientnode) {
- this._ancientnode_marginback = this._ancientnode.style.marginTop;
- this._ancientnode.style.marginTop = bodymargintop + "px";
- }
- else {
- // apply bodymargin to dummy element
- this.e.style.marginBottom = bodymargintop + "px";
- }
- }
- // collect absolute elements
- var elms = owner.children;
- // algorithm 1: search for immediate children consider case of inline style
- for (i = 0; i < elms.length; i++) {
- e = elms[i];
- if (e === undefined) continue;
- if (e.nodeName === undefined) continue;
- if (e.nodeName.toLowerCase() != "div") continue;
- if (e.id == "yass_bottom_edge") continue;
- s = e.ownerDocument.defaultView.getComputedStyle(e, null);
- if (s.getPropertyCSSValue("position").cssText == "absolute") {
- try {
- this.abselms.push([e, s.getPropertyCSSValue("top").getFloatValue(5)]);
- }
- catch (ex) {}
- }
- }
- // algorithm 2: (new!) search for "absolute" in DOM stylesheet and query with those selectors of rules found
- var regexpAbsolute = /position\s*:\s*absolute/;
- var selectorsAbsolute = [];
- for (var sindex = 0; sindex < content.document.styleSheets.length; sindex++) {
- var sheet = content.document.styleSheets[sindex];
- if (!sheet.cssRules) continue;
- var rstack = [{r: sheet.cssRules, i: 0}];
- while (rstack.length > 0) {
- var rules = rstack[0];
- if (rules.i >= rules.r.length) {
- rstack.shift(); // upward node
- }
- else {
- var rule = rules.r[rules.i];
- rules.i++;
- if (rule.cssRules) {
- rstack.unshift({r: rule.cssRules, i: 0}); // downward node
- }
- else {
- if (regexpAbsolute.test(rule.cssText) && rule.selectorText && rule.selectorText.length > 0) {
- selectorsAbsolute.push(rule.selectorText);
- }
- }
- }
- }
- }
- var abselmsCandidate = (selectorsAbsolute.length > 0) ? owner.querySelectorAll(selectorsAbsolute.join(",")) : [];
- // make them sure by gettingComputedStyle
- for (i = 0; i < abselmsCandidate.length; i++) {
- e = abselmsCandidate[i];
- s = e.ownerDocument.defaultView.getComputedStyle(e, null);
- if (s.getPropertyCSSValue("position").cssText == "absolute") {
- // avoid duplicate push
- if (!(this.abselms.find(function(element) { return (element[0] == e); }))) {
- try {
- this.abselms.push([e, s.getPropertyCSSValue("top").getFloatValue(5)]);
- }
- catch (ex) {}
- }
- }
- }
- // delete children of elm in list == positon:absolute
- // delete children of other position style element
- this.abselms = this.abselms.filter(function(earg) {
- for (var e = earg[0].parentElement; e != owner && e != null; e = e.parentElement) {
- var cssValue = e.ownerDocument.defaultView.getComputedStyle(e, null).getPropertyCSSValue("position");
- if (cssValue && cssValue.cssText && cssValue.cssText != "static") {
- return false;
- }
- }
- return true;
- });
- yass.dump("absolute elments:" + this.abselms.length);
- if (bodypaddingtop > 0) {
- if (owner.childNodes.length == 0) owner.appendChild(this.epadding);
- else owner.insertBefore(this.epadding, owner.childNodes[0]);
- this.epadding.style.paddingBottom = bodypaddingtop + "px";
- }
- this.dummy.style.marginTop = -(bodymargintop + bodypaddingtop + 1) + "px"; // negate docuemnt margin
- this.e.style.marginLeft = -bodymarginleft + "px";
- this.e.style.width = "1px";
- if (owner.childNodes.length == 0) owner.appendChild(this.e); // edge is below the dummy element
- else owner.insertBefore(this.e, owner.childNodes[0]);
- if (owner.childNodes.length == 0) owner.appendChild(this.dummy); // dummy is above
- else owner.insertBefore(this.dummy, owner.childNodes[0]);
- return 0;
- },
- adjust: function(ebot) {
- // adjust width
- //this.e.style.width = this._widthtarget.clientWidth + "px";
- // copy width from edge on the bottom
- try {
- this.e.style.width = ebot.e.ownerDocument.defaultView.getComputedStyle(ebot.e, null).getPropertyCSSValue("width").getFloatValue(5) + "px";
- }
- catch (e) {
- this.e.style.width = "1px";
- }
- // for firebug sidewindow : item inserted on top of children so late
- if (this._owner.childNodes[0] != this.dummy) {
- if (this.epadding.style.paddingBottom != "0px") this._owner.insertBefore(this.epadding, this._owner.childNodes[0]);
- this._owner.insertBefore(this.e, this._owner.childNodes[0]);
- this._owner.insertBefore(this.dummy, this._owner.childNodes[0]);
- yass.dump("top edge children order changed\n");
- }
- },
- render_abs: function(offset) {
- for (var i in this.abselms) {
- this.abselms[i][0].style.top = (this.abselms[i][1] + offset) + "px";
- // forcibly restore original value for elements changed after picked as absolute elements
- this.abselms[i][0].style.position = (offset == 0) ? null : "absolute";
- }
- }
- },
- // edgebot object extends div element
- edgebot: {
- e: null,
- ecommon: function(doc) {
- var e = null;
- if ((e = doc.getElementById("yass_bottom_edge"))) return e;
- e = doc.createElement("div");
- e.style.backgroundImage = "url(chrome://yass/content/edgebgbot.png)";
- e.style.backgroundPosition = "0px 0px";
- e.style.height =
- e.style.borderWidth =
- e.style.padding =
- e.style.margin = "0px";
- e.style.width = "100%";
- e.style.display = "block";
- e.setAttribute("id", "yass_bottom_edge");
- return e;
- },
- eabsolute: function(doc) {
- var e = this.ecommon(doc);
- e.style.position = "absolute";
- e.style.top =
- e.style.left = "0px";
- return e;
- },
- estatic: function(doc) {
- var e = this.ecommon(doc);
- e.style.position = "static";
- e.style.top =
- e.style.left = null;
- return e;
- },
- _owner: null,
- setowner: function(owner) {
- // edges must be inserted inside of body not html even if html is scrollable
- var body = owner.ownerDocument.body;
- owner = (owner == owner.ownerDocument.documentElement) ? body : owner;
- // search more proper owner element for bottom edge in DOM (prefer child element having equal clientHeight of parent)
- if (owner !== body) {
- while (owner.children.length > 0) {
- var properOwner = [];
- for (var c = 0; c < owner.children.length; c++) {
- var cowner = owner.children[c];
- var cownerstyle = cowner.ownerDocument.defaultView.getComputedStyle(cowner, "");
- if (cowner.clientHeight == owner.clientHeight && cownerstyle.getPropertyValue("position") != "absolute" && !/left|right/.test(cownerstyle.getPropertyValue("float"))) {
- properOwner.push(cowner);
- }
- }
- if (properOwner.length != 1) { break; }
- owner = properOwner[0];
- }
- }
- if ((this._owner == owner || this.e == owner) && this.e) return this.e.clientHeight;
- if (this._owner) {
- try {
- this._owner.removeChild(this.e);
- }
- catch (e) {}
- }
- if (this.e) this.e.style.height = "0px";
- this._owner = owner;
- if (owner == body) {
- this.e = this.eabsolute(owner.ownerDocument);
- // height hint is from html's if body is owner
- // body or html the one more difference between clientH and scrollH
- var doc = owner.ownerDocument.documentElement;
- var body_h = owner.scrollHeight;
- var doc_h = doc.scrollHeight;
- this._heighttarget = (body_h > doc_h) ? body : doc;
- }
- else {
- this.e = this.estatic(owner.ownerDocument);
- this._heighttarget = owner;
- }
- owner.appendChild(this.e);
- return 0;
- },
- adjust: function() {
- // adjust position top
- if (this._owner == null) return;
- if (this.e.style.position == "absolute") this.e.style.top = this._heighttarget.scrollHeight + "px";
- // adjust children order
- if (this._owner.childNodes[this._owner.childNodes.length - 1] != this.e) {
- this._owner.appendChild(this.e);
- yass.dump("bottom edge children order changed\n");
- }
- }
- },
- funcwheel: function(kbd, idelta, edgemargin) {
- if (this.m_wheelactive == false || this.m_sobj == null) {
- this.m_wheelactive = false;
- if (this.m_sobj) {
- this.m_sobj.stop();
- }
- return;
- }
- var bdumpfunc = (this.m_pref.prefversion <= 2) ?
- (function(t, _d) {
- return yass.range(t / _d + 0.2, 0.05, 1.0);
- }) :
- (function(t, _d) {
- return yass.range(t / _d, 0.05, 1.0);
- });
- var dump = kbd ? this.m_pref.kbddumping : this.m_pref.wheeldumping;
- var bdump = kbd ? this.m_pref.kbdbdumping : this.m_pref.wheelbdumping;
- var tm = (new Date()).getTime();
- var frametime = (tm - this.m_lastfunctime);
- frametime = Math.min(frametime, 51);
- this.m_lastfunctime = tm;
- if (frametime <= 0) {
- this.oneshot(function() {
- yass.funcwheel(kbd, idelta, edgemargin);
- }, 3);
- return;
- }
- var fordest = (this.m_jumpto - this.m_vpos);
- if ((this.m_sobj.offset / fordest) < 0) {
- dump = 0.45;
- bdump = 0.295;
- }
- bdump *= 2000;
- var d = 0;
- var looptimetotal = 0;
- var lastd = 0;
- do {
- var localfordest = fordest - d;
- var looptime = Math.min(17, frametime - looptimetotal);
- looptimetotal += looptime;
- var f = dump * (looptime / 17);
- // dumping of begining
- if (bdump > 0.0) {
- // check reset beginning smooth
- if (this.m_resetsmooth > 0) {
- if (this.m_pref.prefversion >= 3) {
- var lastsmoothtime = this.m_beginsmoothtime;
- var smoothtime = tm;
- var count = 0;
- do { // eslint-disable-line no-constant-condition
- if (smoothtime < lastsmoothtime) {
- break;
- }
- var timefromev = tm - smoothtime + 17 + looptimetotal;
- var b = bdumpfunc(timefromev, bdump);
- var x = this.m_lastd / (localfordest * f * b);
- if (x < 0) {
- this.m_beginsmoothtime = tm;
- break;
- }
- if (x < 1) {
- this.m_beginsmoothtime = smoothtime;
- break;
- }
- smoothtime -= 34;
- count++;
- } while (true);
- }
- else {
- this.m_beginsmoothtime = tm;
- }
- this.m_resetsmooth = 0;
- }
- f *= bdumpfunc(tm - this.m_beginsmoothtime + 17 + looptimetotal, bdump);
- }
- d += localfordest * f;
- if (lastd == 0) lastd = d;
- } while (frametime - looptimetotal > 4);
- this.m_lastd = lastd;
- var lastmvpos = this.m_vpos;
- this.m_vpos += d;
- var lenfordest = fordest * fordest;
- // adjust maxscroll (autopagerize or somthing dynamically add elements on m_sobj.body)
- if (this.m_sobj.adjust(this.m_vpos, lastmvpos) == false) {
- this.m_wheelactive = false;
- this.m_sobj.stop();
- return;
- }
- // get virtual scrolltop offset
- var ofs = this.bouncyEdgeSize(); // offset size
- var offsetscroll = 0;
- if (this.m_vpos < 0) {
- offsetscroll = this.m_vpos;
- if (this.m_jumpto != edgemargin && (d * d < ofs / (ofs - Math.min(-offsetscroll, ofs)) - 1)) {
- this.m_jumpto = edgemargin;
- this.m_resetsmooth = 1;
- }
- if (this.m_sobj.innerframe) {
- this.urgeRefreshTarget(); // forcibly reset scroll target and prompt to check scroll over
- }
- }
- else if (this.m_vpos > this.m_sobj.maxscroll) {
- offsetscroll = this.m_vpos - this.m_sobj.maxscroll;
- if (this.m_jumpto != this.m_sobj.maxscroll - edgemargin && (d * d < ofs / (ofs - Math.min(offsetscroll, ofs)) - 1)) {
- this.m_jumpto = this.m_sobj.maxscroll - edgemargin;
- this.m_resetsmooth = 1;
- }
- if (this.m_sobj.innerframe) {
- this.urgeRefreshTarget(); // forcibly reset scroll target and prompt to check scroll over
- }
- }
- else if (lenfordest <= 1.0 || (lenfordest < 100.0 && d * d < 0.2) || this.m_sobj.body.scrollTop != this.m_lastscrolltop) {
- this.m_wheelactive = false;
- this.m_sobj.stop();
- this.m_jumptoremain = fordest;
- return;
- }
- this.m_sobj.render(offsetscroll, this.m_vpos);
- this.m_lastscrolltop = this.m_sobj.body.scrollTop;
- this.oneshot(function() {
- yass.funcwheel(kbd, idelta, edgemargin);
- }, 10);
- },
- toggleKeyHook: function(b) {
- this.m_keyenable = b;
- },
- refreshTarget: function(target, detail) {
- // externally ordered for releasing of m_sobj
- if (target == null) {
- this.m_wheelactive = false;
- if (this.m_sobj) this.m_sobj.release();
- this.m_sobj = null;
- return;
- }
- this.checkBlackList(target.baseURI);
- var newobj = this.findNodeToScroll(target, detail, "");
- // null : stop immediately
- // object not activated : change to it
- // 1 : do not change the target
- if (newobj === 1) return;
- if (newobj == null) {
- this.m_wheelactive = false;
- if (this.m_sobj) this.m_sobj.release();
- this.m_sobj = null;
- this.dump("N: target null\n");
- }
- else if (this.m_sobj && newobj.body != this.m_sobj.body) {
- this.m_wheelactive = false;
- this.m_sobj.release();
- this.m_sobj = newobj;
- this.m_sobj.activate();
- this.dump("A:");
- }
- else if (this.m_sobj == null) {
- this.m_sobj = newobj;
- this.m_sobj.activate();
- this.dump("B:");
- }
- if (newobj) {
- this.dump(newobj.log + "\n");
- }
- },
- checkDesignMode: function(ev) {
- if (ev.originalTarget == this.m_lastcheckdesignmodearg) return;
- this.m_lastcheckdesignmodearg = ev.originalTarget;
- var b = true;
- var mode = (ev.originalTarget.ownerDocument && ev.originalTarget.ownerDocument.designMode) ? ev.originalTarget.ownerDocument.designMode : "off";
- if (mode && mode.toLowerCase() == "on") b = false;
- if (ev.originalTarget.getAttribute) {
- var ceditable = ev.originalTarget.getAttribute("contenteditable");
- if (ceditable == "") ceditable = "true";
- if (!ev.originalTarget.hasAttribute("contenteditable")) ceditable = "false";
- if (ceditable.toLowerCase() == "true") b = false;
- }
- this.toggleKeyHook(b);
- },
- checkBlackList: function(uri) {
- if (this.m_pref.blacklist_lasturi == uri) return;
- this.m_pref.blacklist_lasturi = uri;
- this.m_pref.blacklist_hit = false;
- this.dump("BL_:" + uri + "\n");
- var bl = this.m_pref.blacklist.split(/\n/);
- for (var i in bl) {
- var pattern = bl[i];
- if (typeof(pattern) != "string") continue;
- if (pattern.length == 0) continue;
- if (RegExp(pattern).test(uri)) {
- // hit
- this.m_pref.blacklist_hit = true;
- this.dump("BL_:blacklist pattern [" + pattern + "] matched to [" + uri + "]\n");
- return;
- }
- }
- },
- // based on the code in All-in-One Gestures
- findNodeToScroll: function(orig, hint, log) {
- function getstyle(e, pname) {
- var p = e.ownerDocument.defaultView.getComputedStyle(e, "").getPropertyValue(pname);
- var val = parseFloat(p);
- if (!isNaN(val)) return Math.ceil(val);
- if (p == "thin") return 1;
- if (p == "medium") return 3;
- if (p == "thick") return 5;
- return 0;
- }
- // 0 neither scrollable 1 vertical only 2 horizontal only 3 both
- function getscrolltype(wscroll, wclient, hscroll, hclient) {
- if (hclient < 50) return 0;
- if (hscroll - hclient < 10) hclient = hscroll; // too small to scroll
- var flag = 0;
- if (wscroll > wclient) flag += 1;
- if (hscroll > hclient) flag += 2;
- return flag;
- }
- function newobject(p) {
- var editable = false;
- if (p.parentNode && p.parentNode.nodeName.toLowerCase() == "textarea") editable = true;
- var mode = (p.ownerDocument && p.ownerDocument.designMode) ? p.ownerDocument.designMode : "off";
- if (mode && mode.toLowerCase() == "on") editable = true;
- if (p.isContentEditable) editable = true;
- if (editable) return yass.ScrollerNoEdge;
- if (yass.m_edgesize == 0 || orig.baseURI.indexOf("//firebug") > 0) return yass.ScrollerNoEdge;
- // give bouncing edge up for display:flex scroll tatget
- var edgeOwner = (p == orig.ownerDocument.documentElement) ? orig.ownerDocument.body : p;
- var styleDisplay = edgeOwner.ownerDocument.defaultView.getComputedStyle(edgeOwner, "").getPropertyValue("display");
- if (/^(flex|inline-flex|-moz-box)$/.test(styleDisplay)) return yass.ScrollerNoEdge;
- return yass.Scroller;
- }
- // true: scrollable / false: bottom most or top most
- // this is called onto html or body only
- var scrollovercheck =
- (!hint) ?
- (function() {
- return true;
- }) : (
- (hint < 0) ?
- (function(fe, n, a, b) {
- if (fe == null) return true;
- return (("_yass_ownedby" in n) && n._yass_ownedby) ? n._yass_ownedby.scrollovercheck(hint) : (a > 0);
- }) :
- (function(fe, n, a, b) {
- if (fe == null) return true;
- // there are some region unmovable really but looks 1px scrollable ----------------------------------v
- return (("_yass_ownedby" in n) && n._yass_ownedby) ? n._yass_ownedby.scrollovercheck(hint) : (a < b) && (b > 1);
- })
- );
- // select element have scrollable parameters even if they are popup window type
- // directly rendered selectable pane return option for origin of event
- if (orig.nodeName.toLowerCase() == "select") orig = orig.parentNode;
- if (!orig.ownerDocument) {
- this.dump("ownerDocument unreachable\n");
- return null;
- }
- var doc = orig.ownerDocument.documentElement;
- if (doc && doc.nodeName.toLowerCase() != "html") {
- this.dump("doc is " + doc.nodeName + " not html\n");
- return null;
- }
- var bodies = doc.getElementsByTagName("body");
- if (!bodies || bodies.length == 0) {
- this.dump("no body\n");
- return null;
- }
- var body = bodies[0];
- var node = (orig == doc) ? body : orig;
- var frameelement = orig.ownerDocument.defaultView.frameElement;
- // if this is in a unscrollable frame element
- if (frameelement && frameelement.scrolling && frameelement.scrolling.toLowerCase() == "no") {
- return this.findNodeToScroll(frameelement.ownerDocument.documentElement, hint, log + "!");
- }
- var bodyOverflowValue = null;
- do {
- var nodename = node.nodeName.toLowerCase();
- /*log*/
- log += nodename;
- /***/
- try {
- // when mouse is on scrollbar in select tag popup, parent is <select>
- if (/^(select|option|optgroup)$/.test(nodename)) {
- this.dump("option found :" + log);
- return null;
- }
- var overflowprop = node.ownerDocument.defaultView.getComputedStyle(node, "").getPropertyValue("overflow-y");
- if (node == body) bodyOverflowValue = overflowprop;
- if (node.clientWidth && node.clientHeight &&
- (overflowprop != "hidden") &&
- (node == doc || node == body || overflowprop != "visible")
- ) {
- var realwidth, realheight;
- if ("scrollLeftMax" in node) realwidth = node.scrollWidth - node.scrollLeftMax;
- else realwidth = node.clientWidth + getstyle(node, "border-left-width") + getstyle(node, "border-right-width");
- if ("scrollTopMax" in node) realheight = node.scrollHeight - node.scrollTopMax;
- else realheight = node.clientHeight + getstyle(node, "border-top-width") + getstyle(node, "border-bottom-width");
- var scrolltype = getscrolltype(node.scrollWidth, realwidth, node.scrollHeight, realheight);
- /*log*/
- log += "(" + node.scrollTop + " " + (node.scrollHeight - realheight) + ")";
- if ((scrolltype >= 2) &&
- // scroll focus overflow applied only on inner frame (HTML|BODY)
- ((node != doc && node != body) || scrollovercheck(frameelement, node, node.scrollTop, node.scrollHeight - realheight))
- ) {
- return new(newobject(node))(orig, node, scrolltype, realwidth, realheight, log);
- }
- }
- if (node == doc) break;
- /***/
- }
- catch (e) {}
- /*log*/
- log += ">";
- node = node.parentNode;
- } while (node);
- if (frameelement) {
- var upper = this.findNodeToScroll(frameelement.ownerDocument.documentElement, hint, log + "!");
- if (upper != null) return upper;
- return 1;
- }
- // no scrollable area found in content ( mainly for image only page to handle )
- // gmail problem - gmail have main fullscreen iframe as main scrollable area
- // while top html have little pixels larger scrollheight than clientheight
- if (body.scrollHeight - body.clientHeight > 10) {
- if (bodyOverflowValue && bodyOverflowValue == "hidden") {
- log += " *DEFAULT hidden body*";
- return null;
- }
- log += " *DEFAULT body*";
- return new(newobject(body))(orig, body, 3, body.clientWidth, body.clientHeight, log);
- }
- if (doc.scrollHeight - doc.clientHeight > 10) {
- log += " *DEFAULT html*";
- return new(newobject(doc))(orig, doc, 3, doc.clientWidth, doc.clientHeight, log);
- }
- this.dump(log + " *continue*\n");
- return 1;
- },
- refreshPreferences: function() {
- this.refreshTarget(null, null);
- this.urgeRefreshTarget();
- var ps = this.m_prefservice;
- var presetid = ps.getCharPref("extensions.yass.selectedpreset");
- var presets = ps.getCharPref("extensions.yass.preset." + presetid).split(",");
- this.m_pref.wheelstep = presets[0];
- this.m_pref.wheeldumping = (900 - presets[1]) / 1000;
- this.m_pref.wheelbdumping = presets[2] / 890;
- this.m_pref.wheelaccel = presets[3];
- this.m_pref.kbdstep = presets[4];
- this.m_pref.kbddumping = (900 - presets[5]) / 1000;
- this.m_pref.kbdbdumping = presets[6] / 890;
- this.m_pref.kbdaccel = presets[7];
- this.m_pref.usekbd = ps.getBoolPref("extensions.yass.usekbd");
- this.m_pref.enabled = (ps.getCharPref("extensions.yass.enabled") == "true") ? true : false;
- this.m_pref.usepagejump = ps.getBoolPref("extensions.yass.usepagejump");
- this.m_pref.pagemargin = ps.getIntPref("extensions.yass.pagemargin");
- this.m_pref.prefversion = ps.getCharPref("extensions.yass.prefversion") - 0;
- this.m_pref.usewholejump = ps.getBoolPref("extensions.yass.usewholejump");
- this.m_pref.useflickemulation = ps.getBoolPref("extensions.yass.useflickemulation");
- this.m_pref.tickwheel = ps.getIntPref("extensions.yass.tickwheel");
- this.m_pref.dolog = false;
- try {
- this.m_pref.dolog = ps.getBoolPref("extensions.yass.dolog");
- }
- catch (e) {}
- this.m_edgesize = ps.getIntPref("extensions.yass.edgetype");
- // event listener
- if ("onwheel" in content.document.createElement("div"))
- addEventListener("wheel", yass, false);
- else
- addEventListener("MozMousePixelScroll", yass, false);
- // blacklist
- {
- var str_trim = function(s) {
- return s.replace(/^\s+|\s+$/g, "");
- };
- var rawlist = ps.getCharPref("extensions.yass.blacklist").split(/\n/);
- var blist = [];
- for (var i in rawlist) {
- var p = rawlist[i];
- if (typeof(p) != "string") continue;
- if (str_trim(p).length == 0) continue;
- blist.push(str_trim(p).replace(/\./g, "\\.").replace(/\+/g, "\\+").replace(/\?/g, "\\?").replace(/\*/g, ".*") + "$");
- }
- this.m_pref.blacklist = blist.join("\n");
- this.m_pref.blacklist_lasturi = "xxxxxx";
- this.checkBlackList(content.document.baseURI);
- }
- // contextmenu
- this.m_pref.showcontextmenu = ps.getBoolPref("extensions.yass.showcontextmenu");
- }
- };
- // runs per tabs open (not content load)
- (function() {
- addEventListener("resize", yass, false);
- addEventListener("keydown", yass, true);
- addEventListener("mousedown", yass, false);
- addEventListener("mouseup", yass, false);
- // we often have only content changed on link navigation
- // so we should reset scroll target by listening to this
- addEventListener("DOMContentLoaded", yass, true); // every content load
- addEventListener("DOMWindowCreated", function() { yass.refreshPreferences(); }, false); // very early from DOMContentLoaded
- addEventListener("unload", yass, true); // tab close
- // message listener
- addMessageListener("YASmoothScrolling@kataho:refreshPreferences", function(msg) {
- yass.refreshPreferences();
- });
- addMessageListener("YASmoothScrolling@kataho:reset_design_mode_check_target", function(msg) {
- yass.m_lastcheckdesignmodearg = null;
- });
- })();
Advertisement
Add Comment
Please, Sign In to add comment