robot.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. define([
  2. "./_base/array",
  3. "./dom",
  4. "./dom-geometry",
  5. "./_base/kernel",
  6. "./_base/lang",
  7. "./_base/window",
  8. "doh/_browserRunner",
  9. "doh/robot",
  10. "./window"
  11. ], function(array, dom, geom, kernel, lang, win, doh, robot, winUtils){
  12. kernel.experimental("dojo.robot");
  13. // users who use doh+dojo get the added convenience of robot.mouseMoveAt(),
  14. // instead of computing the absolute coordinates of their elements themselves
  15. lang.mixin(robot, {
  16. _resolveNode: function(/*String||DOMNode||Function*/ n){
  17. if(typeof n == "function"){
  18. // if the user passed a function returning a node, evaluate it
  19. n = n();
  20. }
  21. return n ? dom.byId(n) : null;
  22. },
  23. _scrollIntoView: function(/*Node*/ n){
  24. // scrolls the passed node into view, scrolling all ancestor frames/windows as well.
  25. // Assumes parent iframes can be made fully visible given the current browser window size
  26. var p = null;
  27. array.forEach(robot._getWindowChain(n), function(w){
  28. // get the position of the node wrt its parent window
  29. // if it is a parent frame, its padding and border extents will get added in
  30. var p2 = geom.position(n, false),
  31. b = geom.getPadBorderExtents(n),
  32. oldp = null;
  33. // if p2 is the position of the original passed node, store the position away as p
  34. // otherwise, node is actually an iframe. in this case, add the iframe's position wrt its parent window and also the iframe's padding and border extents
  35. if(!p){
  36. p = p2;
  37. }else{
  38. oldp = p;
  39. p = {x: p.x+p2.x+b.l,
  40. y: p.y+p2.y+b.t,
  41. w: p.w,
  42. h: p.h};
  43. }
  44. // scroll the parent window so that the node translated into the parent window's coordinate space is in view
  45. winUtils.scrollIntoView(n,p);
  46. // adjust position for the new scroll offsets
  47. p2 = geom.position(n, false);
  48. if(!oldp){
  49. p = p2;
  50. }else{
  51. p = {x: oldp.x+p2.x+b.l,
  52. y: oldp.y+p2.y+b.t,
  53. w: p.w,
  54. h: p.h};
  55. }
  56. // get the parent iframe so it can be scrolled too
  57. n = w.frameElement;
  58. });
  59. },
  60. _position: function(/*Node*/ n){
  61. // Returns the geom.position of the passed node wrt the passed window's viewport,
  62. // following any parent iframes containing the node and clipping the node to each iframe.
  63. // precondition: _scrollIntoView already called
  64. var p = null, max = Math.max, min = Math.min;
  65. // p: the returned position of the node
  66. array.forEach(robot._getWindowChain(n), function(w){
  67. // get the position of the node wrt its parent window
  68. // if it is a parent frame, its padding and border extents will get added in
  69. var p2 = geom.position(n, false), b = geom.getPadBorderExtents(n);
  70. // if p2 is the position of the original passed node, store the position away as p
  71. // otherwise, node is actually an iframe. in this case, add the iframe's position wrt its parent window and also the iframe's padding and border extents
  72. if(!p){
  73. p = p2;
  74. }else{
  75. var view = winUtils.getBox(n.contentWindow.document);
  76. p2.r = p2.x+view.w;
  77. p2.b = p2.y+view.h;
  78. p = {x: max(p.x+p2.x,p2.x)+b.l, // clip left edge of node wrt the iframe
  79. y: max(p.y+p2.y,p2.y)+b.t, // top edge
  80. r: min(p.x+p2.x+p.w,p2.r)+b.l, // right edge (to compute width)
  81. b: min(p.y+p2.y+p.h,p2.b)+b.t}; // bottom edge (to compute height)
  82. // save a few bytes by computing width and height from r and b
  83. p.w = p.r-p.x;
  84. p.h = p.b-p.y;
  85. }
  86. // the new node is now the old node's parent iframe
  87. n=w.frameElement;
  88. });
  89. return p;
  90. },
  91. _getWindowChain : function(/*Node*/ n){
  92. // Returns an array of windows starting from the passed node's parent window and ending at dojo's window
  93. var cW = winUtils.get(n.ownerDocument);
  94. var arr = [cW];
  95. var f = cW.frameElement;
  96. return (cW == win.global || !f) ? arr : arr.concat(robot._getWindowChain(f));
  97. },
  98. scrollIntoView : function(/*String||DOMNode||Function*/ node, /*Number, optional*/ delay){
  99. // summary:
  100. // Scroll the passed node into view, if it is not.
  101. // node:
  102. // The id of the node, or the node itself, to move the mouse to.
  103. // If you pass an id or a function that returns a node, the node will not be evaluated until the movement executes.
  104. // This is useful if you need to move the mouse to an node that is not yet present.
  105. // delay:
  106. // Delay, in milliseconds, to wait before firing.
  107. // The delay is a delta with respect to the previous automation call.
  108. robot.sequence(function(){
  109. robot._scrollIntoView(robot._resolveNode(node));
  110. }, delay);
  111. },
  112. mouseMoveAt : function(/*String||DOMNode||Function*/ node, /*Integer, optional*/ delay, /*Integer, optional*/ duration, /*Number, optional*/ offsetX, /*Number, optional*/ offsetY){
  113. // summary:
  114. // Moves the mouse over the specified node at the specified relative x,y offset.
  115. // description:
  116. // Moves the mouse over the specified node at the specified relative x,y offset.
  117. // If you do not specify an offset, mouseMove will default to move to the middle of the node.
  118. // Example: to move the mouse over a ComboBox's down arrow node, call doh.mouseMoveAt(dijit.byId('setvaluetest').downArrowNode);
  119. // node:
  120. // The id of the node, or the node itself, to move the mouse to.
  121. // If you pass an id or a function that returns a node, the node will not be evaluated until the movement executes.
  122. // This is useful if you need to move the mouse to an node that is not yet present.
  123. // delay:
  124. // Delay, in milliseconds, to wait before firing.
  125. // The delay is a delta with respect to the previous automation call.
  126. // For example, the following code ends after 600ms:
  127. // | robot.mouseClick({left:true}, 100) // first call; wait 100ms
  128. // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all
  129. // duration:
  130. // Approximate time Robot will spend moving the mouse
  131. // The default is 100ms.
  132. // offsetX:
  133. // x offset relative to the node, in pixels, to move the mouse. The default is half the node's width.
  134. // offsetY:
  135. // y offset relative to the node, in pixels, to move the mouse. The default is half the node's height.
  136. robot._assertRobot();
  137. // Schedule an action to scroll the node into view, then calculate it's center point
  138. var point = {};
  139. this.sequence(function(){
  140. node = robot._resolveNode(node);
  141. robot._scrollIntoView(node);
  142. var pos = robot._position(node);
  143. if(offsetY === undefined){
  144. offsetX = pos.w/2;
  145. offsetY = pos.h/2;
  146. }
  147. point.x = pos.x+offsetX;
  148. point.y = pos.y+offsetY;
  149. }, delay);
  150. // Schedule a bunch of actions to move the mouse from the current position to point.
  151. // These actions won't run until after the above callback.
  152. this.mouseMoveTo(point, 0, duration, false);
  153. }
  154. });
  155. return robot;
  156. });