Web Inspector: Elements: Styles: add icons for various CSS rule types
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / RecordingActionTreeElement.js
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.RecordingActionTreeElement = class RecordingActionTreeElement extends WI.GeneralTreeElement
27 {
28     constructor(representedObject, index, recordingType)
29     {
30         console.assert(representedObject instanceof WI.RecordingAction);
31
32         let {titleFragment, copyText} = WI.RecordingActionTreeElement._generateDOM(representedObject, recordingType);
33         let classNames = WI.RecordingActionTreeElement._getClassNames(representedObject);
34
35         const subtitle = null;
36         super(classNames, titleFragment, subtitle, representedObject);
37
38         this._index = index;
39         this._copyText = copyText;
40
41         if (this.representedObject.valid)
42             this.representedObject.addEventListener(WI.RecordingAction.Event.ValidityChanged, this._handleValidityChanged, this);
43     }
44
45     // Static
46
47     static _generateDOM(recordingAction, recordingType)
48     {
49         let parameterCount = recordingAction.parameters.length;
50
51         function createParameterElement(parameter, swizzleType, index) {
52             let parameterElement = document.createElement("span");
53             parameterElement.classList.add("parameter");
54
55             switch (swizzleType) {
56             case WI.Recording.Swizzle.Number:
57                 var constantNameForParameter = WI.RecordingAction.constantNameForParameter(recordingType, recordingAction.name, parameter, index, parameterCount);
58                 if (constantNameForParameter) {
59                     parameterElement.classList.add("constant");
60                     parameterElement.textContent = "context." + constantNameForParameter;
61                 } else
62                     parameterElement.textContent = parameter.maxDecimals(2);
63                 break;
64
65             case WI.Recording.Swizzle.Boolean:
66                 parameterElement.textContent = parameter;
67                 break;
68
69             case WI.Recording.Swizzle.String:
70                 parameterElement.textContent = JSON.stringify(parameter);
71                 break;
72
73             case WI.Recording.Swizzle.Array:
74                 parameterElement.classList.add("swizzled");
75                 parameterElement.textContent = JSON.stringify(parameter);
76                 break;
77
78             case WI.Recording.Swizzle.TypedArray:
79             case WI.Recording.Swizzle.Image:
80             case WI.Recording.Swizzle.ImageBitmap:
81             case WI.Recording.Swizzle.ImageData:
82             case WI.Recording.Swizzle.DOMMatrix:
83             case WI.Recording.Swizzle.Path2D:
84             case WI.Recording.Swizzle.CanvasGradient:
85             case WI.Recording.Swizzle.CanvasPattern:
86             case WI.Recording.Swizzle.WebGLBuffer:
87             case WI.Recording.Swizzle.WebGLFramebuffer:
88             case WI.Recording.Swizzle.WebGLRenderbuffer:
89             case WI.Recording.Swizzle.WebGLTexture:
90             case WI.Recording.Swizzle.WebGLShader:
91             case WI.Recording.Swizzle.WebGLProgram:
92             case WI.Recording.Swizzle.WebGLUniformLocation:
93             case WI.Recording.Swizzle.WebGLQuery:
94             case WI.Recording.Swizzle.WebGLSampler:
95             case WI.Recording.Swizzle.WebGLSync:
96             case WI.Recording.Swizzle.WebGLTransformFeedback:
97             case WI.Recording.Swizzle.WebGLVertexArrayObject:
98                 parameterElement.classList.add("swizzled");
99                 parameterElement.textContent = WI.Recording.displayNameForSwizzleType(swizzleType);
100                 break;
101             }
102
103             if (!parameterElement.textContent) {
104                 parameterElement.classList.add("invalid");
105                 parameterElement.textContent = swizzleType === WI.Recording.Swizzle.None ? parameter : WI.Recording.displayNameForSwizzleType(swizzleType);
106             }
107
108             return parameterElement;
109         }
110
111         let titleFragment = document.createDocumentFragment();
112         let copyText = recordingAction.name;
113
114         let contextReplacer = recordingAction.contextReplacer;
115         if (contextReplacer) {
116             copyText = contextReplacer + "." + copyText;
117
118             let contextReplacerContainer = titleFragment.appendChild(document.createElement("span"));
119             contextReplacerContainer.classList.add("context-replacer");
120             contextReplacerContainer.textContent = contextReplacer;
121         }
122
123         let nameContainer = titleFragment.appendChild(document.createElement("span"));
124         nameContainer.classList.add("name");
125         nameContainer.textContent = recordingAction.name;
126
127         if (!parameterCount)
128             return {titleFragment, copyText};
129
130         let parametersContainer = titleFragment.appendChild(document.createElement("span"));
131         parametersContainer.classList.add("parameters");
132
133         if (recordingAction.isFunction)
134             copyText += "(";
135         else
136             copyText += " = ";
137
138         for (let i = 0; i < parameterCount; ++i) {
139             let parameter = recordingAction.parameters[i];
140             let swizzleType = recordingAction.swizzleTypes[i];
141             let parameterElement = createParameterElement(parameter, swizzleType, i);
142             parametersContainer.appendChild(parameterElement);
143
144             if (i)
145                 copyText += ", ";
146
147             copyText += parameterElement.textContent;
148         }
149
150         if (recordingAction.isFunction)
151             copyText += ")";
152
153         let colorParameters = recordingAction.getColorParameters();
154         if (colorParameters.length) {
155             let swatch = WI.RecordingActionTreeElement._createSwatchForColorParameters(colorParameters);
156             if (swatch) {
157                 let insertionIndex = recordingAction.parameters.indexOf(colorParameters[0]);
158                 parametersContainer.insertBefore(swatch.element, parametersContainer.children[insertionIndex]);
159             }
160         }
161
162         let imageParameters = recordingAction.getImageParameters();
163         let isImage = imageParameters[0] instanceof HTMLImageElement;
164         let isImageBitmap = imageParameters[0] instanceof ImageBitmap;
165         let isImageData = imageParameters[0] instanceof ImageData;
166         let isCanvasGradient = imageParameters[0] instanceof CanvasGradient;
167         let isCanvasPattern = imageParameters[0] instanceof CanvasPattern;
168         if (imageParameters.length && (isImage || isImageBitmap || isImageData || isCanvasGradient || isCanvasPattern)) {
169             let image = imageParameters[0];
170
171             if (isImageBitmap)
172                 image = WI.ImageUtilities.imageFromImageBitmap(image);
173             else if (isImageData)
174                 image = WI.ImageUtilities.imageFromImageData(image);
175             else if (isCanvasGradient)
176                 image = WI.ImageUtilities.imageFromCanvasGradient(image, 100, 100);
177             else if (isCanvasPattern)
178                 image = image.__image;
179
180             if (image) {
181                 let swatch = new WI.InlineSwatch(WI.InlineSwatch.Type.Image, image);
182                 let insertionIndex = recordingAction.parameters.indexOf(imageParameters[0]);
183                 let parameterElement = parametersContainer.children[insertionIndex];
184                 parametersContainer.insertBefore(swatch.element, parameterElement);
185             }
186         }
187
188         return {titleFragment, copyText};
189     }
190
191     static _createSwatchForColorParameters(parameters)
192     {
193         let rgb = [];
194         let color = null;
195
196         switch (parameters.length) {
197         case 1:
198         case 2:
199             if (typeof parameters[0] === "string")
200                 color = WI.Color.fromString(parameters[0].trim());
201             else if (!isNaN(parameters[0]))
202                 rgb = WI.Color.normalized2rgb(parameters[0], parameters[0], parameters[0]);
203             break;
204
205         case 4:
206             rgb = WI.Color.normalized2rgb(parameters[0], parameters[1], parameters[2]);
207             break;
208
209         case 5:
210             rgb = WI.Color.cmyk2rgb(...parameters);
211             break;
212
213         default:
214             console.error("Unexpected number of color parameters.");
215             return null;
216         }
217
218         if (!color) {
219             if (rgb.length !== 3)
220                 return null;
221
222             let alpha = parameters.length === 1 ? 1 : parameters.lastValue;
223             color = new WI.Color(WI.Color.Format.RGBA, [...rgb, alpha]);
224             if (!color)
225                 return null;
226         }
227
228         const readOnly = true;
229         return new WI.InlineSwatch(WI.InlineSwatch.Type.Color, color, readOnly);
230     }
231
232     static _getClassNames(recordingAction)
233     {
234         let classNames = ["recording-action"];
235
236         if (recordingAction instanceof WI.RecordingInitialStateAction) {
237             classNames.push("initial-state");
238             return classNames;
239         }
240
241         if (!recordingAction.isFunction)
242             classNames.push("attribute");
243
244         let actionClassName = WI.RecordingActionTreeElement._classNameForAction(recordingAction);
245         if (actionClassName.length)
246             classNames.push(actionClassName);
247
248         if (recordingAction.isVisual)
249             classNames.push("visual");
250
251         if (!recordingAction.valid)
252             classNames.push("invalid");
253
254         return classNames;
255     }
256
257     static _classNameForAction(recordingAction)
258     {
259         if (recordingAction.contextReplacer)
260             return "has-context-replacer";
261
262         switch (recordingAction.name) {
263         case "arc":
264         case "arcTo":
265             return "arc";
266
267         case "globalAlpha":
268         case "globalCompositeOperation":
269         case "setAlpha":
270         case "setGlobalAlpha":
271         case "setCompositeOperation":
272         case "setGlobalCompositeOperation":
273             return "composite";
274
275         case "bezierCurveTo":
276         case "quadraticCurveTo":
277             return "curve";
278
279         case "clearRect":
280         case "fill":
281         case "fillRect":
282         case "fillText":
283             return "fill";
284
285         case "createImageData":
286         case "drawFocusIfNeeded":
287         case "drawImage":
288         case "drawImageFromRect":
289         case "filter":
290         case "getImageData":
291         case "imageSmoothingEnabled":
292         case "imageSmoothingQuality":
293         case "putImageData":
294         case "transferFromImageBitmap":
295         case "webkitImageSmoothingEnabled":
296             return "image";
297
298         case "getLineDash":
299         case "lineCap":
300         case "lineDashOffset":
301         case "lineJoin":
302         case "lineWidth":
303         case "miterLimit":
304         case "setLineCap":
305         case "setLineDash":
306         case "setLineJoin":
307         case "setLineWidth":
308         case "setMiterLimit":
309         case "webkitLineDash":
310         case "webkitLineDashOffset":
311             return "line-style";
312
313         case "closePath":
314         case "lineTo":
315             return "line-to";
316
317         case "beginPath":
318         case "moveTo":
319             return "move-to";
320
321         case "isPointInPath":
322             return "point-in-path";
323
324         case "isPointInStroke":
325             return "point-in-stroke";
326
327         case "clearShadow":
328         case "setShadow":
329         case "shadowBlur":
330         case "shadowColor":
331         case "shadowOffsetX":
332         case "shadowOffsetY":
333             return "shadow";
334
335         case "createLinearGradient":
336         case "createPattern":
337         case "createRadialGradient":
338         case "fillStyle":
339         case "setFillColor":
340         case "setStrokeColor":
341         case "strokeStyle":
342             return "style";
343
344         case "stroke":
345         case "strokeRect":
346         case "strokeText":
347             return "stroke";
348
349         case "direction":
350         case "font":
351         case "measureText":
352         case "textAlign":
353         case "textBaseline":
354             return "text";
355
356         case "getTransform":
357         case "resetTransform":
358         case "rotate":
359         case "scale":
360         case "setTransform":
361         case "transform":
362         case "translate":
363             return "transform";
364
365         case "clip":
366         case "ellipse":
367         case "rect":
368         case "restore":
369         case "save":
370             return recordingAction.name;
371         }
372
373         return "name-unknown";
374     }
375
376     // Public
377
378     get index() { return this._index; }
379
380     get filterableData()
381     {
382         let text = [];
383
384         function getText(stringOrElement) {
385             if (typeof stringOrElement === "string")
386                 text.push(stringOrElement);
387             else if (stringOrElement instanceof Node)
388                 text.push(stringOrElement.textContent);
389         }
390
391         getText(this._mainTitleElement || this._mainTitle);
392         getText(this._subtitleElement || this._subtitle);
393
394         return {text};
395     }
396
397     // Protected
398
399     customTitleTooltip()
400     {
401         return this._copyText;
402     }
403
404     onattach()
405     {
406         super.onattach();
407
408         this.element.dataset.index = this._index.toLocaleString();
409
410         if (this.representedObject.valid && this.representedObject.warning) {
411             this.addClassName("warning");
412             this.status = WI.ImageUtilities.useSVGSymbol("Images/Warning.svg", "warning", this.representedObject.warning);
413         }
414     }
415
416     populateContextMenu(contextMenu, event)
417     {
418         contextMenu.appendItem(WI.UIString("Copy Action"), () => {
419             InspectorFrontendHost.copyText("context." + this._copyText + ";");
420         });
421
422         contextMenu.appendSeparator();
423
424         let sourceCodeLocation = null;
425         for (let callFrame of this.representedObject.trace) {
426             if (callFrame.sourceCodeLocation) {
427                 sourceCodeLocation = callFrame.sourceCodeLocation;
428                 break;
429             }
430         }
431
432         if (sourceCodeLocation) {
433             let label = null;
434             if (WI.settings.experimentalEnableSourcesTab.value)
435                 label = WI.UIString("Reveal in Sources Tab");
436             else
437                 label = WI.UIString("Reveal in Resources Tab");
438             contextMenu.appendItem(label, () => {
439                 WI.showSourceCodeLocation(sourceCodeLocation, {
440                     ignoreNetworkTab: true,
441                     ignoreSearchTab: true,
442                 });
443             });
444
445             contextMenu.appendSeparator();
446         }
447
448         super.populateContextMenu(contextMenu, event);
449     }
450
451     // Private
452
453     _handleValidityChanged(event)
454     {
455         this.addClassName("invalid");
456
457         this.representedObject.removeEventListener(null, null, this);
458     }
459 };