Chromium Code Reviews| 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 |
| +} |