bump
This commit is contained in:
462
daemon/daemon.go
462
daemon/daemon.go
@@ -6480,6 +6480,16 @@ func (d *Daemon) dragAndDropByOffset(tabID, sourceSelector string, offsetX, offs
|
||||
|
||||
// performDragAndDrop performs the actual drag and drop operation between two elements
|
||||
func (d *Daemon) performDragAndDrop(page *rod.Page, sourceSelector, targetSelector string) error {
|
||||
// First, try the enhanced HTML5 drag and drop approach
|
||||
err := d.performHTML5DragAndDrop(page, sourceSelector, targetSelector)
|
||||
if err == nil {
|
||||
d.debugLog("HTML5 drag and drop completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
d.debugLog("HTML5 drag and drop failed (%v), falling back to mouse events", err)
|
||||
|
||||
// Fallback to the original mouse-based approach
|
||||
// Find source element
|
||||
sourceElement, err := page.Element(sourceSelector)
|
||||
if err != nil {
|
||||
@@ -6524,8 +6534,390 @@ func (d *Daemon) performDragAndDrop(page *rod.Page, sourceSelector, targetSelect
|
||||
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, targetX, targetY)
|
||||
}
|
||||
|
||||
// injectDragDropHelpers injects the JavaScript drag and drop helper functions into the page
|
||||
func (d *Daemon) injectDragDropHelpers(page *rod.Page) error {
|
||||
// Read the JavaScript helper file
|
||||
jsHelpers := `
|
||||
// HTML5 Drag and Drop Helper Functions for Cremote
|
||||
// These functions are injected into web pages to provide reliable drag and drop functionality
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Create a namespace to avoid conflicts
|
||||
window.cremoteDragDrop = window.cremoteDragDrop || {};
|
||||
|
||||
/**
|
||||
* Simulates HTML5 drag and drop between two elements
|
||||
* @param {string} sourceSelector - CSS selector for source element
|
||||
* @param {string} targetSelector - CSS selector for target element
|
||||
* @returns {Promise<boolean>} - Success status
|
||||
*/
|
||||
window.cremoteDragDrop.dragElementToElement = async function(sourceSelector, targetSelector) {
|
||||
const sourceElement = document.querySelector(sourceSelector);
|
||||
const targetElement = document.querySelector(targetSelector);
|
||||
|
||||
if (!sourceElement) {
|
||||
throw new Error('Source element not found: ' + sourceSelector);
|
||||
}
|
||||
if (!targetElement) {
|
||||
throw new Error('Target element not found: ' + targetSelector);
|
||||
}
|
||||
|
||||
// Make source draggable if not already
|
||||
if (!sourceElement.draggable) {
|
||||
sourceElement.draggable = true;
|
||||
}
|
||||
|
||||
// Create and dispatch dragstart event
|
||||
const dragStartEvent = new DragEvent('dragstart', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: new DataTransfer()
|
||||
});
|
||||
|
||||
// Set drag data
|
||||
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||
|
||||
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||
if (!dragStartResult) {
|
||||
console.log('Dragstart was cancelled');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Small delay to simulate realistic drag timing
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// Create and dispatch dragover event on target
|
||||
const dragOverEvent = new DragEvent('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
const dragOverResult = targetElement.dispatchEvent(dragOverEvent);
|
||||
|
||||
// Create and dispatch drop event on target
|
||||
const dropEvent = new DragEvent('drop', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||
|
||||
// Create and dispatch dragend event on source
|
||||
const dragEndEvent = new DragEvent('dragend', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
sourceElement.dispatchEvent(dragEndEvent);
|
||||
|
||||
return dropResult;
|
||||
};
|
||||
|
||||
console.log('Cremote drag and drop helpers loaded successfully');
|
||||
})();
|
||||
`
|
||||
|
||||
// Inject the JavaScript helpers
|
||||
_, err := page.Eval(jsHelpers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to inject drag and drop helpers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// performHTML5DragAndDrop performs drag and drop using HTML5 drag events
|
||||
func (d *Daemon) performHTML5DragAndDrop(page *rod.Page, sourceSelector, targetSelector string) error {
|
||||
// Inject the helper functions
|
||||
err := d.injectDragDropHelpers(page)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to inject helpers: %v", err)
|
||||
}
|
||||
|
||||
// Execute the HTML5 drag and drop
|
||||
jsCode := fmt.Sprintf(`
|
||||
(async function() {
|
||||
try {
|
||||
const result = await window.cremoteDragDrop.dragElementToElement('%s', '%s');
|
||||
return { success: result, error: null };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
})()
|
||||
`, sourceSelector, targetSelector)
|
||||
|
||||
result, err := page.Eval(jsCode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute HTML5 drag and drop: %v", err)
|
||||
}
|
||||
|
||||
// Parse the result
|
||||
resultMap := result.Value.Map()
|
||||
if resultMap == nil {
|
||||
return fmt.Errorf("invalid result from HTML5 drag and drop")
|
||||
}
|
||||
|
||||
success, exists := resultMap["success"]
|
||||
if !exists || !success.Bool() {
|
||||
errorMsg := "unknown error"
|
||||
if errorVal, exists := resultMap["error"]; exists && errorVal.Str() != "" {
|
||||
errorMsg = errorVal.Str()
|
||||
}
|
||||
return fmt.Errorf("HTML5 drag and drop failed: %s", errorMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// injectEnhancedDragDropHelpers injects the complete JavaScript drag and drop helper functions
|
||||
func (d *Daemon) injectEnhancedDragDropHelpers(page *rod.Page) error {
|
||||
// Read the complete JavaScript helper file content
|
||||
jsHelpers := `
|
||||
// Enhanced HTML5 Drag and Drop Helper Functions for Cremote
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Create a namespace to avoid conflicts
|
||||
window.cremoteDragDrop = window.cremoteDragDrop || {};
|
||||
|
||||
/**
|
||||
* Simulates HTML5 drag and drop between two elements
|
||||
*/
|
||||
window.cremoteDragDrop.dragElementToElement = async function(sourceSelector, targetSelector) {
|
||||
const sourceElement = document.querySelector(sourceSelector);
|
||||
const targetElement = document.querySelector(targetSelector);
|
||||
|
||||
if (!sourceElement) {
|
||||
throw new Error('Source element not found: ' + sourceSelector);
|
||||
}
|
||||
if (!targetElement) {
|
||||
throw new Error('Target element not found: ' + targetSelector);
|
||||
}
|
||||
|
||||
// Make source draggable if not already
|
||||
if (!sourceElement.draggable) {
|
||||
sourceElement.draggable = true;
|
||||
}
|
||||
|
||||
// Create and dispatch dragstart event
|
||||
const dragStartEvent = new DragEvent('dragstart', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: new DataTransfer()
|
||||
});
|
||||
|
||||
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||
|
||||
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||
if (!dragStartResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// Create and dispatch dragover event on target
|
||||
const dragOverEvent = new DragEvent('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
targetElement.dispatchEvent(dragOverEvent);
|
||||
|
||||
// Create and dispatch drop event on target
|
||||
const dropEvent = new DragEvent('drop', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||
|
||||
// Create and dispatch dragend event on source
|
||||
const dragEndEvent = new DragEvent('dragend', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
sourceElement.dispatchEvent(dragEndEvent);
|
||||
|
||||
return dropResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if an element can receive drop events
|
||||
*/
|
||||
window.cremoteDragDrop.hasDropEventListener = function(element) {
|
||||
if (element.ondrop) return true;
|
||||
if (element.getAttribute('ondrop')) return true;
|
||||
if (element.ondragover || element.getAttribute('ondragover')) return true;
|
||||
|
||||
const dropIndicators = ['drop-zone', 'dropzone', 'droppable', 'drop-target'];
|
||||
const className = element.className.toLowerCase();
|
||||
return dropIndicators.some(indicator => className.includes(indicator));
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the best drop target at given coordinates
|
||||
*/
|
||||
window.cremoteDragDrop.findDropTargetAtCoordinates = function(x, y) {
|
||||
const elements = document.elementsFromPoint(x, y);
|
||||
|
||||
for (const element of elements) {
|
||||
if (this.hasDropEventListener(element)) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
return elements[0] || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enhanced drag to coordinates that finds the best drop target
|
||||
*/
|
||||
window.cremoteDragDrop.smartDragToCoordinates = async function(sourceSelector, x, y) {
|
||||
const sourceElement = document.querySelector(sourceSelector);
|
||||
|
||||
if (!sourceElement) {
|
||||
throw new Error('Source element not found: ' + sourceSelector);
|
||||
}
|
||||
|
||||
const targetElement = this.findDropTargetAtCoordinates(x, y);
|
||||
if (!targetElement) {
|
||||
throw new Error('No suitable drop target found at coordinates (' + x + ', ' + y + ')');
|
||||
}
|
||||
|
||||
const canReceiveDrops = this.hasDropEventListener(targetElement);
|
||||
|
||||
if (canReceiveDrops) {
|
||||
const success = await this.dragElementToElement(sourceSelector,
|
||||
targetElement.id ? '#' + targetElement.id : targetElement.tagName.toLowerCase());
|
||||
|
||||
return {
|
||||
success: success,
|
||||
method: 'element-to-element',
|
||||
targetElement: {
|
||||
tagName: targetElement.tagName,
|
||||
id: targetElement.id,
|
||||
className: targetElement.className,
|
||||
hasDropListener: true
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Fall back to coordinate-based drag with manual event dispatch
|
||||
const result = await this.dragElementToCoordinates(sourceSelector, x, y);
|
||||
result.method = 'coordinate-based';
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Simulates HTML5 drag and drop from element to coordinates
|
||||
*/
|
||||
window.cremoteDragDrop.dragElementToCoordinates = async function(sourceSelector, x, y) {
|
||||
const sourceElement = document.querySelector(sourceSelector);
|
||||
|
||||
if (!sourceElement) {
|
||||
throw new Error('Source element not found: ' + sourceSelector);
|
||||
}
|
||||
|
||||
const targetElement = document.elementFromPoint(x, y);
|
||||
if (!targetElement) {
|
||||
throw new Error('No element found at coordinates (' + x + ', ' + y + ')');
|
||||
}
|
||||
|
||||
if (!sourceElement.draggable) {
|
||||
sourceElement.draggable = true;
|
||||
}
|
||||
|
||||
const dragStartEvent = new DragEvent('dragstart', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: new DataTransfer()
|
||||
});
|
||||
|
||||
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||
|
||||
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||
if (!dragStartResult) {
|
||||
return { success: false, reason: 'Dragstart was cancelled', targetElement: null };
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
const dragOverEvent = new DragEvent('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
targetElement.dispatchEvent(dragOverEvent);
|
||||
|
||||
const dropEvent = new DragEvent('drop', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||
|
||||
const dragEndEvent = new DragEvent('dragend', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer: dragStartEvent.dataTransfer
|
||||
});
|
||||
|
||||
sourceElement.dispatchEvent(dragEndEvent);
|
||||
|
||||
return {
|
||||
success: dropResult,
|
||||
targetElement: {
|
||||
tagName: targetElement.tagName,
|
||||
id: targetElement.id,
|
||||
className: targetElement.className,
|
||||
hasDropListener: this.hasDropEventListener(targetElement)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
console.log('Enhanced Cremote drag and drop helpers loaded successfully');
|
||||
})();
|
||||
`
|
||||
|
||||
// Inject the JavaScript helpers
|
||||
_, err := page.Eval(jsHelpers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to inject enhanced drag and drop helpers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// performDragAndDropToCoordinates performs drag and drop from element to specific coordinates
|
||||
func (d *Daemon) performDragAndDropToCoordinates(page *rod.Page, sourceSelector string, targetX, targetY int) error {
|
||||
// First, try the enhanced HTML5 approach with smart target detection
|
||||
err := d.performHTML5DragToCoordinates(page, sourceSelector, targetX, targetY)
|
||||
if err == nil {
|
||||
d.debugLog("HTML5 coordinate drag completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
d.debugLog("HTML5 coordinate drag failed (%v), falling back to mouse events", err)
|
||||
|
||||
// Fallback to the original mouse-based approach
|
||||
// Find source element
|
||||
sourceElement, err := page.Element(sourceSelector)
|
||||
if err != nil {
|
||||
@@ -6549,21 +6941,67 @@ func (d *Daemon) performDragAndDropToCoordinates(page *rod.Page, sourceSelector
|
||||
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, float64(targetX), float64(targetY))
|
||||
}
|
||||
|
||||
// performHTML5DragToCoordinates performs HTML5 drag to coordinates with smart target detection
|
||||
func (d *Daemon) performHTML5DragToCoordinates(page *rod.Page, sourceSelector string, targetX, targetY int) error {
|
||||
// First, inject the enhanced helper functions that include coordinate support
|
||||
err := d.injectEnhancedDragDropHelpers(page)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to inject enhanced helpers: %v", err)
|
||||
}
|
||||
|
||||
// Execute the smart coordinate drag
|
||||
jsCode := fmt.Sprintf(`
|
||||
(async function() {
|
||||
try {
|
||||
const result = await window.cremoteDragDrop.smartDragToCoordinates('%s', %d, %d);
|
||||
return { success: result.success, method: result.method, error: null, targetInfo: result.targetElement };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message, method: 'failed', targetInfo: null };
|
||||
}
|
||||
})()
|
||||
`, sourceSelector, targetX, targetY)
|
||||
|
||||
result, err := page.Eval(jsCode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute HTML5 coordinate drag: %v", err)
|
||||
}
|
||||
|
||||
// Parse the result
|
||||
resultMap := result.Value.Map()
|
||||
if resultMap == nil {
|
||||
return fmt.Errorf("invalid result from HTML5 coordinate drag")
|
||||
}
|
||||
|
||||
success, exists := resultMap["success"]
|
||||
if !exists || !success.Bool() {
|
||||
errorMsg := "unknown error"
|
||||
if errorVal, exists := resultMap["error"]; exists && errorVal.Str() != "" {
|
||||
errorMsg = errorVal.Str()
|
||||
}
|
||||
return fmt.Errorf("HTML5 coordinate drag failed: %s", errorMsg)
|
||||
}
|
||||
|
||||
// Log the method used for debugging
|
||||
if method, exists := resultMap["method"]; exists && method.Str() != "" {
|
||||
d.debugLog("HTML5 coordinate drag used method: %s", method.Str())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// performDragAndDropByOffset performs drag and drop from element by relative offset
|
||||
func (d *Daemon) performDragAndDropByOffset(page *rod.Page, sourceSelector string, offsetX, offsetY int) error {
|
||||
// Find source element
|
||||
// First, calculate the target coordinates
|
||||
sourceElement, err := page.Element(sourceSelector)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find source element %s: %v", sourceSelector, err)
|
||||
}
|
||||
|
||||
// Get source element position and size
|
||||
sourceBox, err := sourceElement.Shape()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get source element shape: %v", err)
|
||||
}
|
||||
|
||||
// Calculate source center point from the first quad
|
||||
if len(sourceBox.Quads) == 0 {
|
||||
return fmt.Errorf("source element has no quads")
|
||||
}
|
||||
@@ -6571,11 +7009,21 @@ func (d *Daemon) performDragAndDropByOffset(page *rod.Page, sourceSelector strin
|
||||
sourceX := (sourceQuad[0] + sourceQuad[2] + sourceQuad[4] + sourceQuad[6]) / 4
|
||||
sourceY := (sourceQuad[1] + sourceQuad[3] + sourceQuad[5] + sourceQuad[7]) / 4
|
||||
|
||||
// Calculate target point by adding offset
|
||||
targetX := sourceX + float64(offsetX)
|
||||
targetY := sourceY + float64(offsetY)
|
||||
// Calculate target coordinates
|
||||
targetX := int(sourceX + float64(offsetX))
|
||||
targetY := int(sourceY + float64(offsetY))
|
||||
|
||||
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, targetX, targetY)
|
||||
// Try the enhanced HTML5 approach first (reuse coordinate logic)
|
||||
err = d.performHTML5DragToCoordinates(page, sourceSelector, targetX, targetY)
|
||||
if err == nil {
|
||||
d.debugLog("HTML5 offset drag completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
d.debugLog("HTML5 offset drag failed (%v), falling back to mouse events", err)
|
||||
|
||||
// Fallback to the original mouse-based approach
|
||||
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, float64(targetX), float64(targetY))
|
||||
}
|
||||
|
||||
// performDragAndDropBetweenPoints performs the actual drag and drop using Chrome DevTools Protocol mouse events
|
||||
|
||||
Reference in New Issue
Block a user