toolkit/content/browser-content.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 let Cc = Components.classes;
michael@0 7 let Ci = Components.interfaces;
michael@0 8 let Cu = Components.utils;
michael@0 9
michael@0 10 Cu.import("resource://gre/modules/Services.jsm");
michael@0 11
michael@0 12 var global = this;
michael@0 13
michael@0 14 let ClickEventHandler = {
michael@0 15 init: function init() {
michael@0 16 this._scrollable = null;
michael@0 17 this._scrolldir = "";
michael@0 18 this._startX = null;
michael@0 19 this._startY = null;
michael@0 20 this._screenX = null;
michael@0 21 this._screenY = null;
michael@0 22 this._lastFrame = null;
michael@0 23
michael@0 24 Cc["@mozilla.org/eventlistenerservice;1"]
michael@0 25 .getService(Ci.nsIEventListenerService)
michael@0 26 .addSystemEventListener(global, "mousedown", this, true);
michael@0 27
michael@0 28 addMessageListener("Autoscroll:Stop", this);
michael@0 29 },
michael@0 30
michael@0 31 isAutoscrollBlocker: function(node) {
michael@0 32 let mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
michael@0 33 let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition");
michael@0 34
michael@0 35 while (node) {
michael@0 36 if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) &&
michael@0 37 node.hasAttribute("href")) {
michael@0 38 return true;
michael@0 39 }
michael@0 40
michael@0 41 if (mmPaste && (node instanceof content.HTMLInputElement ||
michael@0 42 node instanceof content.HTMLTextAreaElement)) {
michael@0 43 return true;
michael@0 44 }
michael@0 45
michael@0 46 if (node instanceof content.XULElement && mmScrollbarPosition
michael@0 47 && (node.localName == "scrollbar" || node.localName == "scrollcorner")) {
michael@0 48 return true;
michael@0 49 }
michael@0 50
michael@0 51 node = node.parentNode;
michael@0 52 }
michael@0 53 return false;
michael@0 54 },
michael@0 55
michael@0 56 startScroll: function(event) {
michael@0 57 // this is a list of overflow property values that allow scrolling
michael@0 58 const scrollingAllowed = ['scroll', 'auto'];
michael@0 59
michael@0 60 // go upward in the DOM and find any parent element that has a overflow
michael@0 61 // area and can therefore be scrolled
michael@0 62 for (this._scrollable = event.originalTarget; this._scrollable;
michael@0 63 this._scrollable = this._scrollable.parentNode) {
michael@0 64 // do not use overflow based autoscroll for <html> and <body>
michael@0 65 // Elements or non-html elements such as svg or Document nodes
michael@0 66 // also make sure to skip select elements that are not multiline
michael@0 67 if (!(this._scrollable instanceof content.HTMLElement) ||
michael@0 68 ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) {
michael@0 69 continue;
michael@0 70 }
michael@0 71
michael@0 72 var overflowx = this._scrollable.ownerDocument.defaultView
michael@0 73 .getComputedStyle(this._scrollable, '')
michael@0 74 .getPropertyValue('overflow-x');
michael@0 75 var overflowy = this._scrollable.ownerDocument.defaultView
michael@0 76 .getComputedStyle(this._scrollable, '')
michael@0 77 .getPropertyValue('overflow-y');
michael@0 78 // we already discarded non-multiline selects so allow vertical
michael@0 79 // scroll for multiline ones directly without checking for a
michael@0 80 // overflow property
michael@0 81 var scrollVert = this._scrollable.scrollTopMax &&
michael@0 82 (this._scrollable instanceof content.HTMLSelectElement ||
michael@0 83 scrollingAllowed.indexOf(overflowy) >= 0);
michael@0 84
michael@0 85 // do not allow horizontal scrolling for select elements, it leads
michael@0 86 // to visual artifacts and is not the expected behavior anyway
michael@0 87 if (!(this._scrollable instanceof content.HTMLSelectElement) &&
michael@0 88 this._scrollable.scrollLeftMax &&
michael@0 89 scrollingAllowed.indexOf(overflowx) >= 0) {
michael@0 90 this._scrolldir = scrollVert ? "NSEW" : "EW";
michael@0 91 break;
michael@0 92 } else if (scrollVert) {
michael@0 93 this._scrolldir = "NS";
michael@0 94 break;
michael@0 95 }
michael@0 96 }
michael@0 97
michael@0 98 if (!this._scrollable) {
michael@0 99 this._scrollable = event.originalTarget.ownerDocument.defaultView;
michael@0 100 if (this._scrollable.scrollMaxX > 0) {
michael@0 101 this._scrolldir = this._scrollable.scrollMaxY > 0 ? "NSEW" : "EW";
michael@0 102 } else if (this._scrollable.scrollMaxY > 0) {
michael@0 103 this._scrolldir = "NS";
michael@0 104 } else {
michael@0 105 this._scrollable = null; // abort scrolling
michael@0 106 return;
michael@0 107 }
michael@0 108 }
michael@0 109
michael@0 110 let [enabled] = sendSyncMessage("Autoscroll:Start",
michael@0 111 {scrolldir: this._scrolldir,
michael@0 112 screenX: event.screenX,
michael@0 113 screenY: event.screenY});
michael@0 114 if (!enabled) {
michael@0 115 this._scrollable = null;
michael@0 116 return;
michael@0 117 }
michael@0 118
michael@0 119 Cc["@mozilla.org/eventlistenerservice;1"]
michael@0 120 .getService(Ci.nsIEventListenerService)
michael@0 121 .addSystemEventListener(global, "mousemove", this, true);
michael@0 122 addEventListener("pagehide", this, true);
michael@0 123
michael@0 124 this._ignoreMouseEvents = true;
michael@0 125 this._startX = event.screenX;
michael@0 126 this._startY = event.screenY;
michael@0 127 this._screenX = event.screenX;
michael@0 128 this._screenY = event.screenY;
michael@0 129 this._scrollErrorX = 0;
michael@0 130 this._scrollErrorY = 0;
michael@0 131 this._lastFrame = content.mozAnimationStartTime;
michael@0 132
michael@0 133 content.mozRequestAnimationFrame(this);
michael@0 134 },
michael@0 135
michael@0 136 stopScroll: function() {
michael@0 137 if (this._scrollable) {
michael@0 138 this._scrollable = null;
michael@0 139
michael@0 140 Cc["@mozilla.org/eventlistenerservice;1"]
michael@0 141 .getService(Ci.nsIEventListenerService)
michael@0 142 .removeSystemEventListener(global, "mousemove", this, true);
michael@0 143 removeEventListener("pagehide", this, true);
michael@0 144 }
michael@0 145 },
michael@0 146
michael@0 147 accelerate: function(curr, start) {
michael@0 148 const speed = 12;
michael@0 149 var val = (curr - start) / speed;
michael@0 150
michael@0 151 if (val > 1)
michael@0 152 return val * Math.sqrt(val) - 1;
michael@0 153 if (val < -1)
michael@0 154 return val * Math.sqrt(-val) + 1;
michael@0 155 return 0;
michael@0 156 },
michael@0 157
michael@0 158 roundToZero: function(num) {
michael@0 159 if (num > 0)
michael@0 160 return Math.floor(num);
michael@0 161 return Math.ceil(num);
michael@0 162 },
michael@0 163
michael@0 164 autoscrollLoop: function(timestamp) {
michael@0 165 if (!this._scrollable) {
michael@0 166 // Scrolling has been canceled
michael@0 167 return;
michael@0 168 }
michael@0 169
michael@0 170 // avoid long jumps when the browser hangs for more than
michael@0 171 // |maxTimeDelta| ms
michael@0 172 const maxTimeDelta = 100;
michael@0 173 var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
michael@0 174 // we used to scroll |accelerate()| pixels every 20ms (50fps)
michael@0 175 var timeCompensation = timeDelta / 20;
michael@0 176 this._lastFrame = timestamp;
michael@0 177
michael@0 178 var actualScrollX = 0;
michael@0 179 var actualScrollY = 0;
michael@0 180 // don't bother scrolling vertically when the scrolldir is only horizontal
michael@0 181 // and the other way around
michael@0 182 if (this._scrolldir != 'EW') {
michael@0 183 var y = this.accelerate(this._screenY, this._startY) * timeCompensation;
michael@0 184 var desiredScrollY = this._scrollErrorY + y;
michael@0 185 actualScrollY = this.roundToZero(desiredScrollY);
michael@0 186 this._scrollErrorY = (desiredScrollY - actualScrollY);
michael@0 187 }
michael@0 188 if (this._scrolldir != 'NS') {
michael@0 189 var x = this.accelerate(this._screenX, this._startX) * timeCompensation;
michael@0 190 var desiredScrollX = this._scrollErrorX + x;
michael@0 191 actualScrollX = this.roundToZero(desiredScrollX);
michael@0 192 this._scrollErrorX = (desiredScrollX - actualScrollX);
michael@0 193 }
michael@0 194
michael@0 195 if (this._scrollable instanceof content.Window) {
michael@0 196 this._scrollable.scrollBy(actualScrollX, actualScrollY);
michael@0 197 } else { // an element with overflow
michael@0 198 this._scrollable.scrollLeft += actualScrollX;
michael@0 199 this._scrollable.scrollTop += actualScrollY;
michael@0 200 }
michael@0 201 content.mozRequestAnimationFrame(this);
michael@0 202 },
michael@0 203
michael@0 204 sample: function(timestamp) {
michael@0 205 this.autoscrollLoop(timestamp);
michael@0 206 },
michael@0 207
michael@0 208 handleEvent: function(event) {
michael@0 209 if (event.type == "mousemove") {
michael@0 210 this._screenX = event.screenX;
michael@0 211 this._screenY = event.screenY;
michael@0 212 } else if (event.type == "mousedown") {
michael@0 213 if (event.isTrusted &
michael@0 214 !event.defaultPrevented &&
michael@0 215 event.button == 1 &&
michael@0 216 !this._scrollable &&
michael@0 217 !this.isAutoscrollBlocker(event.originalTarget)) {
michael@0 218 this.startScroll(event);
michael@0 219 }
michael@0 220 } else if (event.type == "pagehide") {
michael@0 221 if (this._scrollable) {
michael@0 222 var doc =
michael@0 223 this._scrollable.ownerDocument || this._scrollable.document;
michael@0 224 if (doc == event.target) {
michael@0 225 sendAsyncMessage("Autoscroll:Cancel");
michael@0 226 }
michael@0 227 }
michael@0 228 }
michael@0 229 },
michael@0 230
michael@0 231 receiveMessage: function(msg) {
michael@0 232 switch (msg.name) {
michael@0 233 case "Autoscroll:Stop": {
michael@0 234 this.stopScroll();
michael@0 235 break;
michael@0 236 }
michael@0 237 }
michael@0 238 },
michael@0 239 };
michael@0 240 ClickEventHandler.init();
michael@0 241
michael@0 242 let PopupBlocking = {
michael@0 243 popupData: null,
michael@0 244 popupDataInternal: null,
michael@0 245
michael@0 246 init: function() {
michael@0 247 addEventListener("DOMPopupBlocked", this, true);
michael@0 248 addEventListener("pageshow", this, true);
michael@0 249 addEventListener("pagehide", this, true);
michael@0 250
michael@0 251 addMessageListener("PopupBlocking:UnblockPopup", this);
michael@0 252 },
michael@0 253
michael@0 254 receiveMessage: function(msg) {
michael@0 255 switch (msg.name) {
michael@0 256 case "PopupBlocking:UnblockPopup": {
michael@0 257 let i = msg.data.index;
michael@0 258 if (this.popupData && this.popupData[i]) {
michael@0 259 let data = this.popupData[i];
michael@0 260 let internals = this.popupDataInternal[i];
michael@0 261 let dwi = internals.requestingWindow;
michael@0 262
michael@0 263 // If we have a requesting window and the requesting document is
michael@0 264 // still the current document, open the popup.
michael@0 265 if (dwi && dwi.document == internals.requestingDocument) {
michael@0 266 dwi.open(data.popupWindowURI, data.popupWindowName, data.popupWindowFeatures);
michael@0 267 }
michael@0 268 }
michael@0 269 break;
michael@0 270 }
michael@0 271 }
michael@0 272 },
michael@0 273
michael@0 274 handleEvent: function(ev) {
michael@0 275 switch (ev.type) {
michael@0 276 case "DOMPopupBlocked":
michael@0 277 return this.onPopupBlocked(ev);
michael@0 278 case "pageshow":
michael@0 279 return this.onPageShow(ev);
michael@0 280 case "pagehide":
michael@0 281 return this.onPageHide(ev);
michael@0 282 }
michael@0 283 },
michael@0 284
michael@0 285 onPopupBlocked: function(ev) {
michael@0 286 if (!this.popupData) {
michael@0 287 this.popupData = new Array();
michael@0 288 this.popupDataInternal = new Array();
michael@0 289 }
michael@0 290
michael@0 291 let obj = {
michael@0 292 popupWindowURI: ev.popupWindowURI.spec,
michael@0 293 popupWindowFeatures: ev.popupWindowFeatures,
michael@0 294 popupWindowName: ev.popupWindowName
michael@0 295 };
michael@0 296
michael@0 297 let internals = {
michael@0 298 requestingWindow: ev.requestingWindow,
michael@0 299 requestingDocument: ev.requestingWindow.document,
michael@0 300 };
michael@0 301
michael@0 302 this.popupData.push(obj);
michael@0 303 this.popupDataInternal.push(internals);
michael@0 304 this.updateBlockedPopups(true);
michael@0 305 },
michael@0 306
michael@0 307 onPageShow: function(ev) {
michael@0 308 if (this.popupData) {
michael@0 309 let i = 0;
michael@0 310 while (i < this.popupData.length) {
michael@0 311 // Filter out irrelevant reports.
michael@0 312 if (this.popupDataInternal[i].requestingWindow &&
michael@0 313 (this.popupDataInternal[i].requestingWindow.document ==
michael@0 314 this.popupDataInternal[i].requestingDocument)) {
michael@0 315 i++;
michael@0 316 } else {
michael@0 317 this.popupData.splice(i, 1);
michael@0 318 this.popupDataInternal.splice(i, 1);
michael@0 319 }
michael@0 320 }
michael@0 321 if (this.popupData.length == 0) {
michael@0 322 this.popupData = null;
michael@0 323 this.popupDataInternal = null;
michael@0 324 }
michael@0 325 this.updateBlockedPopups(false);
michael@0 326 }
michael@0 327 },
michael@0 328
michael@0 329 onPageHide: function(ev) {
michael@0 330 if (this.popupData) {
michael@0 331 this.popupData = null;
michael@0 332 this.popupDataInternal = null;
michael@0 333 this.updateBlockedPopups(false);
michael@0 334 }
michael@0 335 },
michael@0 336
michael@0 337 updateBlockedPopups: function(freshPopup) {
michael@0 338 sendAsyncMessage("PopupBlocking:UpdateBlockedPopups",
michael@0 339 {blockedPopups: this.popupData, freshPopup: freshPopup});
michael@0 340 },
michael@0 341 };
michael@0 342 PopupBlocking.init();

mercurial