UIUtils.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  1. /*
  2. * Copyright (C) 2011 Google Inc. All rights reserved.
  3. * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
  4. * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
  5. * Copyright (C) 2009 Joseph Pecoraro
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * 2. Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  17. * its contributors may be used to endorse or promote products derived
  18. * from this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  21. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  22. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  23. * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  24. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  25. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  26. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  27. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  29. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. /**
  32. * @param {Element} element
  33. * @param {?function(Event): boolean} elementDragStart
  34. * @param {function(Event)} elementDrag
  35. * @param {?function(Event)} elementDragEnd
  36. * @param {string} cursor
  37. */
  38. WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor)
  39. {
  40. element.addEventListener("mousedown", WebInspector._elementDragStart.bind(WebInspector, elementDragStart, elementDrag, elementDragEnd, cursor), false);
  41. }
  42. /**
  43. * @param {?function(Event)} elementDragStart
  44. * @param {function(Event)} elementDrag
  45. * @param {?function(Event)} elementDragEnd
  46. * @param {string} cursor
  47. * @param {Event} event
  48. */
  49. WebInspector._elementDragStart = function(elementDragStart, elementDrag, elementDragEnd, cursor, event)
  50. {
  51. // Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac.
  52. if (event.button || (WebInspector.isMac() && event.ctrlKey))
  53. return;
  54. if (WebInspector._elementDraggingEventListener)
  55. return;
  56. if (elementDragStart && !elementDragStart(event))
  57. return;
  58. if (WebInspector._elementDraggingGlassPane) {
  59. WebInspector._elementDraggingGlassPane.dispose();
  60. delete WebInspector._elementDraggingGlassPane;
  61. }
  62. var targetDocument = event.target.ownerDocument;
  63. WebInspector._elementDraggingEventListener = elementDrag;
  64. WebInspector._elementEndDraggingEventListener = elementDragEnd;
  65. WebInspector._mouseOutWhileDraggingTargetDocument = targetDocument;
  66. targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true);
  67. targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true);
  68. targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
  69. targetDocument.body.style.cursor = cursor;
  70. event.preventDefault();
  71. }
  72. WebInspector._mouseOutWhileDragging = function()
  73. {
  74. WebInspector._unregisterMouseOutWhileDragging();
  75. WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane();
  76. }
  77. WebInspector._unregisterMouseOutWhileDragging = function()
  78. {
  79. if (!WebInspector._mouseOutWhileDraggingTargetDocument)
  80. return;
  81. WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
  82. delete WebInspector._mouseOutWhileDraggingTargetDocument;
  83. }
  84. WebInspector._elementDragMove = function(event)
  85. {
  86. if (WebInspector._elementDraggingEventListener(event))
  87. WebInspector._cancelDragEvents(event);
  88. }
  89. WebInspector._cancelDragEvents = function(event)
  90. {
  91. var targetDocument = event.target.ownerDocument;
  92. targetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true);
  93. targetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true);
  94. WebInspector._unregisterMouseOutWhileDragging();
  95. targetDocument.body.style.removeProperty("cursor");
  96. if (WebInspector._elementDraggingGlassPane)
  97. WebInspector._elementDraggingGlassPane.dispose();
  98. delete WebInspector._elementDraggingGlassPane;
  99. delete WebInspector._elementDraggingEventListener;
  100. delete WebInspector._elementEndDraggingEventListener;
  101. }
  102. WebInspector._elementDragEnd = function(event)
  103. {
  104. var elementDragEnd = WebInspector._elementEndDraggingEventListener;
  105. WebInspector._cancelDragEvents(event);
  106. event.preventDefault();
  107. if (elementDragEnd)
  108. elementDragEnd(event);
  109. }
  110. /**
  111. * @constructor
  112. */
  113. WebInspector.GlassPane = function()
  114. {
  115. this.element = document.createElement("div");
  116. this.element.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;background-color:transparent;z-index:1000;";
  117. this.element.id = "glass-pane";
  118. document.body.appendChild(this.element);
  119. WebInspector._glassPane = this;
  120. }
  121. WebInspector.GlassPane.prototype = {
  122. dispose: function()
  123. {
  124. delete WebInspector._glassPane;
  125. if (WebInspector.HelpScreen.isVisible())
  126. WebInspector.HelpScreen.focus();
  127. else
  128. WebInspector.inspectorView.focus();
  129. this.element.remove();
  130. }
  131. }
  132. WebInspector.animateStyle = function(animations, duration, callback)
  133. {
  134. var startTime = new Date().getTime();
  135. var hasCompleted = false;
  136. const animationsLength = animations.length;
  137. const propertyUnit = {opacity: ""};
  138. const defaultUnit = "px";
  139. // Pre-process animations.
  140. for (var i = 0; i < animationsLength; ++i) {
  141. var animation = animations[i];
  142. var element = null, start = null, end = null, key = null;
  143. for (key in animation) {
  144. if (key === "element")
  145. element = animation[key];
  146. else if (key === "start")
  147. start = animation[key];
  148. else if (key === "end")
  149. end = animation[key];
  150. }
  151. if (!element || !end)
  152. continue;
  153. if (!start) {
  154. var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
  155. start = {};
  156. for (key in end)
  157. start[key] = parseInt(computedStyle.getPropertyValue(key), 10);
  158. animation.start = start;
  159. } else
  160. for (key in start)
  161. element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
  162. }
  163. function animateLoop()
  164. {
  165. if (hasCompleted)
  166. return;
  167. var complete = new Date().getTime() - startTime;
  168. // Make style changes.
  169. for (var i = 0; i < animationsLength; ++i) {
  170. var animation = animations[i];
  171. var element = animation.element;
  172. var start = animation.start;
  173. var end = animation.end;
  174. if (!element || !end)
  175. continue;
  176. var style = element.style;
  177. for (key in end) {
  178. var endValue = end[key];
  179. if (complete < duration) {
  180. var startValue = start[key];
  181. // Linear animation.
  182. var newValue = startValue + (endValue - startValue) * complete / duration;
  183. style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
  184. } else
  185. style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
  186. }
  187. }
  188. // End condition.
  189. if (complete >= duration)
  190. hasCompleted = true;
  191. if (callback)
  192. callback(hasCompleted);
  193. if (!hasCompleted)
  194. window.requestAnimationFrame(animateLoop);
  195. }
  196. function forceComplete()
  197. {
  198. if (hasCompleted)
  199. return;
  200. duration = 0;
  201. animateLoop();
  202. }
  203. window.requestAnimationFrame(animateLoop);
  204. return {
  205. forceComplete: forceComplete
  206. };
  207. }
  208. WebInspector.isBeingEdited = function(element)
  209. {
  210. if (element.hasStyleClass("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
  211. return true;
  212. if (!WebInspector.__editingCount)
  213. return false;
  214. while (element) {
  215. if (element.__editing)
  216. return true;
  217. element = element.parentElement;
  218. }
  219. return false;
  220. }
  221. WebInspector.markBeingEdited = function(element, value)
  222. {
  223. if (value) {
  224. if (element.__editing)
  225. return false;
  226. element.addStyleClass("being-edited");
  227. element.__editing = true;
  228. WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
  229. } else {
  230. if (!element.__editing)
  231. return false;
  232. element.removeStyleClass("being-edited");
  233. delete element.__editing;
  234. --WebInspector.__editingCount;
  235. }
  236. return true;
  237. }
  238. /**
  239. * @constructor
  240. * @param {function(Element,string,string,*,string)} commitHandler
  241. * @param {function(Element,*)} cancelHandler
  242. * @param {*=} context
  243. */
  244. WebInspector.EditingConfig = function(commitHandler, cancelHandler, context)
  245. {
  246. this.commitHandler = commitHandler;
  247. this.cancelHandler = cancelHandler
  248. this.context = context;
  249. /**
  250. * Handles the "paste" event, return values are the same as those for customFinishHandler
  251. * @type {function(Element)|undefined}
  252. */
  253. this.pasteHandler;
  254. /**
  255. * Whether the edited element is multiline
  256. * @type {boolean|undefined}
  257. */
  258. this.multiline;
  259. /**
  260. * Custom finish handler for the editing session (invoked on keydown)
  261. * @type {function(Element,*)|undefined}
  262. */
  263. this.customFinishHandler;
  264. }
  265. WebInspector.EditingConfig.prototype = {
  266. setPasteHandler: function(pasteHandler)
  267. {
  268. this.pasteHandler = pasteHandler;
  269. },
  270. /**
  271. * @param {string} initialValue
  272. * @param {Object} mode
  273. * @param {string} theme
  274. * @param {boolean=} lineWrapping
  275. * @param {boolean=} smartIndent
  276. */
  277. setMultilineOptions: function(initialValue, mode, theme, lineWrapping, smartIndent)
  278. {
  279. this.multiline = true;
  280. this.initialValue = initialValue;
  281. this.mode = mode;
  282. this.theme = theme;
  283. this.lineWrapping = lineWrapping;
  284. this.smartIndent = smartIndent;
  285. },
  286. setCustomFinishHandler: function(customFinishHandler)
  287. {
  288. this.customFinishHandler = customFinishHandler;
  289. }
  290. }
  291. WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
  292. WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
  293. /**
  294. * @param {Event} event
  295. * @return {?string}
  296. */
  297. WebInspector._valueModificationDirection = function(event)
  298. {
  299. var direction = null;
  300. if (event.type === "mousewheel") {
  301. if (event.wheelDeltaY > 0)
  302. direction = "Up";
  303. else if (event.wheelDeltaY < 0)
  304. direction = "Down";
  305. } else {
  306. if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
  307. direction = "Up";
  308. else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
  309. direction = "Down";
  310. }
  311. return direction;
  312. }
  313. /**
  314. * @param {string} hexString
  315. * @param {Event} event
  316. */
  317. WebInspector._modifiedHexValue = function(hexString, event)
  318. {
  319. var direction = WebInspector._valueModificationDirection(event);
  320. if (!direction)
  321. return hexString;
  322. var number = parseInt(hexString, 16);
  323. if (isNaN(number) || !isFinite(number))
  324. return hexString;
  325. var maxValue = Math.pow(16, hexString.length) - 1;
  326. var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
  327. var delta;
  328. if (arrowKeyOrMouseWheelEvent)
  329. delta = (direction === "Up") ? 1 : -1;
  330. else
  331. delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
  332. if (event.shiftKey)
  333. delta *= 16;
  334. var result = number + delta;
  335. if (result < 0)
  336. result = 0; // Color hex values are never negative, so clamp to 0.
  337. else if (result > maxValue)
  338. return hexString;
  339. // Ensure the result length is the same as the original hex value.
  340. var resultString = result.toString(16).toUpperCase();
  341. for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
  342. resultString = "0" + resultString;
  343. return resultString;
  344. }
  345. /**
  346. * @param {number} number
  347. * @param {Event} event
  348. */
  349. WebInspector._modifiedFloatNumber = function(number, event)
  350. {
  351. var direction = WebInspector._valueModificationDirection(event);
  352. if (!direction)
  353. return number;
  354. var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
  355. // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
  356. // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
  357. var changeAmount = 1;
  358. if (event.shiftKey && !arrowKeyOrMouseWheelEvent)
  359. changeAmount = 100;
  360. else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
  361. changeAmount = 10;
  362. else if (event.altKey)
  363. changeAmount = 0.1;
  364. if (direction === "Down")
  365. changeAmount *= -1;
  366. // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
  367. // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
  368. var result = Number((number + changeAmount).toFixed(6));
  369. if (!String(result).match(WebInspector.CSSNumberRegex))
  370. return null;
  371. return result;
  372. }
  373. /**
  374. * @param {Event} event
  375. * @param {Element} element
  376. * @param {function(string,string)=} finishHandler
  377. * @param {function(string)=} suggestionHandler
  378. * @param {function(number):number=} customNumberHandler
  379. */
  380. WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
  381. {
  382. var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
  383. var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
  384. if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed)
  385. return false;
  386. var selection = window.getSelection();
  387. if (!selection.rangeCount)
  388. return false;
  389. var selectionRange = selection.getRangeAt(0);
  390. if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
  391. return false;
  392. var originalValue = element.textContent;
  393. var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
  394. var wordString = wordRange.toString();
  395. if (suggestionHandler && suggestionHandler(wordString))
  396. return false;
  397. var replacementString;
  398. var prefix, suffix, number;
  399. var matches;
  400. matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
  401. if (matches && matches.length) {
  402. prefix = matches[1];
  403. suffix = matches[3];
  404. number = WebInspector._modifiedHexValue(matches[2], event);
  405. if (customNumberHandler)
  406. number = customNumberHandler(number);
  407. replacementString = prefix + number + suffix;
  408. } else {
  409. matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
  410. if (matches && matches.length) {
  411. prefix = matches[1];
  412. suffix = matches[3];
  413. number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
  414. // Need to check for null explicitly.
  415. if (number === null)
  416. return false;
  417. if (customNumberHandler)
  418. number = customNumberHandler(number);
  419. replacementString = prefix + number + suffix;
  420. }
  421. }
  422. if (replacementString) {
  423. var replacementTextNode = document.createTextNode(replacementString);
  424. wordRange.deleteContents();
  425. wordRange.insertNode(replacementTextNode);
  426. var finalSelectionRange = document.createRange();
  427. finalSelectionRange.setStart(replacementTextNode, 0);
  428. finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
  429. selection.removeAllRanges();
  430. selection.addRange(finalSelectionRange);
  431. event.handled = true;
  432. event.preventDefault();
  433. if (finishHandler)
  434. finishHandler(originalValue, replacementString);
  435. return true;
  436. }
  437. return false;
  438. }
  439. /**
  440. * @param {Element} element
  441. * @param {WebInspector.EditingConfig=} config
  442. * @return {?{cancel: function(), commit: function(), codeMirror: CodeMirror, setWidth: function(number)}}
  443. */
  444. WebInspector.startEditing = function(element, config)
  445. {
  446. if (!WebInspector.markBeingEdited(element, true))
  447. return null;
  448. config = config || new WebInspector.EditingConfig(function() {}, function() {});
  449. var committedCallback = config.commitHandler;
  450. var cancelledCallback = config.cancelHandler;
  451. var pasteCallback = config.pasteHandler;
  452. var context = config.context;
  453. var isMultiline = config.multiline || false;
  454. var oldText = isMultiline ? config.initialValue : getContent(element);
  455. var moveDirection = "";
  456. var oldTabIndex;
  457. var codeMirror;
  458. var cssLoadView;
  459. /**
  460. * @param {Event} e
  461. */
  462. function consumeCopy(e)
  463. {
  464. e.consume();
  465. }
  466. if (isMultiline) {
  467. loadScript("CodeMirrorTextEditor.js");
  468. cssLoadView = new WebInspector.CodeMirrorCSSLoadView();
  469. cssLoadView.show(element);
  470. WebInspector.setCurrentFocusElement(element);
  471. element.addEventListener("copy", consumeCopy, false);
  472. codeMirror = window.CodeMirror(element, {
  473. mode: config.mode,
  474. lineWrapping: config.lineWrapping,
  475. smartIndent: config.smartIndent,
  476. autofocus: true,
  477. theme: config.theme,
  478. value: oldText
  479. });
  480. codeMirror.getWrapperElement().addStyleClass("source-code");
  481. codeMirror.on("cursorActivity", function(cm) {
  482. cm.display.cursor.scrollIntoViewIfNeeded(false);
  483. });
  484. } else {
  485. element.addStyleClass("editing");
  486. oldTabIndex = element.getAttribute("tabIndex");
  487. if (typeof oldTabIndex !== "number" || oldTabIndex < 0)
  488. element.tabIndex = 0;
  489. WebInspector.setCurrentFocusElement(element);
  490. }
  491. /**
  492. * @param {number} width
  493. */
  494. function setWidth(width)
  495. {
  496. const padding = 30;
  497. codeMirror.getWrapperElement().style.width = (width - codeMirror.getWrapperElement().offsetLeft - padding) + "px";
  498. codeMirror.refresh();
  499. }
  500. /**
  501. * @param {Event=} e
  502. */
  503. function blurEventListener(e) {
  504. if (!isMultiline || !e || !e.relatedTarget || !e.relatedTarget.isSelfOrDescendant(element))
  505. editingCommitted.call(element);
  506. }
  507. function getContent(element) {
  508. if (isMultiline)
  509. return codeMirror.getValue();
  510. if (element.tagName === "INPUT" && element.type === "text")
  511. return element.value;
  512. return element.textContent;
  513. }
  514. /** @this {Element} */
  515. function cleanUpAfterEditing()
  516. {
  517. WebInspector.markBeingEdited(element, false);
  518. element.removeEventListener("blur", blurEventListener, isMultiline);
  519. element.removeEventListener("keydown", keyDownEventListener, true);
  520. if (pasteCallback)
  521. element.removeEventListener("paste", pasteEventListener, true);
  522. WebInspector.restoreFocusFromElement(element);
  523. if (isMultiline) {
  524. element.removeEventListener("copy", consumeCopy, false);
  525. cssLoadView.detach();
  526. return;
  527. }
  528. this.removeStyleClass("editing");
  529. if (typeof oldTabIndex !== "number")
  530. element.removeAttribute("tabIndex");
  531. else
  532. this.tabIndex = oldTabIndex;
  533. this.scrollTop = 0;
  534. this.scrollLeft = 0;
  535. }
  536. /** @this {Element} */
  537. function editingCancelled()
  538. {
  539. if (isMultiline)
  540. codeMirror.setValue(oldText);
  541. else {
  542. if (this.tagName === "INPUT" && this.type === "text")
  543. this.value = oldText;
  544. else
  545. this.textContent = oldText;
  546. }
  547. cleanUpAfterEditing.call(this);
  548. cancelledCallback(this, context);
  549. }
  550. /** @this {Element} */
  551. function editingCommitted()
  552. {
  553. cleanUpAfterEditing.call(this);
  554. committedCallback(this, getContent(this), oldText, context, moveDirection);
  555. }
  556. function defaultFinishHandler(event)
  557. {
  558. var isMetaOrCtrl = WebInspector.isMac() ?
  559. event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
  560. event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
  561. if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !isMultiline || isMetaOrCtrl))
  562. return "commit";
  563. else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
  564. return "cancel";
  565. else if (!isMultiline && event.keyIdentifier === "U+0009") // Tab key
  566. return "move-" + (event.shiftKey ? "backward" : "forward");
  567. }
  568. function handleEditingResult(result, event)
  569. {
  570. if (result === "commit") {
  571. editingCommitted.call(element);
  572. event.consume(true);
  573. } else if (result === "cancel") {
  574. editingCancelled.call(element);
  575. event.consume(true);
  576. } else if (result && result.startsWith("move-")) {
  577. moveDirection = result.substring(5);
  578. if (event.keyIdentifier !== "U+0009")
  579. blurEventListener();
  580. }
  581. }
  582. function pasteEventListener(event)
  583. {
  584. var result = pasteCallback(event);
  585. handleEditingResult(result, event);
  586. }
  587. function keyDownEventListener(event)
  588. {
  589. var handler = config.customFinishHandler || defaultFinishHandler;
  590. var result = handler(event);
  591. handleEditingResult(result, event);
  592. }
  593. element.addEventListener("blur", blurEventListener, isMultiline);
  594. element.addEventListener("keydown", keyDownEventListener, true);
  595. if (pasteCallback)
  596. element.addEventListener("paste", pasteEventListener, true);
  597. return {
  598. cancel: editingCancelled.bind(element),
  599. commit: editingCommitted.bind(element),
  600. codeMirror: codeMirror, // For testing.
  601. setWidth: setWidth
  602. };
  603. }
  604. /**
  605. * @param {number} seconds
  606. * @param {boolean=} higherResolution
  607. * @return {string}
  608. */
  609. Number.secondsToString = function(seconds, higherResolution)
  610. {
  611. if (!isFinite(seconds))
  612. return "-";
  613. if (seconds === 0)
  614. return "0";
  615. var ms = seconds * 1000;
  616. if (higherResolution && ms < 1000)
  617. return WebInspector.UIString("%.3f\u2009ms", ms);
  618. else if (ms < 1000)
  619. return WebInspector.UIString("%.0f\u2009ms", ms);
  620. if (seconds < 60)
  621. return WebInspector.UIString("%.2f\u2009s", seconds);
  622. var minutes = seconds / 60;
  623. if (minutes < 60)
  624. return WebInspector.UIString("%.1f\u2009min", minutes);
  625. var hours = minutes / 60;
  626. if (hours < 24)
  627. return WebInspector.UIString("%.1f\u2009hrs", hours);
  628. var days = hours / 24;
  629. return WebInspector.UIString("%.1f\u2009days", days);
  630. }
  631. /**
  632. * @param {number} bytes
  633. * @return {string}
  634. */
  635. Number.bytesToString = function(bytes)
  636. {
  637. if (bytes < 1024)
  638. return WebInspector.UIString("%.0f\u2009B", bytes);
  639. var kilobytes = bytes / 1024;
  640. if (kilobytes < 100)
  641. return WebInspector.UIString("%.1f\u2009KB", kilobytes);
  642. if (kilobytes < 1024)
  643. return WebInspector.UIString("%.0f\u2009KB", kilobytes);
  644. var megabytes = kilobytes / 1024;
  645. if (megabytes < 100)
  646. return WebInspector.UIString("%.1f\u2009MB", megabytes);
  647. else
  648. return WebInspector.UIString("%.0f\u2009MB", megabytes);
  649. }
  650. Number.withThousandsSeparator = function(num)
  651. {
  652. var str = num + "";
  653. var re = /(\d+)(\d{3})/;
  654. while (str.match(re))
  655. str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space.
  656. return str;
  657. }
  658. WebInspector.useLowerCaseMenuTitles = function()
  659. {
  660. return WebInspector.platform() === "windows";
  661. }
  662. WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
  663. {
  664. return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
  665. }
  666. WebInspector.openLinkExternallyLabel = function()
  667. {
  668. return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
  669. }
  670. WebInspector.copyLinkAddressLabel = function()
  671. {
  672. return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
  673. }
  674. WebInspector.platform = function()
  675. {
  676. if (!WebInspector._platform)
  677. WebInspector._platform = InspectorFrontendHost.platform();
  678. return WebInspector._platform;
  679. }
  680. WebInspector.isMac = function()
  681. {
  682. if (typeof WebInspector._isMac === "undefined")
  683. WebInspector._isMac = WebInspector.platform() === "mac";
  684. return WebInspector._isMac;
  685. }
  686. WebInspector.isWin = function()
  687. {
  688. if (typeof WebInspector._isWin === "undefined")
  689. WebInspector._isWin = WebInspector.platform() === "windows";
  690. return WebInspector._isWin;
  691. }
  692. WebInspector.PlatformFlavor = {
  693. WindowsVista: "windows-vista",
  694. MacTiger: "mac-tiger",
  695. MacLeopard: "mac-leopard",
  696. MacSnowLeopard: "mac-snowleopard",
  697. MacLion: "mac-lion",
  698. MacMountainLion: "mac-mountain-lion"
  699. }
  700. WebInspector.platformFlavor = function()
  701. {
  702. function detectFlavor()
  703. {
  704. const userAgent = navigator.userAgent;
  705. if (WebInspector.platform() === "windows") {
  706. var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/);
  707. if (match && match[1] >= 6)
  708. return WebInspector.PlatformFlavor.WindowsVista;
  709. return null;
  710. } else if (WebInspector.platform() === "mac") {
  711. var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/);
  712. if (!match || match[1] != 10)
  713. return WebInspector.PlatformFlavor.MacSnowLeopard;
  714. switch (Number(match[2])) {
  715. case 4:
  716. return WebInspector.PlatformFlavor.MacTiger;
  717. case 5:
  718. return WebInspector.PlatformFlavor.MacLeopard;
  719. case 6:
  720. return WebInspector.PlatformFlavor.MacSnowLeopard;
  721. case 7:
  722. return WebInspector.PlatformFlavor.MacLion;
  723. case 8:
  724. return WebInspector.PlatformFlavor.MacMountainLion;
  725. default:
  726. return "";
  727. }
  728. }
  729. }
  730. if (!WebInspector._platformFlavor)
  731. WebInspector._platformFlavor = detectFlavor();
  732. return WebInspector._platformFlavor;
  733. }
  734. WebInspector.port = function()
  735. {
  736. if (!WebInspector._port)
  737. WebInspector._port = InspectorFrontendHost.port();
  738. return WebInspector._port;
  739. }
  740. WebInspector.installPortStyles = function()
  741. {
  742. var platform = WebInspector.platform();
  743. document.body.addStyleClass("platform-" + platform);
  744. var flavor = WebInspector.platformFlavor();
  745. if (flavor)
  746. document.body.addStyleClass("platform-" + flavor);
  747. var port = WebInspector.port();
  748. document.body.addStyleClass("port-" + port);
  749. }
  750. WebInspector._windowFocused = function(event)
  751. {
  752. if (event.target.document.nodeType === Node.DOCUMENT_NODE)
  753. document.body.removeStyleClass("inactive");
  754. }
  755. WebInspector._windowBlurred = function(event)
  756. {
  757. if (event.target.document.nodeType === Node.DOCUMENT_NODE)
  758. document.body.addStyleClass("inactive");
  759. }
  760. WebInspector.previousFocusElement = function()
  761. {
  762. return WebInspector._previousFocusElement;
  763. }
  764. WebInspector.currentFocusElement = function()
  765. {
  766. return WebInspector._currentFocusElement;
  767. }
  768. WebInspector._focusChanged = function(event)
  769. {
  770. WebInspector.setCurrentFocusElement(event.target);
  771. }
  772. WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
  773. WebInspector._isTextEditingElement = function(element)
  774. {
  775. if (element instanceof HTMLInputElement)
  776. return element.type in WebInspector._textInputTypes;
  777. if (element instanceof HTMLTextAreaElement)
  778. return true;
  779. return false;
  780. }
  781. WebInspector.setCurrentFocusElement = function(x)
  782. {
  783. if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
  784. return;
  785. if (WebInspector._currentFocusElement !== x)
  786. WebInspector._previousFocusElement = WebInspector._currentFocusElement;
  787. WebInspector._currentFocusElement = x;
  788. if (WebInspector._currentFocusElement) {
  789. WebInspector._currentFocusElement.focus();
  790. // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
  791. // This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
  792. // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
  793. var selection = window.getSelection();
  794. if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
  795. var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
  796. selectionRange.setStart(WebInspector._currentFocusElement, 0);
  797. selectionRange.setEnd(WebInspector._currentFocusElement, 0);
  798. selection.removeAllRanges();
  799. selection.addRange(selectionRange);
  800. }
  801. } else if (WebInspector._previousFocusElement)
  802. WebInspector._previousFocusElement.blur();
  803. }
  804. WebInspector.restoreFocusFromElement = function(element)
  805. {
  806. if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
  807. WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
  808. }
  809. WebInspector.setToolbarColors = function(backgroundColor, color)
  810. {
  811. if (!WebInspector._themeStyleElement) {
  812. WebInspector._themeStyleElement = document.createElement("style");
  813. document.head.appendChild(WebInspector._themeStyleElement);
  814. }
  815. WebInspector._themeStyleElement.textContent =
  816. "#toolbar {\
  817. background-image: none !important;\
  818. background-color: " + backgroundColor + " !important;\
  819. }\
  820. \
  821. .toolbar-label {\
  822. color: " + color + " !important;\
  823. text-shadow: none;\
  824. }";
  825. }
  826. WebInspector.resetToolbarColors = function()
  827. {
  828. if (WebInspector._themeStyleElement)
  829. WebInspector._themeStyleElement.textContent = "";
  830. }
  831. /**
  832. * @param {Element} element
  833. * @param {number} offset
  834. * @param {number} length
  835. * @param {Array.<Object>=} domChanges
  836. */
  837. WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
  838. {
  839. var result = WebInspector.highlightSearchResults(element, [{offset: offset, length: length }], domChanges);
  840. return result.length ? result[0] : null;
  841. }
  842. /**
  843. * @param {Element} element
  844. * @param {Array.<Object>} resultRanges
  845. * @param {Array.<Object>=} changes
  846. */
  847. WebInspector.highlightSearchResults = function(element, resultRanges, changes)
  848. {
  849. return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
  850. }
  851. /**
  852. * @param {Element} element
  853. * @param {Array.<Object>} resultRanges
  854. * @param {string} styleClass
  855. * @param {Array.<Object>=} changes
  856. */
  857. WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
  858. {
  859. changes = changes || [];
  860. var highlightNodes = [];
  861. var lineText = element.textContent;
  862. var ownerDocument = element.ownerDocument;
  863. var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  864. var snapshotLength = textNodeSnapshot.snapshotLength;
  865. if (snapshotLength === 0)
  866. return highlightNodes;
  867. var nodeRanges = [];
  868. var rangeEndOffset = 0;
  869. for (var i = 0; i < snapshotLength; ++i) {
  870. var range = {};
  871. range.offset = rangeEndOffset;
  872. range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
  873. rangeEndOffset = range.offset + range.length;
  874. nodeRanges.push(range);
  875. }
  876. var startIndex = 0;
  877. for (var i = 0; i < resultRanges.length; ++i) {
  878. var startOffset = resultRanges[i].offset;
  879. var endOffset = startOffset + resultRanges[i].length;
  880. while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
  881. startIndex++;
  882. var endIndex = startIndex;
  883. while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
  884. endIndex++;
  885. if (endIndex === snapshotLength)
  886. break;
  887. var highlightNode = ownerDocument.createElement("span");
  888. highlightNode.className = styleClass;
  889. highlightNode.textContent = lineText.substring(startOffset, endOffset);
  890. var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
  891. var lastText = lastTextNode.textContent;
  892. lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
  893. changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
  894. if (startIndex === endIndex) {
  895. lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
  896. changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
  897. highlightNodes.push(highlightNode);
  898. var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
  899. lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
  900. changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
  901. } else {
  902. var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
  903. var firstText = firstTextNode.textContent;
  904. var anchorElement = firstTextNode.nextSibling;
  905. firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
  906. changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
  907. highlightNodes.push(highlightNode);
  908. firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
  909. changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
  910. for (var j = startIndex + 1; j < endIndex; j++) {
  911. var textNode = textNodeSnapshot.snapshotItem(j);
  912. var text = textNode.textContent;
  913. textNode.textContent = "";
  914. changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
  915. }
  916. }
  917. startIndex = endIndex;
  918. nodeRanges[startIndex].offset = endOffset;
  919. nodeRanges[startIndex].length = lastTextNode.textContent.length;
  920. }
  921. return highlightNodes;
  922. }
  923. WebInspector.applyDomChanges = function(domChanges)
  924. {
  925. for (var i = 0, size = domChanges.length; i < size; ++i) {
  926. var entry = domChanges[i];
  927. switch (entry.type) {
  928. case "added":
  929. entry.parent.insertBefore(entry.node, entry.nextSibling);
  930. break;
  931. case "changed":
  932. entry.node.textContent = entry.newText;
  933. break;
  934. }
  935. }
  936. }
  937. WebInspector.revertDomChanges = function(domChanges)
  938. {
  939. for (var i = domChanges.length - 1; i >= 0; --i) {
  940. var entry = domChanges[i];
  941. switch (entry.type) {
  942. case "added":
  943. entry.node.remove();
  944. break;
  945. case "changed":
  946. entry.node.textContent = entry.oldText;
  947. break;
  948. }
  949. }
  950. }
  951. WebInspector._coalescingLevel = 0;
  952. WebInspector.startBatchUpdate = function()
  953. {
  954. if (!WebInspector._coalescingLevel)
  955. WebInspector._postUpdateHandlers = new Map();
  956. WebInspector._coalescingLevel++;
  957. }
  958. WebInspector.endBatchUpdate = function()
  959. {
  960. if (--WebInspector._coalescingLevel)
  961. return;
  962. var handlers = WebInspector._postUpdateHandlers;
  963. delete WebInspector._postUpdateHandlers;
  964. var keys = handlers.keys();
  965. for (var i = 0; i < keys.length; ++i) {
  966. var object = keys[i];
  967. var methods = handlers.get(object).keys();
  968. for (var j = 0; j < methods.length; ++j)
  969. methods[j].call(object);
  970. }
  971. }
  972. /**
  973. * @param {Object} object
  974. * @param {function()} method
  975. */
  976. WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
  977. {
  978. if (!WebInspector._coalescingLevel) {
  979. method.call(object);
  980. return;
  981. }
  982. var methods = WebInspector._postUpdateHandlers.get(object);
  983. if (!methods) {
  984. methods = new Map();
  985. WebInspector._postUpdateHandlers.put(object, methods);
  986. }
  987. methods.put(method);
  988. }
  989. /**
  990. * This bogus view is needed to load/unload CodeMirror-related CSS on demand.
  991. *
  992. * @constructor
  993. * @extends {WebInspector.View}
  994. */
  995. WebInspector.CodeMirrorCSSLoadView = function()
  996. {
  997. WebInspector.View.call(this);
  998. this.element.addStyleClass("hidden");
  999. this.registerRequiredCSS("cm/codemirror.css");
  1000. this.registerRequiredCSS("cm/cmdevtools.css");
  1001. }
  1002. WebInspector.CodeMirrorCSSLoadView.prototype = {
  1003. __proto__: WebInspector.View.prototype
  1004. }
  1005. ;(function() {
  1006. function windowLoaded()
  1007. {
  1008. window.addEventListener("focus", WebInspector._windowFocused, false);
  1009. window.addEventListener("blur", WebInspector._windowBlurred, false);
  1010. document.addEventListener("focus", WebInspector._focusChanged.bind(this), true);
  1011. window.removeEventListener("DOMContentLoaded", windowLoaded, false);
  1012. }
  1013. window.addEventListener("DOMContentLoaded", windowLoaded, false);
  1014. })();