Skip to content

Commit

Permalink
Only enable HTML5 dragging if default is not prevented on the mousedo…
Browse files Browse the repository at this point in the history
…wn event
  • Loading branch information
twalpole committed Sep 12, 2018
1 parent ef292c8 commit 15de1fa
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 53 deletions.
60 changes: 60 additions & 0 deletions lib/capybara/selenium/extensions/html5_drag.rb
Original file line number Diff line number Diff line change
@@ -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
32 changes: 6 additions & 26 deletions lib/capybara/selenium/nodes/chrome_node.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,37 +15,13 @@ def set_file(value) # rubocop:disable Naming/AccessorMethodName
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

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
31 changes: 5 additions & 26 deletions lib/capybara/selenium/nodes/marionette_node.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -54,10 +57,8 @@ def send_keys(*args)
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
Expand Down Expand Up @@ -116,26 +117,4 @@ def upload(local_file)
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
2 changes: 1 addition & 1 deletion lib/capybara/spec/public/test.js
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
8 changes: 8 additions & 0 deletions lib/capybara/spec/session/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,14 @@
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
Expand Down
2 changes: 2 additions & 0 deletions lib/capybara/spec/views/with_js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand Down
9 changes: 9 additions & 0 deletions spec/shared_selenium_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,15 @@
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
Expand Down

0 comments on commit 15de1fa

Please sign in to comment.