@@ -0,0 +1,187 @@
'use strict';

var Actions = require('marionette-client').Actions;
function SelectionHelper(client, element) {
this.client = client;
this.isInputOrTextarea =
(element.getAttribute('tagName') === 'INPUT' ||
element.getAttribute('tagName') === 'TEXTAREA');
this.element = element;
this.action = new Actions(this.client);
}

module.exports = SelectionHelper;

SelectionHelper.prototype = {
get content() {
if (this.isInputOrTextarea) {
return this.element.getAttribute('value');
} else {
return this.element.text;
}
},

get selectedContent() {
return this.client.executeScript(this.getSelectionCmd() +
'return sel.toString();', [this.element]);
},

getSelectionCmd: function() {
if (this.isInputOrTextarea) {
return 'var sel = SpecialPowers.wrap(arguments[0]).editor.selection;' +
'sel = SpecialPowers.unwrap(sel);';
} else {
return 'var sel = window.getSelection();';
}
},

selectionRectList: function(idx) {
var cmd = this.getSelectionCmd() + 'return sel.getRangeAt(' + idx + ')' +
'.getClientRects();';
return this.client.executeScript(cmd, [this.element]);
},

rangeCount: function() {
var cmd = this.getSelectionCmd() + 'return sel.rangeCount;';
return this.client.executeScript(cmd, [this.element]);
},

/**
* Move caret by absolute position.
*
* @param {Object} data
* {caretA: {
* offset: {
* x: //poistion relative to element,
* y: //poistion relative to element
* },
* caretB: {
* offset: {
* x: //poistion relative to element,
* y: //poistion relative to element
* }
* }}
*
*/
moveCaretByPosition: function(data) {
var caretPositions = this.selectionLocationHelper();
if (data.caretA) {
this.action.flick(this.element, caretPositions.caretA.x,
caretPositions.caretA.y + 15,
caretPositions.caretA.x + data.caretA.offset.x,
caretPositions.caretA.y + 15 + data.caretA.offset.y).perform();
this.client.wait(500);
}
if (data.caretB) {
this.action.flick(this.element, caretPositions.caretB.x,
caretPositions.caretB.y + 15,
caretPositions.caretB.x + data.caretB.offset.x,
caretPositions.caretB.y + 15 + data.caretB.offset.y).perform();
}
},

/**
* Move caret by word's position.
*
* @param {Object} data
* {caretA: {offset: //integer}, caretB: {offset //integer}}
*
*/
moveCaretByWords: function(data) {
if (!this.isInputOrTextarea) {
console.log('only support input or textarea');
return;
}
var caretA = data.caretA;
var caretB = data.caretB;
var caretPositions = this.selectionLocationHelper();
var content = this.content;
var contentLength = content.length;

this.client.executeScript('arguments[0].setSelectionRange(' +
(caretA ? caretA.offset : 0) + ',' +
(contentLength + (caretB ? caretB.offset : 0)) + ' )',
[this.element]);

var newCaretPositions = this.selectionLocationHelper();

var eltSize = this.element.size();
this.action.tap(this.element, eltSize.width / 2, eltSize.height / 2)
.wait(2).press(this.element, eltSize.width / 2, eltSize.height / 2).
moveByOffset(0, 0).wait(2).release().perform();

var flickPosition = {};

if (caretA) {
flickPosition.caretA = {
offset: {
x: newCaretPositions.caretA.x - caretPositions.caretA.x,
y: newCaretPositions.caretA.y - caretPositions.caretA.y
}
};
}
if (caretB) {
flickPosition.caretB = {
offset: {
x: newCaretPositions.caretB.x - caretPositions.caretB.x,
y: newCaretPositions.caretB.y - caretPositions.caretB.y
}
};
}
this.moveCaretByPosition(flickPosition);
},

selectionLocationHelper: function(locationType) {
var rangeCount = this.rangeCount();
var firstRectList = this.selectionRectList(0);
var lastRectList = this.selectionRectList(rangeCount - 1);
var lastListLength = lastRectList.length;
var firstRect = firstRectList[0];
var lastRect = lastRectList[lastListLength - 1];
var originX = this.element.location.x;
var originY = this.element.location.y;
var startPos = 'left';
var endPos = 'right';
var startYOffset, endYOffset;

if (this.element.getAttribute('dir') === 'rtl') {
startPos = 'right';
endPos = 'left';
}
if (locationType === 'center') {
startYOffset = firstRect.height / 2;
endYOffset = lastRect.height / 2;
} else if (locationType === 'caret') {
startYOffset = firstRect.height + 5;
endYOffset = lastRect.height + 5;
} else {
startYOffset = endYOffset = 0;
}

return {
caretA: {
x: firstRect[startPos] - originX,
y: firstRect.top + startYOffset - originY
},
caretB: {
x: lastRect[endPos] - originX,
y: lastRect.top + endYOffset - originY
}
};
},

selectAll: function() {
var cmd;
if (this.isInputOrTextarea) {
cmd = 'var len = arguments[0].value.length; arguments[0].focus();' +
'arguments[0].setSelectionRange(0, len);';
} else {
cmd = 'var range = document.createRange();' +
'range.setStart(arguments[0].firstChild, 0);' +
'range.setEnd(arguments[0].lastChild, 0);' +
'var sel = window.getSelection();' +
'sel.removeAllRanges(); sel.addRange(range);';
}
this.client.executeScript(cmd, [this.element]);
}
};
@@ -53,6 +53,10 @@ TextSelection.prototype = {
return location;
},

switchToCurrentApp: function() {
this.client.apps.switchToApp(this._getDisplayedAppInfo().origin);
},

/**
* Get appWindow's id and origin of displayed app.
* XXXXX: Since gecko is not ready yet, we need to simulate gecko dispatching
@@ -78,7 +82,14 @@ TextSelection.prototype = {
},

longPress: function(element) {
this.actions.tap(element, 10, 10).wait(2).longPress(element, 2).perform();
var eltSize = element.size();
this.longPressByPosition(element, eltSize/2, eltSize/2);
},

longPressByPosition: function(element, x, y) {
// Add moveByOffset to prevent contextmenu event be fired.
this.actions.tap(element, x, y).wait(2).press(element, x, y).
moveByOffset(0, 0).wait(2).release().perform();
},

pressCopy: function() {

Large diffs are not rendered by default.