gecko/browser/base/content/tabview/tabitems.js

1085 lines
31 KiB
JavaScript
Raw Normal View History

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is tabitems.js.
*
* The Initial Developer of the Original Code is
* Ian Gilman <ian@iangilman.com>.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Aza Raskin <aza@mozilla.com>
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
* Ehsan Akhgari <ehsan@mozilla.com>
* Raymond Lee <raymond@appcoast.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// **********
// Title: tabitems.js
// ##########
// Class: TabItem
// An <Item> that represents a tab. Also implements the <Subscribable> interface.
//
// Parameters:
// tab - a xul:tab
window.TabItem = function(tab) {
Utils.assert(tab, "tab");
this.tab = tab;
// register this as the tab's tabItem
this.tab.tabItem = this;
// ___ set up div
var $div = iQ('<div>')
.addClass('tab')
.html("<div class='thumb'><div class='thumb-shadow'></div>" +
"<img class='cached-thumb' style='display:none'/><canvas/></div>" +
"<div class='favicon'><img/></div>" +
"<span class='tab-title'>&nbsp;</span>"
)
.appendTo('body');
this.canvasSizeForced = false;
this.isShowingCachedData = false;
this.favEl = (iQ('.favicon>img', $div))[0];
this.nameEl = (iQ('.tab-title', $div))[0];
this.canvasEl = (iQ('.thumb canvas', $div))[0];
this.cachedThumbEl = (iQ('img.cached-thumb', $div))[0];
this.tabCanvas = new TabCanvas(this.tab, this.canvasEl);
2010-07-18 08:58:10 -07:00
this.defaultSize = new Point(TabItems.tabWidth, TabItems.tabHeight);
this.locked = {};
this.isATabItem = true;
this._zoomPrep = false;
this.sizeExtra = new Point();
this.keepProportional = true;
var self = this;
2010-07-18 08:58:10 -07:00
this.isDragging = false;
2010-07-18 08:58:10 -07:00
this.sizeExtra.x = parseInt($div.css('padding-left'))
+ parseInt($div.css('padding-right'));
2010-07-18 08:58:10 -07:00
this.sizeExtra.y = parseInt($div.css('padding-top'))
+ parseInt($div.css('padding-bottom'));
this.bounds = $div.bounds();
2010-08-10 11:39:28 -07:00
2010-06-15 14:38:55 -07:00
// ___ superclass setup
this._init($div[0]);
2010-07-18 08:58:10 -07:00
2010-06-15 16:33:58 -07:00
// ___ drag/drop
2010-06-15 14:38:55 -07:00
// override dropOptions with custom tabitem methods
// This is mostly to support the phantom groupItems.
this.dropOptions.drop = function(e) {
2010-07-18 08:58:10 -07:00
var $target = iQ(this.container);
this.isDropTarget = false;
2010-07-18 08:58:10 -07:00
var phantom = $target.data("phantomGroupItem");
2010-07-18 08:58:10 -07:00
var groupItem = drag.info.item.parent;
if (groupItem) {
groupItem.add(drag.info.$el);
} else {
phantom.removeClass("phantom acceptsDrop");
new GroupItem([$target, drag.info.$el], {container:phantom, bounds:phantom.bounds()});
}
2010-06-19 12:05:36 -07:00
};
2010-07-18 08:58:10 -07:00
this.dropOptions.over = function(e) {
var $target = iQ(this.container);
this.isDropTarget = true;
2010-06-15 14:38:55 -07:00
$target.removeClass("acceptsDrop");
2010-07-18 08:58:10 -07:00
var phantomMargin = 40;
var groupItemBounds = this.getBoundsWithTitle();
groupItemBounds.inset(-phantomMargin, -phantomMargin);
2010-06-15 14:38:55 -07:00
2010-06-19 12:05:36 -07:00
iQ(".phantom").remove();
var phantom = iQ("<div>")
.addClass("groupItem phantom acceptsDrop")
2010-06-19 12:05:36 -07:00
.css({
position: "absolute",
2010-06-19 12:05:36 -07:00
zIndex: -99
})
.css(groupItemBounds.css())
2010-06-19 12:05:36 -07:00
.hide()
.appendTo("body");
var defaultRadius = Trenches.defaultRadius;
// Extend the margin so that it covers the case where the target tab item
// is right next to a trench.
Trenches.defaultRadius = phantomMargin + 1;
var updatedBounds = drag.info.snapBounds(groupItemBounds,'none');
Trenches.defaultRadius = defaultRadius;
// Utils.log('updatedBounds:',updatedBounds);
if (updatedBounds)
phantom.css(updatedBounds.css());
2010-07-18 08:58:10 -07:00
phantom.fadeIn();
2010-07-18 08:58:10 -07:00
$target.data("phantomGroupItem", phantom);
2010-06-19 12:05:36 -07:00
};
2010-07-18 08:58:10 -07:00
this.dropOptions.out = function(e) {
this.isDropTarget = false;
var phantom = iQ(this.container).data("phantomGroupItem");
2010-07-18 08:58:10 -07:00
if (phantom) {
phantom.fadeOut(function() {
2010-06-19 12:05:36 -07:00
iQ(this).remove();
});
}
};
2010-07-18 08:58:10 -07:00
this.draggable();
this.droppable(true);
2010-07-18 08:58:10 -07:00
2010-06-15 16:33:58 -07:00
// ___ more div setup
$div.mousedown(function(e) {
if (!Utils.isRightClick(e))
self.lastMouseDownTarget = e.target;
});
2010-07-18 08:58:10 -07:00
$div.mouseup(function(e) {
var same = (e.target == self.lastMouseDownTarget);
self.lastMouseDownTarget = null;
if (!same)
return;
2010-07-18 08:58:10 -07:00
if (iQ(e.target).hasClass("close"))
2010-08-10 11:39:28 -07:00
self.close();
else {
2010-07-18 08:58:10 -07:00
if (!Items.item(this).isDragging)
self.zoomIn();
}
});
2010-07-18 08:58:10 -07:00
iQ("<div>")
.addClass('close')
2010-07-18 08:58:10 -07:00
.appendTo($div);
iQ("<div>")
.addClass('expander')
.appendTo($div);
// ___ additional setup
this.reconnected = false;
this._hasBeenDrawn = false;
this.setResizable(true);
this._updateDebugBounds();
2010-07-18 08:58:10 -07:00
TabItems.register(this);
if (!TabItems.reconnect(this))
GroupItems.newTab(this);
};
window.TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// ----------
// Function: forceCanvasSize
// Repaints the thumbnail with the given resolution, and forces it
// to stay that resolution until unforceCanvasSize is called.
forceCanvasSize: function(w, h) {
this.canvasSizeForced = true;
this.canvasEl.width = w;
this.canvasEl.height = h;
this.tabCanvas.paint();
},
// ----------
// Function: unforceCanvasSize
// Stops holding the thumbnail resolution; allows it to shift to the
// size of thumbnail on screen. Note that this call does not nest, unlike
// <TabItems.resumePainting>; if you call forceCanvasSize multiple
// times, you just need a single unforce to clear them all.
unforceCanvasSize: function() {
this.canvasSizeForced = false;
},
// ----------
// Function: showCachedData
// Shows the cached data i.e. image and title. Note: this method should only
// be called at browser startup with the cached data avaliable.
showCachedData: function(tabData) {
this.isShowingCachedData = true;
var $nameElement = iQ(this.nameEl);
var $canvasElement = iQ(this.canvasEl);
var $cachedThumbElement = iQ(this.cachedThumbEl);
$cachedThumbElement.attr("src", tabData.imageData).show();
$canvasElement.css({opacity: 0.0});
$nameElement.text(tabData.title ? tabData.title : "");
},
// ----------
// Function: hideCachedData
// Hides the cached data i.e. image and title and show the canvas.
hideCachedData: function() {
var $canvasElement = iQ(this.canvasEl);
var $cachedThumbElement = iQ(this.cachedThumbEl);
$cachedThumbElement.hide();
$canvasElement.css({opacity: 1.0});
this.isShowingCachedData = false;
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: getStorageData
2010-07-18 08:58:10 -07:00
// Get data to be used for persistent storage of this object.
//
// Parameters:
// getImageData - true to include thumbnail pixels (and page title as well); default false
getStorageData: function(getImageData) {
return {
2010-07-18 08:58:10 -07:00
bounds: this.getBounds(),
userSize: (Utils.isPoint(this.userSize) ? new Point(this.userSize) : null),
url: this.tab.linkedBrowser.currentURI.spec,
groupID: (this.parent ? this.parent.id : 0),
imageData: (getImageData && this.tabCanvas ?
this.tabCanvas.toImageData() : null),
title: getImageData && this.tab.label || null
};
},
// ----------
// Function: save
2010-07-18 08:58:10 -07:00
// Store persistent for this object.
//
// Parameters:
// saveImageData - true to include thumbnail pixels (and page title as well); default false
save: function(saveImageData) {
try{
if (!this.tab || this.tab.parentNode == null || !this.reconnected) // too soon/late to save
return;
var data = this.getStorageData(saveImageData);
if (TabItems.storageSanity(data))
Storage.saveTab(this.tab, data);
} catch(e) {
Utils.log("Error in saving tab value: "+e);
}
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: setBounds
2010-07-18 08:58:10 -07:00
// Moves this item to the specified location and size.
//
// Parameters:
// rect - a <Rect> giving the new bounds
// immediately - true if it should not animate; default false
// options - an object with additional parameters, see below
2010-07-18 08:58:10 -07:00
//
// Possible options:
// force - true to always update the DOM even if the bounds haven't changed; default false
setBounds: function(rect, immediately, options) {
if (!Utils.isRect(rect)) {
Utils.trace('TabItem.setBounds: rect is not a real rectangle!', rect);
return;
}
2010-07-18 08:58:10 -07:00
if (!options)
options = {};
if (this._zoomPrep)
this.bounds.copy(rect);
else {
var $container = iQ(this.container);
var $title = iQ('.tab-title', $container);
var $thumb = iQ('.thumb', $container);
var $close = iQ('.close', $container);
var $fav = iQ('.favicon', $container);
var css = {};
2010-07-18 08:58:10 -07:00
const fontSizeRange = new Range(8,15);
2010-07-18 08:58:10 -07:00
if (rect.left != this.bounds.left || options.force)
css.left = rect.left;
2010-07-18 08:58:10 -07:00
if (rect.top != this.bounds.top || options.force)
css.top = rect.top;
2010-07-18 08:58:10 -07:00
if (rect.width != this.bounds.width || options.force) {
css.width = rect.width - this.sizeExtra.x;
let widthRange = new Range(0,TabItems.tabWidth);
let proportion = widthRange.proportion(css.width, true); // in [0,1]
2010-07-18 08:58:10 -07:00
css.fontSize = fontSizeRange.scale(proportion); // returns a value in the fontSizeRange
css.fontSize += 'px';
}
2010-07-18 08:58:10 -07:00
if (rect.height != this.bounds.height || options.force)
css.height = rect.height - this.sizeExtra.y;
if (Utils.isEmptyObject(css))
return;
2010-07-18 08:58:10 -07:00
this.bounds.copy(rect);
2010-07-18 08:58:10 -07:00
// If this is a brand new tab don't animate it in from
// a random location (i.e., from [0,0]). Instead, just
// have it appear where it should be.
if (immediately || (!this._hasBeenDrawn)) {
/* $container.stop(true, true); */
$container.css(css);
} else {
TabItems.pausePainting();
$container.animate(css, {
duration: 200,
Bug 583044 - Rename code references of TabCandy to TabView Move files and update references to tabview from tabcandy. Only remaining candy reference is the link to aza's webm video. --HG-- rename : browser/base/content/browser-tabcandy.js => browser/base/content/browser-tabview.js rename : browser/base/content/tabcandy/app/drag.js => browser/base/content/tabview/drag.js rename : browser/base/content/tabcandy/app/groups.js => browser/base/content/tabview/groups.js rename : browser/base/content/tabcandy/app/infoitems.js => browser/base/content/tabview/infoitems.js rename : browser/base/content/tabcandy/core/iq.js => browser/base/content/tabview/iq.js rename : browser/base/content/tabcandy/app/items.js => browser/base/content/tabview/items.js rename : browser/base/content/tabcandy/core/profile.js => browser/base/content/tabview/profile.js rename : browser/base/content/tabcandy/app/storage.js => browser/base/content/tabview/storage.js rename : browser/base/content/tabcandy/app/tabitems.js => browser/base/content/tabview/tabitems.js rename : browser/base/content/tabcandy/tabcandy.css => browser/base/content/tabview/tabview.css rename : browser/base/content/tabcandy/tabcandy.html => browser/base/content/tabview/tabview.html rename : browser/base/content/tabcandy/tabcandy.js => browser/base/content/tabview/tabview.js rename : browser/base/content/tabcandy/app/trench.js => browser/base/content/tabview/trench.js rename : browser/base/content/tabcandy/app/ui.js => browser/base/content/tabview/ui.js rename : browser/themes/gnomestripe/browser/tabcandy/edit-light.png => browser/themes/gnomestripe/browser/tabview/edit-light.png rename : browser/themes/gnomestripe/browser/tabcandy/edit.png => browser/themes/gnomestripe/browser/tabview/edit.png rename : browser/themes/gnomestripe/browser/tabcandy/new-tab.png => browser/themes/gnomestripe/browser/tabview/new-tab.png rename : browser/themes/gnomestripe/browser/tabcandy/platform.css => browser/themes/gnomestripe/browser/tabview/platform.css rename : browser/themes/gnomestripe/browser/tabcandy/stack-expander.png => browser/themes/gnomestripe/browser/tabview/stack-expander.png rename : browser/themes/gnomestripe/browser/tabcandy/tabcandy.png => browser/themes/gnomestripe/browser/tabview/tabview.png rename : browser/themes/pinstripe/browser/tabcandy/edit-light.png => browser/themes/pinstripe/browser/tabview/edit-light.png rename : browser/themes/pinstripe/browser/tabcandy/edit.png => browser/themes/pinstripe/browser/tabview/edit.png rename : browser/themes/pinstripe/browser/tabcandy/new-tab.png => browser/themes/pinstripe/browser/tabview/new-tab.png rename : browser/themes/pinstripe/browser/tabcandy/platform.css => browser/themes/pinstripe/browser/tabview/platform.css rename : browser/themes/pinstripe/browser/tabcandy/stack-expander.png => browser/themes/pinstripe/browser/tabview/stack-expander.png rename : browser/themes/pinstripe/browser/tabcandy/tabcandy.png => browser/themes/pinstripe/browser/tabview/tabview.png rename : browser/themes/winstripe/browser/tabcandy/edit-light.png => browser/themes/winstripe/browser/tabview/edit-light.png rename : browser/themes/winstripe/browser/tabcandy/edit.png => browser/themes/winstripe/browser/tabview/edit.png rename : browser/themes/winstripe/browser/tabcandy/new-tab.png => browser/themes/winstripe/browser/tabview/new-tab.png rename : browser/themes/winstripe/browser/tabcandy/platform.css => browser/themes/winstripe/browser/tabview/platform.css rename : browser/themes/winstripe/browser/tabcandy/stack-expander.png => browser/themes/winstripe/browser/tabview/stack-expander.png rename : browser/themes/winstripe/browser/tabcandy/tabcandy.png => browser/themes/winstripe/browser/tabview/tabview.png
2010-07-29 12:37:25 -07:00
easing: "tabviewBounce",
complete: function() {
TabItems.resumePainting();
}
});
/* }).dequeue(); */
}
2010-07-18 08:58:10 -07:00
if (css.fontSize && !this.inStack()) {
if (css.fontSize < fontSizeRange.min)
$title.fadeOut();//.dequeue();
else
$title.fadeIn();//.dequeue();
}
2010-07-18 08:58:10 -07:00
if (css.width) {
TabItems.update(this.tab);
let widthRange, proportion;
if (this.inStack()) {
$fav.css({top:0, left:0});
widthRange = new Range(70, 90);
proportion = widthRange.proportion(css.width); // between 0 and 1
} else {
$fav.css({top:4,left:4});
widthRange = new Range(60, 70);
proportion = widthRange.proportion(css.width); // between 0 and 1
$close.show().css({opacity:proportion});
if (proportion <= .1)
$close.hide()
}
2010-07-18 08:58:10 -07:00
var pad = 1 + 5 * proportion;
var alphaRange = new Range(0.1,0.2);
$fav.css({
"padding-left": pad + "px",
"padding-right": pad + 2 + "px",
"padding-top": pad + "px",
"padding-bottom": pad + "px",
"border-color": "rgba(0,0,0,"+ alphaRange.scale(proportion) +")",
});
2010-07-18 08:58:10 -07:00
}
this._hasBeenDrawn = true;
}
this._updateDebugBounds();
rect = this.getBounds(); // ensure that it's a <Rect>
2010-07-18 08:58:10 -07:00
if (!Utils.isRect(this.bounds))
Utils.trace('TabItem.setBounds: this.bounds is not a real rectangle!', this.bounds);
2010-07-18 08:58:10 -07:00
if (!this.parent && this.tab.parentNode != null)
2010-06-19 12:05:36 -07:00
this.setTrenches(rect);
this.save();
},
// ----------
// Function: getBoundsWithTitle
// Returns a <Rect> for the groupItem's bounds, including the title
getBoundsWithTitle: function() {
var b = this.getBounds();
var $title = iQ(this.container).find('.tab-title');
var height = b.height;
if ( Utils.isNumber($title.height()) )
height += $title.height();
return new Rect(b.left, b.top, b.width, height);
},
// ----------
// Function: inStack
// Returns true if this item is in a stacked groupItem.
inStack: function() {
return iQ(this.container).hasClass("stacked");
},
// ----------
// Function: setZ
2010-07-18 08:58:10 -07:00
// Sets the z-index for this item.
setZ: function(value) {
this.zIndex = value;
iQ(this.container).css({zIndex: value});
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: close
// Closes this item (actually closes the tab associated with it, which automatically
// closes the item.
close: function() {
gBrowser.removeTab(this.tab);
2010-08-10 11:39:28 -07:00
this._sendToSubscribers("tabRemoved");
// No need to explicitly delete the tab data, becasue sessionstore data
// associated with the tab will automatically go away
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: addClass
2010-07-18 08:58:10 -07:00
// Adds the specified CSS class to this item's container DOM element.
addClass: function(className) {
iQ(this.container).addClass(className);
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: removeClass
2010-07-18 08:58:10 -07:00
// Removes the specified CSS class from this item's container DOM element.
removeClass: function(className) {
iQ(this.container).removeClass(className);
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: setResizable
// If value is true, makes this item resizable, otherwise non-resizable.
// Shows/hides a visible resize handle as appropriate.
setResizable: function(value) {
var $resizer = iQ('.expander', this.container);
this.resizeOptions.minWidth = TabItems.minTabWidth;
this.resizeOptions.minHeight = TabItems.minTabWidth * (TabItems.tabHeight / TabItems.tabWidth);
if (value) {
$resizer.fadeIn();
this.resizable(true);
} else {
$resizer.fadeOut();
this.resizable(false);
}
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: makeActive
// Updates this item to visually indicate that it's active.
makeActive: function() {
iQ(this.container).find("canvas").addClass("focus");
iQ(this.container).find("img.cached-thumb").addClass("focus");
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: makeDeactive
// Updates this item to visually indicate that it's not active.
makeDeactive: function() {
iQ(this.container).find("canvas").removeClass("focus");
iQ(this.container).find("img.cached-thumb").removeClass("focus");
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: zoomIn
// Allows you to select the tab and zoom in on it, thereby bringing you
// to the tab in Firefox to interact with.
// Parameters:
// isNewBlankTab - boolean indicates whether it is a newly opened blank tab.
zoomIn: function(isNewBlankTab) {
var self = this;
var $tabEl = iQ(this.container);
var childHitResult = { shouldZoom: true };
if (this.parent)
childHitResult = this.parent.childHit(this);
2010-07-18 08:58:10 -07:00
if (childHitResult.shouldZoom) {
2010-07-18 08:58:10 -07:00
// Zoom in!
var orig = $tabEl.bounds();
var scale = window.innerWidth/orig.width;
var tab = this.tab;
function onZoomDone() {
TabItems.resumePainting();
// If it's not focused, the onFocus lsitener would handle it.
if (gBrowser.selectedTab == tab)
2010-07-23 00:03:40 -07:00
UI.tabOnFocus(tab);
else
gBrowser.selectedTab = tab;
$tabEl
.css(orig.css())
.removeClass("front");
// If the tab is in a groupItem set then set the active
// groupItem to the tab's parent.
if (self.parent) {
var gID = self.parent.id;
var groupItem = GroupItems.groupItem(gID);
GroupItems.setActiveGroupItem(groupItem);
groupItem.setActiveTab(self);
} else {
GroupItems.setActiveGroupItem(null);
GroupItems.setActiveOrphanTab(self);
}
GroupItems.updateTabBar();
2010-07-18 08:58:10 -07:00
if (isNewBlankTab)
gWindow.gURLBar.focus();
if (childHitResult.callback)
2010-07-18 08:58:10 -07:00
childHitResult.callback();
}
2010-07-18 08:58:10 -07:00
// The scaleCheat is a clever way to speed up the zoom-in code.
// Because image scaling is slowest on big images, we cheat and stop the image
// at scaled-down size and placed accordingly. Because the animation is fast, you can't
// see the difference but it feels a lot zippier. The only trick is choosing the
2010-07-18 08:58:10 -07:00
// right animation function so that you don't see a change in percieved
// animation speed.
var scaleCheat = 1.7;
TabItems.pausePainting();
$tabEl
.addClass("front")
.animate({
top: orig.top * (1 - 1/scaleCheat),
left: orig.left * (1 - 1/scaleCheat),
width: orig.width * scale/scaleCheat,
height: orig.height * scale/scaleCheat
}, {
duration: 230,
easing: 'fast',
complete: onZoomDone
});
2010-07-18 08:58:10 -07:00
}
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: zoomOut
Bug 583044 - Rename code references of TabCandy to TabView Move files and update references to tabview from tabcandy. Only remaining candy reference is the link to aza's webm video. --HG-- rename : browser/base/content/browser-tabcandy.js => browser/base/content/browser-tabview.js rename : browser/base/content/tabcandy/app/drag.js => browser/base/content/tabview/drag.js rename : browser/base/content/tabcandy/app/groups.js => browser/base/content/tabview/groups.js rename : browser/base/content/tabcandy/app/infoitems.js => browser/base/content/tabview/infoitems.js rename : browser/base/content/tabcandy/core/iq.js => browser/base/content/tabview/iq.js rename : browser/base/content/tabcandy/app/items.js => browser/base/content/tabview/items.js rename : browser/base/content/tabcandy/core/profile.js => browser/base/content/tabview/profile.js rename : browser/base/content/tabcandy/app/storage.js => browser/base/content/tabview/storage.js rename : browser/base/content/tabcandy/app/tabitems.js => browser/base/content/tabview/tabitems.js rename : browser/base/content/tabcandy/tabcandy.css => browser/base/content/tabview/tabview.css rename : browser/base/content/tabcandy/tabcandy.html => browser/base/content/tabview/tabview.html rename : browser/base/content/tabcandy/tabcandy.js => browser/base/content/tabview/tabview.js rename : browser/base/content/tabcandy/app/trench.js => browser/base/content/tabview/trench.js rename : browser/base/content/tabcandy/app/ui.js => browser/base/content/tabview/ui.js rename : browser/themes/gnomestripe/browser/tabcandy/edit-light.png => browser/themes/gnomestripe/browser/tabview/edit-light.png rename : browser/themes/gnomestripe/browser/tabcandy/edit.png => browser/themes/gnomestripe/browser/tabview/edit.png rename : browser/themes/gnomestripe/browser/tabcandy/new-tab.png => browser/themes/gnomestripe/browser/tabview/new-tab.png rename : browser/themes/gnomestripe/browser/tabcandy/platform.css => browser/themes/gnomestripe/browser/tabview/platform.css rename : browser/themes/gnomestripe/browser/tabcandy/stack-expander.png => browser/themes/gnomestripe/browser/tabview/stack-expander.png rename : browser/themes/gnomestripe/browser/tabcandy/tabcandy.png => browser/themes/gnomestripe/browser/tabview/tabview.png rename : browser/themes/pinstripe/browser/tabcandy/edit-light.png => browser/themes/pinstripe/browser/tabview/edit-light.png rename : browser/themes/pinstripe/browser/tabcandy/edit.png => browser/themes/pinstripe/browser/tabview/edit.png rename : browser/themes/pinstripe/browser/tabcandy/new-tab.png => browser/themes/pinstripe/browser/tabview/new-tab.png rename : browser/themes/pinstripe/browser/tabcandy/platform.css => browser/themes/pinstripe/browser/tabview/platform.css rename : browser/themes/pinstripe/browser/tabcandy/stack-expander.png => browser/themes/pinstripe/browser/tabview/stack-expander.png rename : browser/themes/pinstripe/browser/tabcandy/tabcandy.png => browser/themes/pinstripe/browser/tabview/tabview.png rename : browser/themes/winstripe/browser/tabcandy/edit-light.png => browser/themes/winstripe/browser/tabview/edit-light.png rename : browser/themes/winstripe/browser/tabcandy/edit.png => browser/themes/winstripe/browser/tabview/edit.png rename : browser/themes/winstripe/browser/tabcandy/new-tab.png => browser/themes/winstripe/browser/tabview/new-tab.png rename : browser/themes/winstripe/browser/tabcandy/platform.css => browser/themes/winstripe/browser/tabview/platform.css rename : browser/themes/winstripe/browser/tabcandy/stack-expander.png => browser/themes/winstripe/browser/tabview/stack-expander.png rename : browser/themes/winstripe/browser/tabcandy/tabcandy.png => browser/themes/winstripe/browser/tabview/tabview.png
2010-07-29 12:37:25 -07:00
// Handles the zoom down animation after returning to TabView.
// It is expected that this routine will be called from the chrome thread
2010-07-18 08:58:10 -07:00
//
// Parameters:
// complete - a function to call after the zoom down animation
zoomOut: function(complete) {
var $tab = iQ(this.container);
2010-07-18 08:58:10 -07:00
var box = this.getBounds();
box.width -= this.sizeExtra.x;
box.height -= this.sizeExtra.y;
2010-07-18 08:58:10 -07:00
TabItems.pausePainting();
var self = this;
$tab.animate({
left: box.left,
2010-07-18 08:58:10 -07:00
top: box.top,
width: box.width,
height: box.height
}, {
duration: 300,
easing: 'cubic-bezier', // note that this is legal easing, even without parameters
complete: function() { // note that this will happen on the DOM thread
$tab.removeClass('front');
2010-07-18 08:58:10 -07:00
GroupItems.setActiveOrphanTab(null);
TabItems.resumePainting();
2010-07-18 08:58:10 -07:00
self._zoomPrep = false;
2010-07-18 08:58:10 -07:00
self.setBounds(self.getBounds(), true, {force: true});
if (typeof complete == "function")
complete();
}
});
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: setZoomPrep
2010-07-18 08:58:10 -07:00
// Either go into or return from (depending on <value>) "zoom prep" mode,
// where the tab fills a large portion of the screen in anticipation of
// the zoom out animation.
setZoomPrep: function(value) {
var $div = iQ(this.container);
var data;
2010-07-18 08:58:10 -07:00
var box = this.getBounds();
2010-07-18 08:58:10 -07:00
if (value) {
this._zoomPrep = true;
// The divide by two part here is a clever way to speed up the zoom-out code.
// Because image scaling is slowest on big images, we cheat and start the image
// at half-size and placed accordingly. Because the animation is fast, you can't
// see the difference but it feels a lot zippier. The only trick is choosing the
2010-07-18 08:58:10 -07:00
// right animation function so that you don't see a change in percieved
// animation speed from frame #1 (the tab) to frame #2 (the half-size image) to
// frame #3 (the first frame of real animation). Choosing an animation that starts
// fast is key.
var scaleCheat = 2;
$div
.addClass('front')
.css({
left: box.left * (1-1/scaleCheat),
2010-07-18 08:58:10 -07:00
top: box.top * (1-1/scaleCheat),
width: window.innerWidth/scaleCheat,
height: box.height * (window.innerWidth / box.width)/scaleCheat
});
} else {
this._zoomPrep = false;
$div.removeClass('front');
2010-07-18 08:58:10 -07:00
this.setBounds(box, true, {force: true});
2010-07-18 08:58:10 -07:00
}
}
});
// ##########
// Class: TabItems
// Singleton for managing <TabItem>s
window.TabItems = {
2010-07-18 08:58:10 -07:00
minTabWidth: 40,
tabWidth: 160,
2010-07-18 08:58:10 -07:00
tabHeight: 120,
fontSize: 9,
items: [],
paintingPaused: 0,
_tabsWaitingForUpdate: [],
_heartbeatOn: false,
_heartbeatTiming: 100, // milliseconds between beats
_lastUpdateTime: Date.now(),
// ----------
// Function: init
// Set up the necessary tracking to maintain the <TabItems>s.
init: function() {
Utils.assert(window.AllTabs, "AllTabs must be initialized first");
var self = this;
// When a tab is opened, create the TabItem
AllTabs.register("open", function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
2010-08-10 06:30:23 -07:00
setTimeout(function() { // Marshal event from chrome thread to DOM thread
self.link(tab);
}, 1);
});
// When a tab's content is loaded, show the canvas and hide the cached data
// if necessary.
AllTabs.register("attrModified", function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
2010-08-10 06:30:23 -07:00
setTimeout(function() { // Marshal event from chrome thread to DOM thread
self.update(tab);
}, 1);
});
// When a tab is closed, unlink.
AllTabs.register("close", function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
2010-08-10 06:30:23 -07:00
setTimeout(function() { // Marshal event from chrome thread to DOM thread
self.unlink(tab);
}, 1);
});
// For each tab, create the link.
AllTabs.tabs.forEach(function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
self.link(tab);
self.update(tab);
});
},
// ----------
// Function: update
// Takes in a xul:tab.
update: function(tab) {
try {
Utils.assertThrow(tab, "tab");
let shouldDefer = (
this.isPaintingPaused() ||
this._tabsWaitingForUpdate.length ||
Date.now() - this._lastUpdateTime < this._heartbeatTiming
);
let isCurrentTab = (
!UI._isTabViewVisible() &&
tab == gBrowser.selectedTab
);
if (shouldDefer && !isCurrentTab) {
if (this._tabsWaitingForUpdate.indexOf(tab) == -1)
this._tabsWaitingForUpdate.push(tab);
} else
this._update(tab);
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: _update
// Takes in a xul:tab.
_update: function(tab) {
try {
Utils.assertThrow(tab, "tab");
// ___ remove from waiting list if needed
let index = this._tabsWaitingForUpdate.indexOf(tab);
if (index != -1)
this._tabsWaitingForUpdate.splice(index, 1);
// ___ get the TabItem
Utils.assertThrow(tab.tabItem, "must already be linked");
let tabItem = tab.tabItem;
// ___ icon
let iconUrl = tab.image;
if (iconUrl == null)
iconUrl = "chrome://mozapps/skin/places/defaultFavicon.png";
if (iconUrl != tabItem.favEl.src)
tabItem.favEl.src = iconUrl;
// ___ URL
let tabUrl = tab.linkedBrowser.currentURI.spec;
if (tabUrl != tabItem.url) {
let oldURL = tabItem.url;
tabItem.url = tabUrl;
if (!tabItem.reconnected && (oldURL == 'about:blank' || !oldURL))
this.reconnect(tabItem);
tabItem.save();
}
// ___ label
let label = tab.label;
let $name = iQ(tabItem.nameEl);
if (!tabItem.isShowingCachedData && $name.text() != label)
$name.text(label);
// ___ thumbnail
let $canvas = iQ(tabItem.canvasEl);
if (!tabItem.canvasSizeForced) {
let w = $canvas.width();
let h = $canvas.height();
if (w != tabItem.canvasEl.width || h != tabItem.canvasEl.height) {
tabItem.canvasEl.width = w;
tabItem.canvasEl.height = h;
}
}
tabItem.tabCanvas.paint();
// ___ cache
// TODO: this logic needs to be better; hiding too soon now
if (tabItem.isShowingCachedData && !tab.hasAttribute("busy"))
tabItem.hideCachedData();
} catch(e) {
Utils.log(e);
}
this._lastUpdateTime = Date.now();
},
// ----------
// Function: link
// Takes in a xul:tab.
link: function(tab){
try {
Utils.assertThrow(tab, "tab");
Utils.assertThrow(!tab.tabItem, "shouldn't already be linked");
new TabItem(tab); // sets tab.tabItem to itself
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: unlink
// Takes in a xul:tab.
unlink: function(tab) {
try {
Utils.assertThrow(tab, "tab");
Utils.assertThrow(tab.tabItem, "should already be linked");
this.unregister(tab.tabItem);
2010-08-10 11:39:28 -07:00
tab.tabItem._sendToSubscribers("close");
iQ(tab.tabItem.container).remove();
tab.tabItem.removeTrenches();
Items.unsquish(null, tab.tabItem);
tab.tabItem = null;
let index = this._tabsWaitingForUpdate.indexOf(tab);
if (index != -1)
this._tabsWaitingForUpdate.splice(index, 1);
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: heartbeat
// Allows us to spreadout update calls over a period of time.
heartbeat: function() {
if (!this._heartbeatOn)
return;
if (this._tabsWaitingForUpdate.length) {
this._update(this._tabsWaitingForUpdate[0]);
// _update will remove the tab from the waiting list
}
let self = this;
if (this._tabsWaitingForUpdate.length) {
2010-08-10 06:30:23 -07:00
setTimeout(function() {
self.heartbeat();
}, this._heartbeatTiming);
} else
this._hearbeatOn = false;
},
// ----------
// Function: pausePainting
// Tells TabItems to stop updating thumbnails (so you can do
// animations without thumbnail paints causing stutters).
// pausePainting can be called multiple times, but every call to
// pausePainting needs to be mirrored with a call to <resumePainting>.
pausePainting: function() {
this.paintingPaused++;
if (this.isPaintingPaused() && this._heartbeatOn)
this._heartbeatOn = false;
},
// ----------
// Function: resumePainting
// Undoes a call to <pausePainting>. For instance, if you called
// pausePainting three times in a row, you'll need to call resumePainting
// three times before TabItems will start updating thumbnails again.
resumePainting: function() {
this.paintingPaused--;
if (!this.isPaintingPaused() &&
this._tabsWaitingForUpdate.length &&
!this._heartbeatOn) {
this._heartbeatOn = true;
this.heartbeat();
}
},
// ----------
// Function: isPaintingPaused
// Returns a boolean indicating whether painting
// is paused or not.
isPaintingPaused: function() {
return this.paintingPaused > 0;
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: register
2010-07-18 08:58:10 -07:00
// Adds the given <TabItem> to the master list.
register: function(item) {
Utils.assert(item && item.isAnItem, 'item must be a TabItem');
Utils.assert(this.items.indexOf(item) == -1, 'only register once per item');
this.items.push(item);
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: unregister
2010-07-18 08:58:10 -07:00
// Removes the given <TabItem> from the master list.
unregister: function(item) {
2010-07-13 17:10:53 -07:00
var index = this.items.indexOf(item);
if (index != -1)
2010-07-18 08:58:10 -07:00
this.items.splice(index, 1);
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: getItems
// Returns a copy of the master array of <TabItem>s.
getItems: function() {
return Utils.copy(this.items);
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: saveAll
2010-07-18 08:58:10 -07:00
// Saves all open <TabItem>s.
//
// Parameters:
// saveImageData - true to include thumbnail pixels (and page title as well); default false
saveAll: function(saveImageData) {
var items = this.getItems();
items.forEach(function(item) {
item.save(saveImageData);
});
},
2010-07-18 08:58:10 -07:00
// ----------
// Function: storageSanity
// Checks the specified data (as returned by TabItem.getStorageData or loaded from storage)
// and returns true if it looks valid.
2010-07-18 08:58:10 -07:00
// TODO: check everything
storageSanity: function(data) {
var sane = true;
if (!Utils.isRect(data.bounds)) {
Utils.log('TabItems.storageSanity: bad bounds', data.bounds);
sane = false;
}
2010-07-18 08:58:10 -07:00
return sane;
},
// ----------
// Function: reconnect
// Given a <TabItem>, attempts to load its persistent data from storage.
reconnect: function(item) {
var found = false;
try{
Utils.assert(item, 'item');
Utils.assert(item.tab, 'item.tab');
2010-07-18 08:58:10 -07:00
if (item.reconnected)
return true;
2010-07-18 08:58:10 -07:00
if (!item.tab)
return false;
2010-07-18 08:58:10 -07:00
let tabData = Storage.getTabData(item.tab);
if (tabData && this.storageSanity(tabData)) {
if (item.parent)
item.parent.remove(item);
2010-07-18 08:58:10 -07:00
item.setBounds(tabData.bounds, true);
2010-07-18 08:58:10 -07:00
if (Utils.isPoint(tabData.userSize))
item.userSize = new Point(tabData.userSize);
2010-07-18 08:58:10 -07:00
if (tabData.groupID) {
var groupItem = GroupItems.groupItem(tabData.groupID);
if (groupItem) {
groupItem.add(item);
2010-07-18 08:58:10 -07:00
if (item.tab == gBrowser.selectedTab)
GroupItems.setActiveGroupItem(item.parent);
}
}
2010-07-18 08:58:10 -07:00
if (tabData.imageData) {
item.showCachedData(tabData);
// the code in the progress listener doesn't fire sometimes because
// tab is being restored so need to catch that.
2010-08-10 06:30:23 -07:00
setTimeout(function() {
if (item && item.isShowingCachedData) {
item.hideCachedData();
}
2010-07-17 09:12:31 -07:00
}, 15000);
}
2010-07-18 08:58:10 -07:00
item.reconnected = true;
found = true;
} else
item.reconnected = item.tab.linkedBrowser.currentURI.spec != 'about:blank';
2010-07-18 08:58:10 -07:00
item.save();
} catch(e) {
Utils.log(e);
}
2010-07-18 08:58:10 -07:00
return found;
}
};
// ##########
// Class: TabCanvas
// Takes care of the actual canvas for the tab thumbnail
// Does not need to be accessed from outside of tabitems.js
var TabCanvas = function(tab, canvas) {
this.init(tab, canvas);
};
TabCanvas.prototype = {
// ----------
// Function: init
init: function(tab, canvas) {
this.tab = tab;
this.canvas = canvas;
var $canvas = iQ(canvas);
var w = $canvas.width();
var h = $canvas.height();
canvas.width = w;
canvas.height = h;
},
// ----------
// Function: paint
paint: function(evt) {
var ctx = this.canvas.getContext("2d");
var w = this.canvas.width;
var h = this.canvas.height;
if (!w || !h)
return;
let fromWin = this.tab.linkedBrowser.contentWindow;
if (fromWin == null) {
Utils.log('null fromWin in paint');
return;
}
var scaler = w/fromWin.innerWidth;
// TODO: Potentially only redraw the dirty rect? (Is it worth it?)
ctx.save();
ctx.scale(scaler, scaler);
try{
ctx.drawWindow(fromWin, fromWin.scrollX, fromWin.scrollY, w/scaler, h/scaler, "#fff");
} catch(e) {
Utils.error('paint', e);
}
ctx.restore();
},
// ----------
// Function: toImageData
toImageData: function() {
return this.canvas.toDataURL("image/png", "");
}
};