Utilities.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193
  1. /*
  2. * Copyright (C) 2013 Apple Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
  14. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  15. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  16. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
  17. * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  18. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  19. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  20. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  21. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  22. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  23. * THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. if (!("bind" in Function.prototype)) {
  26. Object.defineProperty(Function.prototype, "bind",
  27. {
  28. value: function(thisObject)
  29. {
  30. var func = this;
  31. var args = Array.prototype.slice.call(arguments, 1);
  32. return function bound()
  33. {
  34. return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0)));
  35. };
  36. }
  37. });
  38. }
  39. Object.defineProperty(Object, "shallowCopy",
  40. {
  41. value: function(object)
  42. {
  43. // Make a new object and copy all the key/values. The values are not copied.
  44. var copy = {};
  45. var keys = Object.keys(object);
  46. for (var i = 0; i < keys.length; ++i)
  47. copy[keys[i]] = object[keys[i]];
  48. return copy;
  49. }
  50. });
  51. Object.defineProperty(Object, "shallowEqual",
  52. {
  53. value: function(a, b)
  54. {
  55. // Checks if two objects have the same top-level properties.
  56. // Check for strict equality in case they are the same object.
  57. if (a === b)
  58. return true;
  59. // Only objects can proceed. null is an object, but Object.keys throws for null.
  60. if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
  61. return false;
  62. var aKeys = Object.keys(a);
  63. var bKeys = Object.keys(b);
  64. // Check that each object has the same number of keys.
  65. if (aKeys.length !== bKeys.length)
  66. return false;
  67. // Check if all the keys and their values are equal.
  68. for (var i = 0; i < aKeys.length; ++i) {
  69. // Check that b has the same key as a.
  70. if (!(aKeys[i] in b))
  71. return false;
  72. // Check that the values are strict equal since this is only
  73. // a shallow check, not a recursive one.
  74. if (a[aKeys[i]] !== b[aKeys[i]])
  75. return false;
  76. }
  77. return true;
  78. }
  79. });
  80. Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey",
  81. {
  82. value: function(key)
  83. {
  84. if (this.hasOwnProperty(key))
  85. return this[key];
  86. var lowerCaseKey = key.toLowerCase();
  87. for (var currentKey in this) {
  88. if (currentKey.toLowerCase() === lowerCaseKey)
  89. return this[currentKey];
  90. }
  91. return undefined;
  92. }
  93. });
  94. Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithClass",
  95. {
  96. value: function(className)
  97. {
  98. for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
  99. if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
  100. return node;
  101. return null;
  102. }
  103. });
  104. Object.defineProperty(Node.prototype, "enclosingNodeWithClass",
  105. {
  106. value: function(className)
  107. {
  108. if (!this.parentNode)
  109. return null;
  110. return this.parentNode.enclosingNodeOrSelfWithClass(className);
  111. }
  112. });
  113. Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeNameInArray",
  114. {
  115. value: function(nameArray)
  116. {
  117. var lowerCaseNameArray = nameArray.map(function(name) { return name.toLowerCase() });
  118. for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) {
  119. for (var i = 0; i < nameArray.length; ++i) {
  120. if (node.nodeName.toLowerCase() === lowerCaseNameArray[i])
  121. return node;
  122. }
  123. }
  124. return null;
  125. }
  126. });
  127. Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeName",
  128. {
  129. value: function(nodeName)
  130. {
  131. return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
  132. }
  133. });
  134. Object.defineProperty(Node.prototype, "isAncestor",
  135. {
  136. value: function(node)
  137. {
  138. if (!node)
  139. return false;
  140. var currentNode = node.parentNode;
  141. while (currentNode) {
  142. if (this === currentNode)
  143. return true;
  144. currentNode = currentNode.parentNode;
  145. }
  146. return false;
  147. }
  148. });
  149. Object.defineProperty(Node.prototype, "isDescendant",
  150. {
  151. value: function(descendant)
  152. {
  153. return !!descendant && descendant.isAncestor(this);
  154. }
  155. });
  156. Object.defineProperty(Node.prototype, "isSelfOrAncestor",
  157. {
  158. value: function(node)
  159. {
  160. return !!node && (node === this || this.isAncestor(node));
  161. }
  162. });
  163. Object.defineProperty(Node.prototype, "isSelfOrDescendant",
  164. {
  165. value: function(node)
  166. {
  167. return !!node && (node === this || this.isDescendant(node));
  168. }
  169. });
  170. Object.defineProperty(Node.prototype, "traverseNextNode",
  171. {
  172. value: function(stayWithin)
  173. {
  174. var node = this.firstChild;
  175. if (node)
  176. return node;
  177. if (stayWithin && this === stayWithin)
  178. return null;
  179. node = this.nextSibling;
  180. if (node)
  181. return node;
  182. node = this;
  183. while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
  184. node = node.parentNode;
  185. if (!node)
  186. return null;
  187. return node.nextSibling;
  188. }
  189. });
  190. Object.defineProperty(Node.prototype, "traversePreviousNode",
  191. {
  192. value: function(stayWithin)
  193. {
  194. if (stayWithin && this === stayWithin)
  195. return null;
  196. var node = this.previousSibling;
  197. while (node && node.lastChild)
  198. node = node.lastChild;
  199. if (node)
  200. return node;
  201. return this.parentNode;
  202. }
  203. });
  204. Object.defineProperty(Node.prototype, "traverseNextTextNode",
  205. {
  206. value: function(stayWithin)
  207. {
  208. var node = this.traverseNextNode(stayWithin);
  209. if (!node)
  210. return;
  211. while (node && node.nodeType !== Node.TEXT_NODE)
  212. node = node.traverseNextNode(stayWithin);
  213. return node;
  214. }
  215. });
  216. Object.defineProperty(Node.prototype, "rangeBoundaryForOffset",
  217. {
  218. value: function(offset)
  219. {
  220. var textNode = this.traverseNextTextNode(this);
  221. while (textNode && offset > textNode.data.length) {
  222. offset -= textNode.data.length;
  223. textNode = textNode.traverseNextTextNode(this);
  224. }
  225. if (!textNode)
  226. return {container: this, offset: 0};
  227. return {container: textNode, offset: offset};
  228. }
  229. });
  230. Object.defineProperty(Node.prototype, "rangeOfWord",
  231. {
  232. value: function(offset, stopCharacters, stayWithinNode, direction)
  233. {
  234. var startNode;
  235. var startOffset = 0;
  236. var endNode;
  237. var endOffset = 0;
  238. if (!stayWithinNode)
  239. stayWithinNode = this;
  240. if (!direction || direction === "backward" || direction === "both") {
  241. var node = this;
  242. while (node) {
  243. if (node === stayWithinNode) {
  244. if (!startNode)
  245. startNode = stayWithinNode;
  246. break;
  247. }
  248. if (node.nodeType === Node.TEXT_NODE) {
  249. var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
  250. for (var i = start; i >= 0; --i) {
  251. if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
  252. startNode = node;
  253. startOffset = i + 1;
  254. break;
  255. }
  256. }
  257. }
  258. if (startNode)
  259. break;
  260. node = node.traversePreviousNode(stayWithinNode);
  261. }
  262. if (!startNode) {
  263. startNode = stayWithinNode;
  264. startOffset = 0;
  265. }
  266. } else {
  267. startNode = this;
  268. startOffset = offset;
  269. }
  270. if (!direction || direction === "forward" || direction === "both") {
  271. node = this;
  272. while (node) {
  273. if (node === stayWithinNode) {
  274. if (!endNode)
  275. endNode = stayWithinNode;
  276. break;
  277. }
  278. if (node.nodeType === Node.TEXT_NODE) {
  279. var start = (node === this ? offset : 0);
  280. for (var i = start; i < node.nodeValue.length; ++i) {
  281. if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
  282. endNode = node;
  283. endOffset = i;
  284. break;
  285. }
  286. }
  287. }
  288. if (endNode)
  289. break;
  290. node = node.traverseNextNode(stayWithinNode);
  291. }
  292. if (!endNode) {
  293. endNode = stayWithinNode;
  294. endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
  295. }
  296. } else {
  297. endNode = this;
  298. endOffset = offset;
  299. }
  300. var result = this.ownerDocument.createRange();
  301. result.setStart(startNode, startOffset);
  302. result.setEnd(endNode, endOffset);
  303. return result;
  304. }
  305. });
  306. if (!("remove" in Element.prototype)) {
  307. Object.defineProperty(Element.prototype, "remove",
  308. {
  309. value: function()
  310. {
  311. if (this.parentNode)
  312. this.parentNode.removeChild(this);
  313. }
  314. });
  315. }
  316. Object.defineProperty(Element.prototype, "totalOffsetLeft",
  317. {
  318. get: function()
  319. {
  320. return this.getBoundingClientRect().left;
  321. }
  322. });
  323. Object.defineProperty(Element.prototype, "totalOffsetTop",
  324. {
  325. get: function()
  326. {
  327. return this.getBoundingClientRect().top;
  328. }
  329. });
  330. Object.defineProperty(Element.prototype, "removeChildren",
  331. {
  332. value: function()
  333. {
  334. // This has been tested to be the fastest removal method.
  335. if (this.firstChild)
  336. this.textContent = "";
  337. }
  338. });
  339. Object.defineProperty(Element.prototype, "isInsertionCaretInside",
  340. {
  341. value: function()
  342. {
  343. var selection = window.getSelection();
  344. if (!selection.rangeCount || !selection.isCollapsed)
  345. return false;
  346. var selectionRange = selection.getRangeAt(0);
  347. return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
  348. }
  349. });
  350. Object.defineProperty(Element.prototype, "removeMatchingStyleClasses",
  351. {
  352. value: function(classNameRegex)
  353. {
  354. var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
  355. if (regex.test(this.className))
  356. this.className = this.className.replace(regex, " ");
  357. }
  358. });
  359. Object.defineProperty(Element.prototype, "createChild",
  360. {
  361. value: function(elementName, className)
  362. {
  363. var element = this.ownerDocument.createElement(elementName);
  364. if (className)
  365. element.className = className;
  366. this.appendChild(element);
  367. return element;
  368. }
  369. });
  370. function AnchorBox(x, y, width, height)
  371. {
  372. this.x = x || 0;
  373. this.y = y || 0;
  374. this.width = width || 0;
  375. this.height = height || 0;
  376. }
  377. Object.defineProperty(Element.prototype, "boxInWindow",
  378. {
  379. value: function()
  380. {
  381. var anchorBox = new AnchorBox;
  382. var clientRect = this.getBoundingClientRect();
  383. console.assert(clientRect);
  384. anchorBox.x = clientRect.left;
  385. anchorBox.y = clientRect.top;
  386. anchorBox.width = clientRect.width;
  387. anchorBox.height = clientRect.height;
  388. return anchorBox;
  389. }
  390. });
  391. Object.defineProperty(Element.prototype, "positionAt",
  392. {
  393. value: function(x, y)
  394. {
  395. this.style.left = x + "px";
  396. this.style.top = y + "px";
  397. }
  398. });
  399. Object.defineProperty(Element.prototype, "pruneEmptyTextNodes",
  400. {
  401. value: function()
  402. {
  403. var sibling = this.firstChild;
  404. while (sibling) {
  405. var nextSibling = sibling.nextSibling;
  406. if (sibling.nodeType === this.TEXT_NODE && sibling.nodeValue === "")
  407. this.removeChild(sibling);
  408. sibling = nextSibling;
  409. }
  410. }
  411. });
  412. Object.defineProperty(Element.prototype, "isScrolledToBottom",
  413. {
  414. value: function()
  415. {
  416. // This code works only for 0-width border
  417. return this.scrollTop + this.clientHeight === this.scrollHeight;
  418. }
  419. });
  420. Object.defineProperty(DocumentFragment.prototype, "createChild",
  421. {
  422. value: Element.prototype.createChild
  423. });
  424. Object.defineProperty(Array.prototype, "contains",
  425. {
  426. value: function(value)
  427. {
  428. return this.indexOf(value) !== -1;
  429. }
  430. });
  431. Object.defineProperty(Array.prototype, "lastValue",
  432. {
  433. get: function()
  434. {
  435. if (!this.length)
  436. return undefined;
  437. return this[this.length - 1];
  438. }
  439. });
  440. Object.defineProperty(Array.prototype, "remove",
  441. {
  442. value: function(value, onlyFirst)
  443. {
  444. for (var i = this.length - 1; i >= 0; --i) {
  445. if (this[i] === value) {
  446. this.splice(i, 1);
  447. if (onlyFirst)
  448. return;
  449. }
  450. }
  451. }
  452. });
  453. Object.defineProperty(Array.prototype, "keySet",
  454. {
  455. value: function()
  456. {
  457. var keys = {};
  458. for (var i = 0; i < this.length; ++i)
  459. keys[this[i]] = true;
  460. return keys;
  461. }
  462. });
  463. Object.defineProperty(Array.prototype, "upperBound",
  464. {
  465. value: function(value)
  466. {
  467. var first = 0;
  468. var count = this.length;
  469. while (count > 0) {
  470. var step = count >> 1;
  471. var middle = first + step;
  472. if (value >= this[middle]) {
  473. first = middle + 1;
  474. count -= step + 1;
  475. } else
  476. count = step;
  477. }
  478. return first;
  479. }
  480. });
  481. Object.defineProperty(Array, "convert",
  482. {
  483. value: function(list, startIndex, endIndex)
  484. {
  485. return Array.prototype.slice.call(list, startIndex, endIndex);
  486. }
  487. });
  488. Object.defineProperty(String.prototype, "trimMiddle",
  489. {
  490. value: function(maxLength)
  491. {
  492. if (this.length <= maxLength)
  493. return this;
  494. var leftHalf = maxLength >> 1;
  495. var rightHalf = maxLength - leftHalf - 1;
  496. return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf);
  497. }
  498. });
  499. Object.defineProperty(String.prototype, "trimEnd",
  500. {
  501. value: function(maxLength)
  502. {
  503. if (this.length <= maxLength)
  504. return this;
  505. return this.substr(0, maxLength - 1) + "\u2026";
  506. }
  507. });
  508. Object.defineProperty(String.prototype, "collapseWhitespace",
  509. {
  510. value: function()
  511. {
  512. return this.replace(/[\s\xA0]+/g, " ");
  513. }
  514. });
  515. Object.defineProperty(String.prototype, "escapeCharacters",
  516. {
  517. value: function(chars)
  518. {
  519. var foundChar = false;
  520. for (var i = 0; i < chars.length; ++i) {
  521. if (this.indexOf(chars.charAt(i)) !== -1) {
  522. foundChar = true;
  523. break;
  524. }
  525. }
  526. if (!foundChar)
  527. return this;
  528. var result = "";
  529. for (var i = 0; i < this.length; ++i) {
  530. if (chars.indexOf(this.charAt(i)) !== -1)
  531. result += "\\";
  532. result += this.charAt(i);
  533. }
  534. return result;
  535. }
  536. });
  537. Object.defineProperty(String.prototype, "escapeForRegExp",
  538. {
  539. value: function()
  540. {
  541. return this.escapeCharacters("^[]{}()\\.$*+?|");
  542. }
  543. });
  544. Object.defineProperty(String.prototype, "capitalize",
  545. {
  546. value: function()
  547. {
  548. return this.charAt(0).toUpperCase() + this.slice(1);
  549. }
  550. });
  551. Object.defineProperty(String, "tokenizeFormatString",
  552. {
  553. value: function(format)
  554. {
  555. var tokens = [];
  556. var substitutionIndex = 0;
  557. function addStringToken(str)
  558. {
  559. tokens.push({ type: "string", value: str });
  560. }
  561. function addSpecifierToken(specifier, precision, substitutionIndex)
  562. {
  563. tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
  564. }
  565. var index = 0;
  566. for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
  567. addStringToken(format.substring(index, precentIndex));
  568. index = precentIndex + 1;
  569. if (format[index] === "%") {
  570. addStringToken("%");
  571. ++index;
  572. continue;
  573. }
  574. if (!isNaN(format[index])) {
  575. // The first character is a number, it might be a substitution index.
  576. var number = parseInt(format.substring(index), 10);
  577. while (!isNaN(format[index]))
  578. ++index;
  579. // If the number is greater than zero and ends with a "$",
  580. // then this is a substitution index.
  581. if (number > 0 && format[index] === "$") {
  582. substitutionIndex = (number - 1);
  583. ++index;
  584. }
  585. }
  586. var precision = -1;
  587. if (format[index] === ".") {
  588. // This is a precision specifier. If no digit follows the ".",
  589. // then the precision should be zero.
  590. ++index;
  591. precision = parseInt(format.substring(index), 10);
  592. if (isNaN(precision))
  593. precision = 0;
  594. while (!isNaN(format[index]))
  595. ++index;
  596. }
  597. addSpecifierToken(format[index], precision, substitutionIndex);
  598. ++substitutionIndex;
  599. ++index;
  600. }
  601. addStringToken(format.substring(index));
  602. return tokens;
  603. }
  604. });
  605. Object.defineProperty(String.prototype, "startsWith",
  606. {
  607. value: function(string)
  608. {
  609. return this.lastIndexOf(string, 0) === 0;
  610. }
  611. });
  612. Object.defineProperty(String.prototype, "hash",
  613. {
  614. get: function()
  615. {
  616. // Matches the wtf/StringHasher.h (SuperFastHash) algorithm.
  617. // Arbitrary start value to avoid mapping all 0's to all 0's.
  618. const stringHashingStartValue = 0x9e3779b9;
  619. var result = stringHashingStartValue;
  620. var pendingCharacter = null;
  621. for (var i = 0; i < this.length; ++i) {
  622. var currentCharacter = this[i].charCodeAt(0);
  623. if (pendingCharacter === null) {
  624. pendingCharacter = currentCharacter
  625. continue;
  626. }
  627. result += pendingCharacter;
  628. result = (result << 16) ^ ((currentCharacter << 11) ^ result);
  629. result += result >> 11;
  630. pendingCharacter = null;
  631. }
  632. // Handle the last character in odd length strings.
  633. if (pendingCharacter !== null) {
  634. result += pendingCharacter;
  635. result ^= result << 11;
  636. result += result >> 17;
  637. }
  638. // Force "avalanching" of final 31 bits.
  639. result ^= result << 3;
  640. result += result >> 5;
  641. result ^= result << 2;
  642. result += result >> 15;
  643. result ^= result << 10;
  644. // Prevent 0 and negative results.
  645. return (0xffffffff + result + 1).toString(36);
  646. }
  647. });
  648. Object.defineProperty(String, "standardFormatters",
  649. {
  650. value: {
  651. d: function(substitution)
  652. {
  653. return !isNaN(substitution) ? substitution : 0;
  654. },
  655. f: function(substitution, token)
  656. {
  657. if (substitution && token.precision > -1)
  658. substitution = substitution.toFixed(token.precision);
  659. return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
  660. },
  661. s: function(substitution)
  662. {
  663. return substitution;
  664. }
  665. }
  666. });
  667. Object.defineProperty(String, "format",
  668. {
  669. value: function(format, substitutions, formatters, initialValue, append)
  670. {
  671. if (!format || !substitutions || !substitutions.length)
  672. return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
  673. function prettyFunctionName()
  674. {
  675. return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
  676. }
  677. function warn(msg)
  678. {
  679. console.warn(prettyFunctionName() + ": " + msg);
  680. }
  681. function error(msg)
  682. {
  683. console.error(prettyFunctionName() + ": " + msg);
  684. }
  685. var result = initialValue;
  686. var tokens = String.tokenizeFormatString(format);
  687. var usedSubstitutionIndexes = {};
  688. for (var i = 0; i < tokens.length; ++i) {
  689. var token = tokens[i];
  690. if (token.type === "string") {
  691. result = append(result, token.value);
  692. continue;
  693. }
  694. if (token.type !== "specifier") {
  695. error("Unknown token type \"" + token.type + "\" found.");
  696. continue;
  697. }
  698. if (token.substitutionIndex >= substitutions.length) {
  699. // If there are not enough substitutions for the current substitutionIndex
  700. // just output the format specifier literally and move on.
  701. error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
  702. result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
  703. continue;
  704. }
  705. usedSubstitutionIndexes[token.substitutionIndex] = true;
  706. if (!(token.specifier in formatters)) {
  707. // Encountered an unsupported format character, treat as a string.
  708. warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
  709. result = append(result, substitutions[token.substitutionIndex]);
  710. continue;
  711. }
  712. result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
  713. }
  714. var unusedSubstitutions = [];
  715. for (var i = 0; i < substitutions.length; ++i) {
  716. if (i in usedSubstitutionIndexes)
  717. continue;
  718. unusedSubstitutions.push(substitutions[i]);
  719. }
  720. return {formattedResult: result, unusedSubstitutions: unusedSubstitutions};
  721. }
  722. });
  723. Object.defineProperty(String.prototype, "format",
  724. {
  725. value: function()
  726. {
  727. return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
  728. }
  729. });
  730. Object.defineProperty(String.prototype, "insertWordBreakCharacters",
  731. {
  732. value: function()
  733. {
  734. // Add zero width spaces after characters that are good to break after.
  735. // Otherwise a string with no spaces will not break and overflow its container.
  736. // This is mainly used on URL strings, so the characters are tailored for URLs.
  737. return this.replace(/([\/;:\)\]\}&?])/g, "$1\u200b");
  738. }
  739. });
  740. Object.defineProperty(String.prototype, "removeWordBreakCharacters",
  741. {
  742. value: function()
  743. {
  744. // Undoes what insertWordBreakCharacters did.
  745. return this.replace(/\u200b/g, "");
  746. }
  747. });
  748. Object.defineProperty(Number, "constrain",
  749. {
  750. value: function(num, min, max)
  751. {
  752. if (num < min)
  753. num = min;
  754. else if (num > max)
  755. num = max;
  756. return num;
  757. }
  758. });
  759. Object.defineProperty(Number, "secondsToString",
  760. {
  761. value: function(seconds, higherResolution)
  762. {
  763. var ms = seconds * 1000;
  764. if (higherResolution && ms < 100)
  765. return WebInspector.UIString("%.2fms").format(ms);
  766. else if (ms < 100)
  767. return WebInspector.UIString("%.1fms").format(ms);
  768. if (higherResolution && ms < 1000)
  769. return WebInspector.UIString("%.1fms").format(ms);
  770. else if (ms < 1000)
  771. return WebInspector.UIString("%.0fms").format(ms);
  772. if (seconds < 60)
  773. return WebInspector.UIString("%.2fs").format(seconds);
  774. var minutes = seconds / 60;
  775. if (minutes < 60)
  776. return WebInspector.UIString("%.1fmin").format(minutes);
  777. var hours = minutes / 60;
  778. if (hours < 24)
  779. return WebInspector.UIString("%.1fhrs").format(hours);
  780. var days = hours / 24;
  781. return WebInspector.UIString("%.1f days").format(days);
  782. }
  783. });
  784. Object.defineProperty(Number, "bytesToString",
  785. {
  786. value: function(bytes, higherResolution)
  787. {
  788. if (higherResolution === undefined)
  789. higherResolution = true;
  790. if (bytes < 1024)
  791. return WebInspector.UIString("%.0f B").format(bytes);
  792. var kilobytes = bytes / 1024;
  793. if (higherResolution && kilobytes < 1024)
  794. return WebInspector.UIString("%.2f KB").format(kilobytes);
  795. else if (kilobytes < 1024)
  796. return WebInspector.UIString("%.0f KB").format(kilobytes);
  797. var megabytes = kilobytes / 1024;
  798. if (higherResolution)
  799. return WebInspector.UIString("%.2f MB").format(megabytes);
  800. else
  801. return WebInspector.UIString("%.0f MB").format(megabytes);
  802. }
  803. });
  804. Object.defineProperty(Uint32Array, "isLittleEndian",
  805. {
  806. value: function()
  807. {
  808. if ("_isLittleEndian" in this)
  809. return this._isLittleEndian;
  810. var buffer = new ArrayBuffer(4);
  811. var longData = new Uint32Array(buffer);
  812. var data = new Uint8Array(buffer);
  813. longData[0] = 0x0a0b0c0d;
  814. this._isLittleEndian = data[0] === 0x0d && data[1] === 0x0c && data[2] === 0x0b && data[3] === 0x0a;
  815. return this._isLittleEndian;
  816. }
  817. });
  818. function isEmptyObject(object)
  819. {
  820. for (var property in object)
  821. return false;
  822. return true;
  823. }
  824. function isEnterKey(event)
  825. {
  826. // Check if this is an IME event.
  827. return event.keyCode !== 229 && event.keyIdentifier === "Enter";
  828. }
  829. function removeURLFragment(url)
  830. {
  831. var hashIndex = url.indexOf("#");
  832. if (hashIndex >= 0)
  833. return url.substring(0, hashIndex);
  834. return url;
  835. }
  836. function relativePath(path, basePath)
  837. {
  838. console.assert(path.charAt(0) === "/");
  839. console.assert(basePath.charAt(0) === "/");
  840. var pathComponents = path.split("/");
  841. var baseComponents = basePath.replace(/\/$/, "").split("/");
  842. var finalComponents = [];
  843. var index = 1;
  844. for (; index < pathComponents.length && index < baseComponents.length; ++index) {
  845. if (pathComponents[index] !== baseComponents[index])
  846. break;
  847. }
  848. for (var i = index; i < baseComponents.length; ++i)
  849. finalComponents.push("..");
  850. for (var i = index; i < pathComponents.length; ++i)
  851. finalComponents.push(pathComponents[i]);
  852. return finalComponents.join("/");
  853. }
  854. function resolveDotsInPath(path)
  855. {
  856. if (!path)
  857. return path;
  858. if (path.indexOf("./") === -1)
  859. return path;
  860. console.assert(path.charAt(0) === "/");
  861. var result = [];
  862. var components = path.split("/");
  863. for (var i = 0; i < components.length; ++i) {
  864. var component = components[i];
  865. // Skip over "./".
  866. if (component === ".")
  867. continue;
  868. // Rewind one component for "../".
  869. if (component === "..") {
  870. if (result.length === 1)
  871. continue;
  872. result.pop();
  873. continue;
  874. }
  875. result.push(component);
  876. }
  877. return result.join("/");
  878. }
  879. function parseURL(url)
  880. {
  881. url = url ? url.trim() : "";
  882. var match = url.match(/^([^:]+):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i);
  883. if (!match)
  884. return {scheme: null, host: null, port: null, path: null, queryString: null, fragment: null, lastPathComponent: null};
  885. var scheme = match[1].toLowerCase();
  886. var host = match[2].toLowerCase();
  887. var port = Number(match[3]) || null;
  888. var wholePath = match[4] || null;
  889. var fragment = match[5] || null;
  890. var path = wholePath;
  891. var queryString = null;
  892. // Split the path and the query string.
  893. if (wholePath) {
  894. var indexOfQuery = wholePath.indexOf("?");
  895. if (indexOfQuery !== -1) {
  896. path = wholePath.substring(0, indexOfQuery);
  897. queryString = wholePath.substring(indexOfQuery + 1);
  898. }
  899. path = resolveDotsInPath(path);
  900. }
  901. // Find last path component.
  902. var lastPathComponent = null;
  903. if (path && path !== "/") {
  904. // Skip the trailing slash if there is one.
  905. var endOffset = path[path.length - 1] === "/" ? 1 : 0;
  906. var lastSlashIndex = path.lastIndexOf("/", path.length - 1 - endOffset);
  907. if (lastSlashIndex !== -1)
  908. lastPathComponent = path.substring(lastSlashIndex + 1, path.length - endOffset);
  909. }
  910. return {scheme: scheme, host: host, port: port, path: path, queryString: queryString, fragment: fragment, lastPathComponent: lastPathComponent};
  911. }
  912. function absoluteURL(partialURL, baseURL)
  913. {
  914. partialURL = partialURL ? partialURL.trim() : "";
  915. // Return data and javascript URLs as-is.
  916. if (partialURL.startsWith("data:") || partialURL.startsWith("javascript:") || partialURL.startsWith("mailto:"))
  917. return partialURL;
  918. // If the URL has a scheme it is already a full URL, so return it.
  919. if (parseURL(partialURL).scheme)
  920. return partialURL;
  921. // If there is no partial URL, just return the base URL.
  922. if (!partialURL)
  923. return baseURL || null;
  924. var baseURLComponents = parseURL(baseURL);
  925. // The base URL needs to be an absolute URL. Return null if it isn't.
  926. if (!baseURLComponents.scheme)
  927. return null;
  928. // A URL that starts with "//" is a full URL without the scheme. Use the base URL scheme.
  929. if (partialURL[0] === "/" && partialURL[1] === "/")
  930. return baseURLComponents.scheme + ":" + partialURL;
  931. // The path can be null for URLs that have just a scheme and host (like "http://apple.com"). So make the path be "/".
  932. if (!baseURLComponents.path)
  933. baseURLComponents.path = "/";
  934. // Generate the base URL prefix that is used in the rest of the cases.
  935. var baseURLPrefix = baseURLComponents.scheme + "://" + baseURLComponents.host + (baseURLComponents.port ? (":" + baseURLComponents.port) : "");
  936. // A URL that starts with "?" is just a query string that gets applied to the base URL (replacing the base URL query string and fragment).
  937. if (partialURL[0] === "?")
  938. return baseURLPrefix + baseURLComponents.path + partialURL;
  939. // A URL that starts with "/" is an absolute path that gets applied to the base URL (replacing the base URL path, query string and fragment).
  940. if (partialURL[0] === "/")
  941. return baseURLPrefix + resolveDotsInPath(partialURL);
  942. // Generate the base path that is used in the final case by removing everything after the last "/" from the base URL's path.
  943. var basePath = baseURLComponents.path.substring(0, baseURLComponents.path.lastIndexOf("/")) + "/";
  944. return baseURLPrefix + resolveDotsInPath(basePath + partialURL);
  945. }
  946. function simpleGlobStringToRegExp(globString, regExpFlags)
  947. {
  948. // Only supports "*" globs.
  949. if (!globString)
  950. return null;
  951. // Escape everything from String.prototype.escapeForRegExp except "*".
  952. var regexString = globString.escapeCharacters("^[]{}()\\.$+?|");
  953. // Unescape all doubly escaped backslashes in front of escaped asterisks.
  954. // So "\\*" will become "\*" again, undoing escapeCharacters escaping of "\".
  955. // This makes "\*" match a literal "*" instead of using the "*" for globbing.
  956. regexString = regexString.replace(/\\\\\*/g, "\\*");
  957. // The following regex doesn't match an asterisk that has a backslash in front.
  958. // It also catches consecutive asterisks so they collapse down when replaced.
  959. var unescapedAsteriskRegex = /(^|[^\\])\*+/g;
  960. if (unescapedAsteriskRegex.test(globString)) {
  961. // Replace all unescaped asterisks with ".*".
  962. regexString = regexString.replace(unescapedAsteriskRegex, "$1.*");
  963. // Match edge boundaries when there is an asterisk to better meet the expectations
  964. // of the user. When someone types "*.js" they don't expect "foo.json" to match. They
  965. // would only expect that if they type "*.js*". We use \b (instead of ^ and $) to allow
  966. // matches inside paths or URLs, so "ba*.js" will match "foo/bar.js" but not "boo/bbar.js".
  967. // When there isn't an asterisk the regexString is just a substring search.
  968. regexString = "\\b" + regexString + "\\b";
  969. }
  970. return new RegExp(regexString, regExpFlags);
  971. }
  972. function parseLocationQueryParameters(arrayResult)
  973. {
  974. // The first character is always the "?".
  975. return parseQueryString(window.location.search.substring(1), arrayResult);
  976. }
  977. function parseQueryString(queryString, arrayResult)
  978. {
  979. if (!queryString)
  980. return arrayResult ? [] : {};
  981. function decode(string)
  982. {
  983. try {
  984. // Replace "+" with " " then decode precent encoded values.
  985. return decodeURIComponent(string.replace(/\+/g, " "));
  986. } catch (e) {
  987. return string;
  988. }
  989. }
  990. var parameters = arrayResult ? [] : {};
  991. var parameterStrings = queryString.split("&");
  992. for (var i = 0; i < parameterStrings.length; ++i) {
  993. var pair = parameterStrings[i].split("=").map(decode);
  994. if (arrayResult)
  995. parameters.push({name: pair[0], value: pair[1]});
  996. else
  997. parameters[pair[0]] = pair[1];
  998. }
  999. return parameters;
  1000. }