123456789_123456789_123456789_123456789_123456789_

Module: Capybara::Selenium::Node::Html5Drag

Relationships & Source Files
Defined in: lib/capybara/selenium/extensions/html5_drag.rb

Constant Summary

  • ATTACH_FILE =
    # File 'lib/capybara/selenium/extensions/html5_drag.rb', line 86
    <<~JS
      (function(){
        var input = document.createElement('INPUT');
        input.type = "file";
        input.id = "_capybara_drop_file";
        input.multiple = true;
        document.body.appendChild(input);
        return input;
      })()
    JS
  • DROP_FILE =
    # File 'lib/capybara/selenium/extensions/html5_drag.rb', line 65
    <<~JS
      var el = arguments[0],
          input = arguments[1],
          files = input.files,
          dt = new DataTransfer(),
          opts = { cancelable: true, bubbles: true, dataTransfer: dt };
      input.parentElement.removeChild(input);
      if (dt.items){
        for (var i=0; i<files.length; i++){
          dt.items.add(files[i]);
        }
      } else {
        Object.defineProperty(dt, "files", {
          value: files,
          writable: false
        });
      }
      var dropEvent = new DragEvent('drop', opts);
      el.dispatchEvent(dropEvent);
    JS
  • DROP_STRING =
    # File 'lib/capybara/selenium/extensions/html5_drag.rb', line 49
    <<~JS
      var strings = arguments[0],
          el = arguments[1],
          dt = new DataTransfer(),
          opts = { cancelable: true, bubbles: true, dataTransfer: dt };
      for (var i=0; i < strings.length; i++){
        if (dt.items) {
          dt.items.add(strings[i]['data'], strings[i]['type']);
        } else {
          dt.setData(strings[i]['type'], strings[i]['data']);
        }
      }
      var dropEvent = new DragEvent('drop', opts);
      el.dispatchEvent(dropEvent);
    JS
  • HTML5_DRAG_DROP_SCRIPT =
    # File 'lib/capybara/selenium/extensions/html5_drag.rb', line 117
    <<~JS
      function rectCenter(rect){
        return new DOMPoint(
          (rect.left + rect.right)/2,
          (rect.top + rect.bottom)/2
        );
      }
    
      function pointOnRect(pt, rect) {
      	var rectPt = rectCenter(rect);
      	var slope = (rectPt.y - pt.y) / (rectPt.x - pt.x);
    
      	if (pt.x <= rectPt.x) { // left side
      		var minXy = slope * (rect.left - pt.x) + pt.y;
      		if (rect.top <= minXy && minXy <= rect.bottom)
            return new DOMPoint(rect.left, minXy);
      	}
    
      	if (pt.x >= rectPt.x) { // right side
      		var maxXy = slope * (rect.right - pt.x) + pt.y;
      		if (rect.top <= maxXy && maxXy <= rect.bottom)
            return new DOMPoint(rect.right, maxXy);
      	}
    
      	if (pt.y <= rectPt.y) { // top side
      		var minYx = (rectPt.top - pt.y) / slope + pt.x;
      		if (rect.left <= minYx && minYx <= rect.right)
            return new DOMPoint(minYx, rect.top);
      	}
    
      	if (pt.y >= rectPt.y) { // bottom side
      		var maxYx = (rect.bottom - pt.y) / slope + pt.x;
      		if (rect.left <= maxYx && maxYx <= rect.right)
            return new DOMPoint(maxYx, rect.bottom);
      	}
    
        return new DOMPoint(pt.x,pt.y);
      }
    
      function dragEnterTarget() {
        target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
        var targetRect = target.getBoundingClientRect();
        var sourceCenter = rectCenter(source.getBoundingClientRect());
    
        for (var i = 0; i < drop_modifier_keys.length; i++) {
          key = drop_modifier_keys[i];
          if (key == "control"){
            key = "ctrl"
          }
          opts[key + 'Key'] = true;
        }
    
        var dragEnterEvent = new DragEvent('dragenter', opts);
        target.dispatchEvent(dragEnterEvent);
    
        // fire 2 dragover events to simulate dragging with a direction
        var entryPoint = pointOnRect(sourceCenter, targetRect)
        var dragOverOpts = Object.assign({clientX: entryPoint.x, clientY: entryPoint.y}, opts);
        var dragOverEvent = new DragEvent('dragover', dragOverOpts);
        target.dispatchEvent(dragOverEvent);
        window.setTimeout(dragOnTarget, step_delay);
      }
    
      function dragOnTarget() {
        var targetCenter = rectCenter(target.getBoundingClientRect());
        var dragOverOpts = Object.assign({clientX: targetCenter.x, clientY: targetCenter.y}, opts);
        var dragOverEvent = new DragEvent('dragover', dragOverOpts);
        target.dispatchEvent(dragOverEvent);
        window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented, dragOverOpts);
      }
    
      function dragLeave(drop, dragOverOpts) {
        var dragLeaveOptions = Object.assign({}, opts, dragOverOpts);
        var dragLeaveEvent = new DragEvent('dragleave', dragLeaveOptions);
        target.dispatchEvent(dragLeaveEvent);
        if (drop) {
          var dropEvent = new DragEvent('drop', dragLeaveOptions);
          target.dispatchEvent(dropEvent);
        }
        var dragEndEvent = new DragEvent('dragend', dragLeaveOptions);
        source.dispatchEvent(dragEndEvent);
        callback.call(true);
      }
    
      var source = arguments[0],
          target = arguments[1],
          step_delay = arguments[2],
          drop_modifier_keys = arguments[3],
          callback = arguments[4];
    
      var dt = new DataTransfer();
      var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
    
      while (source && !source.draggable) {
        source = source.parentElement;
      }
    
      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);
    
      window.setTimeout(dragEnterTarget, step_delay);
    JS
  • LEGACY_DRAG_CHECK =
    # File 'lib/capybara/selenium/extensions/html5_drag.rb', line 104
    <<~JS
      (function(el){
        if ([true, null].indexOf(window.capybara_mousedown_prevented) >= 0){
          return true;
        }
    
        do {
          if (el.draggable) return false;
        } while (el = el.parentElement );
        return true;
      })(arguments[0])
    JS
  • MOUSEDOWN_TRACKER =
    # File 'lib/capybara/selenium/extensions/html5_drag.rb', line 97
    <<~JS
      window.capybara_mousedown_prevented = null;
      document.addEventListener('mousedown', ev => {
        window.capybara_mousedown_prevented = ev.defaultPrevented;
      }, { once: true, passive: true })
    JS

