/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var Cc = Components.classes; var Ci = Components.interfaces; var Cu = Components.utils; var Cr = Components.results; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"); var global = this; // Lazily load the finder code addMessageListener("Finder:Initialize", function () { let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {}); new RemoteFinderListener(global); }); var ClickEventHandler = { init: function init() { this._scrollable = null; this._scrolldir = ""; this._startX = null; this._startY = null; this._screenX = null; this._screenY = null; this._lastFrame = null; this.autoscrollLoop = this.autoscrollLoop.bind(this); Services.els.addSystemEventListener(global, "mousedown", this, true); addMessageListener("Autoscroll:Stop", this); }, isAutoscrollBlocker: function(node) { let mmPaste = Services.prefs.getBoolPref("middlemouse.paste"); let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition"); while (node) { if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) && node.hasAttribute("href")) { return true; } if (mmPaste && (node instanceof content.HTMLInputElement || node instanceof content.HTMLTextAreaElement)) { return true; } if (node instanceof content.XULElement && mmScrollbarPosition && (node.localName == "scrollbar" || node.localName == "scrollcorner")) { return true; } node = node.parentNode; } return false; }, findNearestScrollableElement: function(aNode) { // this is a list of overflow property values that allow scrolling const scrollingAllowed = ['scroll', 'auto']; // go upward in the DOM and find any parent element that has a overflow // area and can therefore be scrolled for (this._scrollable = aNode; this._scrollable; this._scrollable = this._scrollable.parentNode) { // do not use overflow based autoscroll for and
// Elements or non-html elements such as svg or Document nodes // also make sure to skip select elements that are not multiline if (!(this._scrollable instanceof content.HTMLElement) || ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) { continue; } var overflowx = this._scrollable.ownerDocument.defaultView .getComputedStyle(this._scrollable, '') .getPropertyValue('overflow-x'); var overflowy = this._scrollable.ownerDocument.defaultView .getComputedStyle(this._scrollable, '') .getPropertyValue('overflow-y'); // we already discarded non-multiline selects so allow vertical // scroll for multiline ones directly without checking for a // overflow property var scrollVert = this._scrollable.scrollTopMax && (this._scrollable instanceof content.HTMLSelectElement || scrollingAllowed.indexOf(overflowy) >= 0); // do not allow horizontal scrolling for select elements, it leads // to visual artifacts and is not the expected behavior anyway if (!(this._scrollable instanceof content.HTMLSelectElement) && this._scrollable.scrollLeftMin != this._scrollable.scrollLeftMax && scrollingAllowed.indexOf(overflowx) >= 0) { this._scrolldir = scrollVert ? "NSEW" : "EW"; break; } else if (scrollVert) { this._scrolldir = "NS"; break; } } if (!this._scrollable) { this._scrollable = aNode.ownerDocument.defaultView; if (this._scrollable.scrollMaxX != this._scrollable.scrollMinX) { this._scrolldir = this._scrollable.scrollMaxY != this._scrollable.scrollMinY ? "NSEW" : "EW"; } else if (this._scrollable.scrollMaxY != this._scrollable.scrollMinY) { this._scrolldir = "NS"; } else if (this._scrollable.frameElement) { this.findNearestScrollableElement(this._scrollable.frameElement); } else { this._scrollable = null; // abort scrolling } } }, startScroll: function(event) { this.findNearestScrollableElement(event.originalTarget); if (!this._scrollable) return; let [enabled] = sendSyncMessage("Autoscroll:Start", {scrolldir: this._scrolldir, screenX: event.screenX, screenY: event.screenY}); if (!enabled) { this._scrollable = null; return; } Services.els.addSystemEventListener(global, "mousemove", this, true); addEventListener("pagehide", this, true); this._ignoreMouseEvents = true; this._startX = event.screenX; this._startY = event.screenY; this._screenX = event.screenX; this._screenY = event.screenY; this._scrollErrorX = 0; this._scrollErrorY = 0; this._lastFrame = content.performance.now(); content.requestAnimationFrame(this.autoscrollLoop); }, stopScroll: function() { if (this._scrollable) { this._scrollable.mozScrollSnap(); this._scrollable = null; Services.els.removeSystemEventListener(global, "mousemove", this, true); removeEventListener("pagehide", this, true); } }, accelerate: function(curr, start) { const speed = 12; var val = (curr - start) / speed; if (val > 1) return val * Math.sqrt(val) - 1; if (val < -1) return val * Math.sqrt(-val) + 1; return 0; }, roundToZero: function(num) { if (num > 0) return Math.floor(num); return Math.ceil(num); }, autoscrollLoop: function(timestamp) { if (!this._scrollable) { // Scrolling has been canceled return; } // avoid long jumps when the browser hangs for more than // |maxTimeDelta| ms const maxTimeDelta = 100; var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame); // we used to scroll |accelerate()| pixels every 20ms (50fps) var timeCompensation = timeDelta / 20; this._lastFrame = timestamp; var actualScrollX = 0; var actualScrollY = 0; // don't bother scrolling vertically when the scrolldir is only horizontal // and the other way around if (this._scrolldir != 'EW') { var y = this.accelerate(this._screenY, this._startY) * timeCompensation; var desiredScrollY = this._scrollErrorY + y; actualScrollY = this.roundToZero(desiredScrollY); this._scrollErrorY = (desiredScrollY - actualScrollY); } if (this._scrolldir != 'NS') { var x = this.accelerate(this._screenX, this._startX) * timeCompensation; var desiredScrollX = this._scrollErrorX + x; actualScrollX = this.roundToZero(desiredScrollX); this._scrollErrorX = (desiredScrollX - actualScrollX); } const kAutoscroll = 15; // defined in mozilla/layers/ScrollInputMethods.h Services.telemetry.getHistogramById("SCROLL_INPUT_METHODS").add(kAutoscroll); this._scrollable.scrollBy({ left: actualScrollX, top: actualScrollY, behavior: "instant" }); content.requestAnimationFrame(this.autoscrollLoop); }, handleEvent: function(event) { if (event.type == "mousemove") { this._screenX = event.screenX; this._screenY = event.screenY; } else if (event.type == "mousedown") { if (event.isTrusted & !event.defaultPrevented && event.button == 1 && !this._scrollable && !this.isAutoscrollBlocker(event.originalTarget)) { this.startScroll(event); } } else if (event.type == "pagehide") { if (this._scrollable) { var doc = this._scrollable.ownerDocument || this._scrollable.document; if (doc == event.target) { sendAsyncMessage("Autoscroll:Cancel"); } } } }, receiveMessage: function(msg) { switch (msg.name) { case "Autoscroll:Stop": { this.stopScroll(); break; } } }, }; ClickEventHandler.init(); var PopupBlocking = { popupData: null, popupDataInternal: null, init: function() { addEventListener("DOMPopupBlocked", this, true); addEventListener("pageshow", this, true); addEventListener("pagehide", this, true); addMessageListener("PopupBlocking:UnblockPopup", this); addMessageListener("PopupBlocking:GetBlockedPopupList", this); }, receiveMessage: function(msg) { switch (msg.name) { case "PopupBlocking:UnblockPopup": { let i = msg.data.index; if (this.popupData && this.popupData[i]) { let data = this.popupData[i]; let internals = this.popupDataInternal[i]; let dwi = internals.requestingWindow; // If we have a requesting window and the requesting document is // still the current document, open the popup. if (dwi && dwi.document == internals.requestingDocument) { dwi.open(data.popupWindowURIspec, data.popupWindowName, data.popupWindowFeatures); } } break; } case "PopupBlocking:GetBlockedPopupList": { let popupData = []; let length = this.popupData ? this.popupData.length : 0; // Limit 15 popup URLs to be reported through the UI length = Math.min(length, 15); for (let i = 0; i < length; i++) { let popupWindowURIspec = this.popupData[i].popupWindowURIspec; if (popupWindowURIspec == global.content.location.href) { popupWindowURIspec = "