blob: 6c072be2b479de827c80c6a6b5d331938a949752 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
Jan Schefflerc5bc69f2020-07-30 09:51:5231
Tim van der Lippe9b2f8712020-02-12 17:46:2232import * as Components from '../components/components.js';
33import * as UI from '../ui/ui.js';
Patrick Brossete3474dc2020-09-29 12:38:2634import {ConsoleViewMessage} from './ConsoleViewMessage.js'; // eslint-disable-line no-unused-vars
Tim van der Lippe9b2f8712020-02-12 17:46:2235
Blink Reformat4c46d092018-04-07 15:32:3736/**
37 * @unrestricted
38 */
Tim van der Lippeeaacb722020-01-10 12:16:0039export class ConsoleViewport {
Blink Reformat4c46d092018-04-07 15:32:3740 /**
Tim van der Lippeeaacb722020-01-10 12:16:0041 * @param {!ConsoleViewportProvider} provider
Blink Reformat4c46d092018-04-07 15:32:3742 */
43 constructor(provider) {
Sigurd Schneider53f33522020-10-08 15:00:4944 this.element = /** @type {!HTMLElement} */ (document.createElement('div'));
Blink Reformat4c46d092018-04-07 15:32:3745 this.element.style.overflow = 'auto';
46 this._topGapElement = this.element.createChild('div');
47 this._topGapElement.style.height = '0px';
48 this._topGapElement.style.color = 'transparent';
Sigurd Schneidercd3f4292020-10-09 08:56:3149 this._topGapElementActive = false;
Blink Reformat4c46d092018-04-07 15:32:3750 this._contentElement = this.element.createChild('div');
51 this._bottomGapElement = this.element.createChild('div');
52 this._bottomGapElement.style.height = '0px';
53 this._bottomGapElement.style.color = 'transparent';
Sigurd Schneidercd3f4292020-10-09 08:56:3154 this._bottomGapElementActive = false;
Blink Reformat4c46d092018-04-07 15:32:3755
56 // Text content needed for range intersection checks in _updateSelectionModel.
57 // Use Unicode ZERO WIDTH NO-BREAK SPACE, which avoids contributing any height to the element's layout overflow.
58 this._topGapElement.textContent = '\uFEFF';
59 this._bottomGapElement.textContent = '\uFEFF';
60
Joel Einbinderca8fa5a2018-08-24 23:30:2561 UI.ARIAUtils.markAsHidden(this._topGapElement);
62 UI.ARIAUtils.markAsHidden(this._bottomGapElement);
63
Blink Reformat4c46d092018-04-07 15:32:3764 this._provider = provider;
65 this.element.addEventListener('scroll', this._onScroll.bind(this), false);
Sigurd Schneidercd3f4292020-10-09 08:56:3166 this.element.addEventListener('copy', /** @type {!EventListener} */ (this._onCopy.bind(this)), false);
67 this.element.addEventListener('dragstart', /** @type {function(!Event):*} */ (this._onDragStart.bind(this)), false);
68 this._contentElement.addEventListener('focusin', /** @type {!EventListener} */ (this._onFocusIn.bind(this)), false);
69 this._contentElement.addEventListener(
70 'focusout', /** @type {!EventListener} */ (this._onFocusOut.bind(this)), false);
71 this._contentElement.addEventListener('keydown', /** @type {!EventListener} */ (this._onKeyDown.bind(this)), false);
Erik Luo39452ff2018-09-01 01:08:0772 this._virtualSelectedIndex = -1;
Erik Luob5bfff42018-09-20 02:52:3973 this._contentElement.tabIndex = -1;
Blink Reformat4c46d092018-04-07 15:32:3774
75 this._firstActiveIndex = -1;
76 this._lastActiveIndex = -1;
Sigurd Schneidercd3f4292020-10-09 08:56:3177 /** @type {!Array<!ConsoleViewportElement>} */
Blink Reformat4c46d092018-04-07 15:32:3778 this._renderedItems = [];
Sigurd Schneidercd3f4292020-10-09 08:56:3179 /** @type {?{item: number, node: !Node, offset: number}} */
Blink Reformat4c46d092018-04-07 15:32:3780 this._anchorSelection = null;
Sigurd Schneidercd3f4292020-10-09 08:56:3181 /** @type {?{item: number, node: !Node, offset: number}} */
Blink Reformat4c46d092018-04-07 15:32:3782 this._headSelection = null;
83 this._itemCount = 0;
84 this._cumulativeHeights = new Int32Array(0);
85 this._muteCopyHandler = false;
86
87 // Listen for any changes to descendants and trigger a refresh. This ensures
88 // that items updated asynchronously will not break stick-to-bottom behavior
89 // if they change the scroll height.
90 this._observer = new MutationObserver(this.refresh.bind(this));
91 this._observerConfig = {childList: true, subtree: true};
Sigurd Schneidercd3f4292020-10-09 08:56:3192 this._stickToBottom = false;
93 this._selectionIsBackward = false;
Blink Reformat4c46d092018-04-07 15:32:3794 }
95
96 /**
97 * @return {boolean}
98 */
99 stickToBottom() {
100 return this._stickToBottom;
101 }
102
103 /**
104 * @param {boolean} value
105 */
106 setStickToBottom(value) {
107 this._stickToBottom = value;
Tim van der Lippe1d6e57a2019-09-30 11:55:34108 if (this._stickToBottom) {
Blink Reformat4c46d092018-04-07 15:32:37109 this._observer.observe(this._contentElement, this._observerConfig);
Tim van der Lippe1d6e57a2019-09-30 11:55:34110 } else {
Blink Reformat4c46d092018-04-07 15:32:37111 this._observer.disconnect();
Tim van der Lippe1d6e57a2019-09-30 11:55:34112 }
Blink Reformat4c46d092018-04-07 15:32:37113 }
114
Erik Luoad6c91c2018-12-06 03:28:42115 /**
116 * @return {boolean}
117 */
118 hasVirtualSelection() {
119 return this._virtualSelectedIndex !== -1;
120 }
121
Blink Reformat4c46d092018-04-07 15:32:37122 copyWithStyles() {
123 this._muteCopyHandler = true;
124 this.element.ownerDocument.execCommand('copy');
125 this._muteCopyHandler = false;
126 }
127
128 /**
Sigurd Schneidercd3f4292020-10-09 08:56:31129 * @param {!ClipboardEvent} event
Blink Reformat4c46d092018-04-07 15:32:37130 */
131 _onCopy(event) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34132 if (this._muteCopyHandler) {
Blink Reformat4c46d092018-04-07 15:32:37133 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34134 }
Patrick Brosset874a7302020-08-11 13:52:22135
Blink Reformat4c46d092018-04-07 15:32:37136 const text = this._selectedText();
Tim van der Lippe1d6e57a2019-09-30 11:55:34137 if (!text) {
Blink Reformat4c46d092018-04-07 15:32:37138 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34139 }
Patrick Brosset874a7302020-08-11 13:52:22140
Blink Reformat4c46d092018-04-07 15:32:37141 event.preventDefault();
Patrick Brosset874a7302020-08-11 13:52:22142
143 if (this._selectionContainsTable()) {
144 this.copyWithStyles();
Sigurd Schneidercd3f4292020-10-09 08:56:31145 } else if (event.clipboardData) {
Patrick Brosset874a7302020-08-11 13:52:22146 event.clipboardData.setData('text/plain', text);
147 }
Blink Reformat4c46d092018-04-07 15:32:37148 }
149
150 /**
Sigurd Schneidercd3f4292020-10-09 08:56:31151 * @param {!FocusEvent} event
Blink Reformat4c46d092018-04-07 15:32:37152 */
Erik Luo39452ff2018-09-01 01:08:07153 _onFocusIn(event) {
Sigurd Schneidercd3f4292020-10-09 08:56:31154 const renderedIndex =
155 this._renderedItems.findIndex(item => item.element().isSelfOrAncestor(/** @type {?Node} */ (event.target)));
Tim van der Lippe1d6e57a2019-09-30 11:55:34156 if (renderedIndex !== -1) {
Erik Luob5bfff42018-09-20 02:52:39157 this._virtualSelectedIndex = this._firstActiveIndex + renderedIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:34158 }
Erik Luofc6a6302018-11-02 06:48:52159 let focusLastChild = false;
Erik Luo39452ff2018-09-01 01:08:07160 // Make default selection when moving from external (e.g. prompt) to the container.
161 if (this._virtualSelectedIndex === -1 && this._isOutsideViewport(/** @type {?Element} */ (event.relatedTarget)) &&
Erik Luoad6c91c2018-12-06 03:28:42162 event.target === this._contentElement && this._itemCount) {
Erik Luofc6a6302018-11-02 06:48:52163 focusLastChild = true;
Erik Luo39452ff2018-09-01 01:08:07164 this._virtualSelectedIndex = this._itemCount - 1;
Erik Luo840be6b2018-12-03 20:54:27165
166 // Update stick to bottom before scrolling into view.
167 this.refresh();
168 this.scrollItemIntoView(this._virtualSelectedIndex);
Erik Luofc6a6302018-11-02 06:48:52169 }
170 this._updateFocusedItem(focusLastChild);
Erik Luo39452ff2018-09-01 01:08:07171 }
172
173 /**
Sigurd Schneidercd3f4292020-10-09 08:56:31174 * @param {!FocusEvent} event
Erik Luo39452ff2018-09-01 01:08:07175 */
176 _onFocusOut(event) {
177 // Remove selection when focus moves to external location (e.g. prompt).
Tim van der Lippe1d6e57a2019-09-30 11:55:34178 if (this._isOutsideViewport(/** @type {?Element} */ (event.relatedTarget))) {
Erik Luo39452ff2018-09-01 01:08:07179 this._virtualSelectedIndex = -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34180 }
Erik Luo39452ff2018-09-01 01:08:07181 this._updateFocusedItem();
182 }
183
184 /**
185 * @param {?Element} element
186 * @return {boolean}
187 */
188 _isOutsideViewport(element) {
Erik Luob5bfff42018-09-20 02:52:39189 return !!element && !element.isSelfOrDescendant(this._contentElement);
Erik Luo39452ff2018-09-01 01:08:07190 }
191
192 /**
Sigurd Schneidercd3f4292020-10-09 08:56:31193 * @param {!DragEvent} event
Erik Luo39452ff2018-09-01 01:08:07194 */
Blink Reformat4c46d092018-04-07 15:32:37195 _onDragStart(event) {
196 const text = this._selectedText();
Tim van der Lippe1d6e57a2019-09-30 11:55:34197 if (!text) {
Blink Reformat4c46d092018-04-07 15:32:37198 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34199 }
Sigurd Schneidercd3f4292020-10-09 08:56:31200 if (event.dataTransfer) {
201 event.dataTransfer.clearData();
202 event.dataTransfer.setData('text/plain', text);
203 event.dataTransfer.effectAllowed = 'copy';
204 }
Blink Reformat4c46d092018-04-07 15:32:37205 return true;
206 }
207
208 /**
Sigurd Schneidercd3f4292020-10-09 08:56:31209 * @param {!KeyboardEvent} event
Erik Luo39452ff2018-09-01 01:08:07210 */
211 _onKeyDown(event) {
Tim van der Lippe9b2f8712020-02-12 17:46:22212 if (UI.UIUtils.isEditing() || !this._itemCount || event.shiftKey) {
Erik Luo39452ff2018-09-01 01:08:07213 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34214 }
Erik Luo0b8282e2018-10-08 20:37:46215 let isArrowUp = false;
Erik Luo39452ff2018-09-01 01:08:07216 switch (event.key) {
217 case 'ArrowUp':
Erik Luo0b8282e2018-10-08 20:37:46218 if (this._virtualSelectedIndex > 0) {
219 isArrowUp = true;
220 this._virtualSelectedIndex--;
221 } else {
222 return;
223 }
Erik Luo39452ff2018-09-01 01:08:07224 break;
225 case 'ArrowDown':
Tim van der Lippe1d6e57a2019-09-30 11:55:34226 if (this._virtualSelectedIndex < this._itemCount - 1) {
Erik Luo0b8282e2018-10-08 20:37:46227 this._virtualSelectedIndex++;
Tim van der Lippe1d6e57a2019-09-30 11:55:34228 } else {
Erik Luo0b8282e2018-10-08 20:37:46229 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34230 }
Erik Luo39452ff2018-09-01 01:08:07231 break;
232 case 'Home':
233 this._virtualSelectedIndex = 0;
234 break;
235 case 'End':
236 this._virtualSelectedIndex = this._itemCount - 1;
237 break;
238 default:
239 return;
240 }
241 event.consume(true);
242 this.scrollItemIntoView(this._virtualSelectedIndex);
Erik Luo0b8282e2018-10-08 20:37:46243 this._updateFocusedItem(isArrowUp);
Erik Luo39452ff2018-09-01 01:08:07244 }
245
Erik Luo0b8282e2018-10-08 20:37:46246 /**
247 * @param {boolean=} focusLastChild
248 */
249 _updateFocusedItem(focusLastChild) {
Erik Luo39452ff2018-09-01 01:08:07250 const selectedElement = this.renderedElementAt(this._virtualSelectedIndex);
251 const changed = this._lastSelectedElement !== selectedElement;
Erik Luob5bfff42018-09-20 02:52:39252 const containerHasFocus = this._contentElement === this.element.ownerDocument.deepActiveElement();
Tim van der Lippe1d6e57a2019-09-30 11:55:34253 if (this._lastSelectedElement && changed) {
Erik Luo39452ff2018-09-01 01:08:07254 this._lastSelectedElement.classList.remove('console-selected');
Tim van der Lippe1d6e57a2019-09-30 11:55:34255 }
Erik Luo840be6b2018-12-03 20:54:27256 if (selectedElement && (focusLastChild || changed || containerHasFocus) && this.element.hasFocus()) {
Erik Luo39452ff2018-09-01 01:08:07257 selectedElement.classList.add('console-selected');
Erik Luob5bfff42018-09-20 02:52:39258 // Do not focus the message if something within holds focus (e.g. object).
Erik Luo840be6b2018-12-03 20:54:27259 if (focusLastChild) {
260 this.setStickToBottom(false);
261 this._renderedItems[this._virtualSelectedIndex - this._firstActiveIndex].focusLastChildOrSelf();
262 } else if (!selectedElement.hasFocus()) {
Peter Marshall477ef992020-05-12 17:59:55263 selectedElement.focus({preventScroll: true});
Erik Luo0b8282e2018-10-08 20:37:46264 }
Erik Luo39452ff2018-09-01 01:08:07265 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34266 if (this._itemCount && !this._contentElement.hasFocus()) {
Erik Luob5bfff42018-09-20 02:52:39267 this._contentElement.tabIndex = 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34268 } else {
Erik Luob5bfff42018-09-20 02:52:39269 this._contentElement.tabIndex = -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34270 }
Erik Luo39452ff2018-09-01 01:08:07271 this._lastSelectedElement = selectedElement;
Erik Luo39452ff2018-09-01 01:08:07272 }
273
274 /**
Blink Reformat4c46d092018-04-07 15:32:37275 * @return {!Element}
276 */
277 contentElement() {
278 return this._contentElement;
279 }
280
281 invalidate() {
282 delete this._cachedProviderElements;
283 this._itemCount = this._provider.itemCount();
Tim van der Lippe1d6e57a2019-09-30 11:55:34284 if (this._virtualSelectedIndex > this._itemCount - 1) {
Erik Luo39452ff2018-09-01 01:08:07285 this._virtualSelectedIndex = this._itemCount - 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34286 }
Blink Reformat4c46d092018-04-07 15:32:37287 this._rebuildCumulativeHeights();
288 this.refresh();
289 }
290
291 /**
292 * @param {number} index
Tim van der Lippeeaacb722020-01-10 12:16:00293 * @return {?ConsoleViewportElement}
Blink Reformat4c46d092018-04-07 15:32:37294 */
295 _providerElement(index) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34296 if (!this._cachedProviderElements) {
Blink Reformat4c46d092018-04-07 15:32:37297 this._cachedProviderElements = new Array(this._itemCount);
Tim van der Lippe1d6e57a2019-09-30 11:55:34298 }
Blink Reformat4c46d092018-04-07 15:32:37299 let element = this._cachedProviderElements[index];
300 if (!element) {
301 element = this._provider.itemElement(index);
302 this._cachedProviderElements[index] = element;
303 }
304 return element;
305 }
306
307 _rebuildCumulativeHeights() {
308 const firstActiveIndex = this._firstActiveIndex;
309 const lastActiveIndex = this._lastActiveIndex;
310 let height = 0;
311 this._cumulativeHeights = new Int32Array(this._itemCount);
312 for (let i = 0; i < this._itemCount; ++i) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34313 if (firstActiveIndex <= i && i - firstActiveIndex < this._renderedItems.length && i <= lastActiveIndex) {
Blink Reformat4c46d092018-04-07 15:32:37314 height += this._renderedItems[i - firstActiveIndex].element().offsetHeight;
Tim van der Lippe1d6e57a2019-09-30 11:55:34315 } else {
Blink Reformat4c46d092018-04-07 15:32:37316 height += this._provider.fastHeight(i);
Tim van der Lippe1d6e57a2019-09-30 11:55:34317 }
Blink Reformat4c46d092018-04-07 15:32:37318 this._cumulativeHeights[i] = height;
319 }
320 }
321
322 _rebuildCumulativeHeightsIfNeeded() {
Erik Luo4b002322018-07-30 21:23:31323 let totalCachedHeight = 0;
324 let totalMeasuredHeight = 0;
Blink Reformat4c46d092018-04-07 15:32:37325 // Check whether current items in DOM have changed heights. Tolerate 1-pixel
326 // error due to double-to-integer rounding errors.
327 for (let i = 0; i < this._renderedItems.length; ++i) {
328 const cachedItemHeight = this._cachedItemHeight(this._firstActiveIndex + i);
Erik Luo4b002322018-07-30 21:23:31329 const measuredHeight = this._renderedItems[i].element().offsetHeight;
330 if (Math.abs(cachedItemHeight - measuredHeight) > 1) {
Blink Reformat4c46d092018-04-07 15:32:37331 this._rebuildCumulativeHeights();
Erik Luo4b002322018-07-30 21:23:31332 return;
333 }
334 totalMeasuredHeight += measuredHeight;
335 totalCachedHeight += cachedItemHeight;
336 if (Math.abs(totalCachedHeight - totalMeasuredHeight) > 1) {
337 this._rebuildCumulativeHeights();
338 return;
Blink Reformat4c46d092018-04-07 15:32:37339 }
340 }
341 }
342
343 /**
344 * @param {number} index
345 * @return {number}
346 */
347 _cachedItemHeight(index) {
348 return index === 0 ? this._cumulativeHeights[0] :
349 this._cumulativeHeights[index] - this._cumulativeHeights[index - 1];
350 }
351
352 /**
353 * @param {?Selection} selection
354 * @suppressGlobalPropertiesCheck
355 */
356 _isSelectionBackwards(selection) {
Sigurd Schneidercd3f4292020-10-09 08:56:31357 if (!selection || !selection.rangeCount || !selection.anchorNode || !selection.focusNode) {
Blink Reformat4c46d092018-04-07 15:32:37358 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34359 }
Blink Reformat4c46d092018-04-07 15:32:37360 const range = document.createRange();
361 range.setStart(selection.anchorNode, selection.anchorOffset);
362 range.setEnd(selection.focusNode, selection.focusOffset);
363 return range.collapsed;
364 }
365
366 /**
367 * @param {number} itemIndex
368 * @param {!Node} node
369 * @param {number} offset
370 * @return {!{item: number, node: !Node, offset: number}}
371 */
372 _createSelectionModel(itemIndex, node, offset) {
373 return {item: itemIndex, node: node, offset: offset};
374 }
375
376 /**
377 * @param {?Selection} selection
378 */
379 _updateSelectionModel(selection) {
380 const range = selection && selection.rangeCount ? selection.getRangeAt(0) : null;
Sigurd Schneider3fe3c212020-10-06 05:14:41381 if (!range || (!selection || selection.isCollapsed) || !this.element.hasSelection()) {
Blink Reformat4c46d092018-04-07 15:32:37382 this._headSelection = null;
383 this._anchorSelection = null;
384 return false;
385 }
386
Sigurd Schneider3fe3c212020-10-06 05:14:41387 let firstSelectedIndex = Number.MAX_VALUE;
388 let lastSelectedIndex = -1;
Blink Reformat4c46d092018-04-07 15:32:37389
390 let hasVisibleSelection = false;
391 for (let i = 0; i < this._renderedItems.length; ++i) {
392 if (range.intersectsNode(this._renderedItems[i].element())) {
393 const index = i + this._firstActiveIndex;
Sigurd Schneider3fe3c212020-10-06 05:14:41394 firstSelectedIndex = Math.min(firstSelectedIndex, index);
395 lastSelectedIndex = Math.max(lastSelectedIndex, index);
Blink Reformat4c46d092018-04-07 15:32:37396 hasVisibleSelection = true;
397 }
398 }
Sigurd Schneidercd3f4292020-10-09 08:56:31399 const topOverlap = range.intersectsNode(this._topGapElement) && this._topGapElementActive;
400 const bottomOverlap = range.intersectsNode(this._bottomGapElement) && this._bottomGapElementActive;
Blink Reformat4c46d092018-04-07 15:32:37401 if (!topOverlap && !bottomOverlap && !hasVisibleSelection) {
402 this._headSelection = null;
403 this._anchorSelection = null;
404 return false;
405 }
406
407 if (!this._anchorSelection || !this._headSelection) {
408 this._anchorSelection = this._createSelectionModel(0, this.element, 0);
409 this._headSelection = this._createSelectionModel(this._itemCount - 1, this.element, this.element.children.length);
410 this._selectionIsBackward = false;
411 }
412
413 const isBackward = this._isSelectionBackwards(selection);
414 const startSelection = this._selectionIsBackward ? this._headSelection : this._anchorSelection;
415 const endSelection = this._selectionIsBackward ? this._anchorSelection : this._headSelection;
Sigurd Schneider3fe3c212020-10-06 05:14:41416 let firstSelected = null;
417 let lastSelected = null;
418 if (hasVisibleSelection) {
419 firstSelected = this._createSelectionModel(
420 firstSelectedIndex, /** @type {!Node} */ (range.startContainer), range.startOffset);
421 lastSelected =
422 this._createSelectionModel(lastSelectedIndex, /** @type {!Node} */ (range.endContainer), range.endOffset);
423 }
Blink Reformat4c46d092018-04-07 15:32:37424 if (topOverlap && bottomOverlap && hasVisibleSelection) {
Sigurd Schneider3fe3c212020-10-06 05:14:41425 firstSelected = (firstSelected && firstSelected.item < startSelection.item) ? firstSelected : startSelection;
426 lastSelected = (lastSelected && lastSelected.item > endSelection.item) ? lastSelected : endSelection;
Blink Reformat4c46d092018-04-07 15:32:37427 } else if (!hasVisibleSelection) {
428 firstSelected = startSelection;
429 lastSelected = endSelection;
430 } else if (topOverlap) {
431 firstSelected = isBackward ? this._headSelection : this._anchorSelection;
432 } else if (bottomOverlap) {
433 lastSelected = isBackward ? this._anchorSelection : this._headSelection;
434 }
435
436 if (isBackward) {
437 this._anchorSelection = lastSelected;
438 this._headSelection = firstSelected;
439 } else {
440 this._anchorSelection = firstSelected;
441 this._headSelection = lastSelected;
442 }
443 this._selectionIsBackward = isBackward;
444 return true;
445 }
446
447 /**
448 * @param {?Selection} selection
449 */
450 _restoreSelection(selection) {
Sigurd Schneidera6388b02020-10-06 05:16:45451 if (!selection || !this._anchorSelection || !this._headSelection) {
452 return;
Blink Reformat4c46d092018-04-07 15:32:37453 }
454
Sigurd Schneidera6388b02020-10-06 05:16:45455 /**
456 * @param {!{item: number, node: !Node, offset: number}} selection
457 * @param {boolean} isSelectionBackwards
458 * @return {!{element: !Node, offset: number}}
459 */
460 const clampSelection = (selection, isSelectionBackwards) => {
461 if (this._firstActiveIndex <= selection.item && selection.item <= this._lastActiveIndex) {
462 return {element: selection.node, offset: selection.offset};
Tim van der Lippe1d6e57a2019-09-30 11:55:34463 }
Blink Reformat4c46d092018-04-07 15:32:37464
Sigurd Schneidera6388b02020-10-06 05:16:45465 const element = selection.item < this._firstActiveIndex ? this._topGapElement : this._bottomGapElement;
466 return {element, offset: isSelectionBackwards ? 1 : 0};
467 };
468
469 const {element: anchorElement, offset: anchorOffset} =
470 clampSelection(this._anchorSelection, !!this._selectionIsBackward);
471 const {element: headElement, offset: headOffset} = clampSelection(this._headSelection, !this._selectionIsBackward);
Blink Reformat4c46d092018-04-07 15:32:37472 selection.setBaseAndExtent(anchorElement, anchorOffset, headElement, headOffset);
473 }
474
Patrick Brosset874a7302020-08-11 13:52:22475 _selectionContainsTable() {
476 if (!this._anchorSelection || !this._headSelection) {
477 return false;
478 }
479
Patrick Brossetdc278772020-09-07 13:36:37480 const start = this._selectionIsBackward ? this._headSelection.item : this._anchorSelection.item;
481 const end = this._selectionIsBackward ? this._anchorSelection.item : this._headSelection.item;
482
483 for (let i = start; i <= end; i++) {
484 const element = /** @type {!ConsoleViewMessage} */ (this._providerElement(i));
485 if (element && element.consoleMessage().type === 'table') {
Patrick Brosset874a7302020-08-11 13:52:22486 return true;
487 }
488 }
489
490 return false;
491 }
492
Blink Reformat4c46d092018-04-07 15:32:37493 refresh() {
494 this._observer.disconnect();
495 this._innerRefresh();
Tim van der Lippe1d6e57a2019-09-30 11:55:34496 if (this._stickToBottom) {
Blink Reformat4c46d092018-04-07 15:32:37497 this._observer.observe(this._contentElement, this._observerConfig);
Tim van der Lippe1d6e57a2019-09-30 11:55:34498 }
Blink Reformat4c46d092018-04-07 15:32:37499 }
500
501 _innerRefresh() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34502 if (!this._visibleHeight()) {
503 return;
504 } // Do nothing for invisible controls.
Blink Reformat4c46d092018-04-07 15:32:37505
506 if (!this._itemCount) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34507 for (let i = 0; i < this._renderedItems.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37508 this._renderedItems[i].willHide();
Tim van der Lippe1d6e57a2019-09-30 11:55:34509 }
Blink Reformat4c46d092018-04-07 15:32:37510 this._renderedItems = [];
511 this._contentElement.removeChildren();
512 this._topGapElement.style.height = '0px';
513 this._bottomGapElement.style.height = '0px';
514 this._firstActiveIndex = -1;
515 this._lastActiveIndex = -1;
Pavel Feldmandb310912019-01-30 00:31:20516 this._updateFocusedItem();
Blink Reformat4c46d092018-04-07 15:32:37517 return;
518 }
519
520 const selection = this.element.getComponentSelection();
521 const shouldRestoreSelection = this._updateSelectionModel(selection);
522
523 const visibleFrom = this.element.scrollTop;
524 const visibleHeight = this._visibleHeight();
525 const activeHeight = visibleHeight * 2;
526 this._rebuildCumulativeHeightsIfNeeded();
527
528 // When the viewport is scrolled to the bottom, using the cumulative heights estimate is not
529 // precise enough to determine next visible indices. This stickToBottom check avoids extra
530 // calls to refresh in those cases.
531 if (this._stickToBottom) {
532 this._firstActiveIndex =
533 Math.max(this._itemCount - Math.ceil(activeHeight / this._provider.minimumRowHeight()), 0);
534 this._lastActiveIndex = this._itemCount - 1;
535 } else {
536 this._firstActiveIndex =
537 Math.max(this._cumulativeHeights.lowerBound(visibleFrom + 1 - (activeHeight - visibleHeight) / 2), 0);
538 // Proactively render more rows in case some of them will be collapsed without triggering refresh. @see crbug.com/390169
539 this._lastActiveIndex = this._firstActiveIndex + Math.ceil(activeHeight / this._provider.minimumRowHeight()) - 1;
540 this._lastActiveIndex = Math.min(this._lastActiveIndex, this._itemCount - 1);
541 }
542
543 const topGapHeight = this._cumulativeHeights[this._firstActiveIndex - 1] || 0;
544 const bottomGapHeight =
545 this._cumulativeHeights[this._cumulativeHeights.length - 1] - this._cumulativeHeights[this._lastActiveIndex];
546
547 /**
Tim van der Lippeeaacb722020-01-10 12:16:00548 * @this {ConsoleViewport}
Blink Reformat4c46d092018-04-07 15:32:37549 */
550 function prepare() {
551 this._topGapElement.style.height = topGapHeight + 'px';
552 this._bottomGapElement.style.height = bottomGapHeight + 'px';
Sigurd Schneidercd3f4292020-10-09 08:56:31553 this._topGapElementActive = !!topGapHeight;
554 this._bottomGapElementActive = !!bottomGapHeight;
Blink Reformat4c46d092018-04-07 15:32:37555 this._contentElement.style.setProperty('height', '10000000px');
556 }
557
558 this._partialViewportUpdate(prepare.bind(this));
559 this._contentElement.style.removeProperty('height');
560 // Should be the last call in the method as it might force layout.
Tim van der Lippe1d6e57a2019-09-30 11:55:34561 if (shouldRestoreSelection) {
Blink Reformat4c46d092018-04-07 15:32:37562 this._restoreSelection(selection);
Tim van der Lippe1d6e57a2019-09-30 11:55:34563 }
564 if (this._stickToBottom) {
Blink Reformat4c46d092018-04-07 15:32:37565 this.element.scrollTop = 10000000;
Tim van der Lippe1d6e57a2019-09-30 11:55:34566 }
Blink Reformat4c46d092018-04-07 15:32:37567 }
568
569 /**
Sigurd Schneidercd3f4292020-10-09 08:56:31570 * @param {function():void} prepare
Blink Reformat4c46d092018-04-07 15:32:37571 */
572 _partialViewportUpdate(prepare) {
Sigurd Schneidercd3f4292020-10-09 08:56:31573 /** @type {!Set<!ConsoleViewportElement>} */
Blink Reformat4c46d092018-04-07 15:32:37574 const itemsToRender = new Set();
Tim van der Lippe1d6e57a2019-09-30 11:55:34575 for (let i = this._firstActiveIndex; i <= this._lastActiveIndex; ++i) {
Sigurd Schneidercd3f4292020-10-09 08:56:31576 const providerElement = this._providerElement(i);
577 console.assert(!!providerElement, 'Expected provider element to be defined');
578 if (providerElement) {
579 itemsToRender.add(providerElement);
580 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34581 }
Blink Reformat4c46d092018-04-07 15:32:37582 const willBeHidden = this._renderedItems.filter(item => !itemsToRender.has(item));
Tim van der Lippe1d6e57a2019-09-30 11:55:34583 for (let i = 0; i < willBeHidden.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37584 willBeHidden[i].willHide();
Tim van der Lippe1d6e57a2019-09-30 11:55:34585 }
Blink Reformat4c46d092018-04-07 15:32:37586 prepare();
Erik Luo39452ff2018-09-01 01:08:07587 let hadFocus = false;
588 for (let i = 0; i < willBeHidden.length; ++i) {
Pavel Feldmandb310912019-01-30 00:31:20589 hadFocus = hadFocus || willBeHidden[i].element().hasFocus();
Blink Reformat4c46d092018-04-07 15:32:37590 willBeHidden[i].element().remove();
Erik Luo39452ff2018-09-01 01:08:07591 }
Blink Reformat4c46d092018-04-07 15:32:37592
593 const wasShown = [];
594 let anchor = this._contentElement.firstChild;
595 for (const viewportElement of itemsToRender) {
596 const element = viewportElement.element();
597 if (element !== anchor) {
598 const shouldCallWasShown = !element.parentElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:34599 if (shouldCallWasShown) {
Blink Reformat4c46d092018-04-07 15:32:37600 wasShown.push(viewportElement);
Tim van der Lippe1d6e57a2019-09-30 11:55:34601 }
Blink Reformat4c46d092018-04-07 15:32:37602 this._contentElement.insertBefore(element, anchor);
603 } else {
604 anchor = anchor.nextSibling;
605 }
606 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34607 for (let i = 0; i < wasShown.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37608 wasShown[i].wasShown();
Tim van der Lippe1d6e57a2019-09-30 11:55:34609 }
Blink Reformat4c46d092018-04-07 15:32:37610 this._renderedItems = Array.from(itemsToRender);
Erik Luo39452ff2018-09-01 01:08:07611
Tim van der Lippe1d6e57a2019-09-30 11:55:34612 if (hadFocus) {
Pavel Feldmandb310912019-01-30 00:31:20613 this._contentElement.focus();
Tim van der Lippe1d6e57a2019-09-30 11:55:34614 }
Pavel Feldmandb310912019-01-30 00:31:20615 this._updateFocusedItem();
Blink Reformat4c46d092018-04-07 15:32:37616 }
617
618 /**
619 * @return {?string}
620 */
621 _selectedText() {
622 this._updateSelectionModel(this.element.getComponentSelection());
Tim van der Lippe1d6e57a2019-09-30 11:55:34623 if (!this._headSelection || !this._anchorSelection) {
Blink Reformat4c46d092018-04-07 15:32:37624 return null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34625 }
Blink Reformat4c46d092018-04-07 15:32:37626
627 let startSelection = null;
628 let endSelection = null;
629 if (this._selectionIsBackward) {
630 startSelection = this._headSelection;
631 endSelection = this._anchorSelection;
632 } else {
633 startSelection = this._anchorSelection;
634 endSelection = this._headSelection;
635 }
636
637 const textLines = [];
638 for (let i = startSelection.item; i <= endSelection.item; ++i) {
Sigurd Schneidercd3f4292020-10-09 08:56:31639 const providerElement = this._providerElement(i);
640 console.assert(!!providerElement);
641 if (!providerElement) {
642 continue;
643 }
644 const element = providerElement.element();
Tim van der Lippe9b2f8712020-02-12 17:46:22645 const lineContent = element.childTextNodes().map(Components.Linkifier.Linkifier.untruncatedNodeText).join('');
Blink Reformat4c46d092018-04-07 15:32:37646 textLines.push(lineContent);
647 }
648
Sigurd Schneidercd3f4292020-10-09 08:56:31649 const endProviderElement = this._providerElement(endSelection.item);
650 const endSelectionElement = endProviderElement && endProviderElement.element();
651 if (endSelectionElement && endSelection.node && endSelection.node.isSelfOrDescendant(endSelectionElement)) {
Blink Reformat4c46d092018-04-07 15:32:37652 const itemTextOffset = this._textOffsetInNode(endSelectionElement, endSelection.node, endSelection.offset);
Sigurd Schneidercd3f4292020-10-09 08:56:31653 if (textLines.length > 0) {
654 textLines[textLines.length - 1] = textLines[textLines.length - 1].substring(0, itemTextOffset);
655 }
Blink Reformat4c46d092018-04-07 15:32:37656 }
657
Sigurd Schneidercd3f4292020-10-09 08:56:31658 const startProviderElement = this._providerElement(startSelection.item);
659 const startSelectionElement = startProviderElement && startProviderElement.element();
660 if (startSelectionElement && startSelection.node && startSelection.node.isSelfOrDescendant(startSelectionElement)) {
Blink Reformat4c46d092018-04-07 15:32:37661 const itemTextOffset = this._textOffsetInNode(startSelectionElement, startSelection.node, startSelection.offset);
662 textLines[0] = textLines[0].substring(itemTextOffset);
663 }
664
665 return textLines.join('\n');
666 }
667
668 /**
669 * @param {!Element} itemElement
670 * @param {!Node} selectionNode
671 * @param {number} offset
672 * @return {number}
673 */
674 _textOffsetInNode(itemElement, selectionNode, offset) {
675 // If the selectionNode is not a TextNode, we may need to convert a child offset into a character offset.
Sigurd Schneidercd3f4292020-10-09 08:56:31676 const textContentLength = selectionNode.textContent ? selectionNode.textContent.length : 0;
Blink Reformat4c46d092018-04-07 15:32:37677 if (selectionNode.nodeType !== Node.TEXT_NODE) {
678 if (offset < selectionNode.childNodes.length) {
679 selectionNode = /** @type {!Node} */ (selectionNode.childNodes.item(offset));
680 offset = 0;
681 } else {
Sigurd Schneidercd3f4292020-10-09 08:56:31682 offset = textContentLength;
Blink Reformat4c46d092018-04-07 15:32:37683 }
684 }
685
686 let chars = 0;
Sigurd Schneidercd3f4292020-10-09 08:56:31687 /** @type {?Node} */
Blink Reformat4c46d092018-04-07 15:32:37688 let node = itemElement;
689 while ((node = node.traverseNextNode(itemElement)) && node !== selectionNode) {
Sigurd Schneidercd3f4292020-10-09 08:56:31690 if (node.nodeType !== Node.TEXT_NODE ||
691 (node.parentElement &&
692 (node.parentElement.nodeName === 'STYLE' || node.parentElement.nodeName === 'SCRIPT'))) {
Blink Reformat4c46d092018-04-07 15:32:37693 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34694 }
Tim van der Lippe9b2f8712020-02-12 17:46:22695 chars += Components.Linkifier.Linkifier.untruncatedNodeText(node).length;
Blink Reformat4c46d092018-04-07 15:32:37696 }
697 // If the selected node text was truncated, treat any non-zero offset as the full length.
Tim van der Lippe9b2f8712020-02-12 17:46:22698 const untruncatedContainerLength = Components.Linkifier.Linkifier.untruncatedNodeText(selectionNode).length;
Sigurd Schneidercd3f4292020-10-09 08:56:31699 if (offset > 0 && untruncatedContainerLength !== textContentLength) {
Blink Reformat4c46d092018-04-07 15:32:37700 offset = untruncatedContainerLength;
Tim van der Lippe1d6e57a2019-09-30 11:55:34701 }
Blink Reformat4c46d092018-04-07 15:32:37702 return chars + offset;
703 }
704
705 /**
706 * @param {!Event} event
707 */
708 _onScroll(event) {
709 this.refresh();
710 }
711
712 /**
713 * @return {number}
714 */
715 firstVisibleIndex() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34716 if (!this._cumulativeHeights.length) {
Blink Reformat4c46d092018-04-07 15:32:37717 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34718 }
Blink Reformat4c46d092018-04-07 15:32:37719 this._rebuildCumulativeHeightsIfNeeded();
720 return this._cumulativeHeights.lowerBound(this.element.scrollTop + 1);
721 }
722
723 /**
724 * @return {number}
725 */
726 lastVisibleIndex() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34727 if (!this._cumulativeHeights.length) {
Blink Reformat4c46d092018-04-07 15:32:37728 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34729 }
Blink Reformat4c46d092018-04-07 15:32:37730 this._rebuildCumulativeHeightsIfNeeded();
731 const scrollBottom = this.element.scrollTop + this.element.clientHeight;
732 const right = this._itemCount - 1;
733 return this._cumulativeHeights.lowerBound(scrollBottom, undefined, undefined, right);
734 }
735
736 /**
Sigurd Schneidercd3f4292020-10-09 08:56:31737 * @param {number} index
Sigurd Schneider53f33522020-10-08 15:00:49738 * @return {?HTMLElement}
Blink Reformat4c46d092018-04-07 15:32:37739 */
740 renderedElementAt(index) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34741 if (index === -1 || index < this._firstActiveIndex || index > this._lastActiveIndex) {
Blink Reformat4c46d092018-04-07 15:32:37742 return null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34743 }
Blink Reformat4c46d092018-04-07 15:32:37744 return this._renderedItems[index - this._firstActiveIndex].element();
745 }
746
747 /**
748 * @param {number} index
749 * @param {boolean=} makeLast
750 */
751 scrollItemIntoView(index, makeLast) {
752 const firstVisibleIndex = this.firstVisibleIndex();
753 const lastVisibleIndex = this.lastVisibleIndex();
Tim van der Lippe1d6e57a2019-09-30 11:55:34754 if (index > firstVisibleIndex && index < lastVisibleIndex) {
Blink Reformat4c46d092018-04-07 15:32:37755 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34756 }
Erik Luo4b002322018-07-30 21:23:31757 // If the prompt is visible, then the last item must be fully on screen.
Tim van der Lippe1d6e57a2019-09-30 11:55:34758 if (index === lastVisibleIndex &&
759 this._cumulativeHeights[index] <= this.element.scrollTop + this._visibleHeight()) {
Erik Luo4b002322018-07-30 21:23:31760 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34761 }
762 if (makeLast) {
Blink Reformat4c46d092018-04-07 15:32:37763 this.forceScrollItemToBeLast(index);
Tim van der Lippe1d6e57a2019-09-30 11:55:34764 } else if (index <= firstVisibleIndex) {
Blink Reformat4c46d092018-04-07 15:32:37765 this.forceScrollItemToBeFirst(index);
Tim van der Lippe1d6e57a2019-09-30 11:55:34766 } else if (index >= lastVisibleIndex) {
Blink Reformat4c46d092018-04-07 15:32:37767 this.forceScrollItemToBeLast(index);
Tim van der Lippe1d6e57a2019-09-30 11:55:34768 }
Blink Reformat4c46d092018-04-07 15:32:37769 }
770
771 /**
772 * @param {number} index
773 */
774 forceScrollItemToBeFirst(index) {
775 console.assert(index >= 0 && index < this._itemCount, 'Cannot scroll item at invalid index');
776 this.setStickToBottom(false);
777 this._rebuildCumulativeHeightsIfNeeded();
778 this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0;
Sigurd Schneider7393b322020-10-05 14:25:40779 if (UI.UIUtils.isScrolledToBottom(this.element)) {
Blink Reformat4c46d092018-04-07 15:32:37780 this.setStickToBottom(true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34781 }
Blink Reformat4c46d092018-04-07 15:32:37782 this.refresh();
Erik Luo4b002322018-07-30 21:23:31783 // After refresh, the item is in DOM, but may not be visible (items above were larger than expected).
Sigurd Schneidercd3f4292020-10-09 08:56:31784 const renderedElement = this.renderedElementAt(index);
785 if (renderedElement) {
786 renderedElement.scrollIntoView(true /* alignTop */);
787 }
Blink Reformat4c46d092018-04-07 15:32:37788 }
789
790 /**
791 * @param {number} index
792 */
793 forceScrollItemToBeLast(index) {
794 console.assert(index >= 0 && index < this._itemCount, 'Cannot scroll item at invalid index');
795 this.setStickToBottom(false);
796 this._rebuildCumulativeHeightsIfNeeded();
797 this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeight();
Sigurd Schneider7393b322020-10-05 14:25:40798 if (UI.UIUtils.isScrolledToBottom(this.element)) {
Blink Reformat4c46d092018-04-07 15:32:37799 this.setStickToBottom(true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34800 }
Blink Reformat4c46d092018-04-07 15:32:37801 this.refresh();
Erik Luo4b002322018-07-30 21:23:31802 // After refresh, the item is in DOM, but may not be visible (items above were larger than expected).
Sigurd Schneidercd3f4292020-10-09 08:56:31803 const renderedElement = this.renderedElementAt(index);
804 if (renderedElement) {
805 renderedElement.scrollIntoView(false /* alignTop */);
806 }
Blink Reformat4c46d092018-04-07 15:32:37807 }
808
809 /**
810 * @return {number}
811 */
812 _visibleHeight() {
813 // Use offsetHeight instead of clientHeight to avoid being affected by horizontal scroll.
814 return this.element.offsetHeight;
815 }
Paul Lewisbf7aa3c2019-11-20 17:03:38816}
Blink Reformat4c46d092018-04-07 15:32:37817
818/**
819 * @interface
820 */
Tim van der Lippeeaacb722020-01-10 12:16:00821export class ConsoleViewportProvider {
Blink Reformat4c46d092018-04-07 15:32:37822 /**
823 * @param {number} index
824 * @return {number}
825 */
826 fastHeight(index) {
827 return 0;
Paul Lewisbf7aa3c2019-11-20 17:03:38828 }
Blink Reformat4c46d092018-04-07 15:32:37829
830 /**
831 * @return {number}
832 */
833 itemCount() {
834 return 0;
Paul Lewisbf7aa3c2019-11-20 17:03:38835 }
Blink Reformat4c46d092018-04-07 15:32:37836
837 /**
838 * @return {number}
839 */
840 minimumRowHeight() {
841 return 0;
Paul Lewisbf7aa3c2019-11-20 17:03:38842 }
Blink Reformat4c46d092018-04-07 15:32:37843
844 /**
845 * @param {number} index
Tim van der Lippeeaacb722020-01-10 12:16:00846 * @return {?ConsoleViewportElement}
Blink Reformat4c46d092018-04-07 15:32:37847 */
848 itemElement(index) {
849 return null;
850 }
Paul Lewisbf7aa3c2019-11-20 17:03:38851}
Blink Reformat4c46d092018-04-07 15:32:37852
853/**
854 * @interface
855 */
Paul Lewisbf7aa3c2019-11-20 17:03:38856export class ConsoleViewportElement {
857 willHide() {
858 }
Blink Reformat4c46d092018-04-07 15:32:37859
Paul Lewisbf7aa3c2019-11-20 17:03:38860 wasShown() {
861 }
Blink Reformat4c46d092018-04-07 15:32:37862
863 /**
Sigurd Schneider53f33522020-10-08 15:00:49864 * @return {!HTMLElement}
Blink Reformat4c46d092018-04-07 15:32:37865 */
Paul Lewisbf7aa3c2019-11-20 17:03:38866 element() {
Sigurd Schneider53f33522020-10-08 15:00:49867 throw new Error('Unimplemented method');
868 }
869
870 focusLastChildOrSelf() {
Paul Lewisbf7aa3c2019-11-20 17:03:38871 }
872}