Bug 455694 - Animate tab reordering & detaching. r=dolske ui-r=limi

This commit is contained in:
Frank Yan 2011-07-26 22:26:14 -07:00
Родитель fcc5d334f0
Коммит e05442f9a2
8 изменённых файлов: 705 добавлений и 322 удалений

Просмотреть файл

@ -13,6 +13,7 @@ tabbrowser {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
}
#tabbrowser-tabs[drag=detach][closebuttons=hidden] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
#tabbrowser-tabs:not([overflow="true"]) + #new-tab-button,
#tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
#TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
@ -62,6 +63,39 @@ tabbrowser {
display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
}
.tabbrowser-tabs[drag] > .tabbrowser-tab {
pointer-events: none; /* suppress tooltips */
}
.tabbrowser-tabs[drag] > .tabbrowser-tab[selected] {
z-index: 2; /* ensure selected tab stays on top despite -moz-transform */
}
.tabbrowser-tabs[drag] > .tabbrowser-tab[dragged] {
-moz-transition: 0s; /* suppress opening animation when reattaching tab */
}
/* visibility: collapse might collapse the tab bar, so we use this instead */
.tabbrowser-tabs[drag=detach] > .tabbrowser-tab[dragged]:not(:only-child) {
min-width: 0 !important;
max-width: 0 !important;
border: 0 !important;
opacity: 0;
overflow: hidden;
-moz-transition: max-width 150ms ease-out;
}
.tabbrowser-tabs[drag=detach] > .tabbrowser-tab[dragged]:only-child {
visibility: hidden;
}
.tabbrowser-tabs[drag=move] > .tabbrowser-tab[fadein]:not([dragged]) {
-moz-transition: -moz-transform 200ms ease-out;
}
.tabbrowser-tabs[drag=finish] > .tabbrowser-tab[dragged][fadein] {
-moz-transition: -moz-transform 100ms ease-out;
}
#alltabs-popup {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
}

Просмотреть файл

