211 lines
8.6 KiB
JavaScript
Executable File
211 lines
8.6 KiB
JavaScript
Executable File
/*!
|
||
* 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;
|