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

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js

Issue 2343773002: DevTools: Autocomplete class names in ClassesPaneWidget (Closed)
Patch Set: Created 4 years, 3 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
OLDNEW
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @constructor 6 * @constructor
7 * @extends {WebInspector.Widget} 7 * @extends {WebInspector.Widget}
8 */ 8 */
9 WebInspector.ClassesPaneWidget = function() 9 WebInspector.ClassesPaneWidget = function()
10 { 10 {
11 WebInspector.Widget.call(this); 11 WebInspector.Widget.call(this);
12 this.element.className = "styles-element-classes-pane"; 12 this.element.className = "styles-element-classes-pane";
13 var container = this.element.createChild("div", "title-container"); 13 var container = this.element.createChild("div", "title-container");
14 this._input = container.createChild("input", "new-class-input monospace"); 14 this._input = container.createChild("div", "new-class-input monospace");
15 this._input.placeholder = WebInspector.UIString("Add new class"); 15 this._input.setAttribute("placeholder" , WebInspector.UIString("Add new clas s"));
16 this._input.addEventListener("keydown", this._onKeyDown.bind(this), false);
17 this.setDefaultFocusedElement(this._input); 16 this.setDefaultFocusedElement(this._input);
17
18 this._classesContainer = this.element.createChild("div", "source-code"); 18 this._classesContainer = this.element.createChild("div", "source-code");
19 this._classesContainer.classList.add("styles-element-classes-container"); 19 this._classesContainer.classList.add("styles-element-classes-container");
20 this._prompt = new WebInspector.ClassesPaneWidget.ClassNamePrompt();
21 this._prompt.setAutocompletionTimeout(0);
22 this._prompt.renderAsBlock();
23
24 this._frameClassManagers = new Map();
25 this._selectedNode = null;
26 var proxyElement = this._prompt.attach(this._input);
27 proxyElement.addEventListener("keydown", this._onKeyDown.bind(this), false);
20 28
21 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec tor.DOMModel.Events.DOMMutated, this._onDOMMutated, this); 29 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec tor.DOMModel.Events.DOMMutated, this._onDOMMutated, this);
30 WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspec tor.CSSModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
31 WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspec tor.CSSModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
32 WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspec tor.CSSModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
33 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec tor.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
34 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event s.MainFrameNavigated, this._mainFrameNavigated, this);
35
22 /** @type {!Set<!WebInspector.DOMNode>} */ 36 /** @type {!Set<!WebInspector.DOMNode>} */
23 this._mutatingNodes = new Set(); 37 this._mutatingNodes = new Set();
24 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._upd ate, this); 38 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._upd ate, this);
25 } 39 }
26 40
27 WebInspector.ClassesPaneWidget._classesSymbol = Symbol("WebInspector.ClassesPane Widget._classesSymbol"); 41 WebInspector.ClassesPaneWidget._classesSymbol = Symbol("WebInspector.ClassesPane Widget._classesSymbol");
28 42
29 WebInspector.ClassesPaneWidget.prototype = { 43 WebInspector.ClassesPaneWidget.prototype = {
30 /** 44 /**
31 * @param {!Event} event 45 * @param {!Event} event
32 */ 46 */
33 _onKeyDown: function(event) 47 _onKeyDown: function(event)
34 { 48 {
35 var text = event.target.value; 49 var text = event.target.innerText;
36 if (isEscKey(event)) { 50 if (isEscKey(event)) {
37 event.target.value = ""; 51 this._hidePrompt();
52 event.target.innerText = "";
38 if (!text.isWhitespace()) 53 if (!text.isWhitespace())
39 event.consume(true); 54 event.consume(true);
40 return; 55 return;
41 } 56 }
42 57
43 if (!isEnterKey(event)) 58 if (!isEnterKey(event))
44 return; 59 return;
45 var node = WebInspector.context.flavor(WebInspector.DOMNode); 60 var node = WebInspector.context.flavor(WebInspector.DOMNode);
46 if (!node) 61 if (!node)
47 return; 62 return;
48 63 this._hidePrompt();
49 event.target.value = ""; 64 event.target.innerText = "";
50 var classNames = text.split(/[.,\s]/); 65 var classNames = text.split(/[.,\s]/);
51 for (var className of classNames) { 66 for (var className of classNames) {
52 var className = className.trim(); 67 var className = className.trim();
53 if (!className.length) 68 if (!className.length)
54 continue; 69 continue;
55 this._toggleClass(node, className, true); 70 this._toggleClass(node, className, true);
56 } 71 }
57 this._installNodeClasses(node); 72 this._installNodeClasses(node);
58 this._update(); 73 this._update();
59 event.consume(true); 74 event.consume(true);
60 }, 75 },
61 76
62 /** 77 /**
63 * @param {!WebInspector.Event} event 78 * @param {!WebInspector.Event} event
64 */ 79 */
80 _documentUpdated: function(event)
81 {
82 if (event.data && this.isShowing()) {
83 var updatedDocument = /** @type {!WebInspector.DOMDocument} */ (even t.data);
84 this._getDomClasses(updatedDocument);
85 }
86 },
87
88 /**
89 * @param {!WebInspector.Event} event
90 */
91 _styleSheetAdded: function(event)
92 {
93 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.dat a);
94 this._getStylesheetClasses(header);
95 },
96
97 /**
98 * @param {!WebInspector.Event} event
99 */
100 _styleSheetChanged: function(event)
101 {
102 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.dat a);
103
104 this._removeStylesheet(header);
105 this._getStylesheetClasses(header);
106 },
107
108 /**
109 * @param {!WebInspector.CSSStyleSheetHeader} stylesheet
110 */
111 _removeStylesheet: function(stylesheet)
112 {
113 var classManager = this._frameClassManagers.get(stylesheet.frameId);
114 if (classManager)
115 classManager.removeStylesheet(stylesheet.id);
116 },
117
118 /**
119 * @param {!WebInspector.Event} event
120 */
121 _styleSheetRemoved: function(event)
122 {
123 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.dat a);
124 this._removeStylesheet(header);
125 this._updateCompletions(header.frameId);
126 },
127
128 /**
129 * @param {!WebInspector.Event} event
130 */
131 _mainFrameNavigated: function(event)
132 {
133 this._selectedNode = null;
134 this._prompt.clearCompletions();
135 this._frameClassManagers = new Map();
136 },
137
138 _hidePrompt: function()
139 {
140 this._prompt.clearAutoComplete();
141 },
142
143 /**
144 * @param {?WebInspector.DOMDocument=} inspectedDocument
145 */
146 _getDomClasses: function(inspectedDocument)
147 {
148 if (!inspectedDocument && this._selectedNode)
149 inspectedDocument = this._selectedNode.ownerDocument;
150 var frameId = inspectedDocument.frameId() || WebInspector.ResourceTreeMo del.fromTarget(inspectedDocument.target()).mainFrame.id;
151 /**
152 * @param {?Array.<string>} classNames
153 * @this {!WebInspector.ClassesPaneWidget}
154 */
155 function classNamesCallback(classNames)
156 {
157 this._getFrameClassManager(frameId).setDomClassNames(classNames);
158 this._updateCompletions(frameId);
159 }
160
161 inspectedDocument.domModel().classNamesPromise(inspectedDocument.id).the n(classNamesCallback.bind(this));
162 },
163
164
165 _getCssClasses: function()
166 {
167 var frameId = this._selectedNode.frameId();
168 var cssModel = WebInspector.CSSModel.fromTarget(this._selectedNode.targe t());
169 var headers = cssModel.allStyleSheets();
170 for (var stylesheet of cssModel.allStyleSheets())
171 if (stylesheet.frameId === frameId)
172 this._getStylesheetClasses(stylesheet);
173 },
174
175 /**
176 * @param {?PageAgent.FrameId} frameId
177 * @return {!WebInspector.ClassesPaneWidget.FrameClassManager}
178 */
179 _getFrameClassManager: function(frameId)
180 {
181 if (!this._frameClassManagers.get(frameId))
182 this._frameClassManagers.set(frameId, new WebInspector.ClassesPaneWi dget.FrameClassManager());
183
184 return this._frameClassManagers.get(frameId);
185 },
186
187 _selectedFrameChanged: function()
188 {
189 var newFrameId = this._selectedFrameId();
190 this._prompt.sourceFrameId = newFrameId;
191 this._updateCompletions(newFrameId);
192 var frameClassManager = this._getFrameClassManager(newFrameId);
193 if (this.isShowing())
194 this.getFrameClasses(frameClassManager);
195 },
196
197 /**
198 * @return {?PageAgent.FrameId}
199 */
200 _selectedFrameId: function()
201 {
202 return this._selectedNode ? this._selectedNode.frameId() || WebInspector .ResourceTreeModel.fromTarget(this._selectedNode.target()).mainFrame.id : 0;
203 },
204
205 /**
206 * @param {!WebInspector.CSSStyleSheetHeader} stylesheet
207 */
208 _getStylesheetClasses: function(stylesheet)
209 {
210 if (!this.isShowing())
211 return;
212
213 /**
214 * @param {!WebInspector.CSSStyleSheetHeader} stylsheet
215 * @param {?Array.<string>} classNames
216 * @this {!WebInspector.ClassesPaneWidget}
217 */
218 function classNamesCallback(stylsheet, classNames)
219 {
220 this._getFrameClassManager(stylsheet.frameId).addCssClassNames(styls heet.id, classNames);
221 this._updateCompletions(stylsheet.frameId);
222 }
223
224 var cssModel = stylesheet.cssModel();
225 cssModel.classNamesPromise(stylesheet.id).then(classNamesCallback.bind(t his, stylesheet));
226 },
227
228 /**
229 * @param {?PageAgent.FrameId} frameId
230 */
231 _updateCompletions: function(frameId)
232 {
233 if (frameId === this._prompt.sourceFrameId)
234 this._getFrameClassManager(frameId).getMergedList(mergedList => this ._prompt.updateCompletions(mergedList));
235 },
236
237 /**
238 * @param {!WebInspector.ClassesPaneWidget.FrameClassManager} frameClassMan ager
239 */
240 getFrameClasses: function(frameClassManager)
241 {
242 if (frameClassManager.isRequested)
243 return;
244
245 this._getDomClasses();
246 this._getCssClasses();
247
248 frameClassManager.isRequested = true;
249 },
250
251 /**
252 * @param {!WebInspector.Event} event
253 */
65 _onDOMMutated: function(event) 254 _onDOMMutated: function(event)
66 { 255 {
67 var node = /** @type {!WebInspector.DOMNode} */(event.data); 256 var node = /** @type {!WebInspector.DOMNode} */(event.data);
68 if (this._mutatingNodes.has(node)) 257 if (this._mutatingNodes.has(node))
69 return; 258 return;
70 delete node[WebInspector.ClassesPaneWidget._classesSymbol]; 259 delete node[WebInspector.ClassesPaneWidget._classesSymbol];
71 this._update(); 260 this._update();
72 }, 261 },
73 262
74 /** 263 /**
75 * @override 264 * @override
76 */ 265 */
77 wasShown: function() 266 wasShown: function()
78 { 267 {
79 this._update(); 268 this._update();
80 }, 269 },
81 270
82 _update: function() 271 _update: function()
83 { 272 {
84 if (!this.isShowing()) 273 if (!this.isShowing())
85 return; 274 return;
86
87 var node = WebInspector.context.flavor(WebInspector.DOMNode); 275 var node = WebInspector.context.flavor(WebInspector.DOMNode);
88 if (node) 276 if (node)
89 node = node.enclosingElementOrSelf(); 277 node = node.enclosingElementOrSelf();
90 278
91 this._classesContainer.removeChildren(); 279 this._classesContainer.removeChildren();
92 this._input.disabled = !node; 280 this._input.disabled = !node;
93 281
94 if (!node) 282 if (!node)
95 return; 283 return;
96 284
285 var oldFrameId = this._selectedFrameId();
286 this._selectedNode = node;
287 if (this._selectedFrameId() !== oldFrameId)
288 this._selectedFrameChanged();
97 var classes = this._nodeClasses(node); 289 var classes = this._nodeClasses(node);
98 var keys = classes.keysArray(); 290 var keys = classes.keysArray();
99 keys.sort(String.caseInsensetiveComparator); 291 keys.sort(String.caseInsensetiveComparator);
100 for (var i = 0; i < keys.length; ++i) { 292 for (var i = 0; i < keys.length; ++i) {
101 var className = keys[i]; 293 var className = keys[i];
102 var label = createCheckboxLabel(className, classes.get(className)); 294 var label = createCheckboxLabel(className, classes.get(className));
103 label.visualizeFocus = true; 295 label.visualizeFocus = true;
104 label.classList.add("monospace"); 296 label.classList.add("monospace");
105 label.checkboxElement.addEventListener("click", this._onClick.bind(t his, className), false); 297 label.checkboxElement.addEventListener("click", this._onClick.bind(t his, className), false);
106 this._classesContainer.appendChild(label); 298 this._classesContainer.appendChild(label);
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 { 370 {
179 this._mutatingNodes.delete(node); 371 this._mutatingNodes.delete(node);
180 } 372 }
181 }, 373 },
182 374
183 __proto__: WebInspector.Widget.prototype 375 __proto__: WebInspector.Widget.prototype
184 } 376 }
185 377
186 /** 378 /**
187 * @constructor 379 * @constructor
380 */
381 WebInspector.ClassesPaneWidget.FrameClassManager = function()
382 {
383 this._cssClassNames = new Map();
lushnikov 2016/09/15 17:58:31 can we avoid caching class names altogether? it sh
ahmetemirercin 2016/09/15 20:22:16 Speed of getting classnames depends on dom structu
ahmetemirercin 2016/09/16 05:08:44 Done.
384 this._domClassNames = [];
385 this._mergedList = new Set();
386 this._mergeRequired = true;
387 this.isRequested = false;
388 }
389
390 WebInspector.ClassesPaneWidget.FrameClassManager.prototype = {
391 /**
392 * @param {!CSSAgent.StyleSheetId} styleSheetId
393 * @param {?Array.<string>} classNames
394 */
395 addCssClassNames: function(styleSheetId, classNames)
396 {
397 if (!classNames)
398 return;
399
400 this._cssClassNames.set(styleSheetId, classNames);
401 this._mergedList.addAll(classNames);
402 },
403
404 /**
405 * @param {?Array.<string>} classNames
406 */
407 setDomClassNames: function(classNames)
408 {
409 if (!classNames)
410 return;
411
412 this._domClassNames = classNames;
413 this._mergedList.addAll(classNames);
414 },
415
416 /**
417 * @param {!CSSAgent.StyleSheetId} styleSheetId
418 */
419 removeStylesheet: function(styleSheetId)
420 {
421 this._cssClassNames.remove(styleSheetId);
422 this._mergeRequired = true;
423 },
424
425 /**
426 * @param {function(!Array<string>)} callback
427 */
428 getMergedList: function(callback)
429 {
430 if (this._mergeRequired)
431 this._merge();
432 callback(this._mergedList.valuesArray());
433 },
434
435 _merge: function()
436 {
437 this._mergedList = new Set();
438 if (this._domClassNames)
439 this._mergedList.addAll(this._domClassNames);
440
441 for (var styleSheetClasses of this._cssClassNames.values())
442 this._mergedList.addAll(styleSheetClasses);
443
444 this._mergeRequired = false;
445 }
446 }
447
448 /**
449 * @constructor
188 * @implements {WebInspector.ToolbarItem.Provider} 450 * @implements {WebInspector.ToolbarItem.Provider}
189 */ 451 */
190 WebInspector.ClassesPaneWidget.ButtonProvider = function() 452 WebInspector.ClassesPaneWidget.ButtonProvider = function()
191 { 453 {
192 this._button = new WebInspector.ToolbarToggle(WebInspector.UIString("Element Classes"), ""); 454 this._button = new WebInspector.ToolbarToggle(WebInspector.UIString("Element Classes"), "");
193 this._button.setText(".cls"); 455 this._button.setText(".cls");
194 this._button.element.classList.add("monospace"); 456 this._button.element.classList.add("monospace");
195 this._button.addEventListener("click", this._clicked, this); 457 this._button.addEventListener("click", this._clicked, this);
196 this._view = new WebInspector.ClassesPaneWidget(); 458 this._view = new WebInspector.ClassesPaneWidget();
197 } 459 }
198 460
199 WebInspector.ClassesPaneWidget.ButtonProvider.prototype = { 461 WebInspector.ClassesPaneWidget.ButtonProvider.prototype = {
200 _clicked: function() 462 _clicked: function()
201 { 463 {
202 WebInspector.ElementsPanel.instance().showToolbarPane(!this._view.isShow ing() ? this._view : null, this._button); 464 WebInspector.ElementsPanel.instance().showToolbarPane(!this._view.isShow ing() ? this._view : null, this._button);
203 }, 465 },
204 466
205 /** 467 /**
206 * @override 468 * @override
207 * @return {!WebInspector.ToolbarItem} 469 * @return {!WebInspector.ToolbarItem}
208 */ 470 */
209 item: function() 471 item: function()
210 { 472 {
211 return this._button; 473 return this._button;
212 } 474 }
213 } 475 }
476
477 /**
478 * @constructor
479 * @extends {WebInspector.TextPrompt}
480 */
481 WebInspector.ClassesPaneWidget.ClassNamePrompt = function()
482 {
483 WebInspector.TextPrompt.call(this, this._buildClassNameCompletions.bind(this ), " ");
484 this.setSuggestBoxEnabled(true);
485 this.disableDefaultSuggestionForEmptyInput();
486 this._classNameCompletions = [];
487 this.sourceFrameId = 0;
488 }
489
490 WebInspector.ClassesPaneWidget.ClassNamePrompt.prototype = {
491 /**
492 * @override
493 * @param {!Event} event
494 */
495 onKeyDown: function(event)
496 {
497 switch (event.key) {
498 case "Enter":
499 // Accept any available autocompletions and advance to the next fiel d.
500 if (this.autoCompleteElement && this.autoCompleteElement.textContent .length) {
501 this.acceptAutoComplete();
502 return;
503 }
504 break;
505 }
506
507 WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
508 },
509
510 /**
511 * @param {!Array.<string>} classNameList
512 */
513 updateCompletions: function(classNameList)
514 {
515 this._classNameCompletions = classNameList;
516 },
517
518 clearCompletions: function()
519 {
520 this._classNameCompletions = [];
521 },
522
523 /**
524 * @param {!Element} proxyElement
525 * @param {!Range} wordRange
526 * @param {boolean} force
527 * @param {function(!Array.<string>, number=)} completionsReadyCallback
528 */
529 _buildClassNameCompletions: function(proxyElement, wordRange, force, complet ionsReadyCallback)
530 {
531 var prefix = wordRange.toString();
532
533 if (!prefix && !force && !proxyElement.textContent.length) {
534 completionsReadyCallback([]);
535 return;
536 }
537
538 var results = this._classNameCompletions.filter((value) => value.startsW ith(prefix));
539 var selectedIndex = 0;
540 completionsReadyCallback(results, selectedIndex);
541 },
542
543 __proto__: WebInspector.TextPrompt.prototype
544 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698