Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / toolbox / MediaQueryInspector.js
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6  * @constructor
7  * @extends {WebInspector.View}
8  * @implements {WebInspector.TargetManager.Observer}
9  */
10 WebInspector.MediaQueryInspector = function()
11 {
12     WebInspector.View.call(this);
13     this.element.classList.add("media-inspector-view", "media-inspector-view-empty");
14     this.element.addEventListener("click", this._onMediaQueryClicked.bind(this), false);
15     this.element.addEventListener("contextmenu", this._onContextMenu.bind(this), false);
16     this._mediaThrottler = new WebInspector.Throttler(0);
17
18     this._offset = 0;
19     this._scale = 1;
20     this._lastReportedCount = 0;
21
22     WebInspector.targetManager.observeTargets(this);
23
24     WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._renderMediaQueries.bind(this), this);
25 }
26
27 /**
28  * @enum {number}
29  */
30 WebInspector.MediaQueryInspector.Section = {
31     Max: 0,
32     MinMax: 1,
33     Min: 2
34 }
35
36 WebInspector.MediaQueryInspector.Events = {
37     HeightUpdated: "HeightUpdated",
38     CountUpdated: "CountUpdated"
39 }
40
41 WebInspector.MediaQueryInspector.prototype = {
42     /**
43      * @param {!WebInspector.Target} target
44      */
45     targetAdded: function(target)
46     {
47         // FIXME: adapt this to multiple targets.
48         if (this._target)
49             return;
50         this._target = target;
51         target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._scheduleMediaQueriesUpdate, this);
52         target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._scheduleMediaQueriesUpdate, this);
53         target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._scheduleMediaQueriesUpdate, this);
54         target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._scheduleMediaQueriesUpdate, this);
55     },
56
57     /**
58      * @param {!WebInspector.Target} target
59      */
60     targetRemoved: function(target)
61     {
62         if (target !== this._target)
63             return;
64         target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._scheduleMediaQueriesUpdate, this);
65         target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._scheduleMediaQueriesUpdate, this);
66         target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._scheduleMediaQueriesUpdate, this);
67         target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._scheduleMediaQueriesUpdate, this);
68     },
69
70     /**
71      * @param {number} offset
72      * @param {number} scale
73      */
74     setAxisTransform: function(offset, scale)
75     {
76         if (this._offset === offset && Math.abs(this._scale - scale) < 1e-8)
77             return;
78         this._offset = offset;
79         this._scale = scale;
80         this._renderMediaQueries();
81     },
82
83     /**
84      * @param {boolean} enabled
85      */
86     setEnabled: function(enabled)
87     {
88         this._enabled = enabled;
89         this._scheduleMediaQueriesUpdate();
90     },
91
92     /**
93      * @param {!Event} event
94      */
95     _onMediaQueryClicked: function(event)
96     {
97         var mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass("media-inspector-marker");
98         if (!mediaQueryMarker)
99             return;
100
101         /**
102          * @param {number} width
103          */
104         function setWidth(width)
105         {
106             WebInspector.overridesSupport.settings.deviceWidth.set(width);
107             WebInspector.overridesSupport.settings.emulateResolution.set(true);
108         }
109
110         var model = mediaQueryMarker._model;
111         if (model.section() === WebInspector.MediaQueryInspector.Section.Max) {
112             setWidth(model.maxWidthExpression().computedLength());
113             return;
114         }
115         if (model.section() === WebInspector.MediaQueryInspector.Section.Min) {
116             setWidth(model.minWidthExpression().computedLength());
117             return;
118         }
119         var currentWidth = WebInspector.overridesSupport.settings.deviceWidth.get();
120         if (currentWidth !== model.minWidthExpression().computedLength())
121             setWidth(model.minWidthExpression().computedLength());
122         else
123             setWidth(model.maxWidthExpression().computedLength());
124     },
125
126     /**
127      * @param {!Event} event
128      */
129     _onContextMenu: function(event)
130     {
131         var mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass("media-inspector-marker");
132         if (!mediaQueryMarker)
133             return;
134
135         var locations = mediaQueryMarker._locations;
136         var uiLocations = new Map();
137         for (var i = 0; i < locations.length; ++i) {
138             var uiLocation = WebInspector.cssWorkspaceBinding.rawLocationToUILocation(locations[i]);
139             if (!uiLocation)
140                 continue;
141             var descriptor = String.sprintf("%s:%d:%d", uiLocation.uiSourceCode.uri(), uiLocation.lineNumber + 1, uiLocation.columnNumber + 1);
142             uiLocations.set(descriptor, uiLocation);
143         }
144
145         var contextMenuItems = uiLocations.keysArray().sort();
146         var contextMenu = new WebInspector.ContextMenu(event);
147         var subMenuItem = contextMenu.appendSubMenuItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in source code" : "Reveal In Source Code"));
148         for (var i = 0; i < contextMenuItems.length; ++i) {
149             var title = contextMenuItems[i];
150             subMenuItem.appendItem(title, this._revealSourceLocation.bind(this, /** @type {!WebInspector.UILocation} */(uiLocations.get(title))));
151         }
152         contextMenu.show();
153     },
154
155     /**
156      * @param {!WebInspector.UILocation} location
157      */
158     _revealSourceLocation: function(location)
159     {
160         WebInspector.Revealer.reveal(location);
161     },
162
163     _scheduleMediaQueriesUpdate: function()
164     {
165         if (!this._enabled)
166             return;
167         this._mediaThrottler.schedule(this._refetchMediaQueries.bind(this));
168     },
169
170     /**
171      * @param {!WebInspector.Throttler.FinishCallback} finishCallback
172      */
173     _refetchMediaQueries: function(finishCallback)
174     {
175         if (!this._enabled) {
176             finishCallback();
177             return;
178         }
179
180         /**
181          * @param {!Array.<!WebInspector.CSSMedia>} cssMedias
182          * @this {!WebInspector.MediaQueryInspector}
183          */
184         function callback(cssMedias)
185         {
186             this._rebuildMediaQueries(cssMedias);
187             finishCallback();
188         }
189         this._target.cssModel.getMediaQueries(callback.bind(this));
190     },
191
192     /**
193      * @param {!Array.<!WebInspector.MediaQueryInspector.MediaQueryUIModel>} models
194      * @return {!Array.<!WebInspector.MediaQueryInspector.MediaQueryUIModel>}
195      */
196     _squashAdjacentEqual: function(models)
197     {
198         var filtered = [];
199         for (var i = 0; i < models.length; ++i) {
200             var last = filtered.peekLast();
201             if (!last || !last.equals(models[i]))
202                 filtered.push(models[i]);
203         }
204         return filtered;
205     },
206
207     /**
208      * @param {!Array.<!WebInspector.CSSMedia>} cssMedias
209      */
210     _rebuildMediaQueries: function(cssMedias)
211     {
212         var queryModels = [];
213         for (var i = 0; i < cssMedias.length; ++i) {
214             var cssMedia = cssMedias[i];
215             if (!cssMedia.mediaList)
216                 continue;
217             for (var j = 0; j < cssMedia.mediaList.length; ++j) {
218                 var mediaQuery = cssMedia.mediaList[j];
219                 var queryModel = WebInspector.MediaQueryInspector.MediaQueryUIModel.createFromMediaQuery(cssMedia, mediaQuery);
220                 if (queryModel && queryModel.rawLocation())
221                     queryModels.push(queryModel);
222             }
223         }
224         queryModels.sort(compareModels);
225         queryModels = this._squashAdjacentEqual(queryModels);
226
227         var allEqual = this._cachedQueryModels && this._cachedQueryModels.length == queryModels.length;
228         for (var i = 0; allEqual && i < queryModels.length; ++i)
229             allEqual = allEqual && this._cachedQueryModels[i].equals(queryModels[i]);
230         if (allEqual)
231             return;
232         this._cachedQueryModels = queryModels;
233         this._renderMediaQueries();
234
235         /**
236          * @param {!WebInspector.MediaQueryInspector.MediaQueryUIModel} model1
237          * @param {!WebInspector.MediaQueryInspector.MediaQueryUIModel} model2
238          * @return {number}
239          */
240         function compareModels(model1, model2)
241         {
242             return model1.compareTo(model2);
243         }
244     },
245
246     _renderMediaQueries: function()
247     {
248         if (!this._cachedQueryModels)
249             return;
250
251         var markers = [];
252         var lastMarker = null;
253         for (var i = 0; i < this._cachedQueryModels.length; ++i) {
254             var model = this._cachedQueryModels[i];
255             if (lastMarker && lastMarker.model.dimensionsEqual(model)) {
256                 lastMarker.locations.push(model.rawLocation());
257                 lastMarker.active = lastMarker.active || model.active();
258             } else {
259                 lastMarker = {
260                     active: model.active(),
261                     model: model,
262                     locations: [ model.rawLocation() ]
263                 };
264                 markers.push(lastMarker);
265             }
266         }
267
268         if (markers.length !== this._lastReportedCount) {
269             this._lastReportedCount = markers.length;
270             this.dispatchEventToListeners(WebInspector.MediaQueryInspector.Events.CountUpdated, markers.length);
271         }
272
273         if (!this.isShowing())
274             return;
275
276         var oldChildrenCount = this.element.children.length;
277         var scrollTop = this.element.scrollTop;
278         this.element.removeChildren();
279
280         var container = null;
281         for (var i = 0; i < markers.length; ++i) {
282             if (!i || markers[i].model.section() !== markers[i - 1].model.section())
283                 container = this.element.createChild("div", "media-inspector-marker-container");
284             var marker = markers[i];
285             var bar = this._createElementFromMediaQueryModel(marker.model);
286             bar._model = marker.model;
287             bar._locations = marker.locations;
288             bar.classList.toggle("media-inspector-marker-inactive", !marker.active);
289             container.appendChild(bar);
290         }
291         this.element.scrollTop = scrollTop;
292         this.element.classList.toggle("media-inspector-view-empty", !this.element.children.length);
293         if (this.element.children.length !== oldChildrenCount)
294             this.dispatchEventToListeners(WebInspector.MediaQueryInspector.Events.HeightUpdated);
295     },
296
297     /**
298      * @return {number}
299      */
300     _zoomFactor: function()
301     {
302         return WebInspector.zoomManager.zoomFactor() / this._scale;
303     },
304
305     wasShown: function()
306     {
307         this._renderMediaQueries();
308     },
309
310     /**
311      * @param {!WebInspector.MediaQueryInspector.MediaQueryUIModel} model
312      * @return {!Element}
313      */
314     _createElementFromMediaQueryModel: function(model)
315     {
316         var zoomFactor = this._zoomFactor();
317         var minWidthValue = model.minWidthExpression() ? model.minWidthExpression().computedLength() : 0;
318
319         const styleClassPerSection = [
320             "media-inspector-marker-max-width",
321             "media-inspector-marker-min-max-width",
322             "media-inspector-marker-min-width"
323         ];
324         var markerElement = createElementWithClass("div", "media-inspector-marker");
325         var leftPixelValue = minWidthValue ? (minWidthValue - this._offset) / zoomFactor : 0;
326         markerElement.style.left = leftPixelValue + "px";
327         markerElement.classList.add(styleClassPerSection[model.section()]);
328         var widthPixelValue = null;
329         if (model.maxWidthExpression() && model.minWidthExpression())
330             widthPixelValue = (model.maxWidthExpression().computedLength() - minWidthValue) / zoomFactor;
331         else if (model.maxWidthExpression())
332             widthPixelValue = (model.maxWidthExpression().computedLength() - this._offset) / zoomFactor;
333         else
334             markerElement.style.right = "0";
335         if (typeof widthPixelValue === "number")
336             markerElement.style.width = widthPixelValue + "px";
337
338         if (model.minWidthExpression()) {
339             var labelClass = model.section() === WebInspector.MediaQueryInspector.Section.MinMax ? "media-inspector-label-right" : "media-inspector-label-left";
340             var labelContainer = markerElement.createChild("div", "media-inspector-marker-label-container media-inspector-marker-label-container-left");
341             labelContainer.createChild("span", "media-inspector-marker-label " + labelClass).textContent = model.minWidthExpression().value() + model.minWidthExpression().unit();
342         }
343
344         if (model.maxWidthExpression()) {
345             var labelClass = model.section() === WebInspector.MediaQueryInspector.Section.MinMax ? "media-inspector-label-left" : "media-inspector-label-right";
346             var labelContainer = markerElement.createChild("div", "media-inspector-marker-label-container media-inspector-marker-label-container-right");
347             labelContainer.createChild("span", "media-inspector-marker-label " + labelClass).textContent = model.maxWidthExpression().value() + model.maxWidthExpression().unit();
348         }
349         markerElement.title = model.mediaText();
350
351         return markerElement;
352     },
353
354     __proto__: WebInspector.View.prototype
355 };
356
357 /**
358  * @constructor
359  * @param {!WebInspector.CSSMedia} cssMedia
360  * @param {?WebInspector.CSSMediaQueryExpression} minWidthExpression
361  * @param {?WebInspector.CSSMediaQueryExpression} maxWidthExpression
362  * @param {boolean} active
363  */
364 WebInspector.MediaQueryInspector.MediaQueryUIModel = function(cssMedia, minWidthExpression, maxWidthExpression, active)
365 {
366     this._cssMedia = cssMedia;
367     this._minWidthExpression = minWidthExpression;
368     this._maxWidthExpression = maxWidthExpression;
369     this._active = active;
370     if (maxWidthExpression && !minWidthExpression)
371         this._section = WebInspector.MediaQueryInspector.Section.Max;
372     else if (minWidthExpression && maxWidthExpression)
373         this._section = WebInspector.MediaQueryInspector.Section.MinMax;
374     else
375         this._section = WebInspector.MediaQueryInspector.Section.Min;
376 }
377
378 /**
379  * @param {!WebInspector.CSSMedia} cssMedia
380  * @param {!WebInspector.CSSMediaQuery} mediaQuery
381  * @return {?WebInspector.MediaQueryInspector.MediaQueryUIModel}
382  */
383 WebInspector.MediaQueryInspector.MediaQueryUIModel.createFromMediaQuery = function(cssMedia, mediaQuery)
384 {
385     var maxWidthExpression = null;
386     var maxWidthPixels = Number.MAX_VALUE;
387     var minWidthExpression = null;
388     var minWidthPixels = Number.MIN_VALUE;
389     var expressions = mediaQuery.expressions();
390     for (var i = 0; i < expressions.length; ++i) {
391         var expression = expressions[i];
392         var feature = expression.feature();
393         if (feature.indexOf("width") === -1)
394             continue;
395         var pixels = expression.computedLength();
396         if (feature.startsWith("max-") && pixels < maxWidthPixels) {
397             maxWidthExpression = expression;
398             maxWidthPixels = pixels;
399         } else if (feature.startsWith("min-") && pixels > minWidthPixels) {
400             minWidthExpression = expression;
401             minWidthPixels = pixels;
402         }
403     }
404     if (minWidthPixels > maxWidthPixels || (!maxWidthExpression && !minWidthExpression))
405         return null;
406
407     return new WebInspector.MediaQueryInspector.MediaQueryUIModel(cssMedia, minWidthExpression, maxWidthExpression, mediaQuery.active());
408 }
409
410 WebInspector.MediaQueryInspector.MediaQueryUIModel.prototype = {
411     /**
412      * @param {!WebInspector.MediaQueryInspector.MediaQueryUIModel} other
413      * @return {boolean}
414      */
415     equals: function(other)
416     {
417         return this.compareTo(other) === 0;
418     },
419
420     /**
421      * @param {!WebInspector.MediaQueryInspector.MediaQueryUIModel} other
422      * @return {boolean}
423      */
424     dimensionsEqual: function(other)
425     {
426         return this.section() === other.section()
427             && (!this.minWidthExpression() || (this.minWidthExpression().computedLength() === other.minWidthExpression().computedLength()))
428             && (!this.maxWidthExpression() || (this.maxWidthExpression().computedLength() === other.maxWidthExpression().computedLength()));
429     },
430
431     /**
432      * @param {!WebInspector.MediaQueryInspector.MediaQueryUIModel} other
433      * @return {number}
434      */
435     compareTo: function(other)
436     {
437         if (this.section() !== other.section())
438             return this.section() - other.section();
439         if (this.dimensionsEqual(other)) {
440             var myLocation = this.rawLocation();
441             var otherLocation = other.rawLocation();
442             if (!myLocation && !otherLocation)
443                 return this.mediaText().compareTo(other.mediaText());
444             if (myLocation && !otherLocation)
445                 return 1;
446             if (!myLocation && otherLocation)
447                 return -1;
448             if (this.active() !== other.active())
449                 return this.active() ? -1 : 1;
450             return myLocation.url.compareTo(otherLocation.url) || myLocation.lineNumber - otherLocation.lineNumber || myLocation.columnNumber - otherLocation.columnNumber;
451         }
452         if (this.section() === WebInspector.MediaQueryInspector.Section.Max)
453             return other.maxWidthExpression().computedLength() - this.maxWidthExpression().computedLength();
454         if (this.section() === WebInspector.MediaQueryInspector.Section.Min)
455             return this.minWidthExpression().computedLength() - other.minWidthExpression().computedLength();
456         return this.minWidthExpression().computedLength() - other.minWidthExpression().computedLength() || other.maxWidthExpression().computedLength() - this.maxWidthExpression().computedLength();
457     },
458
459     /**
460      * @return {!WebInspector.MediaQueryInspector.Section}
461      */
462     section: function()
463     {
464         return this._section;
465     },
466
467     /**
468      * @return {string}
469      */
470     mediaText: function()
471     {
472         return this._cssMedia.text;
473     },
474
475     /**
476      * @return {?WebInspector.CSSLocation}
477      */
478     rawLocation: function()
479     {
480         if (!this._rawLocation)
481             this._rawLocation = this._cssMedia.rawLocation();
482         return this._rawLocation;
483     },
484
485     /**
486      * @return {?WebInspector.CSSMediaQueryExpression}
487      */
488     minWidthExpression: function()
489     {
490         return this._minWidthExpression;
491     },
492
493     /**
494      * @return {?WebInspector.CSSMediaQueryExpression}
495      */
496     maxWidthExpression: function()
497     {
498         return this._maxWidthExpression;
499     },
500
501     /**
502      * @return {boolean}
503      */
504     active: function()
505     {
506         return this._active;
507     }
508 }