dom.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. /**
  2. * $Id: TinyMCE_DOMUtils.class.js 91 2006-10-02 14:53:22Z spocke $
  3. *
  4. * @author Moxiecode
  5. * @copyright Copyright © 2004-2006, Moxiecode Systems AB, All rights reserved.
  6. */
  7. mox.require([
  8. 'mox.List',
  9. 'mox.geom.Point',
  10. 'mox.geom.Rect'
  11. ], function() {
  12. var List = mox.List;
  13. /**#@+
  14. * @class This class contains common event handling logic.
  15. * @member mox.DOM
  16. * @static
  17. */
  18. mox.create('mox.DOMUtils', {
  19. _counter : 0,
  20. _doc : null,
  21. /**
  22. * Constructs a new DOM instance.
  23. *
  24. * @param {Document} doc DOM document to peform all methods on.
  25. */
  26. DOMUtils : function(doc) {
  27. this._doc = doc;
  28. },
  29. /**#@+
  30. * @method
  31. */
  32. /**
  33. * Returns a array of elements when the specified function matches a node.
  34. *
  35. * @param {Node} n Node to select children from.
  36. * @param {string} na Element name(s) to search for separated by commas.
  37. * @param {function} f Function that returns true/false if the node is to be added or not.
  38. * @return {Array} Array with selected elements.
  39. */
  40. selectElements : function(n, na, f) {
  41. var i, a = [], nl, x;
  42. n = this.get(n);
  43. for (x=0, na = na.split(','); x<na.length; x++)
  44. for (i=0, nl = n.getElementsByTagName(na[x]); i<nl.length; i++)
  45. f(nl[i]) && a.push(nl[i]);
  46. return a;
  47. },
  48. /**
  49. * #id
  50. * p
  51. * p, div
  52. * p.class
  53. * div p
  54. * p[@id=3]
  55. * p#id
  56. */
  57. select : function(p, s) {
  58. var o = [], r = [], i, t = this, pl, ru, pu, x, u, xp;
  59. s = !s ? this._doc.body : this.get(s);
  60. // Parse pattern into rules
  61. for (i=0, pl=p.split(','); i<pl.length; i++) {
  62. ru = [];
  63. xp = '.';
  64. for (x=0, pu=pl[i].split(' '); x<pu.length; x++) {
  65. u = pu[x];
  66. if (u != '') {
  67. u = u.match(/^([\w\\]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@([\w\\]+)([\^\$\*!]?=)([\w\\]+)\])?(?:\:([\w\\]+))?/i);
  68. // Build xpath if supported
  69. if (document.evaluate) {
  70. xp += u[1] ? '//' + u[1] : '//*';
  71. // Id
  72. if (u[2])
  73. xp += "[@id='" + u[2] + "']";
  74. // Class
  75. if (u[3]) {
  76. List.each(u[3].split('.'), function(i, n) {
  77. xp += "[@class = '" + n + "' or contains(concat(' ', @class, ' '), ' " + n + " ')]";
  78. });
  79. }
  80. // Attr
  81. if (u[4]) {
  82. // Comparators
  83. if (u[5]) {
  84. if (u[5] == '^=')
  85. xp += "[starts-with(@" + u[4] + ",'" + u[6] + "')]";
  86. else if (u[5] == '$=')
  87. xp += "[contains(concat(@" + u[4] + ",'\0'),'" + u[6] + "\0')]";
  88. else if (u[5] == '*=')
  89. xp += "[contains(@" + u[4] + ",'" + u[6] + "')]";
  90. else if (u[5] == '!=')
  91. xp += "[@" + u[4] + "!='" + u[6] + "']";
  92. } else
  93. xp += "[@" + u[4] + "='" + u[6] + "']";
  94. }
  95. //console.debug(u);
  96. } else
  97. ru.push({ tag : u[1], id : u[2], cls : u[3] ? new RegExp('\\b' + u[3].replace(/\./g, '|') + '\\b') : null, attr : u[4], val : u[5] });
  98. }
  99. }
  100. //console.debug(xp);
  101. if (xp.length > 1)
  102. ru.xpath = xp;
  103. r.push(ru);
  104. }
  105. // Used by IE since it doesn't support XPath
  106. function find(e, rl, p) {
  107. var nl, i, n, ru = rl[p];
  108. for (i=0, nl = e.getElementsByTagName(!ru.tag ? '*' : ru.tag); i<nl.length; i++) {
  109. n = nl[i];
  110. if (ru.id && n.id != ru.id)
  111. continue;
  112. if (ru.cls && !ru.cls.test(n.className))
  113. continue;
  114. if (ru.attr && t.getAttrib(n, ru.attr) != ru.val)
  115. continue;
  116. if (p < rl.length - 1)
  117. find(n, rl, p + 1);
  118. else
  119. o.push(n);
  120. }
  121. };
  122. // Find elements based on rules
  123. for (i=0; i<r.length; i++) {
  124. if (r[i].xpath) {
  125. ru = this._doc.evaluate(r[i].xpath, s, null, 4, null);
  126. while (u = ru.iterateNext())
  127. o.push(u);
  128. } else
  129. find(s, r[i], 0);
  130. }
  131. return o;
  132. },
  133. /**
  134. * Returns the viewport of the window.
  135. *
  136. * @param {Window} w Optional window to get viewport of.
  137. * @return {Object} Viewport object with fields top, left, width and height.
  138. */
  139. getViewPort : function(w) {
  140. var d, b;
  141. w = !w ? window : w;
  142. d = w.document;
  143. b = d.compatMode == 'CSS1Compat' ? d.documentElement : d.body;
  144. return new mox.geom.Rect(
  145. w.pageXOffset || b.scrollLeft,
  146. w.pageYOffset || b.scrollTop,
  147. w.innerWidth || b.clientWidth,
  148. w.innerHeight || b.clientHeight
  149. );
  150. },
  151. /**
  152. * Returns a node by the specified selector function. This function will
  153. * loop through all parent nodes and call the specified function for each node.
  154. * If the function then returns true it will stop the execution and return that node.
  155. *
  156. * @param {Node} n HTML node to search parents on.
  157. * @param {function} f Selection function to execute on each node.
  158. * @param {Node} r Optional root element, never go below this point.
  159. * @return {Node} DOMNode or null if it wasn't found.
  160. */
  161. getParent : function(n, f, r) {
  162. n = this.get(n);
  163. while (n) {
  164. if (n == r)
  165. return null;
  166. if (f(n))
  167. return n;
  168. n = n.parentNode;
  169. }
  170. return null;
  171. },
  172. add : function(p, n, a, h) {
  173. var t = this, e;
  174. e = this._doc.createElement(n);
  175. for (n in a) {
  176. if (a.hasOwnProperty(n))
  177. t.setAttrib(e, n, a[n]);
  178. }
  179. if (h) {
  180. if (h.nodeType)
  181. e.appendChild(h);
  182. else
  183. e.innerHTML = h;
  184. }
  185. return p ? p.appendChild(e) : e;
  186. },
  187. create : function(n, a, h) {
  188. return this.add(0, n, a, h);
  189. },
  190. /**
  191. * Creates a tag by name and attributes array. This will create a DOM node out of the specified
  192. * data.
  193. *
  194. * @param {string} tn Tag name to create.
  195. * @param {Array} a Optional name/Value array of attributes.
  196. * @param {string} h Optional inner HTML of new tag, raw HTML code.
  197. */
  198. createTag : function(tn, a, h) {
  199. var n, o = this._doc.createElement(tn);
  200. if (a) {
  201. for (n in a) {
  202. if (typeof(a[n]) != 'function' && a[n] != null) {
  203. this.setAttrib(o, n, a[n]);
  204. }
  205. }
  206. }
  207. if (h)
  208. o.innerHTML = h;
  209. return o;
  210. },
  211. /**
  212. * Adds a whole tree of DOM elements by grabing the data from an array.
  213. *
  214. * @example
  215. * DOM.addTags(
  216. * ['div', {id : 'someid', 'class' : 'someclass'}, 'Some text content'
  217. * ['span', {'class' : 'someclass'},
  218. * ['span', {'class' : 'someclass'}, 'Some more content',
  219. * ['span', {'class' : 'someclass'}
  220. * ]
  221. * ]
  222. * );
  223. *
  224. * @param {Element} te Target element to append tree of nodes to.
  225. * @param {Array} ne Array of element to append.
  226. */
  227. addTags : function(te, ne) {
  228. var i, n;
  229. te = this.get(te);
  230. if (typeof(ne) == 'string')
  231. te.appendChild(this._doc.createTextNode(ne));
  232. else if (ne.length) {
  233. te = te.appendChild(this.createTag(ne[0], ne[1]));
  234. for (i=2; i<ne.length; i++)
  235. this.addTags(te, ne[i]);
  236. }
  237. },
  238. /**
  239. * Sets the attribute value for a specific attribute.
  240. *
  241. * @param {Element} e HTML element to set attribute on.
  242. * @param {string} n Attribute name to set.
  243. * @param {string} v Attribute value to set.
  244. */
  245. setAttrib : function(e, n, v) {
  246. e = this.get(e);
  247. if (!e)
  248. return false;
  249. if (n == "style")
  250. e.style.cssText = v;
  251. if (n == "class")
  252. e.className = v;
  253. if (v != null && v != "")
  254. e.setAttribute(n, '' + v);
  255. else
  256. e.removeAttribute(n);
  257. },
  258. /**
  259. * Sets the attributes for a specific attribute.
  260. *
  261. * @param {Element} e HTML element to set attribute on.
  262. * @param {Object} o Name/Value object to set.
  263. */
  264. setAttribs : function(e, o) {
  265. var n;
  266. e = this.get(e);
  267. if (!e)
  268. return false;
  269. for (n in o) {
  270. if (typeof(o[n]) != 'function')
  271. this.setAttrib(e, n, o[n]);
  272. }
  273. },
  274. /**
  275. * Returns a attribute value by name from a HTML element.
  276. *
  277. * @param {Element} e HTML element to get attribute from.
  278. * @param {string} n Name of attribute to retrive.
  279. * @param {string} dv Optional default value if the attribute was undefined.
  280. * @return {string} Attribute value or empty string if it wasn't found.
  281. */
  282. getAttrib : function(e, n, dv) {
  283. var v;
  284. e = this.get(e);
  285. if (!e)
  286. return false;
  287. if (typeof(dv) == "undefined")
  288. dv = "";
  289. v = e.getAttribute(n, 2);
  290. if (!v)
  291. v = e.attributes[n];
  292. if (n == "class" && !v)
  293. v = e.className;
  294. if (n == "style" && !v)
  295. v = e.style.cssText;
  296. return (v && v != "") ? v : dv;
  297. },
  298. /**
  299. * Sets the CSS style value on a HTML element. The name can be a camelcase string
  300. * or the CSS style name like background-color.
  301. *
  302. * @param {Element} n HTML element to set CSS style value on.
  303. * @param {string} na Name of the style value to set.
  304. * @param {string} v Value to set on the style.
  305. */
  306. setStyle : function(n, na, v){
  307. var s, i;
  308. if (!n)
  309. return false;
  310. n = typeof(n) == 'string' ? n.split(',') : [n];
  311. for (i=0; i<n.length; i++) {
  312. s = this.get(n[i]);
  313. if (!s)
  314. continue;
  315. s = s.style;
  316. // Camelcase it, if needed
  317. na = na.replace(/-(\D)/g, function(a, b){
  318. return b.toUpperCase();
  319. });
  320. if (na == 'opacity') {
  321. // IE specific opacity
  322. if (mox.isIE) {
  323. s.filter = "alpha(opacity=" + (v * 100) + ")";
  324. if (!n.currentStyle || !n.currentStyle.hasLayout)
  325. s.display = 'inline-block';
  326. }
  327. // Fix for older browsers
  328. s['-moz-opacity'] = s['-khtml-opacity'] = v;
  329. }
  330. s[na] = v;
  331. }
  332. },
  333. /**
  334. * Sets multiple CSS styles on the specified element.
  335. *
  336. * @param {Element} e HTML element to set CSS styles on.
  337. * @param {Object} o Name/Value object of styles to set.
  338. */
  339. setStyles : function(e, o) {
  340. var n;
  341. if (!e)
  342. return false;
  343. for (n in o) {
  344. if (typeof(o[n]) != 'function')
  345. this.setStyle(e, n, o[n]);
  346. }
  347. },
  348. /**
  349. * Returns the current runtime/computed style value of a element.
  350. *
  351. * @param {Element} n HTML element to get style from.
  352. * @param {string} na Style name to return.
  353. * @param {string} d Optional default value.
  354. * @return {string} Current runtime/computed style value of a element.
  355. */
  356. getStyle : function(n, na, d) {
  357. n = this.get(n);
  358. if (!n)
  359. return false;
  360. // Gecko
  361. if (this._doc.defaultView) {
  362. try {
  363. return this._doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
  364. } catch (n) {
  365. // Old safari might fail
  366. return null;
  367. }
  368. }
  369. // Camelcase it, if needed
  370. na = na.replace(/-(\D)/g, function(a, b){
  371. return b.toUpperCase();
  372. });
  373. // IE & Opera
  374. if (n.currentStyle)
  375. return n.currentStyle[na];
  376. return false;
  377. },
  378. /**
  379. * Returns child elements by class name.
  380. *
  381. * @param {Element} n HTML element to get child elements from.
  382. * @param {string} na HTML element(s) to look for can be a comma separated list.
  383. * @param {string} c Class names to look for can be a regex chunk link (classA|classB).
  384. * @return {Array} Array containing Element instances matching the class.
  385. */
  386. getByClass : function(n, na, c) {
  387. var t = this;
  388. return this.selectElements(this.get(n), na, function(no) {
  389. return t.hasClass(no, c);
  390. });
  391. },
  392. /**
  393. * Returns the specified element by id.
  394. *
  395. * @param {string} n Element id to look for.
  396. * @return {Element} Element matching the specified id or null if it wasn't found.
  397. */
  398. get : function(n) {
  399. return typeof(n) == 'string' ? this._doc.getElementById(n) : n;
  400. },
  401. /**
  402. * Shows the specified element by ID by setting the "display" style.
  403. *
  404. * @param {string} id ID of DOM element to show.
  405. */
  406. show : function(id) {
  407. this.setStyle(id, 'display', 'block');
  408. },
  409. /**
  410. * Hides the specified element by ID by setting the "display" style.
  411. *
  412. * @param {string} id ID of DOM element to hide.
  413. */
  414. hide : function(id) {
  415. this.setStyle(id, 'display', 'none');
  416. },
  417. /**
  418. * Returns true/false if the element is hidden or not by checking the "display" style.
  419. *
  420. * @param {string} id Id of element to check.
  421. * @return {bool} true/false if the element is hidden or not.
  422. */
  423. isHidden : function(id) {
  424. return this.getStyle(id, 'display') == 'none';
  425. },
  426. /**
  427. * Sets the visbility of the current layer. This will set the visibility style attribute of the element.
  428. *
  429. * @param {string} id ID of DOM element to show.
  430. * @param {bool} s Visibility state true/false if it should be visible.
  431. * @return {bool} Input state.
  432. */
  433. setVisible : function(id, s) {
  434. this.setStyle(id, 'visibility', s ? 'visible' : 'hidden');
  435. return s;
  436. },
  437. /**
  438. * Returns true/false if the specified element is visible or not by checking the "visibility" style.
  439. *
  440. * @param {string} id Id of element to check.
  441. * @return {bool} true/false if the element is visible or not.
  442. */
  443. isVisible : function(id) {
  444. return this.getStyle(id, 'visibility') == 'visible';
  445. },
  446. /**
  447. * Returns the absolute x, y position of a node. The position will be returned in a Point object.
  448. *
  449. * @param {Node} n HTML element to get x, y position from.
  450. * @param {bool} ab Optional state if it should use style left/top as x, y cordinates.
  451. * @param {Node} cn Optional HTML element to to stop position calcualtion by.
  452. * @return {mox.geom.Point} Absolute position of the specified element.
  453. */
  454. getPos : function(n, ab, cn) {
  455. var l = 0, t = 0, p, r, d;
  456. n = this.get(n);
  457. // Handle absolute layers
  458. if (ab)
  459. return new mox.geom.Point(parseInt(this.getStyle(n, 'left')), parseInt(this.getStyle(n, 'top')));
  460. // IE specific method (less quirks in IE6)
  461. if (n && n.getBoundingClientRect) {
  462. r = n.getBoundingClientRect();
  463. d = document;
  464. n = d.compatMode == 'CSS1Compat' ? d.documentElement : d.body;
  465. return new mox.geom.Point(
  466. r.left + (n.scrollLeft || 0),
  467. r.top + (n.scrollTop || 0)
  468. );
  469. }
  470. while (n && n != cn) {
  471. l += n.offsetLeft || 0;
  472. t += n.offsetTop || 0;
  473. n = n.offsetParent;
  474. //p = this.getStyle(n, 'position');
  475. //if (p == 'absolute' || p == 'relative')
  476. // break;
  477. }
  478. return new mox.geom.Point(l, t);
  479. },
  480. /**
  481. * Returns a rectangle instance for a specific element.
  482. *
  483. * @param {Element} e Element to get rectange from.
  484. * @param {bool} ab Optional state if it should use style left/top as x, y cordinates.
  485. * @param {mox.geom.Rect} r Optional rectange to fill.
  486. * @return {mox.geom.Rect} Rectange instance for specified element.
  487. */
  488. getRect : function(e, ab, r) {
  489. var p, r;
  490. e = this.get(e);
  491. p = this.getPos(e, ab);
  492. r = !r ? new mox.geom.Rect() : r;
  493. r.set(
  494. p.x,
  495. p.y,
  496. parseInt(this.getStyle(e, 'width')) || e.offsetWidth,
  497. parseInt(this.getStyle(e, 'height')) || e.offsetHeight
  498. );
  499. return r;
  500. },
  501. /**
  502. * Removes the specified element by id.
  503. *
  504. * @param {string} id ID of DOM element to remove, can also be a commaseparated list.
  505. */
  506. remove : function(id) {
  507. var i, n;
  508. for (i=0, id = id.split(','); i<id.length; i++) {
  509. n = this.get(id[i]);
  510. if (n)
  511. n.parentNode.removeChild(n);
  512. }
  513. },
  514. /**
  515. * Adds a CSS class to the specified element. It will remove any previous item with the same name
  516. * so adding a class that already exists will move it to the end.
  517. *
  518. * @param {Element} e HTML element to add CSS class to.
  519. * @param {string] c CSS class to add to HTML element.
  520. * @param {boolean] b Optional parameter, if set to true, class will be added to the beginning.
  521. * @return {string} Returns the new class attribute value.
  522. */
  523. addClass : function(e, c, b) {
  524. var o;
  525. e = this.get(e);
  526. if (!e)
  527. return null;
  528. o = this.removeClass(e, c);
  529. return e.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c;
  530. },
  531. /**
  532. * Removes the specified CSS class from the element.
  533. *
  534. * @param {Element} e HTML element to remove CSS class to.
  535. * @param {string] c CSS class to remove to HTML element.
  536. * @return {string} Returns the new class attribute value.
  537. */
  538. removeClass : function(e, c) {
  539. e = this.get(e);
  540. if (!e)
  541. return null;
  542. c = e.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"), ' ');
  543. return e.className = c != ' ' ? c : '';
  544. },
  545. /**
  546. * Replaces the specified class with a new one.
  547. *
  548. * @param {Element} e HTML element to replace CSS class in.
  549. * @param {string] o CSS class to remove from HTML element.
  550. * @param {string] n New CSS class to add to HTML element.
  551. */
  552. replaceClass : function(e, o, n) {
  553. if (this.hasClass(e, o)) {
  554. this.removeClass(e, o);
  555. this.addClass(e, n);
  556. }
  557. },
  558. /**
  559. * Returns true if the specified element has the specified class.
  560. *
  561. * @param {Element} n HTML element to check CSS class on.
  562. * @param {string] c CSS class to check for.
  563. * @return {bool} true/false if the specified element has the specified class.
  564. */
  565. hasClass : function(n, c) {
  566. n = this.get(n);
  567. return new RegExp('\\b' + c + '\\b', 'g').test(n.className);
  568. },
  569. /**
  570. * Returns the outer HTML of a element, this uses the outerHTML
  571. * property in MSIE and Opera and a workaround for Gecko.
  572. *
  573. * @param {Element} e HTML element to get outerHTML from.
  574. * @return {string} HTML content string.
  575. */
  576. getOuterHTML : function(e) {
  577. var d;
  578. e = this.get(e);
  579. if (mox.isIE || mox.isOpera)
  580. return e.outerHTML;
  581. d = e.ownerDocument.createElement("body");
  582. d.appendChild(e.cloneNode(true));
  583. return d.innerHTML;
  584. },
  585. /**
  586. * Sets the outer HTML of a element, this uses the outerHTML
  587. * property in MSIE and Opera and a workaround for Gecko.
  588. *
  589. * @param {Element} e HTML element to set outerHTML on.
  590. * @param {string} h HTML string to set in property.
  591. */
  592. setOuterHTML : function(e, h) {
  593. var i, nl, t;
  594. e = this.get(e);
  595. if (mox.isIE)
  596. e.outerHTML = h;
  597. else {
  598. t = e.ownerDocument.createElement("body");
  599. t.innerHTML = h;
  600. for (i=0, nl=t.childNodes; i<nl.length; i++)
  601. e.parentNode.insertBefore(nl[i].cloneNode(true), e);
  602. e.parentNode.removeChild(e);
  603. }
  604. },
  605. /**
  606. * Imports a CSS file into a allready loaded document. This will add a link element
  607. * to the head element of the document.
  608. *
  609. * @param {string} css CSS File URL to load or comma separated list of files.
  610. */
  611. importCSS : function(css) {
  612. var i, e, he, d = this._doc;
  613. css = css.split(',');
  614. for (i=0; i<css.length; i++) {
  615. if (!d.createStyleSheet) {
  616. e = d.createElement("link");
  617. e.rel = "stylesheet";
  618. e.href = css[i];
  619. if ((he = d.getElementsByTagName("head")) != null && he.length > 0)
  620. he[0].appendChild(e);
  621. } else
  622. d.createStyleSheet(css[i]);
  623. }
  624. },
  625. /**
  626. * Returns a unique id. This can be useful when generating elements on the fly.
  627. * This method will not check if the element allreay exists.
  628. *
  629. * @param {string} p Optional prefix to add infront of all ids.
  630. * @return {string} Unique id.
  631. */
  632. uniqueId : function(p) {
  633. return (!p ? 'mox_' : p) + (this._counter++);
  634. }
  635. /**#@-*/
  636. });
  637. // Setup base DOM
  638. mox.DOM = new mox.DOMUtils(document);
  639. // Tell it a lie :)
  640. mox.provide('mox.DOM');
  641. });