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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js b/third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js
index b0bada022b10ab39a9c8379afb97d078f1c7817a..071d4cbb24afbcf07dac3becc42fc311d71a0937 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js
@@ -11,14 +11,28 @@ WebInspector.ClassesPaneWidget = function()
WebInspector.Widget.call(this);
this.element.className = "styles-element-classes-pane";
var container = this.element.createChild("div", "title-container");
- this._input = container.createChild("input", "new-class-input monospace");
- this._input.placeholder = WebInspector.UIString("Add new class");
- this._input.addEventListener("keydown", this._onKeyDown.bind(this), false);
+ this._input = container.createChild("div", "new-class-input monospace");
+ this._input.setAttribute("placeholder" , WebInspector.UIString("Add new class"));
this.setDefaultFocusedElement(this._input);
+
this._classesContainer = this.element.createChild("div", "source-code");
this._classesContainer.classList.add("styles-element-classes-container");
+ this._prompt = new WebInspector.ClassesPaneWidget.ClassNamePrompt();
+ this._prompt.setAutocompletionTimeout(0);
+ this._prompt.renderAsBlock();
+
+ this._frameClassManagers = new Map();
+ this._selectedNode = null;
+ var proxyElement = this._prompt.attach(this._input);
+ proxyElement.addEventListener("keydown", this._onKeyDown.bind(this), false);
WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.DOMMutated, this._onDOMMutated, this);
+ WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspector.CSSModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
+ WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspector.CSSModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
+ WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspector.CSSModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
+ WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
+ WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.MainFrameNavigated, this._mainFrameNavigated, this);
+
/** @type {!Set<!WebInspector.DOMNode>} */
this._mutatingNodes = new Set();
WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._update, this);
@@ -32,9 +46,10 @@ WebInspector.ClassesPaneWidget.prototype = {
*/
_onKeyDown: function(event)
{
- var text = event.target.value;
+ var text = event.target.innerText;
if (isEscKey(event)) {
- event.target.value = "";
+ this._hidePrompt();
+ event.target.innerText = "";
if (!text.isWhitespace())
event.consume(true);
return;
@@ -45,8 +60,8 @@ WebInspector.ClassesPaneWidget.prototype = {
var node = WebInspector.context.flavor(WebInspector.DOMNode);
if (!node)
return;
-
- event.target.value = "";
+ this._hidePrompt();
+ event.target.innerText = "";
var classNames = text.split(/[.,\s]/);
for (var className of classNames) {
var className = className.trim();
@@ -62,6 +77,180 @@ WebInspector.ClassesPaneWidget.prototype = {
/**
* @param {!WebInspector.Event} event
*/
+ _documentUpdated: function(event)
+ {
+ if (event.data && this.isShowing()) {
+ var updatedDocument = /** @type {!WebInspector.DOMDocument} */ (event.data);
+ this._getDomClasses(updatedDocument);
+ }
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _styleSheetAdded: function(event)
+ {
+ var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data);
+ this._getStylesheetClasses(header);
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _styleSheetChanged: function(event)
+ {
+ var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data);
+
+ this._removeStylesheet(header);
+ this._getStylesheetClasses(header);
+ },
+
+ /**
+ * @param {!WebInspector.CSSStyleSheetHeader} stylesheet
+ */
+ _removeStylesheet: function(stylesheet)
+ {
+ var classManager = this._frameClassManagers.get(stylesheet.frameId);
+ if (classManager)
+ classManager.removeStylesheet(stylesheet.id);
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _styleSheetRemoved: function(event)
+ {
+ var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data);
+ this._removeStylesheet(header);
+ this._updateCompletions(header.frameId);
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _mainFrameNavigated: function(event)
+ {
+ this._selectedNode = null;
+ this._prompt.clearCompletions();
+ this._frameClassManagers = new Map();
+ },
+
+ _hidePrompt: function()
+ {
+ this._prompt.clearAutoComplete();
+ },
+
+ /**
+ * @param {?WebInspector.DOMDocument=} inspectedDocument
+ */
+ _getDomClasses: function(inspectedDocument)
+ {
+ if (!inspectedDocument && this._selectedNode)
+ inspectedDocument = this._selectedNode.ownerDocument;
+ var frameId = inspectedDocument.frameId() || WebInspector.ResourceTreeModel.fromTarget(inspectedDocument.target()).mainFrame.id;
+ /**
+ * @param {?Array.<string>} classNames
+ * @this {!WebInspector.ClassesPaneWidget}
+ */
+ function classNamesCallback(classNames)
+ {
+ this._getFrameClassManager(frameId).setDomClassNames(classNames);
+ this._updateCompletions(frameId);
+ }
+
+ inspectedDocument.domModel().classNamesPromise(inspectedDocument.id).then(classNamesCallback.bind(this));
+ },
+
+
+ _getCssClasses: function()
+ {
+ var frameId = this._selectedNode.frameId();
+ var cssModel = WebInspector.CSSModel.fromTarget(this._selectedNode.target());
+ var headers = cssModel.allStyleSheets();
+ for (var stylesheet of cssModel.allStyleSheets())
+ if (stylesheet.frameId === frameId)
+ this._getStylesheetClasses(stylesheet);
+ },
+
+ /**
+ * @param {?PageAgent.FrameId} frameId
+ * @return {!WebInspector.ClassesPaneWidget.FrameClassManager}
+ */
+ _getFrameClassManager: function(frameId)
+ {
+ if (!this._frameClassManagers.get(frameId))
+ this._frameClassManagers.set(frameId, new WebInspector.ClassesPaneWidget.FrameClassManager());
+
+ return this._frameClassManagers.get(frameId);
+ },
+
+ _selectedFrameChanged: function()
+ {
+ var newFrameId = this._selectedFrameId();
+ this._prompt.sourceFrameId = newFrameId;
+ this._updateCompletions(newFrameId);
+ var frameClassManager = this._getFrameClassManager(newFrameId);
+ if (this.isShowing())
+ this.getFrameClasses(frameClassManager);
+ },
+
+ /**
+ * @return {?PageAgent.FrameId}
+ */
+ _selectedFrameId: function()
+ {
+ return this._selectedNode ? this._selectedNode.frameId() || WebInspector.ResourceTreeModel.fromTarget(this._selectedNode.target()).mainFrame.id : 0;
+ },
+
+ /**
+ * @param {!WebInspector.CSSStyleSheetHeader} stylesheet
+ */
+ _getStylesheetClasses: function(stylesheet)
+ {
+ if (!this.isShowing())
+ return;
+
+ /**
+ * @param {!WebInspector.CSSStyleSheetHeader} stylsheet
+ * @param {?Array.<string>} classNames
+ * @this {!WebInspector.ClassesPaneWidget}
+ */
+ function classNamesCallback(stylsheet, classNames)
+ {
+ this._getFrameClassManager(stylsheet.frameId).addCssClassNames(stylsheet.id, classNames);
+ this._updateCompletions(stylsheet.frameId);
+ }
+
+ var cssModel = stylesheet.cssModel();
+ cssModel.classNamesPromise(stylesheet.id).then(classNamesCallback.bind(this, stylesheet));
+ },
+
+ /**
+ * @param {?PageAgent.FrameId} frameId
+ */
+ _updateCompletions: function(frameId)
+ {
+ if (frameId === this._prompt.sourceFrameId)
+ this._getFrameClassManager(frameId).getMergedList(mergedList => this._prompt.updateCompletions(mergedList));
+ },
+
+ /**
+ * @param {!WebInspector.ClassesPaneWidget.FrameClassManager} frameClassManager
+ */
+ getFrameClasses: function(frameClassManager)
+ {
+ if (frameClassManager.isRequested)
+ return;
+
+ this._getDomClasses();
+ this._getCssClasses();
+
+ frameClassManager.isRequested = true;
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
_onDOMMutated: function(event)
{
var node = /** @type {!WebInspector.DOMNode} */(event.data);
@@ -83,7 +272,6 @@ WebInspector.ClassesPaneWidget.prototype = {
{
if (!this.isShowing())
return;
-
var node = WebInspector.context.flavor(WebInspector.DOMNode);
if (node)
node = node.enclosingElementOrSelf();
@@ -94,6 +282,10 @@ WebInspector.ClassesPaneWidget.prototype = {
if (!node)
return;
+ var oldFrameId = this._selectedFrameId();
+ this._selectedNode = node;
+ if (this._selectedFrameId() !== oldFrameId)
+ this._selectedFrameChanged();
var classes = this._nodeClasses(node);
var keys = classes.keysArray();
keys.sort(String.caseInsensetiveComparator);
@@ -185,6 +377,76 @@ WebInspector.ClassesPaneWidget.prototype = {
/**
* @constructor
+ */
+WebInspector.ClassesPaneWidget.FrameClassManager = function()
+{
+ 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.
+ this._domClassNames = [];
+ this._mergedList = new Set();
+ this._mergeRequired = true;
+ this.isRequested = false;
+}
+
+WebInspector.ClassesPaneWidget.FrameClassManager.prototype = {
+ /**
+ * @param {!CSSAgent.StyleSheetId} styleSheetId
+ * @param {?Array.<string>} classNames
+ */
+ addCssClassNames: function(styleSheetId, classNames)
+ {
+ if (!classNames)
+ return;
+
+ this._cssClassNames.set(styleSheetId, classNames);
+ this._mergedList.addAll(classNames);
+ },
+
+ /**
+ * @param {?Array.<string>} classNames
+ */
+ setDomClassNames: function(classNames)
+ {
+ if (!classNames)
+ return;
+
+ this._domClassNames = classNames;
+ this._mergedList.addAll(classNames);
+ },
+
+ /**
+ * @param {!CSSAgent.StyleSheetId} styleSheetId
+ */
+ removeStylesheet: function(styleSheetId)
+ {
+ this._cssClassNames.remove(styleSheetId);
+ this._mergeRequired = true;
+ },
+
+ /**
+ * @param {function(!Array<string>)} callback
+ */
+ getMergedList: function(callback)
+ {
+ if (this._mergeRequired)
+ this._merge();
+ callback(this._mergedList.valuesArray());
+ },
+
+ _merge: function()
+ {
+ this._mergedList = new Set();
+ if (this._domClassNames)
+ this._mergedList.addAll(this._domClassNames);
+
+ for (var styleSheetClasses of this._cssClassNames.values())
+ this._mergedList.addAll(styleSheetClasses);
+
+ this._mergeRequired = false;
+ }
+}
+
+/**
+ * @constructor
* @implements {WebInspector.ToolbarItem.Provider}
*/
WebInspector.ClassesPaneWidget.ButtonProvider = function()
@@ -211,3 +473,72 @@ WebInspector.ClassesPaneWidget.ButtonProvider.prototype = {
return this._button;
}
}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TextPrompt}
+ */
+WebInspector.ClassesPaneWidget.ClassNamePrompt = function()
+{
+ WebInspector.TextPrompt.call(this, this._buildClassNameCompletions.bind(this), " ");
+ this.setSuggestBoxEnabled(true);
+ this.disableDefaultSuggestionForEmptyInput();
+ this._classNameCompletions = [];
+ this.sourceFrameId = 0;
+}
+
+WebInspector.ClassesPaneWidget.ClassNamePrompt.prototype = {
+ /**
+ * @override
+ * @param {!Event} event
+ */
+ onKeyDown: function(event)
+ {
+ switch (event.key) {
+ case "Enter":
+ // Accept any available autocompletions and advance to the next field.
+ if (this.autoCompleteElement && this.autoCompleteElement.textContent.length) {
+ this.acceptAutoComplete();
+ return;
+ }
+ break;
+ }
+
+ WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
+ },
+
+ /**
+ * @param {!Array.<string>} classNameList
+ */
+ updateCompletions: function(classNameList)
+ {
+ this._classNameCompletions = classNameList;
+ },
+
+ clearCompletions: function()
+ {
+ this._classNameCompletions = [];
+ },
+
+ /**
+ * @param {!Element} proxyElement
+ * @param {!Range} wordRange
+ * @param {boolean} force
+ * @param {function(!Array.<string>, number=)} completionsReadyCallback
+ */
+ _buildClassNameCompletions: function(proxyElement, wordRange, force, completionsReadyCallback)
+ {
+ var prefix = wordRange.toString();
+
+ if (!prefix && !force && !proxyElement.textContent.length) {
+ completionsReadyCallback([]);
+ return;
+ }
+
+ var results = this._classNameCompletions.filter((value) => value.startsWith(prefix));
+ var selectedIndex = 0;
+ completionsReadyCallback(results, selectedIndex);
+ },
+
+ __proto__: WebInspector.TextPrompt.prototype
+}

Powered by Google App Engine
This is Rietveld 408576698