Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(593)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js

Issue 2392963004: DevTools: Display SuggestBox with a Viewport control (Closed)
Patch Set: Lazy create elements Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/ui/suggestBox.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved. 2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are 5 * modification, are permitted provided that the following conditions are
6 * met: 6 * met:
7 * 7 *
8 * * Redistributions of source code must retain the above copyright 8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer. 9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above 10 * * Redistributions in binary form must reproduce the above
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 applySuggestion: function(suggestion, isIntermediateSuggestion) { }, 43 applySuggestion: function(suggestion, isIntermediateSuggestion) { },
44 44
45 /** 45 /**
46 * acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false. 46 * acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false.
47 */ 47 */
48 acceptSuggestion: function() { }, 48 acceptSuggestion: function() { },
49 } 49 }
50 50
51 /** 51 /**
52 * @constructor 52 * @constructor
53 * @implements {WebInspector.StaticViewportControl.Provider}
53 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate 54 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate
54 * @param {number=} maxItemsHeight 55 * @param {number=} maxItemsHeight
55 * @param {boolean=} captureEnter 56 * @param {boolean=} captureEnter
56 */ 57 */
57 WebInspector.SuggestBox = function(suggestBoxDelegate, maxItemsHeight, captureEn ter) 58 WebInspector.SuggestBox = function(suggestBoxDelegate, maxItemsHeight, captureEn ter)
58 { 59 {
59 this._suggestBoxDelegate = suggestBoxDelegate; 60 this._suggestBoxDelegate = suggestBoxDelegate;
60 this._length = 0; 61 this._length = 0;
61 this._selectedIndex = -1; 62 this._selectedIndex = -1;
62 this._selectedElement = null; 63 this._selectedElement = null;
63 this._maxItemsHeight = maxItemsHeight; 64 this._maxItemsHeight = maxItemsHeight;
64 this._maybeHideBound = this._maybeHide.bind(this); 65 this._maybeHideBound = this._maybeHide.bind(this);
65 this._container = createElementWithClass("div", "suggest-box-container"); 66 this._container = createElementWithClass("div", "suggest-box-container");
66 this._element = this._container.createChild("div", "suggest-box"); 67 this._viewport = new WebInspector.StaticViewportControl(this);
68 this._element = this._viewport.element;
69 this._element.classList.add("suggest-box");
70 this._container.appendChild(this._element);
67 this._element.addEventListener("mousedown", this._onBoxMouseDown.bind(this), true); 71 this._element.addEventListener("mousedown", this._onBoxMouseDown.bind(this), true);
68 this._detailsPopup = this._container.createChild("div", "suggest-box details -popup monospace"); 72 this._detailsPopup = this._container.createChild("div", "suggest-box details -popup monospace");
69 this._detailsPopup.classList.add("hidden"); 73 this._detailsPopup.classList.add("hidden");
70 this._asyncDetailsCallback = null; 74 this._asyncDetailsCallback = null;
71 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */ 75 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */
72 this._asyncDetailsPromises = new Map(); 76 this._asyncDetailsPromises = new Map();
73 this._userInteracted = false; 77 this._userInteracted = false;
74 this._captureEnter = captureEnter; 78 this._captureEnter = captureEnter;
79 /** @type {!Array<!Element>} */
80 this._elementList = [];
81 this._rowHeight = 17;
82 this._viewportWidth = "100vw";
83 this._hasScrollBars = false;
84 this._userEnteredText = "";
85 /** @type {!WebInspector.SuggestBox.Suggestions} */
86 this._items = [];
75 } 87 }
76 88
77 /** 89 /**
78 * @typedef {!Array.<{title: string, className: (string|undefined)}>} 90 * @typedef {!Array.<{title: string, className: (string|undefined)}>}
79 */ 91 */
80 WebInspector.SuggestBox.Suggestions; 92 WebInspector.SuggestBox.Suggestions;
81 93
82 WebInspector.SuggestBox.prototype = { 94 WebInspector.SuggestBox.prototype = {
83 /** 95 /**
84 * @return {boolean} 96 * @return {boolean}
(...skipping 10 matching lines...) Expand all
95 { 107 {
96 this._updateBoxPosition(anchorBox); 108 this._updateBoxPosition(anchorBox);
97 }, 109 },
98 110
99 /** 111 /**
100 * @param {!AnchorBox} anchorBox 112 * @param {!AnchorBox} anchorBox
101 */ 113 */
102 _updateBoxPosition: function(anchorBox) 114 _updateBoxPosition: function(anchorBox)
103 { 115 {
104 console.assert(this._overlay); 116 console.assert(this._overlay);
105 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox)) 117 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this ._lastItemCount === this.itemCount())
106 return; 118 return;
119 this._lastItemCount = this.itemCount();
107 this._lastAnchorBox = anchorBox; 120 this._lastAnchorBox = anchorBox;
108 121
109 // Position relative to main DevTools element. 122 // Position relative to main DevTools element.
110 var container = WebInspector.Dialog.modalHostView().element; 123 var container = WebInspector.Dialog.modalHostView().element;
111 anchorBox = anchorBox.relativeToElement(container); 124 anchorBox = anchorBox.relativeToElement(container);
112 var totalHeight = container.offsetHeight; 125 var totalHeight = container.offsetHeight;
113 var aboveHeight = anchorBox.y; 126 var aboveHeight = anchorBox.y;
114 var underHeight = totalHeight - anchorBox.y - anchorBox.height; 127 var underHeight = totalHeight - anchorBox.y - anchorBox.height;
115 128
116 this._overlay.setLeftOffset(anchorBox.x); 129 this._overlay.setLeftOffset(anchorBox.x);
117 130
118 var under = underHeight >= aboveHeight; 131 var under = underHeight >= aboveHeight;
119 if (under) 132 if (under)
120 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true ); 133 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true );
121 else 134 else
122 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false); 135 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false);
123 136
124 /** const */ var rowHeight = 17; 137 var spacer = 6;
125 /** const */ var spacer = 6; 138 var maxHeight = this._maxItemsHeight ? this._maxItemsHeight * this._rowH eight : Math.max(underHeight, aboveHeight) - spacer;
126 var maxHeight = this._maxItemsHeight ? this._maxItemsHeight * rowHeight : Math.max(underHeight, aboveHeight) - spacer; 139 var height = this._rowHeight * this._items.length;
127 this._element.style.maxHeight = maxHeight + "px"; 140 this._hasScrollBars = height > maxHeight;
lushnikov 2016/10/13 23:45:53 _hasVerticalScroll
einbinder 2016/10/15 00:09:15 Done.
141 this._element.style.height = Math.min(maxHeight, height) + "px";
142 },
143
144 _updateWidth: function()
145 {
146 if (this._hasScrollBars) {
147 this._element.style.width = "100vw";
148 return;
149 }
150 // If there are no scrollbars, set the width to the width of the largest row.
151 var maxIndex = 0;
einbinder 2016/10/11 00:18:16 This might be the wrong element if some have histo
152 for (var i = 0; i < this._items.length; i++) {
153 if (this._items[i].title.length > this._items[maxIndex].title.length )
154 maxIndex = i;
155 }
156 var element = this.itemElement(maxIndex);
lushnikov 2016/10/13 23:45:53 /** @type {!Element} */
einbinder 2016/10/15 00:09:15 Done.
157 if (element)
158 this._element.style.width = WebInspector.measurePreferredSize(elemen t, this._element).width + "px";
128 }, 159 },
129 160
130 /** 161 /**
131 * @param {!Event} event 162 * @param {!Event} event
132 */ 163 */
133 _onBoxMouseDown: function(event) 164 _onBoxMouseDown: function(event)
134 { 165 {
135 if (this._hideTimeoutId) { 166 if (this._hideTimeoutId) {
136 window.clearTimeout(this._hideTimeoutId); 167 window.clearTimeout(this._hideTimeoutId);
137 delete this._hideTimeoutId; 168 delete this._hideTimeoutId;
(...skipping 12 matching lines...) Expand all
150 * @suppressGlobalPropertiesCheck 181 * @suppressGlobalPropertiesCheck
151 */ 182 */
152 _show: function() 183 _show: function()
153 { 184 {
154 if (this.visible()) 185 if (this.visible())
155 return; 186 return;
156 this._bodyElement = document.body; 187 this._bodyElement = document.body;
157 this._bodyElement.addEventListener("mousedown", this._maybeHideBound, tr ue); 188 this._bodyElement.addEventListener("mousedown", this._maybeHideBound, tr ue);
158 this._overlay = new WebInspector.SuggestBox.Overlay(); 189 this._overlay = new WebInspector.SuggestBox.Overlay();
159 this._overlay.setContentElement(this._container); 190 this._overlay.setContentElement(this._container);
191 var measuringElement = this._createItemElement("1", "12");
192 this._viewport.element.appendChild(measuringElement);
193 this._rowHeight = measuringElement.getBoundingClientRect().height;
194 measuringElement.remove();
160 }, 195 },
161 196
162 hide: function() 197 hide: function()
163 { 198 {
164 if (!this.visible()) 199 if (!this.visible())
165 return; 200 return;
166 201
167 this._userInteracted = false; 202 this._userInteracted = false;
168 this._bodyElement.removeEventListener("mousedown", this._maybeHideBound, true); 203 this._bodyElement.removeEventListener("mousedown", this._maybeHideBound, true);
169 delete this._bodyElement; 204 delete this._bodyElement;
170 this._container.remove(); 205 this._container.remove();
171 this._overlay.dispose(); 206 this._overlay.dispose();
172 delete this._overlay; 207 delete this._overlay;
173 delete this._selectedElement; 208 delete this._selectedElement;
174 this._selectedIndex = -1; 209 this._selectedIndex = -1;
175 delete this._lastAnchorBox; 210 delete this._lastAnchorBox;
176 }, 211 },
177 212
178 removeFromElement: function() 213 removeFromElement: function()
179 { 214 {
180 this.hide(); 215 this.hide();
181 }, 216 },
182 217
183 /** 218 /**
184 * @param {boolean=} isIntermediateSuggestion 219 * @param {boolean=} isIntermediateSuggestion
220 * @return {boolean}
185 */ 221 */
186 _applySuggestion: function(isIntermediateSuggestion) 222 _applySuggestion: function(isIntermediateSuggestion)
187 { 223 {
188 if (this._onlyCompletion) { 224 if (this._onlyCompletion) {
189 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isInt ermediateSuggestion); 225 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isInt ermediateSuggestion);
190 return true; 226 return true;
191 } 227 }
192 228
193 if (!this.visible() || !this._selectedElement) 229 if (!this.visible() || !this._selectedElement)
194 return false; 230 return false;
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
249 _onItemMouseDown: function(event) 285 _onItemMouseDown: function(event)
250 { 286 {
251 this._selectedElement = event.currentTarget; 287 this._selectedElement = event.currentTarget;
252 this.acceptSuggestion(); 288 this.acceptSuggestion();
253 event.consume(true); 289 event.consume(true);
254 }, 290 },
255 291
256 /** 292 /**
257 * @param {string} prefix 293 * @param {string} prefix
258 * @param {string} text 294 * @param {string} text
259 * @param {string|undefined} className 295 * @param {string=} className
260 * @param {number} index 296 * @return {!Element}
261 */ 297 */
262 _createItemElement: function(prefix, text, className, index) 298 _createItemElement: function(prefix, text, className)
263 { 299 {
264 var element = createElementWithClass("div", "suggest-box-content-item so urce-code " + (className || "")); 300 var element = createElementWithClass("div", "suggest-box-content-item so urce-code " + (className || ""));
265 element.tabIndex = -1; 301 element.tabIndex = -1;
266 if (prefix && prefix.length && !text.indexOf(prefix)) { 302 if (prefix && prefix.length && !text.indexOf(prefix)) {
267 element.createChild("span", "prefix").textContent = prefix; 303 element.createChild("span", "prefix").textContent = prefix;
268 element.createChild("span", "suffix").textContent = text.substring(p refix.length).trimEnd(50); 304 element.createChild("span", "suffix").textContent = text.substring(p refix.length).trimEnd(50);
269 } else { 305 } else {
270 element.createChild("span", "suffix").textContent = text.trimEnd(50) ; 306 element.createChild("span", "suffix").textContent = text.trimEnd(50) ;
271 } 307 }
272 element.__fullValue = text; 308 element.__fullValue = text;
273 element.createChild("span", "spacer"); 309 element.createChild("span", "spacer");
274 element.addEventListener("mousedown", this._onItemMouseDown.bind(this), false); 310 element.addEventListener("mousedown", this._onItemMouseDown.bind(this), false);
275 return element; 311 return element;
276 }, 312 },
277 313
278 /** 314 /**
279 * @param {!WebInspector.SuggestBox.Suggestions} items 315 * @param {!WebInspector.SuggestBox.Suggestions} items
280 * @param {string} userEnteredText 316 * @param {string} userEnteredText
281 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails 317 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
282 */ 318 */
283 _updateItems: function(items, userEnteredText, asyncDetails) 319 _updateItems: function(items, userEnteredText, asyncDetails)
284 { 320 {
285 this._length = items.length; 321 this._length = items.length;
286 this._asyncDetailsPromises.clear(); 322 this._asyncDetailsPromises.clear();
287 this._asyncDetailsCallback = asyncDetails; 323 this._asyncDetailsCallback = asyncDetails;
288 this._element.removeChildren(); 324 this._elementList = [];
289 delete this._selectedElement; 325 delete this._selectedElement;
290 326
291 for (var i = 0; i < items.length; ++i) { 327 this._userEnteredText = userEnteredText;
292 var item = items[i]; 328 this._items = items;
293 var currentItemElement = this._createItemElement(userEnteredText, it em.title, item.className, i);
294 this._element.appendChild(currentItemElement);
295 }
296 }, 329 },
297 330
298 /** 331 /**
299 * @param {number} index 332 * @param {number} index
300 * @return {!Promise<?{detail: string, description: string}>} 333 * @return {!Promise<?{detail: string, description: string}>}
301 */ 334 */
302 _asyncDetails: function(index) 335 _asyncDetails: function(index)
303 { 336 {
304 if (!this._asyncDetailsCallback) 337 if (!this._asyncDetailsCallback)
305 return Promise.resolve(/** @type {?{description: string, detail: str ing}} */(null)); 338 return Promise.resolve(/** @type {?{description: string, detail: str ing}} */(null));
(...skipping 21 matching lines...) Expand all
327 */ 360 */
328 _selectItem: function(index, scrollIntoView) 361 _selectItem: function(index, scrollIntoView)
329 { 362 {
330 if (this._selectedElement) 363 if (this._selectedElement)
331 this._selectedElement.classList.remove("selected"); 364 this._selectedElement.classList.remove("selected");
332 365
333 this._selectedIndex = index; 366 this._selectedIndex = index;
334 if (index < 0) 367 if (index < 0)
335 return; 368 return;
336 369
337 this._selectedElement = this._element.children[index]; 370 this._selectedElement = this.itemElement(index);
338 this._selectedElement.classList.add("selected"); 371 this._selectedElement.classList.add("selected");
339 this._detailsPopup.classList.add("hidden"); 372 this._detailsPopup.classList.add("hidden");
340 var elem = this._selectedElement; 373 var elem = this._selectedElement;
341 this._asyncDetails(index).then(showDetails.bind(this), function(){}); 374 this._asyncDetails(index).then(showDetails.bind(this), function(){});
342 375
343 if (scrollIntoView) 376 if (scrollIntoView)
344 this._selectedElement.scrollIntoViewIfNeeded(false); 377 this._viewport.scrollItemIntoView(index);
345 378
346 /** 379 /**
347 * @param {?{detail: string, description: string}} details 380 * @param {?{detail: string, description: string}} details
348 * @this {WebInspector.SuggestBox} 381 * @this {WebInspector.SuggestBox}
349 */ 382 */
350 function showDetails(details) 383 function showDetails(details)
351 { 384 {
352 if (elem === this._selectedElement) 385 if (elem === this._selectedElement)
353 this._showDetailsPopup(details); 386 this._showDetailsPopup(details);
354 } 387 }
355 }, 388 },
356 389
357 /** 390 /**
358 * @param {!WebInspector.SuggestBox.Suggestions} completions 391 * @param {!WebInspector.SuggestBox.Suggestions} completions
359 * @param {boolean} canShowForSingleItem 392 * @param {boolean} canShowForSingleItem
360 * @param {string} userEnteredText 393 * @param {string} userEnteredText
394 * @return {boolean}
361 */ 395 */
362 _canShowBox: function(completions, canShowForSingleItem, userEnteredText) 396 _canShowBox: function(completions, canShowForSingleItem, userEnteredText)
363 { 397 {
364 if (!completions || !completions.length) 398 if (!completions || !completions.length)
365 return false; 399 return false;
366 400
367 if (completions.length > 1) 401 if (completions.length > 1)
368 return true; 402 return true;
369 403
370 // Do not show a single suggestion if it is the same as user-entered pre fix, even if allowed to show single-item suggest boxes. 404 // Do not show a single suggestion if it is the same as user-entered pre fix, even if allowed to show single-item suggest boxes.
371 return canShowForSingleItem && completions[0].title !== userEnteredText; 405 return canShowForSingleItem && completions[0].title !== userEnteredText;
372 }, 406 },
373 407
374 _ensureRowCountPerViewport: function() 408 _ensureRowCountPerViewport: function()
375 { 409 {
376 if (this._rowCountPerViewport) 410 if (this._rowCountPerViewport)
377 return; 411 return;
378 if (!this._element.firstChild) 412 if (!this._items.length)
379 return; 413 return;
380 414
381 this._rowCountPerViewport = Math.floor(this._element.offsetHeight / this ._element.firstChild.offsetHeight); 415 this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRe ct().height / this._rowHeight);
382 }, 416 },
383 417
384 /** 418 /**
385 * @param {!AnchorBox} anchorBox 419 * @param {!AnchorBox} anchorBox
386 * @param {!WebInspector.SuggestBox.Suggestions} completions 420 * @param {!WebInspector.SuggestBox.Suggestions} completions
387 * @param {number} selectedIndex 421 * @param {number} selectedIndex
388 * @param {boolean} canShowForSingleItem 422 * @param {boolean} canShowForSingleItem
389 * @param {string} userEnteredText 423 * @param {string} userEnteredText
390 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails 424 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
391 */ 425 */
392 updateSuggestions: function(anchorBox, completions, selectedIndex, canShowFo rSingleItem, userEnteredText, asyncDetails) 426 updateSuggestions: function(anchorBox, completions, selectedIndex, canShowFo rSingleItem, userEnteredText, asyncDetails)
393 { 427 {
394 delete this._onlyCompletion; 428 delete this._onlyCompletion;
395 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText) ) { 429 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText) ) {
396 this._updateItems(completions, userEnteredText, asyncDetails); 430 this._updateItems(completions, userEnteredText, asyncDetails);
397 this._show(); 431 this._show();
398 this._updateBoxPosition(anchorBox); 432 this._updateBoxPosition(anchorBox);
433 this._updateWidth();
434 this._viewport.refresh();
399 this._selectItem(selectedIndex, selectedIndex > 0); 435 this._selectItem(selectedIndex, selectedIndex > 0);
400 delete this._rowCountPerViewport; 436 delete this._rowCountPerViewport;
401 } else { 437 } else {
402 if (completions.length === 1) 438 if (completions.length === 1)
403 this._onlyCompletion = completions[0].title; 439 this._onlyCompletion = completions[0].title;
404 this.hide(); 440 this.hide();
405 } 441 }
406 }, 442 },
407 443
408 /** 444 /**
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
467 { 503 {
468 if (!this._userInteracted && this._captureEnter) 504 if (!this._userInteracted && this._captureEnter)
469 return false; 505 return false;
470 506
471 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion; 507 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion;
472 this.acceptSuggestion(); 508 this.acceptSuggestion();
473 509
474 // Report the event as non-handled if there is no selected item, 510 // Report the event as non-handled if there is no selected item,
475 // to commit the input or handle it otherwise. 511 // to commit the input or handle it otherwise.
476 return hasSelectedItem; 512 return hasSelectedItem;
513 },
514
515 /**
516 * @override
517 * @param {number} index
518 * @return {number}
519 */
520 fastItemHeight: function(index)
521 {
522 return this._rowHeight;
523 },
524
525 /**
526 * @override
527 * @return {number}
528 */
529 itemCount: function()
530 {
531 return this._items.length;
532 },
533
534 /**
535 * @override
536 * @param {number} index
537 * @return {?Element}
538 */
539 itemElement: function(index)
540 {
541 if (!this._elementList[index])
542 this._elementList[index] = this._createItemElement(this._userEntered Text, this._items[index].title, this._items[index].className);
543 return this._elementList[index];
477 } 544 }
478 } 545 }
479 546
480 /** 547 /**
481 * @constructor 548 * @constructor
482 * // FIXME: make SuggestBox work for multiple documents. 549 * // FIXME: make SuggestBox work for multiple documents.
483 * @suppressGlobalPropertiesCheck 550 * @suppressGlobalPropertiesCheck
484 */ 551 */
485 WebInspector.SuggestBox.Overlay = function() 552 WebInspector.SuggestBox.Overlay = function()
486 { 553 {
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
537 this.element.style.top = containerBox.y + "px"; 604 this.element.style.top = containerBox.y + "px";
538 this.element.style.height = containerBox.height + "px"; 605 this.element.style.height = containerBox.height + "px";
539 this.element.style.width = containerBox.width + "px"; 606 this.element.style.width = containerBox.width + "px";
540 }, 607 },
541 608
542 dispose: function() 609 dispose: function()
543 { 610 {
544 this.element.remove(); 611 this.element.remove();
545 } 612 }
546 } 613 }
OLDNEW
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/ui/suggestBox.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698