Only enable HTML5 dragging if default is not prevented on the mousedown event

This commit is contained in:
Thomas Walpole 2018-09-11 13:57:41 -07:00
parent 5ca9b25685
commit 195930d900
7 changed files with 91 additions and 53 deletions

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
class Capybara::Selenium::Node
module Html5Drag
private
def html5_drag_to(element)
driver.execute_script MOUSEDOWN_TRACKER
scroll_if_needed { browser_action.click_and_hold(native).perform }
if driver.evaluate_script('window.capybara_mousedown_prevented')
element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
else
driver.execute_script HTML5_DRAG_DROP_SCRIPT, self, element
end
end
def draggable?
# Workaround https://github.com/SeleniumHQ/selenium/issues/6396
driver.evaluate_script('arguments[0]["draggable"]', self) == true
end
MOUSEDOWN_TRACKER = <<~JS
if (!window.hasOwnProperty('capybara_mousedown_prevented')){
document.addEventListener('mousedown', function(ev){
window.capybara_mousedown_prevented = ev.defaultPrevented;
})
}
JS
HTML5_DRAG_DROP_SCRIPT = <<~JS
var source = arguments[0];
var target = arguments[1];
var dt = new DataTransfer();
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
if (source.tagName == 'A'){
dt.setData('text/uri-list', source.href);
dt.setData('text', source.href);
}
if (source.tagName == 'IMG'){
dt.setData('text/uri-list', source.src);
dt.setData('text', source.src);
}
var dragEvent = new DragEvent('dragstart', opts);
source.dispatchEvent(dragEvent);
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
var dragOverEvent = new DragEvent('dragover', opts);
target.dispatchEvent(dragOverEvent);
var dragLeaveEvent = new DragEvent('dragleave', opts);
target.dispatchEvent(dragLeaveEvent);
if (dragOverEvent.defaultPrevented) {
var dropEvent = new DragEvent('drop', opts);
target.dispatchEvent(dropEvent);
}
var dragEndEvent = new DragEvent('dragend', opts);
source.dispatchEvent(dragEndEvent);
JS
end
end

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
require 'capybara/selenium/extensions/html5_drag'
class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
include Html5Drag
def set_file(value) # rubocop:disable Naming/AccessorMethodName
super(value)
rescue ::Selenium::WebDriver::Error::ExpectedError => err
@ -11,10 +15,8 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
end
def drag_to(element)
return super unless self[:draggable] == 'true'
scroll_if_needed { browser_action.click_and_hold(native).perform }
driver.execute_script HTML5_DRAG_DROP_SCRIPT, self, element
return super unless draggable?
html5_drag_to(element)
end
private
@ -22,26 +24,4 @@ private
def bridge
driver.browser.send(:bridge)
end
HTML5_DRAG_DROP_SCRIPT = <<~JS
var source = arguments[0];
var target = arguments[1];
var dt = new DataTransfer();
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
var dragEvent = new DragEvent('dragstart', opts);
source.dispatchEvent(dragEvent);
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
var dragOverEvent = new DragEvent('dragover', opts);
target.dispatchEvent(dragOverEvent);
var dragLeaveEvent = new DragEvent('dragleave', opts);
target.dispatchEvent(dragLeaveEvent);
if (dragOverEvent.defaultPrevented) {
var dropEvent = new DragEvent('drop', opts);
target.dispatchEvent(dropEvent);
}
var dragEndEvent = new DragEvent('dragend', opts);
source.dispatchEvent(dragEndEvent);
JS
end

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true
require 'capybara/selenium/extensions/html5_drag'
class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
include Html5Drag
def click(keys = [], **options)
super
rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
@ -54,10 +57,8 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
end
def drag_to(element)
return super unless (browser_version >= 62.0) && (self[:draggable] == 'true')
scroll_if_needed { browser_action.click_and_hold(native).perform }
driver.execute_script HTML5_DRAG_DROP_SCRIPT, self, element
return super unless (browser_version >= 62.0) && draggable?
html5_drag_to(element)
end
private
@ -116,26 +117,4 @@ private
def browser_version
driver.browser.capabilities[:browser_version].to_f
end
HTML5_DRAG_DROP_SCRIPT = <<~JS
var source = arguments[0];
var target = arguments[1];
var dt = new DataTransfer();
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
var dragEvent = new DragEvent('dragstart', opts);
source.dispatchEvent(dragEvent);
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
var dragOverEvent = new DragEvent('dragover', opts);
target.dispatchEvent(dragOverEvent);
var dragLeaveEvent = new DragEvent('dragleave', opts);
target.dispatchEvent(dragLeaveEvent);
if (dragOverEvent.defaultPrevented) {
var dropEvent = new DragEvent('drop', opts);
target.dispatchEvent(dropEvent);
}
var dragEndEvent = new DragEvent('dragend', opts);
source.dispatchEvent(dragEndEvent);
JS
end

View File

@ -1,7 +1,7 @@
var activeRequests = 0;
$(function() {
$('#change').text('I changed it');
$('#drag, #drag_scroll').draggable();
$('#drag, #drag_scroll, #drag_link').draggable();
$('#drop, #drop_scroll').droppable({
drop: function(event, ui) {
ui.draggable.remove();

View File

@ -316,6 +316,14 @@ Capybara::SpecHelper.spec 'node' do
element.drag_to(target)
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
end
it 'should drag a link' do
@session.visit('/with_js')
link = @session.find_link('drag_link')
target = @session.find(:id, 'drop')
link.drag_to target
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
end
end
describe '#hover', requires: [:hover] do

View File

@ -12,6 +12,7 @@
<h1>FooBar</h1>
<p id="change">This is text</p>
<a id="drag_link" href='#'>This link is non-HTML5 draggable</a>
<div id="drag">
<p>This is a draggable element.</p>
</div>
@ -27,6 +28,7 @@
<div id="drag_html5" draggable="true">
<p>This is an HTML5 draggable element.</p>
</div>
<a id="drag_link_html5" href="#">This is an HTML5 draggable link</a>
<div id="drop_html5" class="drop">
<p>It should be dropped here.</p>
</div>

View File

@ -313,6 +313,15 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
element.drag_to(target)
expect(session).to have_xpath('//div[contains(., "HTML5 Dropped drag_html5_scroll")]')
end
it 'should drag HTML5 default draggable elements' do
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if marionette_lt?(62.0, session)
session.visit('/with_js')
link = session.find_link('drag_link_html5')
target = session.find(:id, 'drop_html5')
link.drag_to target
expect(session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
end
end
context 'Windows' do