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

Side by Side Diff: Source/devtools/front_end/bindings/SASSSourceMapping.js

Issue 1331083002: DevTools: [STRUCT] edit SASS through SourceMaps (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: rebaseline atop master Created 5 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
« no previous file with comments | « no previous file | Source/devtools/front_end/bindings/SASSSupport.js » ('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) 2012 Google Inc. All rights reserved. 2 * Copyright (C) 2012 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 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
57 /** 57 /**
58 * @param {!WebInspector.Event} event 58 * @param {!WebInspector.Event} event
59 */ 59 */
60 _styleSheetChanged: function(event) 60 _styleSheetChanged: function(event)
61 { 61 {
62 var id = /** @type {!CSSAgent.StyleSheetId} */ (event.data.styleSheetId) ; 62 var id = /** @type {!CSSAgent.StyleSheetId} */ (event.data.styleSheetId) ;
63 if (this._addingRevisionCounter) { 63 if (this._addingRevisionCounter) {
64 --this._addingRevisionCounter; 64 --this._addingRevisionCounter;
65 return; 65 return;
66 } 66 }
67 return;
67 var header = this._cssModel.styleSheetHeaderForId(id); 68 var header = this._cssModel.styleSheetHeaderForId(id);
68 if (!header) 69 if (!header)
69 return; 70 return;
70 71
71 this.removeHeader(header); 72 this.removeHeader(header);
72 }, 73 },
73 74
74 /** 75 /**
75 * @param {!WebInspector.Event} event 76 * @param {!WebInspector.Event} event
76 */ 77 */
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after
229 }, 230 },
230 231
231 /** 232 /**
232 * @param {!WebInspector.CSSStyleSheetHeader} header 233 * @param {!WebInspector.CSSStyleSheetHeader} header
233 * @param {!WebInspector.SourceMap} sourceMap 234 * @param {!WebInspector.SourceMap} sourceMap
234 */ 235 */
235 _bindUISourceCode: function(header, sourceMap) 236 _bindUISourceCode: function(header, sourceMap)
236 { 237 {
237 WebInspector.cssWorkspaceBinding.pushSourceMapping(header, this); 238 WebInspector.cssWorkspaceBinding.pushSourceMapping(header, this);
238 var cssURL = header.sourceURL; 239 var cssURL = header.sourceURL;
240 var rawURL = header.sourceURL;
241 if (!this._structureMapByURL[rawURL]) {
242 var structureMap = new WebInspector.SASSSourceMapping.StructureMap(t his._workspace, this._networkMapping, this._cssModel, this, rawURL, sourceMap);
243 this._structureMapByURL[rawURL] = structureMap;
244 }
239 var sources = sourceMap.sources(); 245 var sources = sourceMap.sources();
240 for (var i = 0; i < sources.length; ++i) { 246 for (var i = 0; i < sources.length; ++i) {
241 var sassURL = sources[i]; 247 var sassURL = sources[i];
242 this._sassURLToCSSURLs.set(sassURL, cssURL); 248 this._sassURLToCSSURLs.set(sassURL, cssURL);
243 if (!this._networkMapping.hasMappingForURL(sassURL) && !this._networ kMapping.uiSourceCodeForURL(sassURL, header.target())) { 249 if (!this._networkMapping.hasMappingForURL(sassURL) && !this._networ kMapping.uiSourceCodeForURL(sassURL, header.target())) {
244 var contentProvider = sourceMap.sourceContentProvider(sassURL, W ebInspector.resourceTypes.Stylesheet); 250 var contentProvider = sourceMap.sourceContentProvider(sassURL, W ebInspector.resourceTypes.Stylesheet);
245 this._networkProject.addFileForURL(sassURL, contentProvider); 251 this._networkProject.addFileForURL(sassURL, contentProvider);
246 } 252 }
247 } 253 }
248 }, 254 },
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
344 this._addingRevisionCounter = 0; 350 this._addingRevisionCounter = 0;
345 this._completeSourceMapURLForCSSURL = {}; 351 this._completeSourceMapURLForCSSURL = {};
346 /** @type {!Multimap<string, string>} */ 352 /** @type {!Multimap<string, string>} */
347 this._sassURLToCSSURLs = new Multimap(); 353 this._sassURLToCSSURLs = new Multimap();
348 /** @type {!Object.<string, !Array.<function(?WebInspector.SourceMap)>>} */ 354 /** @type {!Object.<string, !Array.<function(?WebInspector.SourceMap)>>} */
349 this._pendingSourceMapLoadingCallbacks = {}; 355 this._pendingSourceMapLoadingCallbacks = {};
350 /** @type {!Object.<string, !WebInspector.SourceMap>} */ 356 /** @type {!Object.<string, !WebInspector.SourceMap>} */
351 this._sourceMapByURL = {}; 357 this._sourceMapByURL = {};
352 this._sourceMapByStyleSheetURL = {}; 358 this._sourceMapByStyleSheetURL = {};
353 this._pollManager.reset(); 359 this._pollManager.reset();
360
361 this._structureMapByURL = {};
354 } 362 }
355 } 363 }
356 364
357 /** 365 /**
358 * @constructor 366 * @constructor
359 * @param {!WebInspector.CSSStyleModel} cssModel 367 * @param {!WebInspector.CSSStyleModel} cssModel
360 * @param {!WebInspector.NetworkMapping} networkMapping 368 * @param {!WebInspector.NetworkMapping} networkMapping
361 * @param {function(!WebInspector.UISourceCode, string):boolean} callback 369 * @param {function(!WebInspector.UISourceCode, string):boolean} callback
362 */ 370 */
363 WebInspector.SASSSourceMapping.PollManager = function(cssModel, networkMapping, callback) 371 WebInspector.SASSSourceMapping.PollManager = function(cssModel, networkMapping, callback)
(...skipping 271 matching lines...) Expand 10 before | Expand all | Expand 10 after
635 function contentCallback(content) 643 function contentCallback(content)
636 { 644 {
637 // Empty string is a valid value, null means error. 645 // Empty string is a valid value, null means error.
638 if (content === null) 646 if (content === null)
639 return; 647 return;
640 if (this._callback(cssUISourceCode, content)) 648 if (this._callback(cssUISourceCode, content))
641 this._stopPolling(cssURL, sassURL); 649 this._stopPolling(cssURL, sassURL);
642 } 650 }
643 } 651 }
644 } 652 }
645 } 653 }
654
655 WebInspector.SASSSourceMapping.StructureMap = function(workspace, networkMapping , cssModel, sassSourceMapping, cssURL, sourceMap)
656 {
657 this._workspace = workspace;
658 this._networkMapping = networkMapping;
659 this._cssModel = cssModel;
660 this._sassSourceMapping = sassSourceMapping;
661 this._cssURL = cssURL;
662 this._sourceMap = sourceMap;
663
664 this._throttler = new WebInspector.Throttler(0);
665
666 this._sources = new Map();
667 this._models = new Map();
668 this._mapping = new WebInspector.SASSSourceMapping.CssToSassMapping();
669
670 this._unloadedSourceURLs = new Set();
671 for (var source of this._sourceMap.sources())
672 this._unloadedSourceURLs.add(source);
673 this._unloadedSourceURLs.add(this._cssURL);
674
675 for (var url of this._unloadedSourceURLs) {
676 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, this._cs sModel.target());
677 if (!uiSourceCode)
678 continue;
679 uiSourceCode.requestContentPromise().then(this._onSourceLoaded.bind(this , url));
680 }
681 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeA dded, this._uiSourceCodeAdded, this);
682 }
683
684 WebInspector.SASSSourceMapping.StructureMap.prototype = {
685 _uiSourceCodeAdded: function(event)
686 {
687 var uiSourceCode = event.data;
688 var url = uiSourceCode.originURL();
689 if (this._unloadedSourceURLs.has(url))
690 uiSourceCode.requestContentPromise().then(this._onSourceLoaded.bind( this, url));
691 },
692
693 _onSourceLoaded: function(url, content)
694 {
695 if (typeof content !== "string")
696 throw new Error("Failed to fetch content of " + url);
697 this._sources.set(url, content);
698 this._unloadedSourceURLs.delete(url);
699 if (this._unloadedSourceURLs.size)
700 return;
701 this._workspace.removeEventListener(WebInspector.Workspace.Events.UISour ceCodeAdded, this._uiSourceCodeAdded, this);
702 var tokenizerPromise = this._loadTokenizer();
703 var cssASTPromise = WebInspector.SASSSupport.parseCSS(this._cssURL, this ._sources.get(this._cssURL));
704
705 Promise.all([parsePromise, tokenizerPromise])
706 .spread(this._initialize.bind(this))
707 .catch(this._killMapping.bind(this));
708 },
709
710 _loadTokenizer: function()
711 {
712 return self.runtime.instancePromise(WebInspector.TokenizerFactory).then( onTokenizer.bind(this));
713
714 function onTokenizer(tokenizer)
715 {
716 this._tokenizerFactory = tokenizer;
717 }
718 },
719
720 _initialize: function(cssAST)
721 {
722 this._models.set(this._cssURL, cssAST);
723
724 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(this._cssURL, this._cssModel.target());
725 uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCo pyCommitted, this._sourceCodeCommitted, this);
726
727 // FIXME: this works for O(N^2).
728 for (var rule of cssAST.rules) {
729 for (var property of rule.properties) {
730 this._mapCssNodeToSassNode(property.name);
731 this._mapCssNodeToSassNode(property.value);
732 }
733 }
734 console.log("Initialized!");
735 },
736
737 _mapCssNodeToSassNode: function(cssNode)
738 {
739 var entry = this._sourceMap.findEntry(cssNode.range.endLine, cssNode.ran ge.endColumn);
740 if (!entry) {
741 console.log("Missing mapping for entry!");
742 return;
743 }
744 var sassNode = this._findSassForMapping(entry);
745 if (!sassNode) {
746 console.log("Missing sass node for entry!");
747 return;
748 }
749 this._mapping.mapCssToSass(cssNode, sassNode);
750 },
751
752 _findSassForMapping: function(entry)
753 {
754 var sassModel = this._modelForURL(entry.sourceURL);
755 for (var rule of sassModel.rules) {
756 for (var property of rule.properties) {
757 if (property.name.range.containsLocation(entry.sourceLineNumber, entry.sourceColumnNumber))
758 return property.name;
759 if (property.value.range.containsLocation(entry.sourceLineNumber , entry.sourceColumnNumber))
760 return property.value;
761 }
762 }
763 return null;
764 },
765
766 _modelForURL: function(url)
767 {
768 if (this._models.has(url))
769 return this._models.get(url);
770 var source = this._sources.get(url);
771 var ast = WebInspector.SASSSupport.parseSASS(url, source, this._tokenize rFactory);
772 this._models.set(url, ast);
773 return ast;
774 },
775
776 _killMapping: function(error)
777 {
778 if (error)
779 console.error(error);
780 var ids = this._cssModel.styleSheetIdsForURL(this._cssURL);
781 for (var id of ids) {
782 var header = this._cssModel.styleSheetHeaderForId(id);
783 this._sassSourceMapping.removeHeader(header);
784 }
785 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(this._cssURL, this._cssModel.target());
786 uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.Workin gCopyCommitted, this._sourceCodeCommitted, this);
787 },
788
789 _sourceCodeCommitted: function(event)
790 {
791 if (this._muteSourceCodeCommitted)
792 return;
793
794 var sourceURL = this._networkMapping.networkURL(event.target);
795 // Break mapping on SASS manual edits.
796 if (sourceURL !== this._cssURL) {
797 this._killMapping();
798 return;
799 }
800 this._throttler.schedule(this._handleCSSChange.bind(this));
801 },
802
803 _handleCSSChange: function()
804 {
805 console.log("React!");
806 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(this._cssURL, this._cssModel.target());
807 var newCSSContent = uiSourceCode.history[uiSourceCode.history.length - 1 ].content;
808 return WebInspector.SASSSupport.parseCSS(this._cssURL, newCSSContent).th en(this._onCurrentCSSParsed.bind(this)).catch(this._killMapping.bind(this));
809 },
810
811 _newMappingForDiff: function(oldMapping, diff)
812 {
813 var newMapping = new WebInspector.SASSSourceMapping.CssToSassMapping();
814 for (var currentProperty of diff.bToA.keys()) {
815 var oldProperty = diff.bToA.get(currentProperty);
816 var sassName = oldMapping.cssToSass(oldProperty.name);
817 var sassValue = oldMapping.cssToSass(oldProperty.value);
818 newMapping.mapCssToSass(currentProperty.name, sassName);
819 newMapping.mapCssToSass(currentProperty.value, sassValue);
820 }
821 return newMapping;
822 },
823
824 _onCurrentCSSParsed: function(currentCSSModel)
825 {
826 var oldCSSModel = this._modelForURL(this._cssURL);
827 var modelDiff = WebInspector.SASSSourceMapping.diffModels(oldCSSModel, c urrentCSSModel);
828 var newMapping = this._newMappingForDiff(this._mapping, modelDiff);
829
830 var edits = [];
831 // Traverse diff in a reversed order to preserve add-property sequence.
832 for (var i = modelDiff.structuralDiff.length - 1; i >= 0; --i) {
833 //FIXME: handle other types of edits.
834 var diff = modelDiff.structuralDiff[i];
835 if (diff.type === "ValueChanged") {
836 var cssProperty = diff.property;
837 var newText = " " + cssProperty.value.text.trim();
838 edits = edits.concat(newMapping.setCSSText(cssProperty.value, ne wText));
839 } else if (diff.type === "NameChanged") {
840 var cssProperty = diff.property;
841 var newText = cssProperty.name.text.trim();
842 edits = edits.concat(newMapping.setCSSText(cssProperty.name, new Text));
843 } else if (diff.type === "PropertyAdded") {
844 var cssProperty = diff.property;
845 if (diff.after)
846 edits = edits.concat(newMapping.insertCSSPropertyAfter(cssPr operty, diff.after));
847 else if (diff.before)
848 edits = edits.concat(newMapping.insertCSSPropertyBefore(cssP roperty, diff.before));
849 } else if (diff.type === "PropertyRemoved") {
850 var cssProperty = diff.property;
851 var sassName = this._mapping.cssToSass(cssProperty.name);
852 edits = edits.concat(newMapping.removeSASSProperty(sassName.pare nt));
853 } else if (diff.type === "toggleDisabled") {
854 var cssProperty = diff.property;
855 edits = edits.concat(newMapping.toggleDisabled(cssProperty));
856 }
857 }
858
859 // Categorize edits per url.
860 var editsPerURL = new Multimap();
861 for (var edit of edits)
862 editsPerURL.set(edit.sourceURL, edit);
863
864 // Apply to uiSourceCodes.
865 this._sources.set(this._cssURL, currentCSSModel.text);
866 var urls = editsPerURL.keysArray();
867 for (var url of urls) {
868 var source = this._sources.get(url);
869 var edits = editsPerURL.get(url).valuesArray();
870 edits.stableSort(sequentialOrder);
871 // Apply edits in a reversed order so that they do not conflict with each other.
872 for (var i = edits.length - 1; i >= 0; --i) {
873 var edit = edits[i];
874 source = edit.applyToText(source);
875 }
876 this._sources.set(url, source);
877 this._muteSourceCodeCommitted = true;
878 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, this ._cssModel.target());
879 uiSourceCode.addRevision(source);
880 this._muteSourceCodeCommitted = false;
881 }
882
883 for (var url of urls) {
884 if (url === this._cssURL)
885 continue;
886 this._updateSASSMapping(newMapping, url);
887 }
888
889 return WebInspector.SASSSupport.parseCSS(this._cssURL, this._sources.get (this._cssURL)).then(onNewCSSModel.bind(this));
890
891 function onNewCSSModel(newCSSModel)
892 {
893 var modelDiff = WebInspector.SASSSourceMapping.diffModels(currentCSS Model, newCSSModel);
894 this._models.set(this._cssURL, newCSSModel);
895 this._mapping = this._newMappingForDiff(newMapping, modelDiff);
896 }
897
898 function sequentialOrder(range1, range2)
899 {
900 return range1.oldRange.follows(range2.oldRange) ? 1 : -1;
901 }
902 },
903
904 _updateSASSMapping: function(mapping, url)
905 {
906 var oldAST = this._modelForURL(url);
907 this._models.delete(url);
908 var newAST = this._modelForURL(url);
909 var modelDiff = WebInspector.SASSSourceMapping.diffModels(oldAST, newAST );
910 for (var oldProperty of modelDiff.aToB.keys()) {
911 var cssNames = mapping.sassToCss(oldProperty.name);
912 var cssValues = mapping.sassToCss(oldProperty.value);
913 var currentProperty = modelDiff.aToB.get(oldProperty);
914 for (var i = 0; i < cssNames.length; ++i) {
915 mapping.unmapCssToSass(cssNames[i], oldProperty.name);
916 mapping.mapCssToSass(cssNames[i], currentProperty.name);
917 }
918 for (var i = 0; i < cssValues.length; ++i) {
919 mapping.unmapCssToSass(cssValues[i], oldProperty.value);
920 mapping.mapCssToSass(cssValues[i], currentProperty.value);
921 }
922 }
923 }
924 }
925
926 WebInspector.SASSSourceMapping.CssToSassMapping = function()
927 {
928 this._cssToSass = new Map();
929 this._sassToCss = new Multimap();
930 }
931
932 WebInspector.SASSSourceMapping.CssToSassMapping.prototype = {
933 unmapCssToSass: function(css, sass)
934 {
935 this._cssToSass.delete(css);
936 this._sassToCss.remove(sass, css);
937 },
938
939 mapCssToSass: function(css, sass)
940 {
941 this._cssToSass.set(css, sass);
942 this._sassToCss.set(sass, css);
943 },
944
945 cssToSass: function(css)
946 {
947 return this._cssToSass.get(css);
948 },
949
950 sassToCss: function(sass)
951 {
952 return this._sassToCss.get(sass).valuesArray();
953 },
954
955 setCSSText: function(cssNode, text)
956 {
957 var sassNode = this._cssToSass.get(cssNode);
958 var cssNodes = this._sassToCss.get(sassNode);
959
960 var edits = [];
961 edits.push(WebInspector.SASSSupport.setText(sassNode, text));
962 for (var node of cssNodes) {
963 if (node === cssNode)
964 continue;
965 edits.push(WebInspector.SASSSupport.setText(node, text));
966 }
967 return edits;
968 },
969
970 insertCSSPropertyAfter: function(cssProperty, afterProperty)
971 {
972 var sassNode = this._cssToSass.get(afterProperty.name);
973 var sassClone = cssProperty.clone();
974
975 var edits = [];
976 edits.push(WebInspector.SASSSupport.insertPropertyAfter(sassClone, sassN ode.parent));
977 this.mapCssToSass(cssProperty.name, sassClone.name);
978 this.mapCssToSass(cssProperty.value, sassClone.value);
979
980 var cssNodes = this._sassToCss.get(sassNode);
981 for (var node of cssNodes) {
982 if (node === afterProperty.name)
983 continue;
984 var cssClone = cssProperty.clone();
985 edits.push(WebInspector.SASSSupport.insertPropertyAfter(cssClone, no de.parent));
986 this.mapCssToSass(cssClone.name, sassClone.name);
987 this.mapCssToSass(cssClone.value, sassClone.value);
988 }
989 return edits;
990 },
991
992 insertCSSPropertyBefore: function(cssProperty, beforeProperty)
993 {
994 var sassNode = this._cssToSass.get(beforeProperty.name);
995 var sassClone = cssProperty.clone();
996
997 var edits = [];
998 edits.push(WebInspector.SASSSupport.insertPropertyBefore(sassClone, sass Node.parent));
999 this.mapCssToSass(cssProperty.name, sassClone.name);
1000 this.mapCssToSass(cssProperty.value, sassClone.value);
1001
1002 var cssNodes = this._sassToCss.get(sassNode);
1003 for (var node of cssNodes) {
1004 if (node === beforeProperty.name)
1005 continue;
1006 var cssClone = cssProperty.clone();
1007 edits.push(WebInspector.SASSSupport.insertPropertyBefore(cssClone, n ode.parent));
1008 this.mapCssToSass(cssClone.name, sassClone.name);
1009 this.mapCssToSass(cssClone.value, sassClone.value);
1010 }
1011 return edits;
1012 },
1013
1014 toggleDisabled: function(cssProperty)
1015 {
1016 var sassNode = this._cssToSass.get(cssProperty.name);
1017 var cssNodes = this._sassToCss.get(sassNode);
1018
1019 var edits = WebInspector.SASSSupport.toggleDisabled(sassNode.parent, css Property.disabled);
1020 for (var node of cssNodes) {
1021 if (node === cssProperty.name)
1022 continue;
1023 edits = edits.concat(WebInspector.SASSSupport.toggleDisabled(node.pa rent, cssProperty.disabled));
1024 }
1025 return edits;
1026 },
1027
1028 removeSASSProperty: function(sassProperty)
1029 {
1030 var edits = [];
1031 edits.push(WebInspector.SASSSupport.removeProperty(sassProperty));
1032 var cssNodes = this._sassToCss.get(sassProperty.name);
1033 for (var node of cssNodes)
1034 edits.push(WebInspector.SASSSupport.removeProperty(node.parent));
1035 return edits;
1036 },
1037 }
OLDNEW
« no previous file with comments | « no previous file | Source/devtools/front_end/bindings/SASSSupport.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698