зеркало из https://github.com/mozilla/gecko-dev.git
Bug 455694 - Animate tab reordering & detaching. r=dolske ui-r=limi
This commit is contained in:
Родитель
fcc5d334f0
Коммит
e05442f9a2
|
@ -13,6 +13,7 @@ tabbrowser {
|
||||||
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
|
-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:not([overflow="true"]) + #new-tab-button,
|
||||||
#tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-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,
|
#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 */
|
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 {
|
#alltabs-popup {
|
||||||
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
|
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,16 @@ tabpanels {
|
||||||
background-color: transparent;
|
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 {
|
.tab-drop-indicator {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
|
@ -1497,6 +1497,9 @@
|
||||||
if (!this._beginRemoveTab(aTab, false, null, true))
|
if (!this._beginRemoveTab(aTab, false, null, true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (this.tabContainer.draggedTab == aTab)
|
||||||
|
this.tabContainer._endTabDrag();
|
||||||
|
|
||||||
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
|
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
|
||||||
this.tabContainer._lockTabSizing(aTab);
|
this.tabContainer._lockTabSizing(aTab);
|
||||||
else
|
else
|
||||||
|
@ -1698,6 +1701,8 @@
|
||||||
setTimeout(function(tabs) {
|
setTimeout(function(tabs) {
|
||||||
tabs._lastTabClosedByMouse = false;
|
tabs._lastTabClosedByMouse = false;
|
||||||
}, 0, this.tabContainer);
|
}, 0, this.tabContainer);
|
||||||
|
|
||||||
|
this.tabContainer._handleTabDrag(); // Update drag feedback.
|
||||||
}
|
}
|
||||||
|
|
||||||
// update first-tab/last-tab/beforeselected/afterselected attributes
|
// update first-tab/last-tab/beforeselected/afterselected attributes
|
||||||
|
@ -2676,7 +2681,7 @@
|
||||||
|
|
||||||
<handlers>
|
<handlers>
|
||||||
<handler event="underflow" phase="capturing"><![CDATA[
|
<handler event="underflow" phase="capturing"><![CDATA[
|
||||||
if (event.detail == 0)
|
if (event.originalTarget != this._scrollbox || event.detail == 0)
|
||||||
return; // Ignore vertical events
|
return; // Ignore vertical events
|
||||||
|
|
||||||
var tabs = document.getBindingParent(this);
|
var tabs = document.getBindingParent(this);
|
||||||
|
@ -2691,7 +2696,7 @@
|
||||||
tabs._positionPinnedTabs();
|
tabs._positionPinnedTabs();
|
||||||
]]></handler>
|
]]></handler>
|
||||||
<handler event="overflow"><![CDATA[
|
<handler event="overflow"><![CDATA[
|
||||||
if (event.detail == 0)
|
if (event.originalTarget != this._scrollbox || event.detail == 0)
|
||||||
return; // Ignore vertical events
|
return; // Ignore vertical events
|
||||||
|
|
||||||
var tabs = document.getBindingParent(this);
|
var tabs = document.getBindingParent(this);
|
||||||
|
@ -2707,8 +2712,16 @@
|
||||||
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
|
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
<!-- The onpopupshowing/hiding handlers on the panel are to circumvent
|
||||||
|
noautohide=true disabling level=top on Linux. See bug 448929. -->
|
||||||
<content>
|
<content>
|
||||||
<xul:hbox align="end">
|
<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:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
<xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
|
<xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
|
||||||
|
@ -2804,6 +2817,466 @@
|
||||||
document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
|
document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
|
||||||
</field>
|
</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="_dragOverDelay">350</field>
|
||||||
<field name="_dragTime">0</field>
|
<field name="_dragTime">0</field>
|
||||||
|
|
||||||
|
@ -2964,8 +3437,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._hasTabTempMaxWidth = true;
|
this._hasTabTempMaxWidth = true;
|
||||||
this.tabbrowser.addEventListener("mousemove", this, false);
|
window.addEventListener("mouseout", this);
|
||||||
window.addEventListener("mouseout", this, false);
|
|
||||||
}
|
}
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
@ -2976,15 +3448,13 @@
|
||||||
let spacer = this._closingTabsSpacer;
|
let spacer = this._closingTabsSpacer;
|
||||||
spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
|
spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
|
||||||
this._usingClosingTabsSpacer = true;
|
this._usingClosingTabsSpacer = true;
|
||||||
this.tabbrowser.addEventListener("mousemove", this, false);
|
window.addEventListener("mouseout", this);
|
||||||
window.addEventListener("mouseout", this, false);
|
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="_unlockTabSizing">
|
<method name="_unlockTabSizing">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
this.tabbrowser.removeEventListener("mousemove", this, false);
|
window.removeEventListener("mouseout", this);
|
||||||
window.removeEventListener("mouseout", this, false);
|
|
||||||
if (this._hasTabTempMaxWidth) {
|
if (this._hasTabTempMaxWidth) {
|
||||||
this._hasTabTempMaxWidth = false;
|
this._hasTabTempMaxWidth = false;
|
||||||
let tabs = this.tabbrowser.visibleTabs;
|
let tabs = this.tabbrowser.visibleTabs;
|
||||||
|
@ -3051,14 +3521,36 @@
|
||||||
this.tabbrowser.updateWindowResizers();
|
this.tabbrowser.updateWindowResizers();
|
||||||
break;
|
break;
|
||||||
case "mouseout":
|
case "mouseout":
|
||||||
// If the "related target" (the node to which the pointer went) is not
|
if (this.draggedTab)
|
||||||
// a child of the current document, the mouse just left the window.
|
break;
|
||||||
let relatedTarget = aEvent.relatedTarget;
|
let bo = this.mTabstrip.boxObject;
|
||||||
if (relatedTarget && relatedTarget.ownerDocument == document)
|
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;
|
break;
|
||||||
case "mousemove":
|
case "mousemove":
|
||||||
if (document.getElementById("tabContextMenu").state != "open")
|
this._handleTabDrag(aEvent);
|
||||||
this._unlockTabSizing();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
]]></body>
|
]]></body>
|
||||||
|
@ -3125,19 +3617,45 @@
|
||||||
|
|
||||||
<method name="_getDropIndex">
|
<method name="_getDropIndex">
|
||||||
<parameter name="event"/>
|
<parameter name="event"/>
|
||||||
|
<parameter name="draggedTab"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
var tabs = this.childNodes;
|
function compare(a, b, lessThan) lessThan ? a < b : a > b;
|
||||||
var tab = this._getDragTargetTab(event);
|
let ltr = (window.getComputedStyle(this).direction == "ltr");
|
||||||
if (window.getComputedStyle(this, null).direction == "ltr") {
|
let eX = event.screenX;
|
||||||
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
|
let tabs = this.tabbrowser.visibleTabs;
|
||||||
if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
|
|
||||||
return i;
|
if (draggedTab) {
|
||||||
} else {
|
let dist = draggedTab._dragData._dragDistX;
|
||||||
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
|
let tabX = draggedTab.boxObject.screenX + dist;
|
||||||
if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
|
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;
|
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>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
@ -3149,27 +3667,6 @@
|
||||||
if (dt.mozItemCount > 1)
|
if (dt.mozItemCount > 1)
|
||||||
return dt.effectAllowed = "none";
|
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)) {
|
if (browserDragAndDrop.canDropLink(event)) {
|
||||||
// Here we need to do this manually
|
// Here we need to do this manually
|
||||||
return dt.effectAllowed = dt.dropEffect = "link";
|
return dt.effectAllowed = dt.dropEffect = "link";
|
||||||
|
@ -3178,20 +3675,6 @@
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</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">
|
<method name="_handleNewTab">
|
||||||
<parameter name="tab"/>
|
<parameter name="tab"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
|
@ -3324,190 +3807,47 @@
|
||||||
]]></handler>
|
]]></handler>
|
||||||
|
|
||||||
<handler event="dragstart"><![CDATA[
|
<handler event="dragstart"><![CDATA[
|
||||||
|
if (this.draggedTab)
|
||||||
|
return;
|
||||||
var tab = this._getDragTargetTab(event);
|
var tab = this._getDragTargetTab(event);
|
||||||
if (!tab)
|
if (!tab || !tab._fullyOpen || tab.closing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let dt = event.dataTransfer;
|
this.setAttribute("drag", "move");
|
||||||
dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
|
this.draggedTab = tab;
|
||||||
let uri = this.tabbrowser.getBrowserForTab(tab).currentURI;
|
tab.setAttribute("dragged", "true");
|
||||||
let spec = uri ? uri.spec : "about:blank";
|
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,
|
document.addEventListener("mousemove", this);
|
||||||
// otherwise trying to deatch the tab by dropping it on the desktop
|
document.addEventListener("mouseup", this);
|
||||||
// may result in an "internet shortcut"
|
this.addEventListener("TabSelect", this);
|
||||||
dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
|
this.mTabstrip.addEventListener("scroll", this);
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
]]></handler>
|
]]></handler>
|
||||||
|
|
||||||
<handler event="dragover"><![CDATA[
|
<handler event="dragover" action="this._positionDropIndicator(event);"/>
|
||||||
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="drop"><![CDATA[
|
<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;
|
this._tabDropIndicator.collapsed = true;
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
if (draggedTab && (dropEffect == "copy" ||
|
let dt = event.dataTransfer;
|
||||||
draggedTab.parentNode == this)) {
|
if (dt.dropEffect != "link")
|
||||||
let newIndex = this._getDropIndex(event);
|
return;
|
||||||
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--;
|
|
||||||
|
|
||||||
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, { });
|
let url = browserDragAndDrop.drop(event, { });
|
||||||
|
|
||||||
// valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
|
// Disallow dropping strings that contain spaces (not a valid url
|
||||||
// Also disallow dropping javascript: or data: urls--bail out
|
// character). Also disallow dropping javascript: or data: urls.
|
||||||
if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
|
if (!url || !url.length || url.indexOf(" ") != -1 ||
|
||||||
/^\s*(javascript|data):/.test(url))
|
/^\s*(javascript|data):/.test(url))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -3517,7 +3857,7 @@
|
||||||
bgLoad = !bgLoad;
|
bgLoad = !bgLoad;
|
||||||
|
|
||||||
let tab = this._getDragTargetTab(event);
|
let tab = this._getDragTargetTab(event);
|
||||||
if (!tab || dropEffect == "copy") {
|
if (!tab) {
|
||||||
// We're adding a new tab.
|
// We're adding a new tab.
|
||||||
let newIndex = this._getDropIndex(event);
|
let newIndex = this._getDropIndex(event);
|
||||||
let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
|
let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
|
||||||
|
@ -3528,78 +3868,12 @@
|
||||||
this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
|
this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
|
||||||
if (!bgLoad)
|
if (!bgLoad)
|
||||||
this.selectedItem = tab;
|
this.selectedItem = tab;
|
||||||
|
ind.collapsed = true;
|
||||||
|
return;
|
||||||
} catch(ex) {
|
} 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>
|
||||||
|
|
||||||
<handler event="dragexit"><![CDATA[
|
<handler event="dragexit"><![CDATA[
|
||||||
|
@ -3741,7 +4015,6 @@
|
||||||
|
|
||||||
<field name="mOverCloseButton">false</field>
|
<field name="mOverCloseButton">false</field>
|
||||||
<field name="mCorrespondingMenuitem">null</field>
|
<field name="mCorrespondingMenuitem">null</field>
|
||||||
<field name="_fullyOpen">false</field>
|
|
||||||
<field name="closing">false</field>
|
<field name="closing">false</field>
|
||||||
</implementation>
|
</implementation>
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,6 @@
|
||||||
// Services = object with smart getters for common XPCOM services
|
// Services = object with smart getters for common XPCOM services
|
||||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
|
|
||||||
|
|
||||||
var gBidiUI = false;
|
var gBidiUI = false;
|
||||||
|
|
||||||
function getBrowserURL()
|
function getBrowserURL()
|
||||||
|
|
|
@ -1600,6 +1600,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab drag and drop */
|
/* Tab drag and drop */
|
||||||
|
.tab-drag-label {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.tab-drop-indicator {
|
.tab-drop-indicator {
|
||||||
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
|
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
|
||||||
margin-bottom: -11px;
|
margin-bottom: -11px;
|
||||||
|
|
|
@ -1560,6 +1560,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||||
height: 26px;
|
height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-drag-label,
|
||||||
.tabbrowser-tab,
|
.tabbrowser-tab,
|
||||||
.tabs-newtab-button {
|
.tabs-newtab-button {
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
|
@ -1837,6 +1838,19 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||||
* Tab Drag and Drop
|
* 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 {
|
.tab-drop-indicator {
|
||||||
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
|
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
|
||||||
margin-bottom: -8px;
|
margin-bottom: -8px;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-drag-preview::before,
|
||||||
#appmenu-button {
|
#appmenu-button {
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
-moz-border-left-colors: @appMenuButtonBorderColor@;
|
-moz-border-left-colors: @appMenuButtonBorderColor@;
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
0 0 2px 1px rgba(255,255,255,.25) inset;
|
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 {
|
#main-window[privatebrowsingmode=temporary] #appmenu-button {
|
||||||
-moz-border-left-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
|
-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);
|
-moz-border-bottom-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
|
||||||
|
@ -55,6 +57,7 @@
|
||||||
-moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
|
-moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-drag-label,
|
||||||
.tabbrowser-tab[selected="true"]:not(:-moz-lwtheme) {
|
.tabbrowser-tab[selected="true"]:not(:-moz-lwtheme) {
|
||||||
background-image: -moz-linear-gradient(white, @toolbarHighlight@ 50%),
|
background-image: -moz-linear-gradient(white, @toolbarHighlight@ 50%),
|
||||||
-moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
|
-moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
|
||||||
|
@ -293,6 +296,31 @@
|
||||||
-moz-linear-gradient(rgba(255,255,255,0), #CCD9EA 200px, #C7D5E7);
|
-moz-linear-gradient(rgba(255,255,255,0), #CCD9EA 200px, #C7D5E7);
|
||||||
background-attachment: fixed;
|
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) {
|
@media not all and (-moz-windows-compositor) {
|
||||||
|
|
|
@ -176,6 +176,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%ifdef MOZ_OFFICIAL_BRANDING
|
%ifdef MOZ_OFFICIAL_BRANDING
|
||||||
|
.tab-drag-preview::before,
|
||||||
#appmenu-button {
|
#appmenu-button {
|
||||||
background-image: -moz-linear-gradient(rgb(247,182,82), rgb(215,98,10) 95%);
|
background-image: -moz-linear-gradient(rgb(247,182,82), rgb(215,98,10) 95%);
|
||||||
border-color: rgba(83,42,6,.9);
|
border-color: rgba(83,42,6,.9);
|
||||||
|
@ -199,6 +200,7 @@
|
||||||
}
|
}
|
||||||
%else
|
%else
|
||||||
%if MOZ_UPDATE_CHANNEL == aurora
|
%if MOZ_UPDATE_CHANNEL == aurora
|
||||||
|
.tab-drag-preview::before,
|
||||||
#appmenu-button {
|
#appmenu-button {
|
||||||
background-image: -moz-linear-gradient(hsl(208,99%,37%), hsl(214,90%,23%) 95%);
|
background-image: -moz-linear-gradient(hsl(208,99%,37%), hsl(214,90%,23%) 95%);
|
||||||
border-color: hsla(214,89%,21%,.9);
|
border-color: hsla(214,89%,21%,.9);
|
||||||
|
@ -221,6 +223,7 @@
|
||||||
0 1px 1px rgba(0,0,0,.2) inset;
|
0 1px 1px rgba(0,0,0,.2) inset;
|
||||||
}
|
}
|
||||||
%else
|
%else
|
||||||
|
.tab-drag-preview::before,
|
||||||
#appmenu-button {
|
#appmenu-button {
|
||||||
background-image: -moz-linear-gradient(hsl(211,33%,32%), hsl(209,53%,10%) 95%);
|
background-image: -moz-linear-gradient(hsl(211,33%,32%), hsl(209,53%,10%) 95%);
|
||||||
border-color: hsla(210,59%,13%,.9);
|
border-color: hsla(210,59%,13%,.9);
|
||||||
|
@ -245,6 +248,7 @@
|
||||||
%endif
|
%endif
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
#main-window[privatebrowsingmode=temporary] #tabbrowser-tabs > hbox > .tab-drag-panel > .tab-drag-preview::before,
|
||||||
#main-window[privatebrowsingmode=temporary] #appmenu-button {
|
#main-window[privatebrowsingmode=temporary] #appmenu-button {
|
||||||
background-image: -moz-linear-gradient(rgb(153,38,211), rgb(105,19,163) 95%);
|
background-image: -moz-linear-gradient(rgb(153,38,211), rgb(105,19,163) 95%);
|
||||||
border-color: rgba(43,8,65,.9);
|
border-color: rgba(43,8,65,.9);
|
||||||
|
@ -1806,7 +1810,25 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||||
outline: 1px dotted;
|
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 {
|
.tab-drop-indicator {
|
||||||
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
|
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
|
||||||
margin-bottom: -11px;
|
margin-bottom: -11px;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче