Commit c5bf0f26 authored by John Lee's avatar John Lee Committed by Commit Bot

WebUI Tab Strip: Move drag and drop handling to a separate file

This CLs organizes most of the drag and drop logic to its own
manager service, in preparation for more complex logic. This CL
does not change any implementation and only moves code around.

Follow up CLs will modify the drag interactions to only modify the
DOM and allow canceling drag sessions, with drop being the commit
to move tabs or tab groups. This will also prepare the manager to
be able to support dragging tabs from other windows into place.

Bug: 1048894
Change-Id: Ieb66ee859a09b972f178a8c50513aa2f284e08d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2036932Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Commit-Queue: John Lee <johntlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#738668}
parent 0fd86f27
...@@ -11,6 +11,7 @@ js_type_check("closure_compile") { ...@@ -11,6 +11,7 @@ js_type_check("closure_compile") {
":alert_indicator", ":alert_indicator",
":alert_indicators", ":alert_indicators",
":custom_element", ":custom_element",
":drag_manager",
":tab", ":tab",
":tab_group", ":tab_group",
":tab_list", ":tab_list",
...@@ -40,6 +41,16 @@ js_library("alert_indicators") { ...@@ -40,6 +41,16 @@ js_library("alert_indicators") {
js_library("custom_element") { js_library("custom_element") {
} }
js_library("drag_manager") {
deps = [
":tab",
":tab_group",
":tabs_api_proxy",
"//ui/webui/resources/js:assert.m",
"//ui/webui/resources/js:load_time_data.m",
]
}
js_library("tabs_api_proxy") { js_library("tabs_api_proxy") {
deps = [ "//ui/webui/resources/js:cr.m" ] deps = [ "//ui/webui/resources/js:cr.m" ]
externs_list = [ externs_list = [
...@@ -73,6 +84,7 @@ js_library("tab_group") { ...@@ -73,6 +84,7 @@ js_library("tab_group") {
js_library("tab_list") { js_library("tab_list") {
deps = [ deps = [
":custom_element", ":custom_element",
":drag_manager",
":tab", ":tab",
":tab_group", ":tab_group",
":tab_strip_embedder_proxy", ":tab_strip_embedder_proxy",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './strings.m.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {isTabElement, TabElement} from './tab.js';
import {isTabGroupElement, TabGroupElement} from './tab_group.js';
import {TabsApiProxy} from './tabs_api_proxy.js';
/**
* Gets the data type of tab IDs on DataTransfer objects in drag events. This
* is a function so that loadTimeData can get overridden by tests.
* @return {string}
*/
function getTabIdDataType() {
return loadTimeData.getString('tabIdDataType');
}
/** @return {string} */
function getGroupIdDataType() {
return loadTimeData.getString('tabGroupIdDataType');
}
/**
* @interface
*/
export class DragManagerDelegate {
/**
* @param {!TabElement} tabElement
* @return {number}
*/
getIndexOfTab(tabElement) {}
/** @param {!Element} element */
showDropPlaceholder(element) {}
}
/** @typedef {!DragManagerDelegate|!HTMLElement} */
let DragManagerDelegateElement;
export class DragManager {
/** @param {!DragManagerDelegateElement} delegate */
constructor(delegate) {
/** @private {!DragManagerDelegateElement} */
this.delegate_ = delegate;
/**
* The element currently being dragged.
* @type {?TabElement|?TabGroupElement}
*/
this.draggedItem_ = null;
/** @type {!Element} */
this.dropPlaceholder_ = document.createElement('div');
this.dropPlaceholder_.id = 'dropPlaceholder';
/** @private {!TabsApiProxy} */
this.tabsProxy_ = TabsApiProxy.getInstance();
}
/**
* @param {!DragEvent} event
* @param {number} windowId
*/
continueDrag(event, windowId) {
event.preventDefault();
if (!this.draggedItem_) {
this.delegate_.showDropPlaceholder(this.dropPlaceholder_);
return;
}
event.dataTransfer.dropEffect = 'move';
if (isTabGroupElement(this.draggedItem_)) {
this.continueDragWithGroupElement_(event);
} else if (isTabElement(this.draggedItem_)) {
this.continueDragWithTabElement_(event, windowId);
}
}
/**
* @param {!DragEvent} event
* @private
*/
continueDragWithGroupElement_(event) {
const composedPath = /** @type {!Array<!Element>} */ (event.composedPath());
if (composedPath.includes(assert(this.draggedItem_))) {
// Dragging over itself or a child of itself.
return;
}
const dragOverTabElement =
/** @type {!TabElement|undefined} */ (composedPath.find(isTabElement));
if (dragOverTabElement && !dragOverTabElement.tab.pinned) {
const dragOverIndex = this.delegate_.getIndexOfTab(dragOverTabElement);
this.tabsProxy_.moveGroup(
this.draggedItem_.dataset.groupId, dragOverIndex);
return;
}
const dragOverGroupElement = composedPath.find(isTabGroupElement);
if (dragOverGroupElement) {
const dragOverIndex = this.delegate_.getIndexOfTab(
/** @type {!TabElement} */ (dragOverGroupElement.firstElementChild));
this.tabsProxy_.moveGroup(
this.draggedItem_.dataset.groupId, dragOverIndex);
}
}
/**
* @param {!DragEvent} event
* @param {number} windowId
* @private
*/
continueDragWithTabElement_(event, windowId) {
const composedPath = /** @type {!Array<!Element>} */ (event.composedPath());
const dragOverTabElement =
/** @type {?TabElement} */ (composedPath.find(isTabElement));
if (dragOverTabElement &&
dragOverTabElement.tab.pinned !== this.draggedItem_.tab.pinned) {
// Can only drag between the same pinned states.
return;
}
const dragOverTabGroup =
/** @type {?TabGroupElement} */ (composedPath.find(isTabGroupElement));
if (dragOverTabGroup &&
dragOverTabGroup.dataset.groupId !== this.draggedItem_.tab.groupId) {
this.tabsProxy_.groupTab(
this.draggedItem_.tab.id, dragOverTabGroup.dataset.groupId);
return;
}
if (!dragOverTabGroup && this.draggedItem_.tab.groupId) {
this.tabsProxy_.ungroupTab(this.draggedItem_.tab.id);
return;
}
if (!dragOverTabElement) {
return;
}
const dragOverIndex = this.delegate_.getIndexOfTab(dragOverTabElement);
this.tabsProxy_.moveTab(this.draggedItem_.tab.id, windowId, dragOverIndex);
}
cancelDrag() {
this.dropPlaceholder_.remove();
}
/**
* @param {!DragEvent} event
* @param {number} windowId
*/
drop(event, windowId) {
if (this.draggedItem_) {
// If there is a valid dragged item, the drag originated from this TabList
// and is handled already by previous dragover events.
return;
}
this.dropPlaceholder_.remove();
if (event.dataTransfer.types.includes(getTabIdDataType())) {
const tabId = Number(event.dataTransfer.getData(getTabIdDataType()));
if (Number.isNaN(tabId)) {
// Invalid tab ID. Return silently.
return;
}
this.tabsProxy_.moveTab(tabId, windowId, -1);
} else if (event.dataTransfer.types.includes(getGroupIdDataType())) {
const groupId = event.dataTransfer.getData(getGroupIdDataType());
this.tabsProxy_.moveGroup(groupId, -1);
}
}
/** @param {!DragEvent} event */
startDrag(event) {
const draggedItem =
/** @type {!Array<!Element>} */ (event.composedPath()).find(item => {
return isTabElement(item) || isTabGroupElement(item);
});
if (!draggedItem) {
return;
}
this.draggedItem_ = /** @type {!TabElement} */ (draggedItem);
event.dataTransfer.effectAllowed = 'move';
const draggedItemRect = this.draggedItem_.getBoundingClientRect();
this.draggedItem_.setDragging(true);
event.dataTransfer.setDragImage(
this.draggedItem_.getDragImage(), event.clientX - draggedItemRect.left,
event.clientY - draggedItemRect.top);
if (isTabElement(draggedItem)) {
event.dataTransfer.setData(
getTabIdDataType(), this.draggedItem_.tab.id.toString());
} else if (isTabGroupElement(draggedItem)) {
event.dataTransfer.setData(
getGroupIdDataType(), this.draggedItem_.dataset.groupId);
}
}
/** @param {!DragEvent} event */
stopDrag(event) {
if (!this.draggedItem_) {
return;
}
this.draggedItem_.setDragging(false);
this.draggedItem_ = null;
}
}
...@@ -379,3 +379,11 @@ export class TabElement extends CustomElement { ...@@ -379,3 +379,11 @@ export class TabElement extends CustomElement {
} }
customElements.define('tabstrip-tab', TabElement); customElements.define('tabstrip-tab', TabElement);
/**
* @param {!Element} element
* @return {boolean}
*/
export function isTabElement(element) {
return element.tagName === 'TABSTRIP-TAB';
}
...@@ -42,3 +42,11 @@ export class TabGroupElement extends CustomElement { ...@@ -42,3 +42,11 @@ export class TabGroupElement extends CustomElement {
} }
customElements.define('tabstrip-tab-group', TabGroupElement); customElements.define('tabstrip-tab-group', TabGroupElement);
/**
* @param {!Element} element
* @return {boolean}
*/
export function isTabGroupElement(element) {
return element.tagName === 'TABSTRIP-TAB-GROUP';
}
...@@ -14,8 +14,9 @@ import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; ...@@ -14,8 +14,9 @@ import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {isRTL} from 'chrome://resources/js/util.m.js'; import {isRTL} from 'chrome://resources/js/util.m.js';
import {CustomElement} from './custom_element.js'; import {CustomElement} from './custom_element.js';
import {DragManager, DragManagerDelegate} from './drag_manager.js';
import {TabElement} from './tab.js'; import {TabElement} from './tab.js';
import {TabGroupElement} from './tab_group.js'; import {isTabGroupElement, TabGroupElement} from './tab_group.js';
import {TabStripEmbedderProxy} from './tab_strip_embedder_proxy.js'; import {TabStripEmbedderProxy} from './tab_strip_embedder_proxy.js';
import {tabStripOptions} from './tab_strip_options.js'; import {tabStripOptions} from './tab_strip_options.js';
import {TabData, TabGroupVisualData, TabsApiProxy} from './tabs_api_proxy.js'; import {TabData, TabGroupVisualData, TabsApiProxy} from './tabs_api_proxy.js';
...@@ -36,20 +37,6 @@ export function setScrollAnimationEnabledForTesting(enabled) { ...@@ -36,20 +37,6 @@ export function setScrollAnimationEnabledForTesting(enabled) {
scrollAnimationEnabled = enabled; scrollAnimationEnabled = enabled;
} }
/**
* Gets the data type of tab IDs on DataTransfer objects in drag events. This
* is a function so that loadTimeData can get overridden by tests.
* @return {string}
*/
function getTabIdDataType() {
return loadTimeData.getString('tabIdDataType');
}
/** @return {string} */
function getGroupIdDataType() {
return loadTimeData.getString('tabGroupIdDataType');
}
/** /**
* @enum {string} * @enum {string}
*/ */
...@@ -60,22 +47,7 @@ const LayoutVariable = { ...@@ -60,22 +47,7 @@ const LayoutVariable = {
TAB_WIDTH: '--tabstrip-tab-thumbnail-width', TAB_WIDTH: '--tabstrip-tab-thumbnail-width',
}; };
/** /** @implements {DragManagerDelegate} */
* @param {!Element} element
* @return {boolean}
*/
function isTabElement(element) {
return element.tagName === 'TABSTRIP-TAB';
}
/**
* @param {!Element} element
* @return {boolean}
*/
function isTabGroupElement(element) {
return element.tagName === 'TABSTRIP-TAB-GROUP';
}
class TabListElement extends CustomElement { class TabListElement extends CustomElement {
static get template() { static get template() {
return `{__html_template__}`; return `{__html_template__}`;
...@@ -182,16 +154,6 @@ class TabListElement extends CustomElement { ...@@ -182,16 +154,6 @@ class TabListElement extends CustomElement {
this.addWebUIListener_( this.addWebUIListener_(
'tab-thumbnail-updated', this.tabThumbnailUpdated_.bind(this)); 'tab-thumbnail-updated', this.tabThumbnailUpdated_.bind(this));
this.addEventListener(
'dragstart', (e) => this.onDragStart_(/** @type {!DragEvent} */ (e)));
this.addEventListener(
'dragend', (e) => this.onDragEnd_(/** @type {!DragEvent} */ (e)));
this.addEventListener('dragleave', () => this.onDragLeave_());
this.addEventListener(
'dragover', (e) => this.onDragOver_(/** @type {!DragEvent} */ (e)));
this.addEventListener(
'drop', e => this.onDrop_(/** @type {!DragEvent} */ (e)));
document.addEventListener('contextmenu', this.contextMenuListener_); document.addEventListener('contextmenu', this.contextMenuListener_);
document.addEventListener( document.addEventListener(
'visibilitychange', this.documentVisibilityChangeListener_); 'visibilitychange', this.documentVisibilityChangeListener_);
...@@ -203,6 +165,20 @@ class TabListElement extends CustomElement { ...@@ -203,6 +165,20 @@ class TabListElement extends CustomElement {
this.tabsApi_.createNewTab(); this.tabsApi_.createNewTab();
}); });
const dragManager = new DragManager(this);
this.addEventListener(
'dragstart', e => dragManager.startDrag(/** @type {!DragEvent} */ (e)));
this.addEventListener(
'dragend', e => dragManager.stopDrag(/** @type {!DragEvent} */ (e)));
this.addEventListener('dragleave', () => dragManager.cancelDrag());
this.addEventListener(
'dragover',
e => dragManager.continueDrag(
/** @type {!DragEvent} */ (e), this.windowId_));
this.addEventListener(
'drop',
e => dragManager.drop(/** @type {!DragEvent} */ (e), this.windowId_));
if (loadTimeData.getBoolean('showDemoOptions')) { if (loadTimeData.getBoolean('showDemoOptions')) {
this.$('#demoOptions').style.display = 'block'; this.$('#demoOptions').style.display = 'block';
...@@ -397,6 +373,14 @@ class TabListElement extends CustomElement { ...@@ -397,6 +373,14 @@ class TabListElement extends CustomElement {
return /** @type {?TabElement} */ (this.$('tabstrip-tab[active]')); return /** @type {?TabElement} */ (this.$('tabstrip-tab[active]'));
} }
/**
* @param {!TabElement} tabElement
* @return {number}
*/
getIndexOfTab(tabElement) {
return Array.prototype.indexOf.call(this.$all('tabstrip-tab'), tabElement);
}
/** /**
* @param {!LayoutVariable} variable * @param {!LayoutVariable} variable
* @return {number} in pixels * @return {number} in pixels
...@@ -492,164 +476,6 @@ class TabListElement extends CustomElement { ...@@ -492,164 +476,6 @@ class TabListElement extends CustomElement {
}); });
} }
/**
* @param {!DragEvent} event
* @private
*/
onDragEnd_(event) {
if (!this.draggedItem_) {
return;
}
this.draggedItem_.setDragging(false);
this.draggedItem_ = undefined;
}
/** @private */
onDragLeave_() {
this.dropPlaceholder_.remove();
}
/**
* @param {!DragEvent} event
* @private
*/
onDragOver_(event) {
event.preventDefault();
if (!this.draggedItem_) {
this.unpinnedTabsElement_.appendChild(this.dropPlaceholder_);
this.animateScrollPosition_(this.dropPlaceholder_.offsetLeft);
return;
}
event.dataTransfer.dropEffect = 'move';
if (isTabGroupElement(this.draggedItem_)) {
this.onDragOverWithGroupElement_(event);
} else if (isTabElement(this.draggedItem_)) {
this.onDragOverWithTabElement_(event);
}
}
/**
* @param {!DragEvent} event
* @private
*/
onDragOverWithGroupElement_(event) {
const composedPath = /** @type {!Array<!Element>} */ (event.composedPath());
if (composedPath.includes(assert(this.draggedItem_))) {
// Dragging over itself or a child of itself.
return;
}
const allTabElements = Array.from(this.$all('tabstrip-tab'));
const dragOverTabElement = composedPath.find(isTabElement);
if (dragOverTabElement && !dragOverTabElement.tab.pinned) {
const dragOverIndex = allTabElements.indexOf(dragOverTabElement);
this.tabsApi_.moveGroup(this.draggedItem_.dataset.groupId, dragOverIndex);
return;
}
const dragOverGroupElement = composedPath.find(isTabGroupElement);
if (dragOverGroupElement) {
const dragOverIndex =
allTabElements.indexOf(dragOverGroupElement.firstElementChild);
this.tabsApi_.moveGroup(this.draggedItem_.dataset.groupId, dragOverIndex);
}
}
/**
* @param {!DragEvent} event
* @private
*/
onDragOverWithTabElement_(event) {
const composedPath = /** @type {!Array<!Element>} */ (event.composedPath());
const dragOverTabElement = composedPath.find(isTabElement);
if (dragOverTabElement &&
dragOverTabElement.tab.pinned !== this.draggedItem_.tab.pinned) {
// Can only drag between the same pinned states.
return;
}
const dragOverTabGroup = composedPath.find(isTabGroupElement);
if (dragOverTabGroup &&
dragOverTabGroup.dataset.groupId !== this.draggedItem_.tab.groupId) {
this.tabsApi_.groupTab(
this.draggedItem_.tab.id, dragOverTabGroup.dataset.groupId);
return;
}
if (!dragOverTabGroup && this.draggedItem_.tab.groupId) {
this.tabsApi_.ungroupTab(this.draggedItem_.tab.id);
return;
}
if (!dragOverTabElement) {
return;
}
const dragOverIndex =
Array.from(this.$all('tabstrip-tab')).indexOf(dragOverTabElement);
this.tabsApi_.moveTab(
this.draggedItem_.tab.id, this.windowId_, dragOverIndex);
}
/**
* @param {!DragEvent} event
* @private
*/
onDragStart_(event) {
const draggedItem =
/** @type {!Array<!Element>} */ (event.composedPath()).find(item => {
return isTabElement(item) || isTabGroupElement(item);
});
if (!draggedItem) {
return;
}
this.draggedItem_ = /** @type {!TabElement} */ (draggedItem);
event.dataTransfer.effectAllowed = 'move';
const draggedItemRect = this.draggedItem_.getBoundingClientRect();
this.draggedItem_.setDragging(true);
event.dataTransfer.setDragImage(
this.draggedItem_.getDragImage(), event.clientX - draggedItemRect.left,
event.clientY - draggedItemRect.top);
if (isTabElement(draggedItem)) {
event.dataTransfer.setData(
getTabIdDataType(), this.draggedItem_.tab.id.toString());
} else if (isTabGroupElement(draggedItem)) {
event.dataTransfer.setData(
getGroupIdDataType(), this.draggedItem_.dataset.groupId);
}
}
/**
* @param {!DragEvent} event
* @private
*/
onDrop_(event) {
if (this.draggedItem_) {
// If there is a valid dragged item, the drag originated from this TabList
// and is handled already by previous dragover events.
return;
}
this.dropPlaceholder_.remove();
if (event.dataTransfer.types.includes(getTabIdDataType())) {
const tabId = Number(event.dataTransfer.getData(getTabIdDataType()));
if (Number.isNaN(tabId)) {
// Invalid tab ID. Return silently.
return;
}
this.tabsApi_.moveTab(tabId, this.windowId_, -1);
} else if (event.dataTransfer.types.includes(getGroupIdDataType())) {
const groupId = event.dataTransfer.getData(getGroupIdDataType());
this.tabsApi_.moveGroup(groupId, -1);
}
}
/** @private */ /** @private */
onReceivedKeyboardFocus_() { onReceivedKeyboardFocus_() {
// FocusOutlineManager relies on the most recent event fired on the // FocusOutlineManager relies on the most recent event fired on the
...@@ -907,6 +733,12 @@ class TabListElement extends CustomElement { ...@@ -907,6 +733,12 @@ class TabListElement extends CustomElement {
this.animateScrollPosition_(scrollBy); this.animateScrollPosition_(scrollBy);
} }
/** @param {!Element} element */
showDropPlaceholder(element) {
this.unpinnedTabsElement_.appendChild(element);
this.animateScrollPosition_(element.offsetLeft);
}
/** /**
* @param {number} tabId * @param {number} tabId
* @param {string} imgData * @param {string} imgData
......
...@@ -72,6 +72,11 @@ ...@@ -72,6 +72,11 @@
file="tab_swiper.js" file="tab_swiper.js"
type="chrome_html" type="chrome_html"
compress="gzip"/> compress="gzip"/>
<structure
name="IDR_TAB_STRIP_DRAG_MANAGER_JS"
file="drag_manager.js"
type="chrome_html"
compress="gzip"/>
</structures> </structures>
<includes> <includes>
......
...@@ -139,6 +139,7 @@ export class TabsApiProxy { ...@@ -139,6 +139,7 @@ export class TabsApiProxy {
/** /**
* @param {number} tabId * @param {number} tabId
* @param {number} windowId
* @param {number} newIndex * @param {number} newIndex
* @return {!Promise<!ExtensionsApiTab>} * @return {!Promise<!ExtensionsApiTab>}
*/ */
......
This diff is collapsed.
...@@ -12,43 +12,6 @@ import {TabsApiProxy} from 'chrome://tab-strip/tabs_api_proxy.js'; ...@@ -12,43 +12,6 @@ import {TabsApiProxy} from 'chrome://tab-strip/tabs_api_proxy.js';
import {TestTabStripEmbedderProxy} from './test_tab_strip_embedder_proxy.js'; import {TestTabStripEmbedderProxy} from './test_tab_strip_embedder_proxy.js';
import {TestTabsApiProxy} from './test_tabs_api_proxy.js'; import {TestTabsApiProxy} from './test_tabs_api_proxy.js';
class MockDataTransfer extends DataTransfer {
constructor() {
super();
this.dragImageData = {
image: undefined,
offsetX: undefined,
offsetY: undefined,
};
this.dropEffect_ = 'none';
this.effectAllowed_ = 'none';
}
get dropEffect() {
return this.dropEffect_;
}
set dropEffect(effect) {
this.dropEffect_ = effect;
}
get effectAllowed() {
return this.effectAllowed_;
}
set effectAllowed(effect) {
this.effectAllowed_ = effect;
}
setDragImage(image, offsetX, offsetY) {
this.dragImageData.image = image;
this.dragImageData.offsetX = offsetX;
this.dragImageData.offsetY = offsetY;
}
}
suite('TabList', () => { suite('TabList', () => {
let callbackRouter; let callbackRouter;
let optionsCalled; let optionsCalled;
...@@ -84,10 +47,6 @@ suite('TabList', () => { ...@@ -84,10 +47,6 @@ suite('TabList', () => {
]; ];
const currentWindowId = 1000; const currentWindowId = 1000;
const strings = {
tabIdDataType: 'application/tab-id',
};
function pinTabAt(tab, index) { function pinTabAt(tab, index) {
const changeInfo = {index: index, pinned: true}; const changeInfo = {index: index, pinned: true};
const updatedTab = Object.assign({}, tab, changeInfo); const updatedTab = Object.assign({}, tab, changeInfo);
...@@ -113,7 +72,6 @@ suite('TabList', () => { ...@@ -113,7 +72,6 @@ suite('TabList', () => {
} }
setup(() => { setup(() => {
loadTimeData.overrideValues(strings);
document.body.innerHTML = ''; document.body.innerHTML = '';
document.body.style.margin = 0; document.body.style.margin = 0;
...@@ -509,228 +467,6 @@ suite('TabList', () => { ...@@ -509,228 +467,6 @@ suite('TabList', () => {
assertEquals(tabAtIndex0.tab.id, tabToGroup.id); assertEquals(tabAtIndex0.tab.id, tabToGroup.id);
}); });
test('dragstart sets a drag image offset by the event coordinates', () => {
// Drag and drop only works for pinned tabs
tabs.forEach(pinTabAt);
const draggedTab = getPinnedTabs()[0];
const mockDataTransfer = new MockDataTransfer();
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
draggedTab.dispatchEvent(dragStartEvent);
assertEquals(dragStartEvent.dataTransfer.effectAllowed, 'move');
assertEquals(
mockDataTransfer.dragImageData.image, draggedTab.getDragImage());
assertEquals(
mockDataTransfer.dragImageData.offsetX, 100 - draggedTab.offsetLeft);
assertEquals(
mockDataTransfer.dragImageData.offsetY, 150 - draggedTab.offsetTop);
});
test('dragover moves tabs', async () => {
// Drag and drop only works for pinned tabs
tabs.forEach(pinTabAt);
const draggedIndex = 0;
const dragOverIndex = 1;
const draggedTab = getPinnedTabs()[draggedIndex];
const dragOverTab = getPinnedTabs()[dragOverIndex];
const mockDataTransfer = new MockDataTransfer();
// Dispatch a dragstart event to start the drag process
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
draggedTab.dispatchEvent(dragStartEvent);
// Move the draggedTab over the 2nd tab
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
composed: true,
dataTransfer: mockDataTransfer,
});
dragOverTab.dispatchEvent(dragOverEvent);
assertEquals(dragOverEvent.dataTransfer.dropEffect, 'move');
const [tabId, windowId, newIndex] =
await testTabsApiProxy.whenCalled('moveTab');
assertEquals(tabId, tabs[draggedIndex].id);
assertEquals(currentWindowId, windowId);
assertEquals(newIndex, dragOverIndex);
});
test('DragTabOverTabGroup', async () => {
const tabElements = getUnpinnedTabs();
// Group the first tab.
webUIListenerCallback(
'tab-group-state-changed', tabElements[0].tab.id, 0, 'group0');
// Start dragging the second tab.
const draggedTab = tabElements[1];
const mockDataTransfer = new MockDataTransfer();
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
draggedTab.dispatchEvent(dragStartEvent);
// Drag the second tab over the newly created tab group.
const dragOverTabGroup = getTabGroups()[0];
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
composed: true,
dataTransfer: mockDataTransfer,
});
dragOverTabGroup.dispatchEvent(dragOverEvent);
const [tabId, groupId] = await testTabsApiProxy.whenCalled('groupTab');
assertEquals(draggedTab.tab.id, tabId);
assertEquals('group0', groupId);
});
test('DragTabOutOfTabGroup', async () => {
const tabElements = getUnpinnedTabs();
// Group the first tab.
webUIListenerCallback(
'tab-group-state-changed', tabElements[0].tab.id, 0, 'group0');
// Start dragging the first tab.
const draggedTab = tabElements[0];
const mockDataTransfer = new MockDataTransfer();
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
draggedTab.dispatchEvent(dragStartEvent);
// Drag the first tab out.
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
composed: true,
dataTransfer: mockDataTransfer,
});
tabList.dispatchEvent(dragOverEvent);
const [tabId] = await testTabsApiProxy.whenCalled('ungroupTab');
assertEquals(draggedTab.tab.id, tabId);
});
test('DragGroupOverTab', async () => {
const tabElements = getUnpinnedTabs();
// Group the first tab.
webUIListenerCallback(
'tab-group-state-changed', tabElements[0].tab.id, 0, 'group0');
// Start dragging the group.
const draggedGroup = getTabGroups()[0];
const mockDataTransfer = new MockDataTransfer();
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
draggedGroup.dispatchEvent(dragStartEvent);
// Drag the group over the second tab.
const dragOverTab = tabElements[1];
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
composed: true,
dataTransfer: mockDataTransfer,
});
dragOverTab.dispatchEvent(dragOverEvent);
const [groupId, index] = await testTabsApiProxy.whenCalled('moveGroup');
assertEquals('group0', groupId);
assertEquals(1, index);
});
test('DragGroupOverGroup', async () => {
const tabElements = getUnpinnedTabs();
// Group the first tab and second tab separately.
webUIListenerCallback(
'tab-group-state-changed', tabElements[0].tab.id, 0, 'group0');
webUIListenerCallback(
'tab-group-state-changed', tabElements[1].tab.id, 1, 'group1');
// Start dragging the first group.
const draggedGroup = getTabGroups()[0];
const mockDataTransfer = new MockDataTransfer();
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
draggedGroup.dispatchEvent(dragStartEvent);
// Drag the group over the second tab.
const dragOverGroup = getTabGroups()[1];
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
composed: true,
dataTransfer: mockDataTransfer,
});
dragOverGroup.dispatchEvent(dragOverEvent);
const [groupId, index] = await testTabsApiProxy.whenCalled('moveGroup');
assertEquals('group0', groupId);
assertEquals(1, index);
});
test('DragTabIntoListFromOutside', () => {
const mockDataTransfer = new MockDataTransfer();
mockDataTransfer.setData(strings.tabIdDataType, '1000');
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
composed: true,
dataTransfer: mockDataTransfer,
});
tabList.dispatchEvent(dragOverEvent);
assertTrue(
tabList.$('#unpinnedTabs').lastElementChild.id === 'dropPlaceholder');
tabList.dispatchEvent(new DragEvent('drop', dragOverEvent));
assertEquals(null, tabList.$('dropPlaceholder'));
});
test('DropTabIntoList', async () => {
const droppedTabId = 9000;
const mockDataTransfer = new MockDataTransfer();
mockDataTransfer.setData(strings.tabIdDataType, droppedTabId);
const dropEvent = new DragEvent('drop', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
tabList.dispatchEvent(dropEvent);
const [tabId, windowId, index] =
await testTabsApiProxy.whenCalled('moveTab');
assertEquals(droppedTabId, tabId);
assertEquals(currentWindowId, windowId);
assertEquals(-1, index);
});
test('tracks and untracks thumbnails based on viewport', async () => { test('tracks and untracks thumbnails based on viewport', async () => {
// Wait for slideIn animations to complete updating widths and reset // Wait for slideIn animations to complete updating widths and reset
// resolvers to track new calls. // resolvers to track new calls.
......
...@@ -85,3 +85,13 @@ var TabStripTabGroupTest = class extends TabStripBrowserTest { ...@@ -85,3 +85,13 @@ var TabStripTabGroupTest = class extends TabStripBrowserTest {
TEST_F('TabStripTabGroupTest', 'All', function() { TEST_F('TabStripTabGroupTest', 'All', function() {
mocha.run(); mocha.run();
}); });
var TabStripDragManagerTest = class extends TabStripBrowserTest {
get browsePreload() {
return 'chrome://tab-strip/test_loader.html?module=tab_strip/drag_manager_test.js';
}
};
TEST_F('TabStripDragManagerTest', 'All', function() {
mocha.run();
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment