/*! * Casper is a navigation utility for PhantomJS. * * Documentation: http://casperjs.org/ * Repository: http://github.com/casperjs/casperjs * * Copyright (c) 2011-2012 Nicolas Perriault * * Part of source code is Copyright Joyent, Inc. and other Node contributors. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global CasperError, exports, patchRequire, require:true*/ require = patchRequire(require); var utils = require('utils'); var Mouse = function Mouse(casper) { "use strict"; if (!utils.isCasperObject(casper)) { throw new CasperError('Mouse() needs a Casper instance'); } var slice = Array.prototype.slice, nativeEvents = ['mouseup', 'mousedown', 'click', 'mousemove'], nativeButtons = ['left', 'middle', 'right']; if (utils.gteVersion(phantom.version, '1.8.0')) { nativeEvents.push('doubleclick'); } if (utils.gteVersion(phantom.version, '2.1.0')) { nativeEvents.push('contextmenu'); } var emulatedEvents = ['mouseover', 'mouseout', 'mouseenter', 'mouseleave'], supportedEvents = nativeEvents.concat(emulatedEvents); var computeCenter = function computeCenter(selector) { var bounds = casper.getElementBounds(selector, true); if (utils.isClipRect(bounds)) { var x = Math.round(bounds.left + bounds.width / 2), y = Math.round(bounds.top + bounds.height / 2); return [x, y]; } return [0, 0]; }; var getPointFromViewPort = function getPointFromViewPort(page, x, y){ var px = x - x % page.viewportSize.width; var py = y - y % page.viewportSize.height; var max = casper.evaluate(function() { return [__utils__.getDocumentWidth(), __utils__.getDocumentHeight()]; }); if (py > max[0] - page.viewportSize.width && max[0] > page.viewportSize.width){ px = max[0] - page.viewportSize.width; } if (py > max[1] - page.viewportSize.height && max[1] > page.viewportSize.height){ py = max[1] - page.viewportSize.height; } page.scrollPosition = { 'left': px, 'top': py }; return [ x - px, y - py ]; }; var getPointFromSelectorCoords = function getPointFromSelectorCoords(selector, clientX, clientY){ var convertNumberToIntAndPercentToFloat = function convertNumberToIntAndPercentToFloat(a, def){ return !!a && !isNaN(a) && parseInt(a, 10) || !!a && !isNaN(parseFloat(a)) && parseFloat(a) >= 0 && parseFloat(a) <= 100 && parseFloat(a) / 100 || def; }; var bounds = casper.getElementBounds(selector, true), px = convertNumberToIntAndPercentToFloat(clientX, 0.5), py = convertNumberToIntAndPercentToFloat(clientY, 0.5); if (utils.isClipRect(bounds)) { return [ bounds.left + (px ^ 0) + Math.round(bounds.width * (px - (px ^ 0)).toFixed(10)), bounds.top + (py ^ 0) + Math.round(bounds.height * (py - (py ^ 0)).toFixed(10)) ]; } return [1, 1]; }; var processEvent = function processEvent(type, args) { var button = nativeButtons[0], selector = 'html', index = 0, point, scroll = casper.page.scrollPosition; if (!utils.isString(type) || supportedEvents.indexOf(type) === -1) { throw new CasperError('Mouse.processEvent(): Unsupported mouse event type: ' + type); } if (emulatedEvents.indexOf(type) > -1) { casper.log("Mouse.processEvent(): no native fallback for type " + type, "warning"); } args = [].slice.call(args); // cast Arguments -> Array if (args.length === 0) { throw new CasperError('Mouse.processEvent(): Too few arguments'); } if (isNaN(parseInt(args[0], 10)) && casper.exists(args[0])) { selector = args[0]; index++; } if (args.length >= index + 2) { point = getPointFromSelectorCoords(selector, args[index], args[index + 1]); } else { point = computeCenter(selector); } index = nativeButtons.indexOf(args[args.length - 1]); if (index > -1) { button = nativeButtons[index]; } casper.emit('mouse.' + type.replace('mouse', ''), args); point = getPointFromViewPort(casper.page, point[0], point[1]); casper.page.sendEvent.apply(casper.page, [type].concat(point).concat([button])); casper.page.scrollPosition = scroll; }; this.click = function click() { processEvent('click', arguments); }; this.doubleclick = function doubleclick() { processEvent('doubleclick', arguments); }; this.down = function down() { processEvent('mousedown', arguments); }; this.move = function move() { processEvent('mousemove', arguments); }; this.processEvent = function() { processEvent(arguments[0], [].slice.call(arguments, 1)); }; this.rightclick = function rightclick() { try { processEvent('contextmenu', arguments); } catch (e) { var args = slice.call(arguments); switch (args.length) { case 0: throw new CasperError('Mouse.rightclick(): Too few arguments'); case 1: casper.mouseEvent('contextmenu', args[0]); break; case 2: if (!utils.isNumber(args[0]) || !utils.isNumber(args[1])) { throw new CasperError('Mouse.rightclick(): No valid coordinates passed: ' + args); } var struct = casper.page.evaluate(function (clientX, clientY) { var xpath = function xpath(el) { if (typeof el === "string") { return document.evaluate(el, document, null, 0, null); } if (!el || el.nodeType !== 1) { return ''; } if (el.id) { return "//*[@id='" + el.id + "']"; } var sames = [].filter.call(el.parentNode.children, function (x) { return x.tagName === el.tagName; }); return xpath(el.parentNode) + '/' + el.tagName.toLowerCase() + (sames.length > 1 ? '[' + ([].indexOf.call(sames, el) + 1) + ']' : ''); }; try { var elem = document.elementFromPoint(clientX, clientY); var rec = elem.getBoundingClientRect(); return { "selector": {"type": "xpath", "path": xpath(elem)}, "relX": clientX - rec.left, "relY": clientY - rec.top }; } catch (ex) { return { "selector": {"type": "xpath", "path": "//html"}, "relX": clientX, "relY": clientY }; } }, args[0], args[1]); casper.mouseEvent('contextmenu', struct.selector, struct.relX, struct.relY); break; default: throw new CasperError('Mouse.rightclick(): Too many arguments'); } } }; this.up = function up() { processEvent('mouseup', arguments); }; }; exports.create = function create(casper) { "use strict"; return new Mouse(casper); }; exports.Mouse = Mouse;