diff options
Diffstat (limited to 'application/basilisk/base/content/browser-gestureSupport.js')
-rw-r--r-- | application/basilisk/base/content/browser-gestureSupport.js | 1240 |
1 files changed, 0 insertions, 1240 deletions
diff --git a/application/basilisk/base/content/browser-gestureSupport.js b/application/basilisk/base/content/browser-gestureSupport.js deleted file mode 100644 index 11b55b3e4..000000000 --- a/application/basilisk/base/content/browser-gestureSupport.js +++ /dev/null @@ -1,1240 +0,0 @@ -/* 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/. */ - -// Simple gestures support -// -// As per bug #412486, web content must not be allowed to receive any -// simple gesture events. Multi-touch gesture APIs are in their -// infancy and we do NOT want to be forced into supporting an API that -// will probably have to change in the future. (The current Mac OS X -// API is undocumented and was reverse-engineered.) Until support is -// implemented in the event dispatcher to keep these events as -// chrome-only, we must listen for the simple gesture events during -// the capturing phase and call stopPropagation on every event. - -var gGestureSupport = { - _currentRotation: 0, - _lastRotateDelta: 0, - _rotateMomentumThreshold: .75, - - /** - * Add or remove mouse gesture event listeners - * - * @param aAddListener - * True to add/init listeners and false to remove/uninit - */ - init: function GS_init(aAddListener) { - const gestureEvents = ["SwipeGestureMayStart", "SwipeGestureStart", - "SwipeGestureUpdate", "SwipeGestureEnd", "SwipeGesture", - "MagnifyGestureStart", "MagnifyGestureUpdate", "MagnifyGesture", - "RotateGestureStart", "RotateGestureUpdate", "RotateGesture", - "TapGesture", "PressTapGesture"]; - - let addRemove = aAddListener ? window.addEventListener : - window.removeEventListener; - - for (let event of gestureEvents) { - addRemove("Moz" + event, this, true); - } - }, - - /** - * Dispatch events based on the type of mouse gesture event. For now, make - * sure to stop propagation of every gesture event so that web content cannot - * receive gesture events. - * - * @param aEvent - * The gesture event to handle - */ - handleEvent: function GS_handleEvent(aEvent) { - if (!Services.prefs.getBoolPref( - "dom.debug.propagate_gesture_events_through_content")) { - aEvent.stopPropagation(); - } - - // Create a preference object with some defaults - let def = (aThreshold, aLatched) => - ({ threshold: aThreshold, latched: !!aLatched }); - - switch (aEvent.type) { - case "MozSwipeGestureMayStart": - if (this._shouldDoSwipeGesture(aEvent)) { - aEvent.preventDefault(); - } - break; - case "MozSwipeGestureStart": - aEvent.preventDefault(); - this._setupSwipeGesture(); - break; - case "MozSwipeGestureUpdate": - aEvent.preventDefault(); - this._doUpdate(aEvent); - break; - case "MozSwipeGestureEnd": - aEvent.preventDefault(); - this._doEnd(aEvent); - break; - case "MozSwipeGesture": - aEvent.preventDefault(); - this.onSwipe(aEvent); - break; - case "MozMagnifyGestureStart": - aEvent.preventDefault(); -#ifdef XP_WIN - let pinchPref = def(25, 0) -#else - let pinchPref = def(150, 1) -#endif - this._setupGesture(aEvent, "pinch", pinchPref, "out", "in"); - break; - case "MozRotateGestureStart": - aEvent.preventDefault(); - this._setupGesture(aEvent, "twist", def(25, 0), "right", "left"); - break; - case "MozMagnifyGestureUpdate": - case "MozRotateGestureUpdate": - aEvent.preventDefault(); - this._doUpdate(aEvent); - break; - case "MozTapGesture": - aEvent.preventDefault(); - this._doAction(aEvent, ["tap"]); - break; - case "MozRotateGesture": - aEvent.preventDefault(); - this._doAction(aEvent, ["twist", "end"]); - break; - /* case "MozPressTapGesture": - break; */ - } - }, - - /** - * Called at the start of "pinch" and "twist" gestures to setup all of the - * information needed to process the gesture - * - * @param aEvent - * The continual motion start event to handle - * @param aGesture - * Name of the gesture to handle - * @param aPref - * Preference object with the names of preferences and defaults - * @param aInc - * Command to trigger for increasing motion (without gesture name) - * @param aDec - * Command to trigger for decreasing motion (without gesture name) - */ - _setupGesture: function GS__setupGesture(aEvent, aGesture, aPref, aInc, aDec) { - // Try to load user-set values from preferences - for (let [pref, def] of Object.entries(aPref)) - aPref[pref] = this._getPref(aGesture + "." + pref, def); - - // Keep track of the total deltas and latching behavior - let offset = 0; - let latchDir = aEvent.delta > 0 ? 1 : -1; - let isLatched = false; - - // Create the update function here to capture closure state - this._doUpdate = function GS__doUpdate(updateEvent) { - // Update the offset with new event data - offset += updateEvent.delta; - - // Check if the cumulative deltas exceed the threshold - if (Math.abs(offset) > aPref["threshold"]) { - // Trigger the action if we don't care about latching; otherwise, make - // sure either we're not latched and going the same direction of the - // initial motion; or we're latched and going the opposite way - let sameDir = (latchDir ^ offset) >= 0; - if (!aPref["latched"] || (isLatched ^ sameDir)) { - this._doAction(updateEvent, [aGesture, offset > 0 ? aInc : aDec]); - - // We must be getting latched or leaving it, so just toggle - isLatched = !isLatched; - } - - // Reset motion counter to prepare for more of the same gesture - offset = 0; - } - }; - - // The start event also contains deltas, so handle an update right away - this._doUpdate(aEvent); - }, - - /** - * Checks whether a swipe gesture event can navigate the browser history or - * not. - * - * @param aEvent - * The swipe gesture event. - * @return true if the swipe event may navigate the history, false othwerwise. - */ - _swipeNavigatesHistory: function GS__swipeNavigatesHistory(aEvent) { - return this._getCommand(aEvent, ["swipe", "left"]) - == "Browser:BackOrBackDuplicate" && - this._getCommand(aEvent, ["swipe", "right"]) - == "Browser:ForwardOrForwardDuplicate"; - }, - - /** - * Checks whether we want to start a swipe for aEvent and sets - * aEvent.allowedDirections to the right values. - * - * @param aEvent - * The swipe gesture "MayStart" event. - * @return true if we're willing to start a swipe for this event, false - * otherwise. - */ - _shouldDoSwipeGesture: function GS__shouldDoSwipeGesture(aEvent) { - if (!this._swipeNavigatesHistory(aEvent)) { - return false; - } - - let isVerticalSwipe = false; - if (aEvent.direction == aEvent.DIRECTION_UP) { - if (gMultiProcessBrowser || content.pageYOffset > 0) { - return false; - } - isVerticalSwipe = true; - } else if (aEvent.direction == aEvent.DIRECTION_DOWN) { - if (gMultiProcessBrowser || content.pageYOffset < content.scrollMaxY) { - return false; - } - isVerticalSwipe = true; - } - if (isVerticalSwipe) { - // Vertical overscroll has been temporarily disabled until bug 939480 is - // fixed. - return false; - } - - let canGoBack = gHistorySwipeAnimation.canGoBack(); - let canGoForward = gHistorySwipeAnimation.canGoForward(); - let isLTR = gHistorySwipeAnimation.isLTR; - - if (canGoBack) { - aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_LEFT : - aEvent.DIRECTION_RIGHT; - } - if (canGoForward) { - aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_RIGHT : - aEvent.DIRECTION_LEFT; - } - - return true; - }, - - /** - * Sets up swipe gestures. This includes setting up swipe animations for the - * gesture, if enabled. - * - * @param aEvent - * The swipe gesture start event. - * @return true if swipe gestures could successfully be set up, false - * othwerwise. - */ - _setupSwipeGesture: function GS__setupSwipeGesture() { - gHistorySwipeAnimation.startAnimation(false); - - this._doUpdate = function GS__doUpdate(aEvent) { - gHistorySwipeAnimation.updateAnimation(aEvent.delta); - }; - - this._doEnd = function GS__doEnd(aEvent) { - gHistorySwipeAnimation.swipeEndEventReceived(); - - this._doUpdate = function() {}; - this._doEnd = function() {}; - } - }, - - /** - * Generator producing the powerset of the input array where the first result - * is the complete set and the last result (before StopIteration) is empty. - * - * @param aArray - * Source array containing any number of elements - * @yield Array that is a subset of the input array from full set to empty - */ - _power: function* GS__power(aArray) { - // Create a bitmask based on the length of the array - let num = 1 << aArray.length; - while (--num >= 0) { - // Only select array elements where the current bit is set - yield aArray.reduce(function(aPrev, aCurr, aIndex) { - if (num & 1 << aIndex) - aPrev.push(aCurr); - return aPrev; - }, []); - } - }, - - /** - * Determine what action to do for the gesture based on which keys are - * pressed and which commands are set, and execute the command. - * - * @param aEvent - * The original gesture event to convert into a fake click event - * @param aGesture - * Array of gesture name parts (to be joined by periods) - * @return Name of the executed command. Returns null if no command is - * found. - */ - _doAction: function GS__doAction(aEvent, aGesture) { - let command = this._getCommand(aEvent, aGesture); - return command && this._doCommand(aEvent, command); - }, - - /** - * Determine what action to do for the gesture based on which keys are - * pressed and which commands are set - * - * @param aEvent - * The original gesture event to convert into a fake click event - * @param aGesture - * Array of gesture name parts (to be joined by periods) - */ - _getCommand: function GS__getCommand(aEvent, aGesture) { - // Create an array of pressed keys in a fixed order so that a command for - // "meta" is preferred over "ctrl" when both buttons are pressed (and a - // command for both don't exist) - let keyCombos = []; - for (let key of ["shift", "alt", "ctrl", "meta"]) { - if (aEvent[key + "Key"]) - keyCombos.push(key); - } - - // Try each combination of key presses in decreasing order for commands - for (let subCombo of this._power(keyCombos)) { - // Convert a gesture and pressed keys into the corresponding command - // action where the preference has the gesture before "shift" before - // "alt" before "ctrl" before "meta" all separated by periods - let command; - try { - command = this._getPref(aGesture.concat(subCombo).join(".")); - } catch (e) {} - - if (command) - return command; - } - return null; - }, - - /** - * Execute the specified command. - * - * @param aEvent - * The original gesture event to convert into a fake click event - * @param aCommand - * Name of the command found for the event's keys and gesture. - */ - _doCommand: function GS__doCommand(aEvent, aCommand) { - let node = document.getElementById(aCommand); - if (node) { - if (node.getAttribute("disabled") != "true") { - let cmdEvent = document.createEvent("xulcommandevent"); - cmdEvent.initCommandEvent("command", true, true, window, 0, - aEvent.ctrlKey, aEvent.altKey, - aEvent.shiftKey, aEvent.metaKey, aEvent); - node.dispatchEvent(cmdEvent); - } - - } else { - goDoCommand(aCommand); - } - }, - - /** - * Handle continual motion events. This function will be set by - * _setupGesture or _setupSwipe. - * - * @param aEvent - * The continual motion update event to handle - */ - _doUpdate(aEvent) {}, - - /** - * Handle gesture end events. This function will be set by _setupSwipe. - * - * @param aEvent - * The gesture end event to handle - */ - _doEnd(aEvent) {}, - - /** - * Convert the swipe gesture into a browser action based on the direction. - * - * @param aEvent - * The swipe event to handle - */ - onSwipe: function GS_onSwipe(aEvent) { - // Figure out which one (and only one) direction was triggered - for (let dir of ["UP", "RIGHT", "DOWN", "LEFT"]) { - if (aEvent.direction == aEvent["DIRECTION_" + dir]) { - this._coordinateSwipeEventWithAnimation(aEvent, dir); - break; - } - } - }, - - /** - * Process a swipe event based on the given direction. - * - * @param aEvent - * The swipe event to handle - * @param aDir - * The direction for the swipe event - */ - processSwipeEvent: function GS_processSwipeEvent(aEvent, aDir) { - this._doAction(aEvent, ["swipe", aDir.toLowerCase()]); - }, - - /** - * Coordinates the swipe event with the swipe animation, if any. - * If an animation is currently running, the swipe event will be - * processed once the animation stops. This will guarantee a fluid - * motion of the animation. - * - * @param aEvent - * The swipe event to handle - * @param aDir - * The direction for the swipe event - */ - _coordinateSwipeEventWithAnimation: - function GS__coordinateSwipeEventWithAnimation(aEvent, aDir) { - if ((gHistorySwipeAnimation.isAnimationRunning()) && - (aDir == "RIGHT" || aDir == "LEFT")) { - gHistorySwipeAnimation.processSwipeEvent(aEvent, aDir); - } else { - this.processSwipeEvent(aEvent, aDir); - } - }, - - /** - * Get a gesture preference or use a default if it doesn't exist - * - * @param aPref - * Name of the preference to load under the gesture branch - * @param aDef - * Default value if the preference doesn't exist - */ - _getPref: function GS__getPref(aPref, aDef) { - // Preferences branch under which all gestures preferences are stored - const branch = "browser.gesture."; - - try { - // Determine what type of data to load based on default value's type - let type = typeof aDef; - let getFunc = "Char"; - if (type == "boolean") - getFunc = "Bool"; - else if (type == "number") - getFunc = "Int"; - return gPrefService["get" + getFunc + "Pref"](branch + aPref); - } catch (e) { - return aDef; - } - }, - - /** - * Perform rotation for ImageDocuments - * - * @param aEvent - * The MozRotateGestureUpdate event triggering this call - */ - rotate(aEvent) { - if (!(content.document instanceof ImageDocument)) - return; - - let contentElement = content.document.body.firstElementChild; - if (!contentElement) - return; - // If we're currently snapping, cancel that snap - if (contentElement.classList.contains("completeRotation")) - this._clearCompleteRotation(); - - this.rotation = Math.round(this.rotation + aEvent.delta); - contentElement.style.transform = "rotate(" + this.rotation + "deg)"; - this._lastRotateDelta = aEvent.delta; - }, - - /** - * Perform a rotation end for ImageDocuments - */ - rotateEnd() { - if (!(content.document instanceof ImageDocument)) - return; - - let contentElement = content.document.body.firstElementChild; - if (!contentElement) - return; - - let transitionRotation = 0; - - // The reason that 360 is allowed here is because when rotating between - // 315 and 360, setting rotate(0deg) will cause it to rotate the wrong - // direction around--spinning wildly. - if (this.rotation <= 45) - transitionRotation = 0; - else if (this.rotation > 45 && this.rotation <= 135) - transitionRotation = 90; - else if (this.rotation > 135 && this.rotation <= 225) - transitionRotation = 180; - else if (this.rotation > 225 && this.rotation <= 315) - transitionRotation = 270; - else - transitionRotation = 360; - - // If we're going fast enough, and we didn't already snap ahead of rotation, - // then snap ahead of rotation to simulate momentum - if (this._lastRotateDelta > this._rotateMomentumThreshold && - this.rotation > transitionRotation) - transitionRotation += 90; - else if (this._lastRotateDelta < -1 * this._rotateMomentumThreshold && - this.rotation < transitionRotation) - transitionRotation -= 90; - - // Only add the completeRotation class if it is is necessary - if (transitionRotation != this.rotation) { - contentElement.classList.add("completeRotation"); - contentElement.addEventListener("transitionend", this._clearCompleteRotation); - } - - contentElement.style.transform = "rotate(" + transitionRotation + "deg)"; - this.rotation = transitionRotation; - }, - - /** - * Gets the current rotation for the ImageDocument - */ - get rotation() { - return this._currentRotation; - }, - - /** - * Sets the current rotation for the ImageDocument - * - * @param aVal - * The new value to take. Can be any value, but it will be bounded to - * 0 inclusive to 360 exclusive. - */ - set rotation(aVal) { - this._currentRotation = aVal % 360; - if (this._currentRotation < 0) - this._currentRotation += 360; - return this._currentRotation; - }, - - /** - * When the location/tab changes, need to reload the current rotation for the - * image - */ - restoreRotationState() { - // Bug 863514 - Make gesture support work in electrolysis - if (gMultiProcessBrowser) - return; - - if (!(content.document instanceof ImageDocument)) - return; - - let contentElement = content.document.body.firstElementChild; - let transformValue = content.window.getComputedStyle(contentElement, null) - .transform; - - if (transformValue == "none") { - this.rotation = 0; - return; - } - - // transformValue is a rotation matrix--split it and do mathemagic to - // obtain the real rotation value - transformValue = transformValue.split("(")[1] - .split(")")[0] - .split(","); - this.rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) * - (180 / Math.PI)); - }, - - /** - * Removes the transition rule by removing the completeRotation class - */ - _clearCompleteRotation() { - let contentElement = content.document && - content.document instanceof ImageDocument && - content.document.body && - content.document.body.firstElementChild; - if (!contentElement) - return; - contentElement.classList.remove("completeRotation"); - contentElement.removeEventListener("transitionend", this._clearCompleteRotation); - }, -}; - -// History Swipe Animation Support (bug 678392) -var gHistorySwipeAnimation = { - - active: false, - isLTR: false, - - /** - * Initializes the support for history swipe animations, if it is supported - * by the platform/configuration. - */ - init: function HSA_init() { - if (!this._isSupported()) - return; - - this.active = false; - this.isLTR = document.documentElement.matches(":-moz-locale-dir(ltr)"); - this._trackedSnapshots = []; - this._startingIndex = -1; - this._historyIndex = -1; - this._boxWidth = -1; - this._boxHeight = -1; - this._maxSnapshots = this._getMaxSnapshots(); - this._lastSwipeDir = ""; - this._direction = "horizontal"; - - // We only want to activate history swipe animations if we store snapshots. - // If we don't store any, we handle horizontal swipes without animations. - if (this._maxSnapshots > 0) { - this.active = true; - gBrowser.addEventListener("pagehide", this); - gBrowser.addEventListener("pageshow", this); - gBrowser.addEventListener("popstate", this); - gBrowser.addEventListener("DOMModalDialogClosed", this); - gBrowser.tabContainer.addEventListener("TabClose", this); - } - }, - - /** - * Uninitializes the support for history swipe animations. - */ - uninit: function HSA_uninit() { - gBrowser.removeEventListener("pagehide", this); - gBrowser.removeEventListener("pageshow", this); - gBrowser.removeEventListener("popstate", this); - gBrowser.removeEventListener("DOMModalDialogClosed", this); - gBrowser.tabContainer.removeEventListener("TabClose", this); - - this.active = false; - this.isLTR = false; - }, - - /** - * Starts the swipe animation and handles fast swiping (i.e. a swipe animation - * is already in progress when a new one is initiated). - * - * @param aIsVerticalSwipe - * Whether we're dealing with a vertical swipe or not. - */ - startAnimation: function HSA_startAnimation(aIsVerticalSwipe) { - this._direction = aIsVerticalSwipe ? "vertical" : "horizontal"; - - if (this.isAnimationRunning()) { - // If this is a horizontal scroll, or if this is a vertical scroll that - // was started while a horizontal scroll was still running, handle it as - // as a fast swipe. In the case of the latter scenario, this allows us to - // start the vertical animation without first loading the final page, or - // taking another snapshot. If vertical scrolls are initiated repeatedly - // without prior horizontal scroll we skip this and restart the animation - // from 0. - if (this._direction == "horizontal" || this._lastSwipeDir != "") { - gBrowser.stop(); - this._lastSwipeDir = "RELOAD"; // just ensure that != "" - this._canGoBack = this.canGoBack(); - this._canGoForward = this.canGoForward(); - this._handleFastSwiping(); - } - this.updateAnimation(0); - } else { - // Get the session history from SessionStore. - let updateSessionHistory = sessionHistory => { - this._startingIndex = sessionHistory.index; - this._historyIndex = this._startingIndex; - this._canGoBack = this.canGoBack(); - this._canGoForward = this.canGoForward(); - if (this.active) { - this._addBoxes(); - this._takeSnapshot(); - this._installPrevAndNextSnapshots(); - this._lastSwipeDir = ""; - } - this.updateAnimation(0); - } - SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory); - } - }, - - /** - * Stops the swipe animation. - */ - stopAnimation: function HSA_stopAnimation() { - gHistorySwipeAnimation._removeBoxes(); - this._historyIndex = this._getCurrentHistoryIndex(); - }, - - /** - * Updates the animation between two pages in history. - * - * @param aVal - * A floating point value that represents the progress of the - * swipe gesture. - */ - updateAnimation: function HSA_updateAnimation(aVal) { - if (!this.isAnimationRunning()) { - return; - } - - // We use the following value to decrease the bounce effect when scrolling - // to the top or bottom of the page, or when swiping back/forward past the - // browsing history. This value was determined experimentally. - let dampValue = 4; - if (this._direction == "vertical") { - this._prevBox.collapsed = true; - this._nextBox.collapsed = true; - this._positionBox(this._curBox, -1 * aVal / dampValue); - } else if ((aVal >= 0 && this.isLTR) || - (aVal <= 0 && !this.isLTR)) { - let tempDampValue = 1; - if (this._canGoBack) { - this._prevBox.collapsed = false; - } else { - tempDampValue = dampValue; - this._prevBox.collapsed = true; - } - - // The current page is pushed to the right (LTR) or left (RTL), - // the intention is to go back. - // If there is a page to go back to, it should show in the background. - this._positionBox(this._curBox, aVal / tempDampValue); - - // The forward page should be pushed offscreen all the way to the right. - this._positionBox(this._nextBox, 1); - } else if (this._canGoForward) { - // The intention is to go forward. If there is a page to go forward to, - // it should slide in from the right (LTR) or left (RTL). - // Otherwise, the current page should slide to the left (LTR) or - // right (RTL) and the backdrop should appear in the background. - // For the backdrop to be visible in that case, the previous page needs - // to be hidden (if it exists). - this._nextBox.collapsed = false; - let offset = this.isLTR ? 1 : -1; - this._positionBox(this._curBox, 0); - this._positionBox(this._nextBox, offset + aVal); - } else { - this._prevBox.collapsed = true; - this._positionBox(this._curBox, aVal / dampValue); - } - }, - - _getCurrentHistoryIndex() { - return SessionStore.getSessionHistory(gBrowser.selectedTab).index; - }, - - /** - * Event handler for events relevant to the history swipe animation. - * - * @param aEvent - * An event to process. - */ - handleEvent: function HSA_handleEvent(aEvent) { - let browser = gBrowser.selectedBrowser; - switch (aEvent.type) { - case "TabClose": - let browserForTab = gBrowser.getBrowserForTab(aEvent.target); - this._removeTrackedSnapshot(-1, browserForTab); - break; - case "DOMModalDialogClosed": - this.stopAnimation(); - break; - case "pageshow": - if (aEvent.target == browser.contentDocument) { - this.stopAnimation(); - } - break; - case "popstate": - if (aEvent.target == browser.contentDocument.defaultView) { - this.stopAnimation(); - } - break; - case "pagehide": - if (aEvent.target == browser.contentDocument) { - // Take and compress a snapshot of a page whenever it's about to be - // navigated away from. We already have a snapshot of the page if an - // animation is running, so we're left with compressing it. - if (!this.isAnimationRunning()) { - this._takeSnapshot(); - } - this._compressSnapshotAtCurrentIndex(); - } - break; - } - }, - - /** - * Checks whether the history swipe animation is currently running or not. - * - * @return true if the animation is currently running, false otherwise. - */ - isAnimationRunning: function HSA_isAnimationRunning() { - return !!this._container; - }, - - /** - * Process a swipe event based on the given direction. - * - * @param aEvent - * The swipe event to handle - * @param aDir - * The direction for the swipe event - */ - processSwipeEvent: function HSA_processSwipeEvent(aEvent, aDir) { - if (aDir == "RIGHT") - this._historyIndex += this.isLTR ? 1 : -1; - else if (aDir == "LEFT") - this._historyIndex += this.isLTR ? -1 : 1; - else - return; - this._lastSwipeDir = aDir; - }, - - /** - * Checks if there is a page in the browser history to go back to. - * - * @return true if there is a previous page in history, false otherwise. - */ - canGoBack: function HSA_canGoBack() { - if (this.isAnimationRunning()) - return this._doesIndexExistInHistory(this._historyIndex - 1); - return gBrowser.webNavigation.canGoBack; - }, - - /** - * Checks if there is a page in the browser history to go forward to. - * - * @return true if there is a next page in history, false otherwise. - */ - canGoForward: function HSA_canGoForward() { - if (this.isAnimationRunning()) - return this._doesIndexExistInHistory(this._historyIndex + 1); - return gBrowser.webNavigation.canGoForward; - }, - - /** - * Used to notify the history swipe animation that the OS sent a swipe end - * event and that we should navigate to the page that the user swiped to, if - * any. This will also result in the animation overlay to be torn down. - */ - swipeEndEventReceived: function HSA_swipeEndEventReceived() { - // Update the session history before continuing. - let updateSessionHistory = sessionHistory => { - if (this._lastSwipeDir != "" && this._historyIndex != this._startingIndex) - this._navigateToHistoryIndex(); - else - this.stopAnimation(); - } - SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory); - }, - - /** - * Checks whether a particular index exists in the browser history or not. - * - * @param aIndex - * The index to check for availability for in the history. - * @return true if the index exists in the browser history, false otherwise. - */ - _doesIndexExistInHistory: function HSA__doesIndexExistInHistory(aIndex) { - try { - return SessionStore.getSessionHistory(gBrowser.selectedTab).entries[aIndex] != null; - } catch (ex) { - return false; - } - }, - - /** - * Navigates to the index in history that is currently being tracked by - * |this|. - */ - _navigateToHistoryIndex: function HSA__navigateToHistoryIndex() { - if (this._doesIndexExistInHistory(this._historyIndex)) - gBrowser.webNavigation.gotoIndex(this._historyIndex); - else - this.stopAnimation(); - }, - - /** - * Checks to see if history swipe animations are supported by this - * platform/configuration. - * - * return true if supported, false otherwise. - */ - _isSupported: function HSA__isSupported() { - return window.matchMedia("(-moz-swipe-animation-enabled)").matches; - }, - - /** - * Handle fast swiping (i.e. a swipe animation is already in - * progress when a new one is initiated). This will swap out the snapshots - * used in the previous animation with the appropriate new ones. - */ - _handleFastSwiping: function HSA__handleFastSwiping() { - this._installCurrentPageSnapshot(null); - this._installPrevAndNextSnapshots(); - }, - - /** - * Adds the boxes that contain the snapshots used during the swipe animation. - */ - _addBoxes: function HSA__addBoxes() { - let browserStack = - document.getAnonymousElementByAttribute(gBrowser.getNotificationBox(), - "class", "browserStack"); - this._container = this._createElement("historySwipeAnimationContainer", - "stack"); - browserStack.appendChild(this._container); - - this._prevBox = this._createElement("historySwipeAnimationPreviousPage", - "box"); - this._container.appendChild(this._prevBox); - - this._curBox = this._createElement("historySwipeAnimationCurrentPage", - "box"); - this._container.appendChild(this._curBox); - - this._nextBox = this._createElement("historySwipeAnimationNextPage", - "box"); - this._container.appendChild(this._nextBox); - - // Cache width and height. - this._boxWidth = this._curBox.getBoundingClientRect().width; - this._boxHeight = this._curBox.getBoundingClientRect().height; - }, - - /** - * Removes the boxes. - */ - _removeBoxes: function HSA__removeBoxes() { - this._curBox = null; - this._prevBox = null; - this._nextBox = null; - if (this._container) - this._container.parentNode.removeChild(this._container); - this._container = null; - this._boxWidth = -1; - this._boxHeight = -1; - }, - - /** - * Creates an element with a given identifier and tag name. - * - * @param aID - * An identifier to create the element with. - * @param aTagName - * The name of the tag to create the element for. - * @return the newly created element. - */ - _createElement: function HSA__createElement(aID, aTagName) { - let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - let element = document.createElementNS(XULNS, aTagName); - element.id = aID; - return element; - }, - - /** - * Moves a given box to a given X coordinate position. - * - * @param aBox - * The box element to position. - * @param aPosition - * The position (in X coordinates) to move the box element to. - */ - _positionBox: function HSA__positionBox(aBox, aPosition) { - let transform = ""; - - if (this._direction == "vertical") - transform = "translateY(" + this._boxHeight * aPosition + "px)"; - else - transform = "translateX(" + this._boxWidth * aPosition + "px)"; - - aBox.style.transform = transform; - }, - - /** - * Verifies that we're ready to take snapshots based on the global pref and - * the current index in history. - * - * @return true if we're ready to take snapshots, false otherwise. - */ - _readyToTakeSnapshots: function HSA__readyToTakeSnapshots() { - return (this._maxSnapshots >= 1 && this._getCurrentHistoryIndex() >= 0); - }, - - /** - * Takes a snapshot of the page the browser is currently on. - */ - _takeSnapshot: function HSA__takeSnapshot() { - if (!this._readyToTakeSnapshots()) { - return; - } - - let canvas = null; - - let browser = gBrowser.selectedBrowser; - let r = browser.getBoundingClientRect(); - canvas = document.createElementNS("http://www.w3.org/1999/xhtml", - "canvas"); - canvas.mozOpaque = true; - let scale = window.devicePixelRatio; - canvas.width = r.width * scale; - canvas.height = r.height * scale; - let ctx = canvas.getContext("2d"); - let zoom = browser.markupDocumentViewer.fullZoom * scale; - ctx.scale(zoom, zoom); - ctx.drawWindow(browser.contentWindow, - 0, 0, canvas.width / zoom, canvas.height / zoom, "white", - ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_VIEW | - ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES | - ctx.DRAWWINDOW_USE_WIDGET_LAYERS); - - TelemetryStopwatch.start("FX_GESTURE_INSTALL_SNAPSHOT_OF_PAGE"); - try { - this._installCurrentPageSnapshot(canvas); - this._assignSnapshotToCurrentBrowser(canvas); - } finally { - TelemetryStopwatch.finish("FX_GESTURE_INSTALL_SNAPSHOT_OF_PAGE"); - } - }, - - /** - * Retrieves the maximum number of snapshots that should be kept in memory. - * This limit is a global limit and is valid across all open tabs. - */ - _getMaxSnapshots: function HSA__getMaxSnapshots() { - return gPrefService.getIntPref("browser.snapshots.limit"); - }, - - /** - * Adds a snapshot to the list and initiates the compression of said snapshot. - * Once the compression is completed, it will replace the uncompressed - * snapshot in the list. - * - * @param aCanvas - * The snapshot to add to the list and compress. - */ - _assignSnapshotToCurrentBrowser: - function HSA__assignSnapshotToCurrentBrowser(aCanvas) { - let browser = gBrowser.selectedBrowser; - let currIndex = this._getCurrentHistoryIndex(); - - this._removeTrackedSnapshot(currIndex, browser); - this._addSnapshotRefToArray(currIndex, browser); - - if (!("snapshots" in browser)) - browser.snapshots = []; - let snapshots = browser.snapshots; - // Temporarily store the canvas as the compressed snapshot. - // This avoids a blank page if the user swipes quickly - // between pages before the compression could complete. - snapshots[currIndex] = { - image: aCanvas, - scale: window.devicePixelRatio - }; - }, - - /** - * Compresses the HTMLCanvasElement that's stored at the current history - * index in the snapshot array and stores the compressed image in its place. - */ - _compressSnapshotAtCurrentIndex: - function HSA__compressSnapshotAtCurrentIndex() { - if (!this._readyToTakeSnapshots()) { - // We didn't take a snapshot earlier because we weren't ready to, so - // there's nothing to compress. - return; - } - - TelemetryStopwatch.start("FX_GESTURE_COMPRESS_SNAPSHOT_OF_PAGE"); - try { - let browser = gBrowser.selectedBrowser; - let snapshots = browser.snapshots; - let currIndex = _getCurrentHistoryIndex(); - - // Kick off snapshot compression. - let canvas = snapshots[currIndex].image; - canvas.toBlob(function(aBlob) { - if (snapshots[currIndex]) { - snapshots[currIndex].image = aBlob; - } - }, "image/png" - ); - } finally { - TelemetryStopwatch.finish("FX_GESTURE_COMPRESS_SNAPSHOT_OF_PAGE"); - } - }, - - /** - * Removes a snapshot identified by the browser and index in the array of - * snapshots for that browser, if present. If no snapshot could be identified - * the method simply returns without taking any action. If aIndex is negative, - * all snapshots for a particular browser will be removed. - * - * @param aIndex - * The index in history of the new snapshot, or negative value if all - * snapshots for a browser should be removed. - * @param aBrowser - * The browser the new snapshot was taken in. - */ - _removeTrackedSnapshot: function HSA__removeTrackedSnapshot(aIndex, aBrowser) { - let arr = this._trackedSnapshots; - let requiresExactIndexMatch = aIndex >= 0; - for (let i = 0; i < arr.length; i++) { - if ((arr[i].browser == aBrowser) && - (aIndex < 0 || aIndex == arr[i].index)) { - delete aBrowser.snapshots[arr[i].index]; - arr.splice(i, 1); - if (requiresExactIndexMatch) - return; // Found and removed the only element. - i--; // Make sure to revisit the index that we just removed an - // element at. - } - } - }, - - /** - * Adds a new snapshot reference for a given index and browser to the array - * of references to tracked snapshots. - * - * @param aIndex - * The index in history of the new snapshot. - * @param aBrowser - * The browser the new snapshot was taken in. - */ - _addSnapshotRefToArray: - function HSA__addSnapshotRefToArray(aIndex, aBrowser) { - let id = { index: aIndex, - browser: aBrowser }; - let arr = this._trackedSnapshots; - arr.unshift(id); - - while (arr.length > this._maxSnapshots) { - let lastElem = arr[arr.length - 1]; - delete lastElem.browser.snapshots[lastElem.index].image; - delete lastElem.browser.snapshots[lastElem.index]; - arr.splice(-1, 1); - } - }, - - /** - * Converts a compressed blob to an Image object. In some situations - * (especially during fast swiping) aBlob may still be a canvas, not a - * compressed blob. In this case, we simply return the canvas. - * - * @param aBlob - * The compressed blob to convert, or a canvas if a blob compression - * couldn't complete before this method was called. - * @return A new Image object representing the converted blob. - */ - _convertToImg: function HSA__convertToImg(aBlob) { - if (!aBlob) - return null; - - // Return aBlob if it's still a canvas and not a compressed blob yet. - if (aBlob instanceof HTMLCanvasElement) - return aBlob; - - let img = new Image(); - let url = ""; - try { - url = URL.createObjectURL(aBlob); - img.onload = function() { - URL.revokeObjectURL(url); - }; - } finally { - img.src = url; - } - return img; - }, - - /** - * Scales the background of a given box element (which uses a given snapshot - * as background) based on a given scale factor. - * @param aSnapshot - * The snapshot that is used as background of aBox. - * @param aScale - * The scale factor to use. - * @param aBox - * The box element that uses aSnapshot as background. - */ - _scaleSnapshot: function HSA__scaleSnapshot(aSnapshot, aScale, aBox) { - if (aSnapshot && aScale != 1 && aBox) { - if (aSnapshot instanceof HTMLCanvasElement) { - aBox.style.backgroundSize = - aSnapshot.width / aScale + "px " + aSnapshot.height / aScale + "px"; - } else { - // snapshot is instanceof HTMLImageElement - aSnapshot.addEventListener("load", function() { - aBox.style.backgroundSize = - aSnapshot.width / aScale + "px " + aSnapshot.height / aScale + "px"; - }); - } - } - }, - - /** - * Sets the snapshot of the current page to the snapshot passed as parameter, - * or to the one previously stored for the current index in history if the - * parameter is null. - * - * @param aCanvas - * The snapshot to set the current page to. If this parameter is null, - * the previously stored snapshot for this index (if any) will be used. - */ - _installCurrentPageSnapshot: - function HSA__installCurrentPageSnapshot(aCanvas) { - let currSnapshot = aCanvas; - let scale = window.devicePixelRatio; - if (!currSnapshot) { - let snapshots = gBrowser.selectedBrowser.snapshots || {}; - let currIndex = this._historyIndex; - if (currIndex in snapshots) { - currSnapshot = this._convertToImg(snapshots[currIndex].image); - scale = snapshots[currIndex].scale; - } - } - this._scaleSnapshot(currSnapshot, scale, this._curBox ? this._curBox : - null); - document.mozSetImageElement("historySwipeAnimationCurrentPageSnapshot", - currSnapshot); - }, - - /** - * Sets the snapshots of the previous and next pages to the snapshots - * previously stored for their respective indeces. - */ - _installPrevAndNextSnapshots: - function HSA__installPrevAndNextSnapshots() { - let snapshots = gBrowser.selectedBrowser.snapshots || []; - let currIndex = this._historyIndex; - let prevIndex = currIndex - 1; - let prevSnapshot = null; - if (prevIndex in snapshots) { - prevSnapshot = this._convertToImg(snapshots[prevIndex].image); - this._scaleSnapshot(prevSnapshot, snapshots[prevIndex].scale, - this._prevBox); - } - document.mozSetImageElement("historySwipeAnimationPreviousPageSnapshot", - prevSnapshot); - - let nextIndex = currIndex + 1; - let nextSnapshot = null; - if (nextIndex in snapshots) { - nextSnapshot = this._convertToImg(snapshots[nextIndex].image); - this._scaleSnapshot(nextSnapshot, snapshots[nextIndex].scale, - this._nextBox); - } - document.mozSetImageElement("historySwipeAnimationNextPageSnapshot", - nextSnapshot); - }, -}; |