forked from mirrors/gecko-dev
526 lines
17 KiB
JavaScript
526 lines
17 KiB
JavaScript
# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/.
|
|
|
|
// History Swipe Animation Support (bug 678392)
|
|
let 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;
|
|
|
|
gBrowser.addEventListener("pagehide", this, false);
|
|
gBrowser.addEventListener("pageshow", this, false);
|
|
gBrowser.addEventListener("popstate", this, false);
|
|
gBrowser.tabContainer.addEventListener("TabClose", this, false);
|
|
|
|
this.active = true;
|
|
this.isLTR = document.documentElement.mozMatchesSelector(
|
|
":-moz-locale-dir(ltr)");
|
|
this._trackedSnapshots = [];
|
|
this._historyIndex = -1;
|
|
this._boxWidth = -1;
|
|
this._maxSnapshots = this._getMaxSnapshots();
|
|
this._lastSwipeDir = "";
|
|
},
|
|
|
|
/**
|
|
* Uninitializes the support for history swipe animations.
|
|
*/
|
|
uninit: function HSA_uninit() {
|
|
gBrowser.removeEventListener("pagehide", this, false);
|
|
gBrowser.removeEventListener("pageshow", this, false);
|
|
gBrowser.removeEventListener("popstate", this, false);
|
|
gBrowser.tabContainer.removeEventListener("TabClose", this, false);
|
|
|
|
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).
|
|
*/
|
|
startAnimation: function HSA_startAnimation() {
|
|
if (this.isAnimationRunning()) {
|
|
gBrowser.stop();
|
|
this._lastSwipeDir = "RELOAD"; // just ensure that != ""
|
|
this._canGoBack = this.canGoBack();
|
|
this._canGoForward = this.canGoForward();
|
|
this._handleFastSwiping();
|
|
}
|
|
else {
|
|
this._historyIndex = gBrowser.webNavigation.sessionHistory.index;
|
|
this._canGoBack = this.canGoBack();
|
|
this._canGoForward = this.canGoForward();
|
|
this._takeSnapshot();
|
|
this._installPrevAndNextSnapshots();
|
|
this._addBoxes();
|
|
this._lastSwipeDir = "";
|
|
}
|
|
this.updateAnimation(0);
|
|
},
|
|
|
|
/**
|
|
* Stops the swipe animation.
|
|
*/
|
|
stopAnimation: function HSA_stopAnimation() {
|
|
gHistorySwipeAnimation._removeBoxes();
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
|
|
if ((aVal >= 0 && this.isLTR) ||
|
|
(aVal <= 0 && !this.isLTR)) {
|
|
if (aVal > 1)
|
|
aVal = 1; // Cap value to avoid sliding the page further than allowed.
|
|
|
|
if (this._canGoBack)
|
|
this._prevBox.collapsed = false;
|
|
else
|
|
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);
|
|
|
|
// The forward page should be pushed offscreen all the way to the right.
|
|
this._positionBox(this._nextBox, 1);
|
|
}
|
|
else {
|
|
if (aVal < -1)
|
|
aVal = -1; // Cap value to avoid sliding the page further than allowed.
|
|
|
|
// 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).
|
|
if (this._canGoForward) {
|
|
let offset = this.isLTR ? 1 : -1;
|
|
this._positionBox(this._curBox, 0);
|
|
this._positionBox(this._nextBox, offset + aVal); // aVal is negative
|
|
}
|
|
else {
|
|
this._prevBox.collapsed = true;
|
|
this._positionBox(this._curBox, aVal);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Event handler for events relevant to the history swipe animation.
|
|
*
|
|
* @param aEvent
|
|
* An event to process.
|
|
*/
|
|
handleEvent: function HSA_handleEvent(aEvent) {
|
|
switch (aEvent.type) {
|
|
case "TabClose":
|
|
let browser = gBrowser.getBrowserForTab(aEvent.target);
|
|
this._removeTrackedSnapshot(-1, browser);
|
|
break;
|
|
case "pageshow":
|
|
case "popstate":
|
|
if (this.isAnimationRunning()) {
|
|
if (aEvent.target != gBrowser.selectedBrowser.contentDocument)
|
|
break;
|
|
this.stopAnimation();
|
|
}
|
|
this._historyIndex = gBrowser.webNavigation.sessionHistory.index;
|
|
break;
|
|
case "pagehide":
|
|
if (aEvent.target == gBrowser.selectedBrowser.contentDocument) {
|
|
// Take a snapshot of a page whenever it's about to be navigated away
|
|
// from.
|
|
this._takeSnapshot();
|
|
}
|
|
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() {
|
|
if (this._lastSwipeDir != "")
|
|
this._navigateToHistoryIndex();
|
|
else
|
|
this.stopAnimation();
|
|
},
|
|
|
|
/**
|
|
* 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 {
|
|
gBrowser.webNavigation.sessionHistory.getEntryAtIndex(aIndex, false);
|
|
}
|
|
catch(ex) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Checks to see if history swipe animations are supported by this
|
|
* platform/configuration.
|
|
*
|
|
* return true if supported, false otherwise.
|
|
*/
|
|
_isSupported: function HSA__isSupported() {
|
|
// Only activate on Lion.
|
|
// TODO: Only if [NSEvent isSwipeTrackingFromScrollEventsEnabled]
|
|
return window.matchMedia("(-moz-mac-lion-theme)").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);
|
|
|
|
this._boxWidth = this._curBox.getBoundingClientRect().width; // cache width
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
},
|
|
|
|
/**
|
|
* 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) {
|
|
aBox.style.transform = "translateX(" + this._boxWidth * aPosition + "px)";
|
|
},
|
|
|
|
/**
|
|
* Takes a snapshot of the page the browser is currently on.
|
|
*/
|
|
_takeSnapshot: function HSA__takeSnapshot() {
|
|
if ((this._maxSnapshots < 1) ||
|
|
(gBrowser.webNavigation.sessionHistory.index < 0))
|
|
return;
|
|
|
|
let browser = gBrowser.selectedBrowser;
|
|
let r = browser.getBoundingClientRect();
|
|
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
|
|
"canvas");
|
|
canvas.mozOpaque = true;
|
|
canvas.width = r.width;
|
|
canvas.height = r.height;
|
|
let ctx = canvas.getContext("2d");
|
|
let zoom = browser.markupDocumentViewer.fullZoom;
|
|
ctx.scale(zoom, zoom);
|
|
ctx.drawWindow(browser.contentWindow, 0, 0, r.width, r.height, "white",
|
|
ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_VIEW |
|
|
ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
|
|
ctx.DRAWWINDOW_USE_WIDGET_LAYERS);
|
|
|
|
this._installCurrentPageSnapshot(canvas);
|
|
this._assignSnapshotToCurrentBrowser(canvas);
|
|
},
|
|
|
|
/**
|
|
* 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 = browser.webNavigation.sessionHistory.index;
|
|
|
|
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] = aCanvas;
|
|
|
|
// Kick off snapshot compression.
|
|
aCanvas.toBlob(function(aBlob) {
|
|
snapshots[currIndex] = aBlob;
|
|
}, "image/png"
|
|
);
|
|
},
|
|
|
|
/**
|
|
* 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];
|
|
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 = URL.createObjectURL(aBlob);
|
|
img.onload = function() {
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
img.src = url;
|
|
return img;
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
if (!currSnapshot) {
|
|
let snapshots = gBrowser.selectedBrowser.snapshots || {};
|
|
let currIndex = this._historyIndex;
|
|
if (currIndex in snapshots)
|
|
currSnapshot = this._convertToImg(snapshots[currIndex]);
|
|
}
|
|
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]);
|
|
document.mozSetImageElement("historySwipeAnimationPreviousPageSnapshot",
|
|
prevSnapshot);
|
|
|
|
let nextIndex = currIndex + 1;
|
|
let nextSnapshot = null;
|
|
if (nextIndex in snapshots)
|
|
nextSnapshot = this._convertToImg(snapshots[nextIndex]);
|
|
document.mozSetImageElement("historySwipeAnimationNextPageSnapshot",
|
|
nextSnapshot);
|
|
},
|
|
};
|