PageRenderTime 59ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/Source/WebCore/inspector/front-end/TextViewer.js

https://bitbucket.org/cyanogenmod/android_external_webkit
JavaScript | 1912 lines | 1536 code | 294 blank | 82 comment | 311 complexity | 87458b95861de0b1c64c56ca90cdca0d MD5 | raw file
Possible License(s): LGPL-2.0, BSD-3-Clause, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * Copyright (C) 2011 Google Inc. All rights reserved.
  3. * Copyright (C) 2010 Apple Inc. All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. * * Neither the name of Google Inc. nor the names of its
  16. * contributors may be used to endorse or promote products derived from
  17. * this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. WebInspector.TextViewer = function(textModel, platform, url, delegate)
  32. {
  33. WebInspector.View.call(this);
  34. this._textModel = textModel;
  35. this._textModel.changeListener = this._textChanged.bind(this);
  36. this._textModel.resetUndoStack();
  37. this._delegate = delegate;
  38. this.element.className = "text-editor monospace";
  39. var enterTextChangeMode = this._enterInternalTextChangeMode.bind(this);
  40. var exitTextChangeMode = this._exitInternalTextChangeMode.bind(this);
  41. var syncScrollListener = this._syncScroll.bind(this);
  42. var syncDecorationsForLineListener = this._syncDecorationsForLine.bind(this);
  43. this._mainPanel = new WebInspector.TextEditorMainPanel(this._textModel, url, syncScrollListener, syncDecorationsForLineListener, enterTextChangeMode, exitTextChangeMode);
  44. this._gutterPanel = new WebInspector.TextEditorGutterPanel(this._textModel, syncDecorationsForLineListener);
  45. this.element.appendChild(this._mainPanel.element);
  46. this.element.appendChild(this._gutterPanel.element);
  47. // Forward mouse wheel events from the unscrollable gutter to the main panel.
  48. this._gutterPanel.element.addEventListener("mousewheel", function(e) {
  49. this._mainPanel.element.dispatchEvent(e);
  50. }.bind(this), false);
  51. this.element.addEventListener("dblclick", this._doubleClick.bind(this), true);
  52. this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
  53. this._registerShortcuts();
  54. }
  55. WebInspector.TextViewer.prototype = {
  56. set mimeType(mimeType)
  57. {
  58. this._mainPanel.mimeType = mimeType;
  59. },
  60. set readOnly(readOnly)
  61. {
  62. if (this._mainPanel.readOnly === readOnly)
  63. return;
  64. this._mainPanel.readOnly = readOnly;
  65. this._delegate.readOnlyStateChanged(readOnly);
  66. },
  67. get readOnly()
  68. {
  69. return this._mainPanel.readOnly;
  70. },
  71. get textModel()
  72. {
  73. return this._textModel;
  74. },
  75. revealLine: function(lineNumber)
  76. {
  77. this._mainPanel.revealLine(lineNumber);
  78. },
  79. addDecoration: function(lineNumber, decoration)
  80. {
  81. this._mainPanel.addDecoration(lineNumber, decoration);
  82. this._gutterPanel.addDecoration(lineNumber, decoration);
  83. },
  84. removeDecoration: function(lineNumber, decoration)
  85. {
  86. this._mainPanel.removeDecoration(lineNumber, decoration);
  87. this._gutterPanel.removeDecoration(lineNumber, decoration);
  88. },
  89. markAndRevealRange: function(range)
  90. {
  91. this._mainPanel.markAndRevealRange(range);
  92. },
  93. highlightLine: function(lineNumber)
  94. {
  95. if (typeof lineNumber !== "number" || lineNumber < 0)
  96. return;
  97. this._mainPanel.highlightLine(lineNumber);
  98. },
  99. clearLineHighlight: function()
  100. {
  101. this._mainPanel.clearLineHighlight();
  102. },
  103. freeCachedElements: function()
  104. {
  105. this._mainPanel.freeCachedElements();
  106. this._gutterPanel.freeCachedElements();
  107. },
  108. get scrollTop()
  109. {
  110. return this._mainPanel.element.scrollTop;
  111. },
  112. set scrollTop(scrollTop)
  113. {
  114. this._mainPanel.element.scrollTop = scrollTop;
  115. },
  116. get scrollLeft()
  117. {
  118. return this._mainPanel.element.scrollLeft;
  119. },
  120. set scrollLeft(scrollLeft)
  121. {
  122. this._mainPanel.element.scrollLeft = scrollLeft;
  123. },
  124. beginUpdates: function()
  125. {
  126. this._mainPanel.beginUpdates();
  127. this._gutterPanel.beginUpdates();
  128. },
  129. endUpdates: function()
  130. {
  131. this._mainPanel.endUpdates();
  132. this._gutterPanel.endUpdates();
  133. this._updatePanelOffsets();
  134. },
  135. resize: function()
  136. {
  137. this._mainPanel.resize();
  138. this._gutterPanel.resize();
  139. this._updatePanelOffsets();
  140. },
  141. // WebInspector.TextModel listener
  142. _textChanged: function(oldRange, newRange, oldText, newText)
  143. {
  144. if (!this._internalTextChangeMode)
  145. this._textModel.resetUndoStack();
  146. this._mainPanel.textChanged(oldRange, newRange);
  147. this._gutterPanel.textChanged(oldRange, newRange);
  148. this._updatePanelOffsets();
  149. },
  150. _enterInternalTextChangeMode: function()
  151. {
  152. this._internalTextChangeMode = true;
  153. this._delegate.startEditing();
  154. },
  155. _exitInternalTextChangeMode: function(oldRange, newRange)
  156. {
  157. this._internalTextChangeMode = false;
  158. this._delegate.endEditing(oldRange, newRange);
  159. },
  160. _updatePanelOffsets: function()
  161. {
  162. var lineNumbersWidth = this._gutterPanel.element.offsetWidth;
  163. if (lineNumbersWidth)
  164. this._mainPanel.element.style.setProperty("left", lineNumbersWidth + "px");
  165. else
  166. this._mainPanel.element.style.removeProperty("left"); // Use default value set in CSS.
  167. },
  168. _syncScroll: function()
  169. {
  170. // Async call due to performance reasons.
  171. setTimeout(function() {
  172. var mainElement = this._mainPanel.element;
  173. var gutterElement = this._gutterPanel.element;
  174. // Handle horizontal scroll bar at the bottom of the main panel.
  175. this._gutterPanel.syncClientHeight(mainElement.clientHeight);
  176. gutterElement.scrollTop = mainElement.scrollTop;
  177. }.bind(this), 0);
  178. },
  179. _syncDecorationsForLine: function(lineNumber)
  180. {
  181. if (lineNumber >= this._textModel.linesCount)
  182. return;
  183. var mainChunk = this._mainPanel.chunkForLine(lineNumber);
  184. if (mainChunk.linesCount === 1 && mainChunk.decorated) {
  185. var gutterChunk = this._gutterPanel.makeLineAChunk(lineNumber);
  186. var height = mainChunk.height;
  187. if (height)
  188. gutterChunk.element.style.setProperty("height", height + "px");
  189. else
  190. gutterChunk.element.style.removeProperty("height");
  191. } else {
  192. var gutterChunk = this._gutterPanel.chunkForLine(lineNumber);
  193. if (gutterChunk.linesCount === 1)
  194. gutterChunk.element.style.removeProperty("height");
  195. }
  196. },
  197. _doubleClick: function(event)
  198. {
  199. if (!this.readOnly || this._commitEditingInProgress)
  200. return;
  201. var lineRow = event.target.enclosingNodeOrSelfWithClass("webkit-line-content");
  202. if (!lineRow)
  203. return; // Do not trigger editing from line numbers.
  204. if (!this._delegate.isContentEditable())
  205. return;
  206. this.readOnly = false;
  207. window.getSelection().collapseToStart();
  208. },
  209. _registerShortcuts: function()
  210. {
  211. var keys = WebInspector.KeyboardShortcut.Keys;
  212. var modifiers = WebInspector.KeyboardShortcut.Modifiers;
  213. this._shortcuts = {};
  214. var commitEditing = this._commitEditing.bind(this);
  215. var cancelEditing = this._cancelEditing.bind(this);
  216. this._shortcuts[WebInspector.KeyboardShortcut.makeKey("s", modifiers.CtrlOrMeta)] = commitEditing;
  217. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Enter.code, modifiers.CtrlOrMeta)] = commitEditing;
  218. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Esc.code)] = cancelEditing;
  219. var handleUndo = this._mainPanel.handleUndoRedo.bind(this._mainPanel, false);
  220. var handleRedo = this._mainPanel.handleUndoRedo.bind(this._mainPanel, true);
  221. this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.CtrlOrMeta)] = handleUndo;
  222. this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.Shift | modifiers.CtrlOrMeta)] = handleRedo;
  223. var handleTabKey = this._mainPanel.handleTabKeyPress.bind(this._mainPanel, false);
  224. var handleShiftTabKey = this._mainPanel.handleTabKeyPress.bind(this._mainPanel, true);
  225. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code)] = handleTabKey;
  226. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code, modifiers.Shift)] = handleShiftTabKey;
  227. },
  228. _handleKeyDown: function(e)
  229. {
  230. var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e);
  231. var handler = this._shortcuts[shortcutKey];
  232. if (handler && handler.call(this)) {
  233. e.preventDefault();
  234. e.stopPropagation();
  235. }
  236. },
  237. _commitEditing: function()
  238. {
  239. if (this.readOnly)
  240. return false;
  241. this.readOnly = true;
  242. function didCommitEditing(error)
  243. {
  244. this._commitEditingInProgress = false;
  245. if (error)
  246. this.readOnly = false;
  247. }
  248. this._commitEditingInProgress = true;
  249. this._delegate.commitEditing(didCommitEditing.bind(this));
  250. return true;
  251. },
  252. _cancelEditing: function()
  253. {
  254. if (this.readOnly)
  255. return false;
  256. this.readOnly = true;
  257. this._delegate.cancelEditing();
  258. return true;
  259. }
  260. }
  261. WebInspector.TextViewer.prototype.__proto__ = WebInspector.View.prototype;
  262. WebInspector.TextViewerDelegate = function()
  263. {
  264. }
  265. WebInspector.TextViewerDelegate.prototype = {
  266. isContentEditable: function()
  267. {
  268. // Should be implemented by subclasses.
  269. },
  270. readOnlyStateChanged: function(readOnly)
  271. {
  272. // Should be implemented by subclasses.
  273. },
  274. startEditing: function()
  275. {
  276. // Should be implemented by subclasses.
  277. },
  278. endEditing: function(oldRange, newRange)
  279. {
  280. // Should be implemented by subclasses.
  281. },
  282. commitEditing: function()
  283. {
  284. // Should be implemented by subclasses.
  285. },
  286. cancelEditing: function()
  287. {
  288. // Should be implemented by subclasses.
  289. }
  290. }
  291. WebInspector.TextViewerDelegate.prototype.__proto__ = WebInspector.Object.prototype;
  292. WebInspector.TextEditorChunkedPanel = function(textModel)
  293. {
  294. this._textModel = textModel;
  295. this._defaultChunkSize = 50;
  296. this._paintCoalescingLevel = 0;
  297. this._domUpdateCoalescingLevel = 0;
  298. }
  299. WebInspector.TextEditorChunkedPanel.prototype = {
  300. get textModel()
  301. {
  302. return this._textModel;
  303. },
  304. revealLine: function(lineNumber)
  305. {
  306. if (lineNumber >= this._textModel.linesCount)
  307. return;
  308. var chunk = this.makeLineAChunk(lineNumber);
  309. chunk.element.scrollIntoViewIfNeeded();
  310. },
  311. addDecoration: function(lineNumber, decoration)
  312. {
  313. if (lineNumber >= this._textModel.linesCount)
  314. return;
  315. var chunk = this.makeLineAChunk(lineNumber);
  316. chunk.addDecoration(decoration);
  317. },
  318. removeDecoration: function(lineNumber, decoration)
  319. {
  320. if (lineNumber >= this._textModel.linesCount)
  321. return;
  322. var chunk = this.chunkForLine(lineNumber);
  323. chunk.removeDecoration(decoration);
  324. },
  325. _buildChunks: function()
  326. {
  327. this.beginDomUpdates();
  328. this._container.removeChildren();
  329. this._textChunks = [];
  330. for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) {
  331. var chunk = this._createNewChunk(i, i + this._defaultChunkSize);
  332. this._textChunks.push(chunk);
  333. this._container.appendChild(chunk.element);
  334. }
  335. this._repaintAll();
  336. this.endDomUpdates();
  337. },
  338. makeLineAChunk: function(lineNumber)
  339. {
  340. var chunkNumber = this._chunkNumberForLine(lineNumber);
  341. var oldChunk = this._textChunks[chunkNumber];
  342. if (!oldChunk) {
  343. console.error("No chunk for line number: " + lineNumber);
  344. return;
  345. }
  346. if (oldChunk.linesCount === 1)
  347. return oldChunk;
  348. return this._splitChunkOnALine(lineNumber, chunkNumber);
  349. },
  350. _splitChunkOnALine: function(lineNumber, chunkNumber)
  351. {
  352. this.beginDomUpdates();
  353. var oldChunk = this._textChunks[chunkNumber];
  354. var wasExpanded = oldChunk.expanded;
  355. oldChunk.expanded = false;
  356. var insertIndex = chunkNumber + 1;
  357. // Prefix chunk.
  358. if (lineNumber > oldChunk.startLine) {
  359. var prefixChunk = this._createNewChunk(oldChunk.startLine, lineNumber);
  360. this._textChunks.splice(insertIndex++, 0, prefixChunk);
  361. this._container.insertBefore(prefixChunk.element, oldChunk.element);
  362. }
  363. // Line chunk.
  364. var lineChunk = this._createNewChunk(lineNumber, lineNumber + 1);
  365. this._textChunks.splice(insertIndex++, 0, lineChunk);
  366. this._container.insertBefore(lineChunk.element, oldChunk.element);
  367. // Suffix chunk.
  368. if (oldChunk.startLine + oldChunk.linesCount > lineNumber + 1) {
  369. var suffixChunk = this._createNewChunk(lineNumber + 1, oldChunk.startLine + oldChunk.linesCount);
  370. this._textChunks.splice(insertIndex, 0, suffixChunk);
  371. this._container.insertBefore(suffixChunk.element, oldChunk.element);
  372. }
  373. // Remove enclosing chunk.
  374. this._textChunks.splice(chunkNumber, 1);
  375. this._container.removeChild(oldChunk.element);
  376. if (wasExpanded) {
  377. if (prefixChunk)
  378. prefixChunk.expanded = true;
  379. lineChunk.expanded = true;
  380. if (suffixChunk)
  381. suffixChunk.expanded = true;
  382. }
  383. this.endDomUpdates();
  384. return lineChunk;
  385. },
  386. _scroll: function()
  387. {
  388. // FIXME: Replace the "2" with the padding-left value from CSS.
  389. if (this.element.scrollLeft <= 2)
  390. this.element.scrollLeft = 0;
  391. this._scheduleRepaintAll();
  392. if (this._syncScrollListener)
  393. this._syncScrollListener();
  394. },
  395. _scheduleRepaintAll: function()
  396. {
  397. if (this._repaintAllTimer)
  398. clearTimeout(this._repaintAllTimer);
  399. this._repaintAllTimer = setTimeout(this._repaintAll.bind(this), 50);
  400. },
  401. beginUpdates: function()
  402. {
  403. this._paintCoalescingLevel++;
  404. },
  405. endUpdates: function()
  406. {
  407. this._paintCoalescingLevel--;
  408. if (!this._paintCoalescingLevel)
  409. this._repaintAll();
  410. },
  411. beginDomUpdates: function()
  412. {
  413. this._domUpdateCoalescingLevel++;
  414. },
  415. endDomUpdates: function()
  416. {
  417. this._domUpdateCoalescingLevel--;
  418. },
  419. _chunkNumberForLine: function(lineNumber)
  420. {
  421. function compareLineNumbers(value, chunk)
  422. {
  423. return value < chunk.startLine ? -1 : 1;
  424. }
  425. var insertBefore = insertionIndexForObjectInListSortedByFunction(lineNumber, this._textChunks, compareLineNumbers);
  426. return insertBefore - 1;
  427. },
  428. chunkForLine: function(lineNumber)
  429. {
  430. return this._textChunks[this._chunkNumberForLine(lineNumber)];
  431. },
  432. _findFirstVisibleChunkNumber: function(visibleFrom)
  433. {
  434. function compareOffsetTops(value, chunk)
  435. {
  436. return value < chunk.offsetTop ? -1 : 1;
  437. }
  438. var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, this._textChunks, compareOffsetTops);
  439. return insertBefore - 1;
  440. },
  441. _findVisibleChunks: function(visibleFrom, visibleTo)
  442. {
  443. var from = this._findFirstVisibleChunkNumber(visibleFrom);
  444. for (var to = from + 1; to < this._textChunks.length; ++to) {
  445. if (this._textChunks[to].offsetTop >= visibleTo)
  446. break;
  447. }
  448. return { start: from, end: to };
  449. },
  450. _findFirstVisibleLineNumber: function(visibleFrom)
  451. {
  452. var chunk = this._textChunks[this._findFirstVisibleChunkNumber(visibleFrom)];
  453. if (!chunk.expanded)
  454. return chunk.startLine;
  455. var lineNumbers = [];
  456. for (var i = 0; i < chunk.linesCount; ++i) {
  457. lineNumbers.push(chunk.startLine + i);
  458. }
  459. function compareLineRowOffsetTops(value, lineNumber)
  460. {
  461. var lineRow = chunk.getExpandedLineRow(lineNumber);
  462. return value < lineRow.offsetTop ? -1 : 1;
  463. }
  464. var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, lineNumbers, compareLineRowOffsetTops);
  465. return lineNumbers[insertBefore - 1];
  466. },
  467. _repaintAll: function()
  468. {
  469. delete this._repaintAllTimer;
  470. if (this._paintCoalescingLevel || this._dirtyLines)
  471. return;
  472. var visibleFrom = this.element.scrollTop;
  473. var visibleTo = this.element.scrollTop + this.element.clientHeight;
  474. if (visibleTo) {
  475. var result = this._findVisibleChunks(visibleFrom, visibleTo);
  476. this._expandChunks(result.start, result.end);
  477. }
  478. },
  479. _expandChunks: function(fromIndex, toIndex)
  480. {
  481. // First collapse chunks to collect the DOM elements into a cache to reuse them later.
  482. for (var i = 0; i < fromIndex; ++i)
  483. this._textChunks[i].expanded = false;
  484. for (var i = toIndex; i < this._textChunks.length; ++i)
  485. this._textChunks[i].expanded = false;
  486. for (var i = fromIndex; i < toIndex; ++i)
  487. this._textChunks[i].expanded = true;
  488. },
  489. _totalHeight: function(firstElement, lastElement)
  490. {
  491. lastElement = (lastElement || firstElement).nextElementSibling;
  492. if (lastElement)
  493. return lastElement.offsetTop - firstElement.offsetTop;
  494. var offsetParent = firstElement.offsetParent;
  495. if (offsetParent && offsetParent.scrollHeight > offsetParent.clientHeight)
  496. return offsetParent.scrollHeight - firstElement.offsetTop;
  497. var total = 0;
  498. while (firstElement && firstElement !== lastElement) {
  499. total += firstElement.offsetHeight;
  500. firstElement = firstElement.nextElementSibling;
  501. }
  502. return total;
  503. },
  504. resize: function()
  505. {
  506. this._repaintAll();
  507. }
  508. }
  509. WebInspector.TextEditorGutterPanel = function(textModel, syncDecorationsForLineListener)
  510. {
  511. WebInspector.TextEditorChunkedPanel.call(this, textModel);
  512. this._syncDecorationsForLineListener = syncDecorationsForLineListener;
  513. this.element = document.createElement("div");
  514. this.element.className = "text-editor-lines";
  515. this._container = document.createElement("div");
  516. this._container.className = "inner-container";
  517. this.element.appendChild(this._container);
  518. this.element.addEventListener("scroll", this._scroll.bind(this), false);
  519. this.freeCachedElements();
  520. this._buildChunks();
  521. }
  522. WebInspector.TextEditorGutterPanel.prototype = {
  523. freeCachedElements: function()
  524. {
  525. this._cachedRows = [];
  526. },
  527. _createNewChunk: function(startLine, endLine)
  528. {
  529. return new WebInspector.TextEditorGutterChunk(this, startLine, endLine);
  530. },
  531. textChanged: function(oldRange, newRange)
  532. {
  533. this.beginDomUpdates();
  534. var linesDiff = newRange.linesCount - oldRange.linesCount;
  535. if (linesDiff) {
  536. // Remove old chunks (if needed).
  537. for (var chunkNumber = this._textChunks.length - 1; chunkNumber >= 0 ; --chunkNumber) {
  538. var chunk = this._textChunks[chunkNumber];
  539. if (chunk.startLine + chunk.linesCount <= this._textModel.linesCount)
  540. break;
  541. chunk.expanded = false;
  542. this._container.removeChild(chunk.element);
  543. }
  544. this._textChunks.length = chunkNumber + 1;
  545. // Add new chunks (if needed).
  546. var totalLines = 0;
  547. if (this._textChunks.length) {
  548. var lastChunk = this._textChunks[this._textChunks.length - 1];
  549. totalLines = lastChunk.startLine + lastChunk.linesCount;
  550. }
  551. for (var i = totalLines; i < this._textModel.linesCount; i += this._defaultChunkSize) {
  552. var chunk = this._createNewChunk(i, i + this._defaultChunkSize);
  553. this._textChunks.push(chunk);
  554. this._container.appendChild(chunk.element);
  555. }
  556. this._repaintAll();
  557. } else {
  558. // Decorations may have been removed, so we may have to sync those lines.
  559. var chunkNumber = this._chunkNumberForLine(newRange.startLine);
  560. var chunk = this._textChunks[chunkNumber];
  561. while (chunk && chunk.startLine <= newRange.endLine) {
  562. if (chunk.linesCount === 1)
  563. this._syncDecorationsForLineListener(chunk.startLine);
  564. chunk = this._textChunks[++chunkNumber];
  565. }
  566. }
  567. this.endDomUpdates();
  568. },
  569. syncClientHeight: function(clientHeight)
  570. {
  571. if (this.element.offsetHeight > clientHeight)
  572. this._container.style.setProperty("padding-bottom", (this.element.offsetHeight - clientHeight) + "px");
  573. else
  574. this._container.style.removeProperty("padding-bottom");
  575. }
  576. }
  577. WebInspector.TextEditorGutterPanel.prototype.__proto__ = WebInspector.TextEditorChunkedPanel.prototype;
  578. WebInspector.TextEditorGutterChunk = function(textViewer, startLine, endLine)
  579. {
  580. this._textViewer = textViewer;
  581. this._textModel = textViewer._textModel;
  582. this.startLine = startLine;
  583. endLine = Math.min(this._textModel.linesCount, endLine);
  584. this.linesCount = endLine - startLine;
  585. this._expanded = false;
  586. this.element = document.createElement("div");
  587. this.element.lineNumber = startLine;
  588. this.element.className = "webkit-line-number";
  589. if (this.linesCount === 1) {
  590. // Single line chunks are typically created for decorations. Host line number in
  591. // the sub-element in order to allow flexible border / margin management.
  592. var innerSpan = document.createElement("span");
  593. innerSpan.className = "webkit-line-number-inner";
  594. innerSpan.textContent = startLine + 1;
  595. var outerSpan = document.createElement("div");
  596. outerSpan.className = "webkit-line-number-outer";
  597. outerSpan.appendChild(innerSpan);
  598. this.element.appendChild(outerSpan);
  599. } else {
  600. var lineNumbers = [];
  601. for (var i = startLine; i < endLine; ++i)
  602. lineNumbers.push(i + 1);
  603. this.element.textContent = lineNumbers.join("\n");
  604. }
  605. }
  606. WebInspector.TextEditorGutterChunk.prototype = {
  607. addDecoration: function(decoration)
  608. {
  609. this._textViewer.beginDomUpdates();
  610. if (typeof decoration === "string")
  611. this.element.addStyleClass(decoration);
  612. this._textViewer.endDomUpdates();
  613. },
  614. removeDecoration: function(decoration)
  615. {
  616. this._textViewer.beginDomUpdates();
  617. if (typeof decoration === "string")
  618. this.element.removeStyleClass(decoration);
  619. this._textViewer.endDomUpdates();
  620. },
  621. get expanded()
  622. {
  623. return this._expanded;
  624. },
  625. set expanded(expanded)
  626. {
  627. if (this.linesCount === 1)
  628. this._textViewer._syncDecorationsForLineListener(this.startLine);
  629. if (this._expanded === expanded)
  630. return;
  631. this._expanded = expanded;
  632. if (this.linesCount === 1)
  633. return;
  634. this._textViewer.beginDomUpdates();
  635. if (expanded) {
  636. this._expandedLineRows = [];
  637. var parentElement = this.element.parentElement;
  638. for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
  639. var lineRow = this._createRow(i);
  640. parentElement.insertBefore(lineRow, this.element);
  641. this._expandedLineRows.push(lineRow);
  642. }
  643. parentElement.removeChild(this.element);
  644. } else {
  645. var elementInserted = false;
  646. for (var i = 0; i < this._expandedLineRows.length; ++i) {
  647. var lineRow = this._expandedLineRows[i];
  648. var parentElement = lineRow.parentElement;
  649. if (parentElement) {
  650. if (!elementInserted) {
  651. elementInserted = true;
  652. parentElement.insertBefore(this.element, lineRow);
  653. }
  654. parentElement.removeChild(lineRow);
  655. }
  656. this._textViewer._cachedRows.push(lineRow);
  657. }
  658. delete this._expandedLineRows;
  659. }
  660. this._textViewer.endDomUpdates();
  661. },
  662. get height()
  663. {
  664. if (!this._expandedLineRows)
  665. return this._textViewer._totalHeight(this.element);
  666. return this._textViewer._totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]);
  667. },
  668. get offsetTop()
  669. {
  670. return (this._expandedLineRows && this._expandedLineRows.length) ? this._expandedLineRows[0].offsetTop : this.element.offsetTop;
  671. },
  672. _createRow: function(lineNumber)
  673. {
  674. var lineRow = this._textViewer._cachedRows.pop() || document.createElement("div");
  675. lineRow.lineNumber = lineNumber;
  676. lineRow.className = "webkit-line-number";
  677. lineRow.textContent = lineNumber + 1;
  678. return lineRow;
  679. }
  680. }
  681. WebInspector.TextEditorMainPanel = function(textModel, url, syncScrollListener, syncDecorationsForLineListener, enterTextChangeMode, exitTextChangeMode)
  682. {
  683. WebInspector.TextEditorChunkedPanel.call(this, textModel);
  684. this._syncScrollListener = syncScrollListener;
  685. this._syncDecorationsForLineListener = syncDecorationsForLineListener;
  686. this._enterTextChangeMode = enterTextChangeMode;
  687. this._exitTextChangeMode = exitTextChangeMode;
  688. this._url = url;
  689. this._highlighter = new WebInspector.TextEditorHighlighter(textModel, this._highlightDataReady.bind(this));
  690. this._readOnly = true;
  691. this.element = document.createElement("div");
  692. this.element.className = "text-editor-contents";
  693. this.element.tabIndex = 0;
  694. this._container = document.createElement("div");
  695. this._container.className = "inner-container";
  696. this._container.tabIndex = 0;
  697. this.element.appendChild(this._container);
  698. this.element.addEventListener("scroll", this._scroll.bind(this), false);
  699. // In WebKit the DOMNodeRemoved event is fired AFTER the node is removed, thus it should be
  700. // attached to all DOM nodes that we want to track. Instead, we attach the DOMNodeRemoved
  701. // listeners only on the line rows, and use DOMSubtreeModified to track node removals inside
  702. // the line rows. For more info see: https://bugs.webkit.org/show_bug.cgi?id=55666
  703. this._handleDOMUpdatesCallback = this._handleDOMUpdates.bind(this);
  704. this._container.addEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false);
  705. this._container.addEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false);
  706. this._container.addEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false);
  707. this.freeCachedElements();
  708. this._buildChunks();
  709. }
  710. WebInspector.TextEditorMainPanel.prototype = {
  711. set mimeType(mimeType)
  712. {
  713. this._highlighter.mimeType = mimeType;
  714. },
  715. set readOnly(readOnly)
  716. {
  717. if (this._readOnly === readOnly)
  718. return;
  719. this.beginDomUpdates();
  720. this._readOnly = readOnly;
  721. if (this._readOnly)
  722. this._container.removeStyleClass("text-editor-editable");
  723. else
  724. this._container.addStyleClass("text-editor-editable");
  725. this.endDomUpdates();
  726. },
  727. get readOnly()
  728. {
  729. return this._readOnly;
  730. },
  731. markAndRevealRange: function(range)
  732. {
  733. if (this._rangeToMark) {
  734. var markedLine = this._rangeToMark.startLine;
  735. delete this._rangeToMark;
  736. // Remove the marked region immediately.
  737. if (!this._dirtyLines) {
  738. this.beginDomUpdates();
  739. var chunk = this.chunkForLine(markedLine);
  740. var wasExpanded = chunk.expanded;
  741. chunk.expanded = false;
  742. chunk.updateCollapsedLineRow();
  743. chunk.expanded = wasExpanded;
  744. this.endDomUpdates();
  745. } else
  746. this._paintLines(markedLine, markedLine + 1);
  747. }
  748. if (range) {
  749. this._rangeToMark = range;
  750. this.revealLine(range.startLine);
  751. var chunk = this.makeLineAChunk(range.startLine);
  752. this._paintLine(chunk.element);
  753. if (this._markedRangeElement)
  754. this._markedRangeElement.scrollIntoViewIfNeeded();
  755. }
  756. delete this._markedRangeElement;
  757. },
  758. highlightLine: function(lineNumber)
  759. {
  760. this.clearLineHighlight();
  761. this._highlightedLine = lineNumber;
  762. this.revealLine(lineNumber);
  763. this.addDecoration(lineNumber, "webkit-highlighted-line");
  764. },
  765. clearLineHighlight: function()
  766. {
  767. if (typeof this._highlightedLine === "number") {
  768. this.removeDecoration(this._highlightedLine, "webkit-highlighted-line");
  769. delete this._highlightedLine;
  770. }
  771. },
  772. freeCachedElements: function()
  773. {
  774. this._cachedSpans = [];
  775. this._cachedTextNodes = [];
  776. this._cachedRows = [];
  777. },
  778. handleUndoRedo: function(redo)
  779. {
  780. if (this._readOnly || this._dirtyLines)
  781. return false;
  782. this.beginUpdates();
  783. this._enterTextChangeMode();
  784. var callback = function(oldRange, newRange) {
  785. this._exitTextChangeMode(oldRange, newRange);
  786. this._enterTextChangeMode();
  787. }.bind(this);
  788. var range = redo ? this._textModel.redo(callback) : this._textModel.undo(callback);
  789. if (range)
  790. this._setCaretLocation(range.endLine, range.endColumn, true);
  791. this._exitTextChangeMode(null, null);
  792. this.endUpdates();
  793. return true;
  794. },
  795. handleTabKeyPress: function(shiftKey)
  796. {
  797. if (this._readOnly || this._dirtyLines)
  798. return false;
  799. var selection = this._getSelection();
  800. if (!selection)
  801. return false;
  802. if (shiftKey)
  803. return true;
  804. this.beginUpdates();
  805. this._enterTextChangeMode();
  806. var range = selection;
  807. if (range.startLine > range.endLine || (range.startLine === range.endLine && range.startColumn > range.endColumn))
  808. range = new WebInspector.TextRange(range.endLine, range.endColumn, range.startLine, range.startColumn);
  809. var newRange = this._setText(range, "\t");
  810. this._exitTextChangeMode(range, newRange);
  811. this.endUpdates();
  812. this._setCaretLocation(newRange.endLine, newRange.endColumn, true);
  813. return true;
  814. },
  815. _splitChunkOnALine: function(lineNumber, chunkNumber)
  816. {
  817. var selection = this._getSelection();
  818. var chunk = WebInspector.TextEditorChunkedPanel.prototype._splitChunkOnALine.call(this, lineNumber, chunkNumber);
  819. this._restoreSelection(selection);
  820. return chunk;
  821. },
  822. _buildChunks: function()
  823. {
  824. for (var i = 0; i < this._textModel.linesCount; ++i)
  825. this._textModel.removeAttribute(i, "highlight");
  826. WebInspector.TextEditorChunkedPanel.prototype._buildChunks.call(this);
  827. },
  828. _createNewChunk: function(startLine, endLine)
  829. {
  830. return new WebInspector.TextEditorMainChunk(this, startLine, endLine);
  831. },
  832. _expandChunks: function(fromIndex, toIndex)
  833. {
  834. var lastChunk = this._textChunks[toIndex - 1];
  835. var lastVisibleLine = lastChunk.startLine + lastChunk.linesCount;
  836. var selection = this._getSelection();
  837. this._muteHighlightListener = true;
  838. this._highlighter.highlight(lastVisibleLine);
  839. delete this._muteHighlightListener;
  840. this._restorePaintLinesOperationsCredit();
  841. WebInspector.TextEditorChunkedPanel.prototype._expandChunks.call(this, fromIndex, toIndex);
  842. this._adjustPaintLinesOperationsRefreshValue();
  843. this._restoreSelection(selection);
  844. },
  845. _highlightDataReady: function(fromLine, toLine)
  846. {
  847. if (this._muteHighlightListener)
  848. return;
  849. this._restorePaintLinesOperationsCredit();
  850. this._paintLines(fromLine, toLine, true /*restoreSelection*/);
  851. },
  852. _schedulePaintLines: function(startLine, endLine)
  853. {
  854. if (startLine >= endLine)
  855. return;
  856. if (!this._scheduledPaintLines) {
  857. this._scheduledPaintLines = [ { startLine: startLine, endLine: endLine } ];
  858. this._paintScheduledLinesTimer = setTimeout(this._paintScheduledLines.bind(this), 0);
  859. } else {
  860. for (var i = 0; i < this._scheduledPaintLines.length; ++i) {
  861. var chunk = this._scheduledPaintLines[i];
  862. if (chunk.startLine <= endLine && chunk.endLine >= startLine) {
  863. chunk.startLine = Math.min(chunk.startLine, startLine);
  864. chunk.endLine = Math.max(chunk.endLine, endLine);
  865. return;
  866. }
  867. if (chunk.startLine > endLine) {
  868. this._scheduledPaintLines.splice(i, 0, { startLine: startLine, endLine: endLine });
  869. return;
  870. }
  871. }
  872. this._scheduledPaintLines.push({ startLine: startLine, endLine: endLine });
  873. }
  874. },
  875. _paintScheduledLines: function(skipRestoreSelection)
  876. {
  877. if (this._paintScheduledLinesTimer)
  878. clearTimeout(this._paintScheduledLinesTimer);
  879. delete this._paintScheduledLinesTimer;
  880. if (!this._scheduledPaintLines)
  881. return;
  882. // Reschedule the timer if we can not paint the lines yet, or the user is scrolling.
  883. if (this._dirtyLines || this._repaintAllTimer) {
  884. this._paintScheduledLinesTimer = setTimeout(this._paintScheduledLines.bind(this), 50);
  885. return;
  886. }
  887. var scheduledPaintLines = this._scheduledPaintLines;
  888. delete this._scheduledPaintLines;
  889. this._restorePaintLinesOperationsCredit();
  890. this._paintLineChunks(scheduledPaintLines, !skipRestoreSelection);
  891. this._adjustPaintLinesOperationsRefreshValue();
  892. },
  893. _restorePaintLinesOperationsCredit: function()
  894. {
  895. if (!this._paintLinesOperationsRefreshValue)
  896. this._paintLinesOperationsRefreshValue = 250;
  897. this._paintLinesOperationsCredit = this._paintLinesOperationsRefreshValue;
  898. this._paintLinesOperationsLastRefresh = Date.now();
  899. },
  900. _adjustPaintLinesOperationsRefreshValue: function()
  901. {
  902. var operationsDone = this._paintLinesOperationsRefreshValue - this._paintLinesOperationsCredit;
  903. if (operationsDone <= 0)
  904. return;
  905. var timePast = Date.now() - this._paintLinesOperationsLastRefresh;
  906. if (timePast <= 0)
  907. return;
  908. // Make the synchronous CPU chunk for painting the lines 50 msec.
  909. var value = Math.floor(operationsDone / timePast * 50);
  910. this._paintLinesOperationsRefreshValue = Number.constrain(value, 150, 1500);
  911. },
  912. _paintLines: function(fromLine, toLine, restoreSelection)
  913. {
  914. this._paintLineChunks([ { startLine: fromLine, endLine: toLine } ], restoreSelection);
  915. },
  916. _paintLineChunks: function(lineChunks, restoreSelection)
  917. {
  918. // First, paint visible lines, so that in case of long lines we should start highlighting
  919. // the visible area immediately, instead of waiting for the lines above the visible area.
  920. var visibleFrom = this.element.scrollTop;
  921. var firstVisibleLineNumber = this._findFirstVisibleLineNumber(visibleFrom);
  922. var chunk;
  923. var selection;
  924. var invisibleLineRows = [];
  925. for (var i = 0; i < lineChunks.length; ++i) {
  926. var lineChunk = lineChunks[i];
  927. if (this._dirtyLines || this._scheduledPaintLines) {
  928. this._schedulePaintLines(lineChunk.startLine, lineChunk.endLine);
  929. continue;
  930. }
  931. for (var lineNumber = lineChunk.startLine; lineNumber < lineChunk.endLine; ++lineNumber) {
  932. if (!chunk || lineNumber < chunk.startLine || lineNumber >= chunk.startLine + chunk.linesCount)
  933. chunk = this.chunkForLine(lineNumber);
  934. var lineRow = chunk.getExpandedLineRow(lineNumber);
  935. if (!lineRow)
  936. continue;
  937. if (lineNumber < firstVisibleLineNumber) {
  938. invisibleLineRows.push(lineRow);
  939. continue;
  940. }
  941. if (restoreSelection && !selection)
  942. selection = this._getSelection();
  943. this._paintLine(lineRow);
  944. if (this._paintLinesOperationsCredit < 0) {
  945. this._schedulePaintLines(lineNumber + 1, lineChunk.endLine);
  946. break;
  947. }
  948. }
  949. }
  950. for (var i = 0; i < invisibleLineRows.length; ++i) {
  951. if (restoreSelection && !selection)
  952. selection = this._getSelection();
  953. this._paintLine(invisibleLineRows[i]);
  954. }
  955. if (restoreSelection)
  956. this._restoreSelection(selection);
  957. },
  958. _paintLine: function(lineRow)
  959. {
  960. var lineNumber = lineRow.lineNumber;
  961. if (this._dirtyLines) {
  962. this._schedulePaintLines(lineNumber, lineNumber + 1);
  963. return;
  964. }
  965. this.beginDomUpdates();
  966. try {
  967. if (this._scheduledPaintLines || this._paintLinesOperationsCredit < 0) {
  968. this._schedulePaintLines(lineNumber, lineNumber + 1);
  969. return;
  970. }
  971. var highlight = this._textModel.getAttribute(lineNumber, "highlight");
  972. if (!highlight)
  973. return;
  974. lineRow.removeChildren();
  975. var line = this._textModel.line(lineNumber);
  976. if (!line)
  977. lineRow.appendChild(document.createElement("br"));
  978. var plainTextStart = -1;
  979. for (var j = 0; j < line.length;) {
  980. if (j > 1000) {
  981. // This line is too long - do not waste cycles on minified js highlighting.
  982. if (plainTextStart === -1)
  983. plainTextStart = j;
  984. break;
  985. }
  986. var attribute = highlight[j];
  987. if (!attribute || !attribute.tokenType) {
  988. if (plainTextStart === -1)
  989. plainTextStart = j;
  990. j++;
  991. } else {
  992. if (plainTextStart !== -1) {
  993. this._appendTextNode(lineRow, line.substring(plainTextStart, j));
  994. plainTextStart = -1;
  995. --this._paintLinesOperationsCredit;
  996. }
  997. this._appendSpan(lineRow, line.substring(j, j + attribute.length), attribute.tokenType);
  998. j += attribute.length;
  999. --this._paintLinesOperationsCredit;
  1000. }
  1001. }
  1002. if (plainTextStart !== -1) {
  1003. this._appendTextNode(lineRow, line.substring(plainTextStart, line.length));
  1004. --this._paintLinesOperationsCredit;
  1005. }
  1006. if (lineRow.decorationsElement)
  1007. lineRow.appendChild(lineRow.decorationsElement);
  1008. } finally {
  1009. if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
  1010. this._markedRangeElement = highlightSearchResult(lineRow, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
  1011. this.endDomUpdates();
  1012. }
  1013. },
  1014. _releaseLinesHighlight: function(lineRow)
  1015. {
  1016. if (!lineRow)
  1017. return;
  1018. if ("spans" in lineRow) {
  1019. var spans = lineRow.spans;
  1020. for (var j = 0; j < spans.length; ++j)
  1021. this._cachedSpans.push(spans[j]);
  1022. delete lineRow.spans;
  1023. }
  1024. if ("textNodes" in lineRow) {
  1025. var textNodes = lineRow.textNodes;
  1026. for (var j = 0; j < textNodes.length; ++j)
  1027. this._cachedTextNodes.push(textNodes[j]);
  1028. delete lineRow.textNodes;
  1029. }
  1030. this._cachedRows.push(lineRow);
  1031. },
  1032. _getSelection: function()
  1033. {
  1034. var selection = window.getSelection();
  1035. if (!selection.rangeCount)
  1036. return null;
  1037. var selectionRange = selection.getRangeAt(0);
  1038. // Selection may be outside of the viewer.
  1039. if (!this._container.isAncestor(selectionRange.startContainer) || !this._container.isAncestor(selectionRange.endContainer))
  1040. return null;
  1041. var start = this._selectionToPosition(selectionRange.startContainer, selectionRange.startOffset);
  1042. var end = selectionRange.collapsed ? start : this._selectionToPosition(selectionRange.endContainer, selectionRange.endOffset);
  1043. if (selection.anchorNode === selectionRange.startContainer && selection.anchorOffset === selectionRange.startOffset)
  1044. return new WebInspector.TextRange(start.line, start.column, end.line, end.column);
  1045. else
  1046. return new WebInspector.TextRange(end.line, end.column, start.line, start.column);
  1047. },
  1048. _restoreSelection: function(range, scrollIntoView)
  1049. {
  1050. if (!range)
  1051. return;
  1052. var start = this._positionToSelection(range.startLine, range.startColumn);
  1053. var end = range.isEmpty() ? start : this._positionToSelection(range.endLine, range.endColumn);
  1054. window.getSelection().setBaseAndExtent(start.container, start.offset, end.container, end.offset);
  1055. if (scrollIntoView) {
  1056. for (var node = end.container; node; node = node.parentElement) {
  1057. if (node.scrollIntoViewIfNeeded) {
  1058. node.scrollIntoViewIfNeeded();
  1059. break;
  1060. }
  1061. }
  1062. }
  1063. },
  1064. _setCaretLocation: function(line, column, scrollIntoView)
  1065. {
  1066. var range = new WebInspector.TextRange(line, column, line, column);
  1067. this._restoreSelection(range, scrollIntoView);
  1068. },
  1069. _selectionToPosition: function(container, offset)
  1070. {
  1071. if (container === this._container && offset === 0)
  1072. return { line: 0, column: 0 };
  1073. if (container === this._container && offset === 1)
  1074. return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) };
  1075. var lineRow = this._enclosingLineRowOrSelf(container);
  1076. var lineNumber = lineRow.lineNumber;
  1077. if (container === lineRow && offset === 0)
  1078. return { line: lineNumber, column: 0 };
  1079. // This may be chunk and chunks may contain \n.
  1080. var column = 0;
  1081. var node = lineRow.nodeType === Node.TEXT_NODE ? lineRow : lineRow.traverseNextTextNode(lineRow);
  1082. while (node && node !== container) {
  1083. var text = node.textContent;
  1084. for (var i = 0; i < text.length; ++i) {
  1085. if (text.charAt(i) === "\n") {
  1086. lineNumber++;
  1087. column = 0;
  1088. } else
  1089. column++;
  1090. }
  1091. node = node.traverseNextTextNode(lineRow);
  1092. }
  1093. if (node === container && offset) {
  1094. var text = node.textContent;
  1095. for (var i = 0; i < offset; ++i) {
  1096. if (text.charAt(i) === "\n") {
  1097. lineNumber++;
  1098. column = 0;
  1099. } else
  1100. column++;
  1101. }
  1102. }
  1103. return { line: lineNumber, column: column };
  1104. },
  1105. _positionToSelection: function(line, column)
  1106. {
  1107. var chunk = this.chunkForLine(line);
  1108. // One-lined collapsed chunks may still stay highlighted.
  1109. var lineRow = chunk.linesCount === 1 ? chunk.element : chunk.getExpandedLineRow(line);
  1110. if (lineRow)
  1111. var rangeBoundary = lineRow.rangeBoundaryForOffset(column);
  1112. else {
  1113. var offset = column;
  1114. for (var i = chunk.startLine; i < line; ++i)
  1115. offset += this._textModel.lineLength(i) + 1; // \n
  1116. lineRow = chunk.element;
  1117. if (lineRow.firstChild)
  1118. var rangeBoundary = { container: lineRow.firstChild, offset: offset };
  1119. else
  1120. var rangeBoundary = { container: lineRow, offset: 0 };
  1121. }
  1122. return rangeBoundary;
  1123. },
  1124. _enclosingLineRowOrSelf: function(element)
  1125. {
  1126. var lineRow = element.enclosingNodeOrSelfWithClass("webkit-line-content");
  1127. if (lineRow)
  1128. return lineRow;
  1129. for (var lineRow = element; lineRow; lineRow = lineRow.parentElement) {
  1130. if (lineRow.parentElement === this._container)
  1131. return lineRow;
  1132. }
  1133. return null;
  1134. },
  1135. _appendSpan: function(element, content, className)
  1136. {
  1137. if (className === "html-resource-link" || className === "html-external-link") {
  1138. element.appendChild(this._createLink(content, className === "html-external-link"));
  1139. return;
  1140. }
  1141. var span = this._cachedSpans.pop() || document.createElement("span");
  1142. span.className = "webkit-" + className;
  1143. span.textContent = content;
  1144. element.appendChild(span);
  1145. if (!("spans" in element))
  1146. element.spans = [];
  1147. element.spans.push(span);
  1148. },
  1149. _appendTextNode: function(element, text)
  1150. {
  1151. var textNode = this._cachedTextNodes.pop();
  1152. if (textNode)
  1153. textNode.nodeValue = text;
  1154. else
  1155. textNode = document.createTextNode(text);
  1156. element.appendChild(textNode);
  1157. if (!("textNodes" in element))
  1158. element.textNodes = [];
  1159. element.textNodes.push(textNode);
  1160. },
  1161. _createLink: function(content, isExternal)
  1162. {
  1163. var quote = content.charAt(0);
  1164. if (content.length > 1 && (quote === "\"" || quote === "'"))
  1165. content = content.substring(1, content.length - 1);
  1166. else
  1167. quote = null;
  1168. var a = WebInspector.linkifyURLAsNode(this._rewriteHref(content), content, null, isExternal);
  1169. var span = document.createElement("span");
  1170. span.className = "webkit-html-attribute-value";
  1171. if (quote)
  1172. span.appendChild(document.createTextNode(quote));
  1173. span.appendChild(a);
  1174. if (quote)
  1175. span.appendChild(document.createTextNode(quote));
  1176. return span;
  1177. },
  1178. _rewriteHref: function(hrefValue, isExternal)
  1179. {
  1180. if (!this._url || !hrefValue || hrefValue.indexOf("://") > 0)
  1181. return hrefValue;
  1182. return WebInspector.completeURL(this._url, hrefValue);
  1183. },
  1184. _handleDOMUpdates: function(e)
  1185. {
  1186. if (this._domUpdateCoalescingLevel)
  1187. return;
  1188. var target = e.target;
  1189. if (target === this._container)
  1190. return;
  1191. var lineRow = this._enclosingLineRowOrSelf(target);
  1192. if (!lineRow)
  1193. return;
  1194. if (lineRow.decorationsElement && (lineRow.decorationsElement === target || lineRow.decorationsElement.isAncestor(target))) {
  1195. if (this._syncDecorationsForLineListener)
  1196. this._syncDecorationsForLineListener(lineRow.lineNumber);
  1197. return;
  1198. }
  1199. if (this._readOnly)
  1200. return;
  1201. if (target === lineRow && e.type === "DOMNodeInserted") {
  1202. // Ensure that the newly inserted line row has no lineNumber.
  1203. delete lineRow.lineNumber;
  1204. }
  1205. var startLine = 0;
  1206. for (var row = lineRow; row; row = row.previousSibling) {
  1207. if (typeof row.lineNumber === "number") {
  1208. startLine = row.lineNumber;
  1209. break;
  1210. }
  1211. }
  1212. var endLine = startLine + 1;
  1213. for (var row = lineRow.nextSibling; row; row = row.nextSibling) {
  1214. if (typeof row.lineNumber === "number" && row.lineNumber > startLine) {
  1215. endLine = row.lineNumber;
  1216. break;
  1217. }
  1218. }
  1219. if (target === lineRow && e.type === "DOMNodeRemoved") {
  1220. // Now this will no longer be valid.
  1221. delete lineRow.lineNumber;
  1222. }
  1223. if (this._dirtyLines) {
  1224. this._dirtyLines.start = Math.min(this._dirtyLines.start, startLine);
  1225. this._dirtyLines.end = Math.max(thi

Large files files are truncated, but you can click here to view the full file