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