Instance Method Summary

Instance Method Details

#drag_to(element, html5: nil, delay: 0.05, drop_modifiers: [])

Implement methods to emulate HTML5 drag and drop

[ GitHub ]

  
# File 'lib/capybara/selenium/extensions/html5_drag.rb', line 7

def drag_to(element, html5: nil, delay: 0.05, drop_modifiers: [])
  drop_modifiers = Array(drop_modifiers)

  driver.execute_script MOUSEDOWN_TRACKER
  scroll_if_needed { browser_action.click_and_hold(native).perform }
  html5 = !driver.evaluate_script(LEGACY_DRAG_CHECK, self) if html5.nil?
  if html5
    perform_html5_drag(element, delay, drop_modifiers)
  else
    perform_legacy_drag(element, drop_modifiers)
  end
end

#html5_drop(*args) (private)

[ GitHub ]

  
# File 'lib/capybara/selenium/extensions/html5_drag.rb', line 36

def html5_drop(*args)
  if args[0].is_a? String
    input = driver.evaluate_script ATTACH_FILE
    input.set_file(args)
    driver.execute_script DROP_FILE, self, input
  else
    items = args.flat_map do |arg|
      arg.map { |(type, data)| { type: type, data: data } }
    end
    driver.execute_script DROP_STRING, items, self
  end
end

#perform_html5_drag(element, delay, drop_modifiers) (private)

[ GitHub ]

  
# File 'lib/capybara/selenium/extensions/html5_drag.rb', line 31

def perform_html5_drag(element, delay, drop_modifiers)
  driver.evaluate_async_script HTML5_DRAG_DROP_SCRIPT, self, element, delay * 1000, normalize_keys(drop_modifiers)
  browser_action.release.perform
end

#perform_legacy_drag(element, drop_modifiers) (private)

[ GitHub ]

  
# File 'lib/capybara/selenium/extensions/html5_drag.rb', line 22

def perform_legacy_drag(element, drop_modifiers)
  element.scroll_if_needed do
    # browser_action.move_to(element.native).release.perform
    keys_down = modifiers_down(browser_action, drop_modifiers)
    keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
    keys_up.perform
  end
end