@ -38,6 +38,16 @@ tabpanels {
background-color: transparent;
}
.tab-drag-preview {
background: -moz-element(#content) left top;
background-clip: content-box;
background-size: cover;
}
.tab-drag-panel[target] > .tab-drag-preview {
display: none;
}
.tab-drop-indicator {
position: relative;
z-index: 2;

Просмотреть файл

@ -1497,6 +1497,9 @@
if (!this._beginRemoveTab(aTab, false, null, true))
return;
if (this.tabContainer.draggedTab == aTab)
this.tabContainer._endTabDrag();
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
this.tabContainer._lockTabSizing(aTab);
else
@ -1698,6 +1701,8 @@
setTimeout(function(tabs) {
tabs._lastTabClosedByMouse = false;
}, 0, this.tabContainer);
this.tabContainer._handleTabDrag(); // Update drag feedback.
}
// update first-tab/last-tab/beforeselected/afterselected attributes
@ -2676,7 +2681,7 @@
<handlers>
<handler event="underflow" phase="capturing"><![CDATA[
if (event.detail == 0)
if (event.originalTarget != this._scrollbox || event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
@ -2691,7 +2696,7 @@
tabs._positionPinnedTabs();
]]></handler>
<handler event="overflow"><![CDATA[
if (event.detail == 0)
if (event.originalTarget != this._scrollbox || event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
@ -2707,8 +2712,16 @@
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
</resources>
<!-- The onpopupshowing/hiding handlers on the panel are to circumvent
noautohide=true disabling level=top on Linux. See bug 448929. -->
<content>
<xul:hbox align="end">
<xul:panel class="tab-drag-panel" anonid="tab-drag-panel" hidden="true" level="top"
onpopupshowing="this.setAttribute('noautohide', true);"
onpopuphiding="this.removeAttribute('noautohide');">
<xul:label class="tab-drag-label" crop="end"/>
<xul:box class="tab-drag-preview"/>
</xul:panel>
<xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
</xul:hbox>
<xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
@ -2804,6 +2817,466 @@
document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
</field>
<method name="_positionDropIndicator">
<parameter name="event"/>
<parameter name="scrollOnly"/>
<body><![CDATA[
var effects = event.dataTransfer ? this._setEffectAllowedForDataTransfer(event) : "";
var ind = this._tabDropIndicator;
if (effects == "none") {
ind.collapsed = true;
return;
}
event.preventDefault();
event.stopPropagation();
var tabStrip = this.mTabstrip;
var ltr = (window.getComputedStyle(this).direction == "ltr");
// Autoscroll the tab strip if we drag over the scroll
// buttons, even if we aren't dragging a tab, but then
// return to avoid drawing the drop indicator.
var pixelsToScroll = 0;
var target = event.originalTarget;
if (target.ownerDocument == document &&
this.getAttribute("overflow") == "true") {
let targetAnonid = target.getAttribute("anonid");
switch (targetAnonid) {
case "scrollbutton-up":
pixelsToScroll = tabStrip.scrollIncrement * -1;
break;
case "scrollbutton-down":
pixelsToScroll = tabStrip.scrollIncrement;
break;
}
if (pixelsToScroll) {
if (effects)
tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
else
tabStrip._startScroll(pixelsToScroll < 0 ? -1 : 1);
}
}
if (scrollOnly) {
ind.collapsed = true;
return;
}
if (effects == "link") {
let tab = this._getDragTargetTab(event);
if (tab) {
if (!this._dragTime)
this._dragTime = Date.now();
if (Date.now() >= this._dragTime + this._dragOverDelay)
this.selectedItem = tab;
ind.collapsed = true;
return;
}
}
var newIndex = this._getDropIndex(event);
var scrollRect = tabStrip.scrollClientRect;
var rect = this.getBoundingClientRect();
var minMargin = scrollRect.left - rect.left;
var maxMargin = Math.min(minMargin + scrollRect.width,
scrollRect.right);
if (!ltr)
[minMargin, maxMargin] = [this.clientWidth - maxMargin,
this.clientWidth - minMargin];
var newMargin;
if (pixelsToScroll) {
// If we are scrolling, put the drop indicator at the edge,
// so that it doesn't jump while scrolling.
newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
}
else {
if (newIndex == this.childNodes.length) {
let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
if (ltr)
newMargin = tabRect.right - rect.left;
else
newMargin = rect.right - tabRect.left;
}
else {
let tabRect = this.childNodes[newIndex].getBoundingClientRect();
if (ltr)
newMargin = tabRect.left - rect.left;
else
newMargin = rect.right - tabRect.right;
}
}
ind.collapsed = false;
newMargin += ind.clientWidth / 2;
if (!ltr)
newMargin *= -1;
ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)";
ind.style.MozMarginStart = (-ind.clientWidth) + "px";
]]></body>
</method>
<field name="_tabDragPanel">
document.getAnonymousElementByAttribute(this, "anonid", "tab-drag-panel");
</field>
<field name="draggedTab">null</field>
<method name="_handleTabDrag">
<parameter name="event"/>
<body><![CDATA[
let draggedTab = this.draggedTab;
if (!draggedTab)
return;
if (event)
draggedTab._dragData._savedEvent = event;
else
event = draggedTab._dragData._savedEvent;
if (this._updateTabDetachState(event, draggedTab))
return;
// Keep the dragged tab visually within the region of like tabs.
let tabs = this.tabbrowser.visibleTabs;
let numPinned = this.tabbrowser._numPinnedTabs;
let leftmostTab = draggedTab.pinned ? tabs[0] : tabs[numPinned];
let rightmostTab = draggedTab.pinned ? tabs[numPinned-1] : tabs[tabs.length-1];
let tabWidth = draggedTab.getBoundingClientRect().width;
let ltr = (window.getComputedStyle(this).direction == "ltr");
if (!ltr)
[leftmostTab, rightmostTab] = [rightmostTab, leftmostTab];
let left = leftmostTab.boxObject.screenX;
let right = rightmostTab.boxObject.screenX + tabWidth;
let transformX = event.screenX - draggedTab._dragData._dragStartX;
if (!draggedTab.pinned)
transformX += this.mTabstrip.scrollPosition;
let tabX = draggedTab.boxObject.screenX + transformX;
draggedTab._dragData._dragDistX = transformX;
if (tabX < left)
transformX += left - tabX;
// Prevent unintended overflow, especially in RTL mode.
else if (tabX + tabWidth > right)
transformX += right - tabX - tabWidth - (ltr ? 0 : 1);
draggedTab.style.MozTransform = "translate(" + transformX + "px)";
let newIndex = this._getDropIndex(event, draggedTab);
let tabAtNewIndex = this.childNodes[newIndex > draggedTab._tPos ?
newIndex-1 : newIndex];
this._positionDropIndicator(event, tabAtNewIndex.pinned == draggedTab.pinned);
if (newIndex == draggedTab._dragData._dropIndex)
return;
draggedTab._dragData._dropIndex = newIndex;
if (!ltr)
tabWidth *= -1;
tabs.forEach(function(tab) {
if (tab == draggedTab || tab.pinned != draggedTab.pinned)
return;
else if (tab._tPos < draggedTab._tPos && tab._tPos >= newIndex)
tab.style.MozTransform = "translate(" + tabWidth + "px)";
else if (tab._tPos > draggedTab._tPos && tab._tPos < newIndex)
tab.style.MozTransform = "translate(" + -tabWidth + "px)";
else
tab.style.MozTransform = "";
});
]]></body>
</method>
<method name="_updateTabDetachState">
<parameter name="event"/>
<parameter name="draggedTab"/>
<body><![CDATA[
let data = draggedTab._dragData;
if (data._targetWindow.closed) {
data._targetWindow = window;
window.focus();
}
else if (data._dropTarget) {
data._dropTarget._tabDropIndicator.collapsed = true;
}
delete data._dropTarget;
function isEventOutsideWindow(event, win) {
return (event.screenX < win.screenX || event.screenX >= win.screenX + win.outerWidth ||
event.screenY < win.screenY || event.screenY >= win.screenY + win.outerHeight);
}
if (isEventOutsideWindow(event, data._targetWindow) ||
Services.ww.activeWindow != data._targetWindow) {
// Iterate through browser windows in hopefully front-to-back order.
let winEnum = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
let wins = [];
while (winEnum.hasMoreElements())
wins.push(winEnum.getNext());
// Work around broken z-order enumerator on Linux. See bug 156333.
if (!wins.length) {
winEnum = Services.wm.getEnumerator("navigator:browser");
while (winEnum.hasMoreElements())
wins.unshift(winEnum.getNext());
}
wins.every(function(win) {
if (win.closed || win.windowState == STATE_MINIMIZED ||
isEventOutsideWindow(event, win))
return true;
data._targetWindow = win;
win.focus(); // Raise window when cursor moves over it.
});
}
let detached = (this.getAttribute("drag") == "detach");
let loneTab = (this.childElementCount == 1);
let bo = loneTab ? draggedTab.boxObject : this.parentNode.boxObject;
// Detach tab if outside window, to the left or right of tab strip, or
// at least one tab height above or below it.
if (data._targetWindow != window ||
event.screenX < bo.screenX || event.screenX >= bo.screenX + bo.width ||
event.screenY < bo.screenY - (detached || loneTab ? 0 : 1) * bo.height ||
event.screenY >= bo.screenY + (detached || loneTab ? 1 : 2) * bo.height) {
if (data._targetWindow != window &&
data._targetWindow.windowState != STATE_MINIMIZED) {
let that = data._targetWindow.gBrowser.tabContainer;
let bo = that.parentNode.boxObject;
if (event.screenX >= bo.screenX && event.screenX < bo.screenX + bo.width &&
event.screenY >= bo.screenY && event.screenY < bo.screenY + bo.height) {
that._positionDropIndicator(event);
data._dropTarget = that;
}
}
let dragPanel = this._tabDragPanel;
if (data._dropTarget)
dragPanel.setAttribute("target", "true");
else
dragPanel.removeAttribute("target");
if (!detached) {
this.setAttribute("drag", "detach");
this._clearDragTransforms();
this._tabDropIndicator.collapsed = true;
if (draggedTab.style.maxWidth) {
data._maxWidth = draggedTab.style.maxWidth;
draggedTab.style.maxWidth = "";
}
delete draggedTab._dropIndex;
let label = dragPanel.firstChild;
let preview = dragPanel.lastChild;
label.value = draggedTab.label;
label.width = preview.width = Math.min(outerWidth / 2, screen.availWidth / 5);
let aspectRatio = this.tabbrowser.clientWidth / this.tabbrowser.clientHeight;
preview.height = Math.min(preview.width / aspectRatio, screen.availHeight / 5);
dragPanel.hidden = false;
dragPanel.openPopupAtScreen(event.screenX, event.screenY, false);
}
let width = dragPanel.clientWidth;
let [left, top] = this._getAdjustedCoords(event.screenX, event.screenY, width,
dragPanel.clientHeight, width / 2, 12, true);
dragPanel.moveTo(left, top);
return true;
}
if (detached) { // Otherwise, put tab back in the tab strip.
this.setAttribute("drag", "move");
if (data._maxWidth) {
draggedTab.style.setProperty("max-width", data._maxWidth, "important");
delete draggedTab._maxWidth;
}
this.mTabstrip._updateScrollButtonsDisabledState();
this._tabDragPanel.hidePopup();
}
]]></body>
</method>
<method name="_getAdjustedCoords">
<parameter name="aLeft"/>
<parameter name="aTop"/>
<parameter name="aWidth"/>
<parameter name="aHeight"/>
<parameter name="aOffsetX"/>
<parameter name="aOffsetY"/>
<parameter name="isPanel"/>
<body><![CDATA[
// screen.availTop et al. only check the source window's screen, but
// we want to look at the target window's screen.
let sX = {}, sY = {}, sWidth = {}, sHeight = {};
Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager)
.screenForRect(aLeft, aTop, 1, 1)
.GetAvailRect(sX, sY, sWidth, sHeight);
// Window manager repositions panels that are too close to the right
// or bottom of a screen, so leave a gutter to avoid that.
if (isPanel) {
sWidth.value -= 3;
sHeight.value -= 3;
}
// Ensure rect will be entirely onscreen.
let width = Math.min(aWidth, sWidth.value);
let height = Math.min(aHeight, sHeight.value);
let left = Math.min(Math.max(aLeft - aOffsetX, sX.value),
sX.value + sWidth.value - width);
let top = Math.min(Math.max(aTop - aOffsetY, sY.value),
sY.value + sHeight.value - height);
return [left, top, width, height];
]]></body>
</method>
<method name="_handleTabDrop">
<parameter name="event"/>
<body><![CDATA[
let draggedTab = this.draggedTab;
if (this.getAttribute("drag") == "move") {
this._slideTab(event, draggedTab);
return;
}
do {
let that = draggedTab._dragData._dropTarget;
let win = draggedTab._dragData._targetWindow;
if (!that || win.closed)
break;
that._tabDropIndicator.collapsed = true;
if (win.windowState == STATE_MINIMIZED)
break;
this._endTabDrag();
// User dropped the tab onto another window's tab strip, so swap the
// tab with a new one we create in that window, and then close it in
// this window (making it seem to have moved between windows).
let newIndex = that._getDropIndex(event);
let newTab = that.tabbrowser.addTab("about:blank");
let newBrowser = that.tabbrowser.getBrowserForTab(newTab);
newBrowser.stop(); // Stop the about:blank load.
newBrowser.docShell; // Make sure it has a docshell.
let numPinned = that.tabbrowser._numPinnedTabs;
if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
that.tabbrowser.pinTab(newTab);
that.tabbrowser.moveTabTo(newTab, newIndex);
that.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
// We need to select the tab after we've done
// swapBrowsersAndCloseOther, so that the updateCurrentBrowser
// it triggers will correctly update our URL bar.
that.selectedItem = newTab;
return;
} while (false);
let [left, top, width, height] = this._getAdjustedCoords(
event.screenX, event.screenY, outerWidth, outerHeight,
this._tabDragPanel.clientWidth / 2, draggedTab._dragData._dragOffsetY);
this._endTabDrag();
if (this.childElementCount == 1) {
// Resize _before_ move to ensure the window fits the new screen. If
// the window is too large for its screen, the window manager may do
// automatic repositioning.
window.resizeTo(width, height);
window.moveTo(left, top);
window.focus();
return;
}
draggedTab.collapsed = true;
this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
screenY: top,
#ifndef XP_WIN
outerWidth: width,
outerHeight: height
#endif
});
]]></body>
</method>
<method name="_slideTab">
<parameter name="event"/>
<parameter name="draggedTab"/>
<body><![CDATA[
let oldIndex = draggedTab._tPos;
let newIndex = draggedTab._dragData._dropIndex;
if (newIndex > oldIndex)
newIndex--;
this.removeAttribute("drag");
this._endTabDrag();
if (!draggedTab.pinned && newIndex < this.tabbrowser._numPinnedTabs)
this.tabbrowser.pinTab(draggedTab);
else if (draggedTab.pinned && newIndex >= this.tabbrowser._numPinnedTabs)
this.tabbrowser.unpinTab(draggedTab);
else if (Services.prefs.getBoolPref("browser.tabs.animate")) {
let difference = 0;
// Calculate number of visible tabs between start and destination.
if (newIndex != oldIndex) {
let tabs = this.tabbrowser.visibleTabs;
for (let i = 0; i < tabs.length; i++) {
let position = tabs[i]._tPos;
if (position <= newIndex && position > oldIndex)
difference++;
else if (position >= newIndex && position < oldIndex)
difference--;
}
}
let displacement = difference * draggedTab.getBoundingClientRect().width;
if (window.getComputedStyle(this).direction == "rtl")
displacement *= -1;
let destination = "translate(" + displacement + "px)";
if (draggedTab.style.MozTransform != destination) {
this.setAttribute("drag", "finish");
draggedTab.style.MozTransform = destination;
draggedTab.addEventListener("transitionend", function finish(event) {
if (event.eventPhase != Event.AT_TARGET ||
event.propertyName != "-moz-transform")
return;
draggedTab.removeEventListener("transitionend", finish);
draggedTab.removeAttribute("dragged");
let that = draggedTab.parentNode;
that.removeAttribute("drag");
that._clearDragTransforms();
that.tabbrowser.moveTabTo(draggedTab, newIndex);
});
return;
}
}
draggedTab.removeAttribute("dragged");
this._clearDragTransforms();
this.tabbrowser.moveTabTo(draggedTab, newIndex);
]]></body>
</method>
<method name="_endTabDrag">
<body><![CDATA[
let tab = this.draggedTab;
if (!tab)
return;
this.draggedTab = null;
delete tab._dragData;
document.removeEventListener("mousemove", this);
document.removeEventListener("mouseup", this);
this.removeEventListener("TabSelect", this);
this.mTabstrip.removeEventListener("scroll", this);
this._tabDragPanel.hidePopup();
this._tabDragPanel.hidden = true;
this._tabDropIndicator.collapsed = true;
if (this.hasAttribute("drag")) {
if (this.getAttribute("drag") == "detach")
this._unlockTabSizing();
tab.removeAttribute("dragged");
this.removeAttribute("drag");
this._clearDragTransforms();
}
]]></body>
</method>
<method name="_clearDragTransforms">
<body>
this.tabbrowser.visibleTabs.forEach(function(visibleTab) {
visibleTab.style.MozTransform = "";
});
</body>
</method>
<field name="_dragOverDelay">350</field>
<field name="_dragTime">0</field>
@ -2964,8 +3437,7 @@
}
}
this._hasTabTempMaxWidth = true;
this.tabbrowser.addEventListener("mousemove", this, false);
window.addEventListener("mouseout", this, false);
window.addEventListener("mouseout", this);
}
]]></body>
</method>
@ -2976,15 +3448,13 @@
let spacer = this._closingTabsSpacer;
spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
this._usingClosingTabsSpacer = true;
this.tabbrowser.addEventListener("mousemove", this, false);
window.addEventListener("mouseout", this, false);
window.addEventListener("mouseout", this);
]]></body>
</method>
<method name="_unlockTabSizing">
<body><![CDATA[
this.tabbrowser.removeEventListener("mousemove", this, false);
window.removeEventListener("mouseout", this, false);
window.removeEventListener("mouseout", this);
if (this._hasTabTempMaxWidth) {
this._hasTabTempMaxWidth = false;
let tabs = this.tabbrowser.visibleTabs;
@ -3051,14 +3521,36 @@
this.tabbrowser.updateWindowResizers();
break;
case "mouseout":
// If the "related target" (the node to which the pointer went) is not
// a child of the current document, the mouse just left the window.
let relatedTarget = aEvent.relatedTarget;
if (relatedTarget && relatedTarget.ownerDocument == document)
if (this.draggedTab)
break;
let bo = this.mTabstrip.boxObject;
if (aEvent.screenX >= bo.screenX && aEvent.screenX < bo.screenX + bo.width &&
aEvent.screenY >= bo.screenY && aEvent.screenY < bo.screenY + bo.height)
break;
let tabContextMenu = document.getElementById("tabContextMenu");
if (tabContextMenu.state == "open")
tabContextMenu.addEventListener("popuphidden", this);
else
this._unlockTabSizing();
break;
case "popuphidden": // Tab context menu was closed.
if (aEvent.eventPhase != Event.AT_TARGET)
break;
aEvent.target.removeEventListener("popuphidden", this);
this._unlockTabSizing();
break;
case "mousemove":
if (document.getElementById("tabContextMenu").state != "open")
this._unlockTabSizing();
this._handleTabDrag(aEvent);
break;
case "mouseup":
this._handleTabDrop(aEvent);
break;
case "TabSelect": // Focus was stolen from dragged tab!
this._endTabDrag(aEvent);
window.focus();
break;
case "scroll": // Tab strip was scrolled.
this._handleTabDrag();
break;
}
]]></body>
@ -3125,19 +3617,45 @@
<method name="_getDropIndex">
<parameter name="event"/>
<parameter name="draggedTab"/>
<body><![CDATA[
var tabs = this.childNodes;
var tab = this._getDragTargetTab(event);
if (window.getComputedStyle(this, null).direction == "ltr") {
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
return i;
} else {
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
function compare(a, b, lessThan) lessThan ? a < b : a > b;
let ltr = (window.getComputedStyle(this).direction == "ltr");
let eX = event.screenX;
let tabs = this.tabbrowser.visibleTabs;
if (draggedTab) {
let dist = draggedTab._dragData._dragDistX;
let tabX = draggedTab.boxObject.screenX + dist;
let draggingRight = dist > 0;
if (draggingRight)
tabX += draggedTab.boxObject.width;
// iterate through app tabs first, since their z-index is higher
else if (!draggedTab.pinned)
for (let i = 0, numPinned = this.tabbrowser._numPinnedTabs; i < numPinned; i++)
if (compare(eX, tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2, ltr))
return i;
let i = tabs.indexOf(draggedTab), tab = draggedTab, next;
while (next = ltr ^ draggingRight ? tabs[--i] : tabs[++i]) {
let x = next.pinned == draggedTab.pinned ? tabX : eX;
let middleOfNextTab = next.boxObject.screenX + next.boxObject.width / 2;
if (!compare(x, middleOfNextTab, !draggingRight))
break;
// ensure an app tab is actually inside the normal tab region
if (draggedTab.pinned && !next.pinned &&
x < this.mTabstrip._scrollButtonUp.boxObject.screenX)
break;
tab = next;
}
return tabs.length;
return tab._tPos + (ltr ^ draggingRight ? 0 : 1);
}
let tab = this._getDragTargetTab(event);
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
if (compare(eX, tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2, ltr))
return tabs[i]._tPos;
return this.childElementCount;
]]></body>
</method>
@ -3149,27 +3667,6 @@
if (dt.mozItemCount > 1)
return dt.effectAllowed = "none";
var types = dt.mozTypesAt(0);
var sourceNode = null;
// tabs are always added as the first type
if (types[0] == TAB_DROP_TYPE) {
var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (sourceNode instanceof XULElement &&
sourceNode.localName == "tab" &&
(sourceNode.parentNode == this ||
(sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
if (sourceNode.parentNode == this &&
(event.screenX >= sourceNode.boxObject.screenX &&
event.screenX <= (sourceNode.boxObject.screenX +
sourceNode.boxObject.width))) {
return dt.effectAllowed = "none";
}
return dt.effectAllowed = "copyMove";
}
}
if (browserDragAndDrop.canDropLink(event)) {
// Here we need to do this manually
return dt.effectAllowed = dt.dropEffect = "link";
@ -3178,20 +3675,6 @@
]]></body>
</method>
<method name="_continueScroll">
<parameter name="event"/>
<body><![CDATA[
// Workaround for bug 481904: Dragging a tab stops scrolling at
// the tab's position when dragging to the first/last tab and back.
var t = this.selectedItem;
if (event.screenX >= t.boxObject.screenX &&
event.screenX <= t.boxObject.screenX + t.boxObject.width &&
event.screenY >= t.boxObject.screenY &&
event.screenY <= t.boxObject.screenY + t.boxObject.height)
this.mTabstrip.ensureElementIsVisible(t);
]]></body>
</method>
<method name="_handleNewTab">
<parameter name="tab"/>
<body><![CDATA[
@ -3324,190 +3807,47 @@
]]></handler>
<handler event="dragstart"><![CDATA[
if (this.draggedTab)
return;
var tab = this._getDragTargetTab(event);
if (!tab)
if (!tab || !tab._fullyOpen || tab.closing)
return;
let dt = event.dataTransfer;
dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
let uri = this.tabbrowser.getBrowserForTab(tab).currentURI;
let spec = uri ? uri.spec : "about:blank";
this.setAttribute("drag", "move");
this.draggedTab = tab;
tab.setAttribute("dragged", "true");
let data = tab._dragData = {};
data._dragStartX = event.screenX;
if (!tab.pinned)
data._dragStartX += this.mTabstrip.scrollPosition;
data._dragDistX = 0;
data._dragOffsetY = event.screenY - window.screenY;
data._dropIndex = tab._tPos;
data._savedEvent = event;
data._targetWindow = window;
// We must not set text/x-moz-url or text/plain data here,
// otherwise trying to deatch the tab by dropping it on the desktop
// may result in an "internet shortcut"
dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
// Set the cursor to an arrow during tab drags.
dt.mozCursor = "default";
let canvas = tabPreviews.capture(tab, false);
dt.setDragImage(canvas, 0, 0);
// _dragOffsetX/Y give the coordinates that the mouse should be
// positioned relative to the corner of the new window created upon
// dragend such that the mouse appears to have the same position
// relative to the corner of the dragged tab.
function clientX(ele) ele.getBoundingClientRect().left;
let tabOffsetX = clientX(tab) -
clientX(this.children[0].pinned ? this.children[0] : this);
tab._dragOffsetX = event.screenX - window.screenX - tabOffsetX;
tab._dragOffsetY = event.screenY - window.screenY;
document.addEventListener("mousemove", this);
document.addEventListener("mouseup", this);
this.addEventListener("TabSelect", this);
this.mTabstrip.addEventListener("scroll", this);
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
var effects = this._setEffectAllowedForDataTransfer(event);
var ind = this._tabDropIndicator;
if (effects == "" || effects == "none") {
ind.collapsed = true;
this._continueScroll(event);
return;
}
event.preventDefault();
event.stopPropagation();
var tabStrip = this.mTabstrip;
var ltr = (window.getComputedStyle(this, null).direction == "ltr");
// autoscroll the tab strip if we drag over the scroll
// buttons, even if we aren't dragging a tab, but then
// return to avoid drawing the drop indicator
var pixelsToScroll = 0;
if (this.getAttribute("overflow") == "true") {
var targetAnonid = event.originalTarget.getAttribute("anonid");
switch (targetAnonid) {
case "scrollbutton-up":
pixelsToScroll = tabStrip.scrollIncrement * -1;
break;
case "scrollbutton-down":
pixelsToScroll = tabStrip.scrollIncrement;
break;
}
if (pixelsToScroll)
tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
}
if (effects == "link") {
let tab = this._getDragTargetTab(event);
if (tab) {
if (!this._dragTime)
this._dragTime = Date.now();
if (Date.now() >= this._dragTime + this._dragOverDelay)
this.selectedItem = tab;
ind.collapsed = true;
return;
}
}
var newIndex = this._getDropIndex(event);
var scrollRect = tabStrip.scrollClientRect;
var rect = this.getBoundingClientRect();
var minMargin = scrollRect.left - rect.left;
var maxMargin = Math.min(minMargin + scrollRect.width,
scrollRect.right);
if (!ltr)
[minMargin, maxMargin] = [this.clientWidth - maxMargin,
this.clientWidth - minMargin];
var newMargin;
if (pixelsToScroll) {
// if we are scrolling, put the drop indicator at the edge
// so that it doesn't jump while scrolling
newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
}
else {
if (newIndex == this.childNodes.length) {
let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
if (ltr)
newMargin = tabRect.right - rect.left;
else
newMargin = rect.right - tabRect.left;
}
else {
let tabRect = this.childNodes[newIndex].getBoundingClientRect();
if (ltr)
newMargin = tabRect.left - rect.left;
else
newMargin = rect.right - tabRect.right;
}
}
ind.collapsed = false;
newMargin += ind.clientWidth / 2;
if (!ltr)
newMargin *= -1;
ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)";
ind.style.MozMarginStart = (-ind.clientWidth) + "px";
]]></handler>
<handler event="dragover" action="this._positionDropIndicator(event);"/>
<handler event="drop"><![CDATA[
var dt = event.dataTransfer;
var dropEffect = dt.dropEffect;
var draggedTab;
if (dropEffect != "link") { // copy or move
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// not our drop then
if (!draggedTab)
return;
}
this._tabDropIndicator.collapsed = true;
event.stopPropagation();
if (draggedTab && (dropEffect == "copy" ||
draggedTab.parentNode == this)) {
let newIndex = this._getDropIndex(event);
if (dropEffect == "copy") {
// copy the dropped tab (wherever it's from)
let newTab = this.tabbrowser.duplicateTab(draggedTab);
this.tabbrowser.moveTabTo(newTab, newIndex);
if (draggedTab.parentNode != this || event.shiftKey)
this.selectedItem = newTab;
} else {
// move the dropped tab
if (newIndex > draggedTab._tPos)
newIndex--;
let dt = event.dataTransfer;
if (dt.dropEffect != "link")
return;
if (draggedTab.pinned) {
if (newIndex >= this.tabbrowser._numPinnedTabs)
this.tabbrowser.unpinTab(draggedTab);
} else {
if (newIndex <= this.tabbrowser._numPinnedTabs - 1)
this.tabbrowser.pinTab(draggedTab);
}
this.tabbrowser.moveTabTo(draggedTab, newIndex);
}
} else if (draggedTab) {
// swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows)
let newIndex = this._getDropIndex(event);
let newTab = this.tabbrowser.addTab("about:blank");
let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
// Stop the about:blank load
newBrowser.stop();
// make sure it has a docshell
newBrowser.docShell;
this.tabbrowser.moveTabTo(newTab, newIndex);
this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
// We need to select the tab after we've done
// swapBrowsersAndCloseOther, so that the updateCurrentBrowser
// it triggers will correctly update our URL bar.
this.tabbrowser.selectedTab = newTab;
} else {
let url = browserDragAndDrop.drop(event, { });
// valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
// Also disallow dropping javascript: or data: urls--bail out
if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
// Disallow dropping strings that contain spaces (not a valid url
// character). Also disallow dropping javascript: or data: urls.
if (!url || !url.length || url.indexOf(" ") != -1 ||
/^\s*(javascript|data):/.test(url))
return;
@ -3517,7 +3857,7 @@
bgLoad = !bgLoad;
let tab = this._getDragTargetTab(event);
if (!tab || dropEffect == "copy") {
if (!tab) {
// We're adding a new tab.
let newIndex = this._getDropIndex(event);
let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
@ -3528,78 +3868,12 @@
this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
if (!bgLoad)
this.selectedItem = tab;
ind.collapsed = true;
return;
} catch(ex) {
// Just ignore invalid urls
// Just ignore invalid urls.
}
}
}
// these offsets are only used in dragend, but we need to free them here
// as well
delete draggedTab._dragOffsetX;
delete draggedTab._dragOffsetY;
]]></handler>
<handler event="dragend"><![CDATA[
// Note: while this case is correctly handled here, this event
// isn't dispatched when the tab is moved within the tabstrip,
// see bug 460801.
// * mozUserCancelled = the user pressed ESC to cancel the drag
var dt = event.dataTransfer;
if (dt.mozUserCancelled || dt.dropEffect != "none")
return;
// Disable detach within the browser toolbox
var eX = event.screenX;
var eY = event.screenY;
var wX = window.screenX;
// check if the drop point is horizontally within the window
if (eX > wX && eX < (wX + window.outerWidth)) {
let bo = this.mTabstrip.boxObject;
// also avoid detaching if the the tab was dropped too close to
// the tabbar (half a tab)
let endScreenY = bo.screenY + 1.5 * bo.height;
if (eY < endScreenY && eY > window.screenY)
return;
}
var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// screen.availLeft et. al. only check the screen that this window is on,
// but we want to look at the screen the tab is being dropped onto.
var sX = {}, sY = {}, sWidth = {}, sHeight = {};
Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager)
.screenForRect(eX, eY, 1, 1)
.GetAvailRect(sX, sY, sWidth, sHeight);
// ensure new window entirely within screen
var winWidth = Math.min(window.outerWidth, sWidth.value);
var winHeight = Math.min(window.outerHeight, sHeight.value);
var left = Math.min(Math.max(eX - draggedTab._dragOffsetX, sX.value),
sX.value + sWidth.value - winWidth);
var top = Math.min(Math.max(eY - draggedTab._dragOffsetY, sY.value),
sY.value + sHeight.value - winHeight);
delete draggedTab._dragOffsetX;
delete draggedTab._dragOffsetY;
if (this.tabbrowser.tabs.length == 1) {
// resize _before_ move to ensure the window fits the new screen. if
// the window is too large for its screen, the window manager may do
// automatic repositioning.
window.resizeTo(winWidth, winHeight);
window.moveTo(left, top);
window.focus();
} else {
this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
screenY: top,
#ifndef XP_WIN
outerWidth: winWidth,
outerHeight: winHeight
#endif
});
}
event.stopPropagation();
]]></handler>
<handler event="dragexit"><![CDATA[
@ -3741,7 +4015,6 @@
<field name="mOverCloseButton">false</field>
<field name="mCorrespondingMenuitem">null</field>
<field name="_fullyOpen">false</field>
<field name="closing">false</field>
</implementation>

Просмотреть файл

@ -42,8 +42,6 @@
// Services = object with smart getters for common XPCOM services
Components.utils.import("resource://gre/modules/Services.jsm");
var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
var gBidiUI = false;
function getBrowserURL()

Просмотреть файл

@ -1600,6 +1600,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
/* Tab drag and drop */
.tab-drag-label {
padding: 2px;
}
.tab-drop-indicator {
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
margin-bottom: -11px;

Просмотреть файл

@ -1560,6 +1560,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
height: 26px;
}
.tab-drag-label,
.tabbrowser-tab,
.tabs-newtab-button {
-moz-appearance: none;
@ -1837,6 +1838,19 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
* Tab Drag and Drop
*/
.tab-drag-label {
background: -moz-linear-gradient(#eee, #ccc);
padding: 4px 8px;
border-radius: 4px;
box-shadow: inset 0 1px 0 rgba(255,255,255,.6);
}
.tab-drag-panel:not([target]) > .tab-drag-label {
background: -moz-linear-gradient(#ddd, #bbb);
border-bottom: 1px solid #999;
border-radius: 3px 3px 0 0;
}
.tab-drop-indicator {
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
margin-bottom: -8px;

Просмотреть файл

@ -11,6 +11,7 @@
margin-top: 1px;
}
.tab-drag-preview::before,
#appmenu-button {
border-width: 2px;
-moz-border-left-colors: @appMenuButtonBorderColor@;
@ -21,6 +22,7 @@
0 0 2px 1px rgba(255,255,255,.25) inset;
}
#main-window[privatebrowsingmode=temporary] #tabbrowser-tabs > hbox > .tab-drag-panel > .tab-drag-preview::before,
#main-window[privatebrowsingmode=temporary] #appmenu-button {
-moz-border-left-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
-moz-border-bottom-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
@ -55,6 +57,7 @@
-moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
}
.tab-drag-label,
.tabbrowser-tab[selected="true"]:not(:-moz-lwtheme) {
background-image: -moz-linear-gradient(white, @toolbarHighlight@ 50%),
-moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
@ -293,6 +296,31 @@
-moz-linear-gradient(rgba(255,255,255,0), #CCD9EA 200px, #C7D5E7);
background-attachment: fixed;
}
.tab-drag-panel {
-moz-appearance: -moz-win-borderless-glass;
}
.tab-drag-label {
padding: 4px;
background-color: -moz-dialog;
border-radius: 3px;
}
.tab-drag-preview {
margin: 15px 7px 7px;
}
.tab-drag-panel:not([target]) > .tab-drag-preview {
display: block;
}
.tab-drag-preview::before { /* miniature appmenu button */
content: "";
display: block;
margin-top: -15px;
-moz-margin-start: -2px;
padding: 0;
width: 32px;
height: 7px;
border-radius: 0 0 3px 3px;
}
}
@media not all and (-moz-windows-compositor) {

Просмотреть файл

@ -176,6 +176,7 @@
}
%ifdef MOZ_OFFICIAL_BRANDING
.tab-drag-preview::before,
#appmenu-button {
background-image: -moz-linear-gradient(rgb(247,182,82), rgb(215,98,10) 95%);
border-color: rgba(83,42,6,.9);
@ -199,6 +200,7 @@
}
%else
%if MOZ_UPDATE_CHANNEL == aurora
.tab-drag-preview::before,
#appmenu-button {
background-image: -moz-linear-gradient(hsl(208,99%,37%), hsl(214,90%,23%) 95%);
border-color: hsla(214,89%,21%,.9);
@ -221,6 +223,7 @@
0 1px 1px rgba(0,0,0,.2) inset;
}
%else
.tab-drag-preview::before,
#appmenu-button {
background-image: -moz-linear-gradient(hsl(211,33%,32%), hsl(209,53%,10%) 95%);
border-color: hsla(210,59%,13%,.9);
@ -245,6 +248,7 @@
%endif
%endif
#main-window[privatebrowsingmode=temporary] #tabbrowser-tabs > hbox > .tab-drag-panel > .tab-drag-preview::before,
#main-window[privatebrowsingmode=temporary] #appmenu-button {
background-image: -moz-linear-gradient(rgb(153,38,211), rgb(105,19,163) 95%);
border-color: rgba(43,8,65,.9);
@ -1806,7 +1810,25 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
outline: 1px dotted;
}
/* Tab DnD indicator */
/* Tab drag and drop */
.tab-drag-panel {
border: 0 !important;
}
.tab-drag-label {
margin: 0 !important;
padding: 5px;
border: 1px solid DimGray;
}
.tab-drag-panel:not([target]) > .tab-drag-label {
display: none;
}
.tab-drag-preview {
border: 1px solid rgba(0,0,0,.5);
}
.tab-drop-indicator {
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
margin-bottom: -11px;