1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/content/browser-content.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,342 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +let Cc = Components.classes; 1.10 +let Ci = Components.interfaces; 1.11 +let Cu = Components.utils; 1.12 + 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 + 1.15 +var global = this; 1.16 + 1.17 +let ClickEventHandler = { 1.18 + init: function init() { 1.19 + this._scrollable = null; 1.20 + this._scrolldir = ""; 1.21 + this._startX = null; 1.22 + this._startY = null; 1.23 + this._screenX = null; 1.24 + this._screenY = null; 1.25 + this._lastFrame = null; 1.26 + 1.27 + Cc["@mozilla.org/eventlistenerservice;1"] 1.28 + .getService(Ci.nsIEventListenerService) 1.29 + .addSystemEventListener(global, "mousedown", this, true); 1.30 + 1.31 + addMessageListener("Autoscroll:Stop", this); 1.32 + }, 1.33 + 1.34 + isAutoscrollBlocker: function(node) { 1.35 + let mmPaste = Services.prefs.getBoolPref("middlemouse.paste"); 1.36 + let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition"); 1.37 + 1.38 + while (node) { 1.39 + if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) && 1.40 + node.hasAttribute("href")) { 1.41 + return true; 1.42 + } 1.43 + 1.44 + if (mmPaste && (node instanceof content.HTMLInputElement || 1.45 + node instanceof content.HTMLTextAreaElement)) { 1.46 + return true; 1.47 + } 1.48 + 1.49 + if (node instanceof content.XULElement && mmScrollbarPosition 1.50 + && (node.localName == "scrollbar" || node.localName == "scrollcorner")) { 1.51 + return true; 1.52 + } 1.53 + 1.54 + node = node.parentNode; 1.55 + } 1.56 + return false; 1.57 + }, 1.58 + 1.59 + startScroll: function(event) { 1.60 + // this is a list of overflow property values that allow scrolling 1.61 + const scrollingAllowed = ['scroll', 'auto']; 1.62 + 1.63 + // go upward in the DOM and find any parent element that has a overflow 1.64 + // area and can therefore be scrolled 1.65 + for (this._scrollable = event.originalTarget; this._scrollable; 1.66 + this._scrollable = this._scrollable.parentNode) { 1.67 + // do not use overflow based autoscroll for <html> and <body> 1.68 + // Elements or non-html elements such as svg or Document nodes 1.69 + // also make sure to skip select elements that are not multiline 1.70 + if (!(this._scrollable instanceof content.HTMLElement) || 1.71 + ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) { 1.72 + continue; 1.73 + } 1.74 + 1.75 + var overflowx = this._scrollable.ownerDocument.defaultView 1.76 + .getComputedStyle(this._scrollable, '') 1.77 + .getPropertyValue('overflow-x'); 1.78 + var overflowy = this._scrollable.ownerDocument.defaultView 1.79 + .getComputedStyle(this._scrollable, '') 1.80 + .getPropertyValue('overflow-y'); 1.81 + // we already discarded non-multiline selects so allow vertical 1.82 + // scroll for multiline ones directly without checking for a 1.83 + // overflow property 1.84 + var scrollVert = this._scrollable.scrollTopMax && 1.85 + (this._scrollable instanceof content.HTMLSelectElement || 1.86 + scrollingAllowed.indexOf(overflowy) >= 0); 1.87 + 1.88 + // do not allow horizontal scrolling for select elements, it leads 1.89 + // to visual artifacts and is not the expected behavior anyway 1.90 + if (!(this._scrollable instanceof content.HTMLSelectElement) && 1.91 + this._scrollable.scrollLeftMax && 1.92 + scrollingAllowed.indexOf(overflowx) >= 0) { 1.93 + this._scrolldir = scrollVert ? "NSEW" : "EW"; 1.94 + break; 1.95 + } else if (scrollVert) { 1.96 + this._scrolldir = "NS"; 1.97 + break; 1.98 + } 1.99 + } 1.100 + 1.101 + if (!this._scrollable) { 1.102 + this._scrollable = event.originalTarget.ownerDocument.defaultView; 1.103 + if (this._scrollable.scrollMaxX > 0) { 1.104 + this._scrolldir = this._scrollable.scrollMaxY > 0 ? "NSEW" : "EW"; 1.105 + } else if (this._scrollable.scrollMaxY > 0) { 1.106 + this._scrolldir = "NS"; 1.107 + } else { 1.108 + this._scrollable = null; // abort scrolling 1.109 + return; 1.110 + } 1.111 + } 1.112 + 1.113 + let [enabled] = sendSyncMessage("Autoscroll:Start", 1.114 + {scrolldir: this._scrolldir, 1.115 + screenX: event.screenX, 1.116 + screenY: event.screenY}); 1.117 + if (!enabled) { 1.118 + this._scrollable = null; 1.119 + return; 1.120 + } 1.121 + 1.122 + Cc["@mozilla.org/eventlistenerservice;1"] 1.123 + .getService(Ci.nsIEventListenerService) 1.124 + .addSystemEventListener(global, "mousemove", this, true); 1.125 + addEventListener("pagehide", this, true); 1.126 + 1.127 + this._ignoreMouseEvents = true; 1.128 + this._startX = event.screenX; 1.129 + this._startY = event.screenY; 1.130 + this._screenX = event.screenX; 1.131 + this._screenY = event.screenY; 1.132 + this._scrollErrorX = 0; 1.133 + this._scrollErrorY = 0; 1.134 + this._lastFrame = content.mozAnimationStartTime; 1.135 + 1.136 + content.mozRequestAnimationFrame(this); 1.137 + }, 1.138 + 1.139 + stopScroll: function() { 1.140 + if (this._scrollable) { 1.141 + this._scrollable = null; 1.142 + 1.143 + Cc["@mozilla.org/eventlistenerservice;1"] 1.144 + .getService(Ci.nsIEventListenerService) 1.145 + .removeSystemEventListener(global, "mousemove", this, true); 1.146 + removeEventListener("pagehide", this, true); 1.147 + } 1.148 + }, 1.149 + 1.150 + accelerate: function(curr, start) { 1.151 + const speed = 12; 1.152 + var val = (curr - start) / speed; 1.153 + 1.154 + if (val > 1) 1.155 + return val * Math.sqrt(val) - 1; 1.156 + if (val < -1) 1.157 + return val * Math.sqrt(-val) + 1; 1.158 + return 0; 1.159 + }, 1.160 + 1.161 + roundToZero: function(num) { 1.162 + if (num > 0) 1.163 + return Math.floor(num); 1.164 + return Math.ceil(num); 1.165 + }, 1.166 + 1.167 + autoscrollLoop: function(timestamp) { 1.168 + if (!this._scrollable) { 1.169 + // Scrolling has been canceled 1.170 + return; 1.171 + } 1.172 + 1.173 + // avoid long jumps when the browser hangs for more than 1.174 + // |maxTimeDelta| ms 1.175 + const maxTimeDelta = 100; 1.176 + var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame); 1.177 + // we used to scroll |accelerate()| pixels every 20ms (50fps) 1.178 + var timeCompensation = timeDelta / 20; 1.179 + this._lastFrame = timestamp; 1.180 + 1.181 + var actualScrollX = 0; 1.182 + var actualScrollY = 0; 1.183 + // don't bother scrolling vertically when the scrolldir is only horizontal 1.184 + // and the other way around 1.185 + if (this._scrolldir != 'EW') { 1.186 + var y = this.accelerate(this._screenY, this._startY) * timeCompensation; 1.187 + var desiredScrollY = this._scrollErrorY + y; 1.188 + actualScrollY = this.roundToZero(desiredScrollY); 1.189 + this._scrollErrorY = (desiredScrollY - actualScrollY); 1.190 + } 1.191 + if (this._scrolldir != 'NS') { 1.192 + var x = this.accelerate(this._screenX, this._startX) * timeCompensation; 1.193 + var desiredScrollX = this._scrollErrorX + x; 1.194 + actualScrollX = this.roundToZero(desiredScrollX); 1.195 + this._scrollErrorX = (desiredScrollX - actualScrollX); 1.196 + } 1.197 + 1.198 + if (this._scrollable instanceof content.Window) { 1.199 + this._scrollable.scrollBy(actualScrollX, actualScrollY); 1.200 + } else { // an element with overflow 1.201 + this._scrollable.scrollLeft += actualScrollX; 1.202 + this._scrollable.scrollTop += actualScrollY; 1.203 + } 1.204 + content.mozRequestAnimationFrame(this); 1.205 + }, 1.206 + 1.207 + sample: function(timestamp) { 1.208 + this.autoscrollLoop(timestamp); 1.209 + }, 1.210 + 1.211 + handleEvent: function(event) { 1.212 + if (event.type == "mousemove") { 1.213 + this._screenX = event.screenX; 1.214 + this._screenY = event.screenY; 1.215 + } else if (event.type == "mousedown") { 1.216 + if (event.isTrusted & 1.217 + !event.defaultPrevented && 1.218 + event.button == 1 && 1.219 + !this._scrollable && 1.220 + !this.isAutoscrollBlocker(event.originalTarget)) { 1.221 + this.startScroll(event); 1.222 + } 1.223 + } else if (event.type == "pagehide") { 1.224 + if (this._scrollable) { 1.225 + var doc = 1.226 + this._scrollable.ownerDocument || this._scrollable.document; 1.227 + if (doc == event.target) { 1.228 + sendAsyncMessage("Autoscroll:Cancel"); 1.229 + } 1.230 + } 1.231 + } 1.232 + }, 1.233 + 1.234 + receiveMessage: function(msg) { 1.235 + switch (msg.name) { 1.236 + case "Autoscroll:Stop": { 1.237 + this.stopScroll(); 1.238 + break; 1.239 + } 1.240 + } 1.241 + }, 1.242 +}; 1.243 +ClickEventHandler.init(); 1.244 + 1.245 +let PopupBlocking = { 1.246 + popupData: null, 1.247 + popupDataInternal: null, 1.248 + 1.249 + init: function() { 1.250 + addEventListener("DOMPopupBlocked", this, true); 1.251 + addEventListener("pageshow", this, true); 1.252 + addEventListener("pagehide", this, true); 1.253 + 1.254 + addMessageListener("PopupBlocking:UnblockPopup", this); 1.255 + }, 1.256 + 1.257 + receiveMessage: function(msg) { 1.258 + switch (msg.name) { 1.259 + case "PopupBlocking:UnblockPopup": { 1.260 + let i = msg.data.index; 1.261 + if (this.popupData && this.popupData[i]) { 1.262 + let data = this.popupData[i]; 1.263 + let internals = this.popupDataInternal[i]; 1.264 + let dwi = internals.requestingWindow; 1.265 + 1.266 + // If we have a requesting window and the requesting document is 1.267 + // still the current document, open the popup. 1.268 + if (dwi && dwi.document == internals.requestingDocument) { 1.269 + dwi.open(data.popupWindowURI, data.popupWindowName, data.popupWindowFeatures); 1.270 + } 1.271 + } 1.272 + break; 1.273 + } 1.274 + } 1.275 + }, 1.276 + 1.277 + handleEvent: function(ev) { 1.278 + switch (ev.type) { 1.279 + case "DOMPopupBlocked": 1.280 + return this.onPopupBlocked(ev); 1.281 + case "pageshow": 1.282 + return this.onPageShow(ev); 1.283 + case "pagehide": 1.284 + return this.onPageHide(ev); 1.285 + } 1.286 + }, 1.287 + 1.288 + onPopupBlocked: function(ev) { 1.289 + if (!this.popupData) { 1.290 + this.popupData = new Array(); 1.291 + this.popupDataInternal = new Array(); 1.292 + } 1.293 + 1.294 + let obj = { 1.295 + popupWindowURI: ev.popupWindowURI.spec, 1.296 + popupWindowFeatures: ev.popupWindowFeatures, 1.297 + popupWindowName: ev.popupWindowName 1.298 + }; 1.299 + 1.300 + let internals = { 1.301 + requestingWindow: ev.requestingWindow, 1.302 + requestingDocument: ev.requestingWindow.document, 1.303 + }; 1.304 + 1.305 + this.popupData.push(obj); 1.306 + this.popupDataInternal.push(internals); 1.307 + this.updateBlockedPopups(true); 1.308 + }, 1.309 + 1.310 + onPageShow: function(ev) { 1.311 + if (this.popupData) { 1.312 + let i = 0; 1.313 + while (i < this.popupData.length) { 1.314 + // Filter out irrelevant reports. 1.315 + if (this.popupDataInternal[i].requestingWindow && 1.316 + (this.popupDataInternal[i].requestingWindow.document == 1.317 + this.popupDataInternal[i].requestingDocument)) { 1.318 + i++; 1.319 + } else { 1.320 + this.popupData.splice(i, 1); 1.321 + this.popupDataInternal.splice(i, 1); 1.322 + } 1.323 + } 1.324 + if (this.popupData.length == 0) { 1.325 + this.popupData = null; 1.326 + this.popupDataInternal = null; 1.327 + } 1.328 + this.updateBlockedPopups(false); 1.329 + } 1.330 + }, 1.331 + 1.332 + onPageHide: function(ev) { 1.333 + if (this.popupData) { 1.334 + this.popupData = null; 1.335 + this.popupDataInternal = null; 1.336 + this.updateBlockedPopups(false); 1.337 + } 1.338 + }, 1.339 + 1.340 + updateBlockedPopups: function(freshPopup) { 1.341 + sendAsyncMessage("PopupBlocking:UpdateBlockedPopups", 1.342 + {blockedPopups: this.popupData, freshPopup: freshPopup}); 1.343 + }, 1.344 +}; 1.345 +PopupBlocking.init(); 1.346 \ No newline at end of file