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

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

mercurial