| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.VBox} |
| * @implements {WebInspector.OverridesSupport.PageResizer} |
| * @param {!WebInspector.InspectedPagePlaceholder} inspectedPagePlaceholder |
| */ |
| WebInspector.ResponsiveDesignView = function(inspectedPagePlaceholder) |
| { |
| WebInspector.VBox.call(this); |
| this.setMinimumSize(150, 150); |
| this.registerRequiredCSS("responsiveDesignView.css"); |
| this.element.classList.add("overflow-hidden"); |
| |
| this._responsiveDesignContainer = new WebInspector.VBox(); |
| |
| this._createToolbar(); |
| |
| this._mediaInspector = new WebInspector.MediaQueryInspector(); |
| this._mediaInspectorContainer = this._responsiveDesignContainer.element.createChild("div"); |
| this._updateMediaQueryInspector(); |
| |
| this._canvasContainer = new WebInspector.View(); |
| this._canvasContainer.element.classList.add("responsive-design"); |
| this._canvasContainer.show(this._responsiveDesignContainer.element); |
| |
| this._canvas = this._canvasContainer.element.createChild("canvas", "fill"); |
| |
| this._rulerGlasspane = this._canvasContainer.element.createChild("div", "responsive-design-ruler-glasspane"); |
| this._rulerGlasspane.appendChild(this._mediaInspector.rulerDecorationLayer()); |
| |
| this._warningMessage = this._canvasContainer.element.createChild("div", "responsive-design-warning hidden"); |
| this._warningMessage.createChild("div", "warning-icon-small"); |
| this._warningMessage.createChild("span"); |
| var warningCloseButton = this._warningMessage.createChild("div", "close-button"); |
| warningCloseButton.addEventListener("click", WebInspector.overridesSupport.clearWarningMessage.bind(WebInspector.overridesSupport), false); |
| WebInspector.overridesSupport.addEventListener(WebInspector.OverridesSupport.Events.OverridesWarningUpdated, this._overridesWarningUpdated, this); |
| |
| this._slidersContainer = this._canvasContainer.element.createChild("div", "vbox responsive-design-sliders-container"); |
| var hbox = this._slidersContainer.createChild("div", "hbox flex-auto"); |
| this._heightSliderContainer = this._slidersContainer.createChild("div", "hbox responsive-design-slider-height"); |
| this._pageContainer = hbox.createChild("div", "vbox flex-auto"); |
| this._widthSliderContainer = hbox.createChild("div", "vbox responsive-design-slider-width"); |
| |
| this._widthSlider = this._widthSliderContainer.createChild("div", "responsive-design-slider-thumb"); |
| this._widthSlider.createChild("div", "responsive-design-thumb-handle"); |
| this._createResizer(this._widthSlider, false); |
| this._heightSlider = this._heightSliderContainer.createChild("div", "responsive-design-slider-thumb"); |
| this._heightSlider.createChild("div", "responsive-design-thumb-handle"); |
| this._createResizer(this._heightSlider, true); |
| |
| this._inspectedPagePlaceholder = inspectedPagePlaceholder; |
| inspectedPagePlaceholder.show(this.element); |
| |
| this._enabled = false; |
| |
| WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this); |
| WebInspector.overridesSupport.addEventListener(WebInspector.OverridesSupport.Events.EmulationStateChanged, this._emulationEnabledChanged, this); |
| this._mediaInspector.addEventListener(WebInspector.MediaQueryInspector.Events.HeightUpdated, this.onResize, this); |
| this._emulationEnabledChanged(); |
| this._overridesWarningUpdated(); |
| }; |
| |
| // Measured in DIP. |
| WebInspector.ResponsiveDesignView.SliderWidth = 19; |
| WebInspector.ResponsiveDesignView.RulerWidth = 22; |
| |
| WebInspector.ResponsiveDesignView.prototype = { |
| _invalidateCache: function() |
| { |
| delete this._cachedScale; |
| delete this._cachedCssCanvasWidth; |
| delete this._cachedCssCanvasHeight; |
| delete this._cachedCssHeight; |
| delete this._cachedCssWidth; |
| delete this._cachedZoomFactor; |
| delete this._availableSize; |
| }, |
| |
| _emulationEnabledChanged: function() |
| { |
| var enabled = WebInspector.overridesSupport.emulationEnabled(); |
| this._mediaInspector.setEnabled(enabled); |
| if (enabled && !this._enabled) { |
| this._invalidateCache(); |
| this._ignoreResize = true; |
| this._enabled = true; |
| this._inspectedPagePlaceholder.clearMinimumSizeAndMargins(); |
| this._inspectedPagePlaceholder.show(this._pageContainer); |
| this._responsiveDesignContainer.show(this.element); |
| delete this._ignoreResize; |
| this.onResize(); |
| } else if (!enabled && this._enabled) { |
| this._invalidateCache(); |
| this._ignoreResize = true; |
| this._enabled = false; |
| this._scale = 1; |
| this._inspectedPagePlaceholder.restoreMinimumSizeAndMargins(); |
| this._responsiveDesignContainer.detach(); |
| this._inspectedPagePlaceholder.show(this.element); |
| delete this._ignoreResize; |
| this.onResize(); |
| } |
| }, |
| |
| /** |
| * WebInspector.OverridesSupport.PageResizer override. |
| * @param {number} dipWidth |
| * @param {number} dipHeight |
| * @param {number} scale |
| */ |
| update: function(dipWidth, dipHeight, scale) |
| { |
| this._scale = scale; |
| this._dipWidth = dipWidth; |
| this._dipHeight = dipHeight; |
| this._updateUI(); |
| }, |
| |
| updatePageResizer: function() |
| { |
| WebInspector.overridesSupport.setPageResizer(this, this._availableDipSize()); |
| }, |
| |
| /** |
| * @return {!Size} |
| */ |
| _availableDipSize: function() |
| { |
| if (typeof this._availableSize === "undefined") { |
| var zoomFactor = WebInspector.zoomManager.zoomFactor(); |
| var rect = this._canvasContainer.element.getBoundingClientRect(); |
| this._availableSize = new Size(rect.width * zoomFactor - WebInspector.ResponsiveDesignView.RulerWidth, |
| rect.height * zoomFactor - WebInspector.ResponsiveDesignView.RulerWidth); |
| } |
| return this._availableSize; |
| }, |
| |
| /** |
| * @param {!Element} element |
| * @param {boolean} vertical |
| * @return {!WebInspector.ResizerWidget} |
| */ |
| _createResizer: function(element, vertical) |
| { |
| var resizer = new WebInspector.ResizerWidget(); |
| resizer.addElement(element); |
| resizer.setVertical(vertical); |
| resizer.addEventListener(WebInspector.ResizerWidget.Events.ResizeStart, this._onResizeStart, this); |
| resizer.addEventListener(WebInspector.ResizerWidget.Events.ResizeUpdate, this._onResizeUpdate, this); |
| resizer.addEventListener(WebInspector.ResizerWidget.Events.ResizeEnd, this._onResizeEnd, this); |
| return resizer; |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _onResizeStart: function(event) |
| { |
| var available = this._availableDipSize(); |
| this._slowPositionStart = null; |
| this._resizeStartSize = event.target.isVertical() ? (this._dipHeight || available.height) : (this._dipWidth || available.width); |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _onResizeUpdate: function(event) |
| { |
| if (event.data.shiftKey !== !!this._slowPositionStart) |
| this._slowPositionStart = event.data.shiftKey ? event.data.currentPosition : null; |
| var cssOffset = this._slowPositionStart ? (event.data.currentPosition - this._slowPositionStart) / 10 + this._slowPositionStart - event.data.startPosition : event.data.currentPosition - event.data.startPosition; |
| var dipOffset = Math.round(cssOffset * WebInspector.zoomManager.zoomFactor()); |
| var newSize = Math.max(this._resizeStartSize + dipOffset, 1); |
| var requested = {}; |
| if (event.target.isVertical()) |
| requested.height = newSize; |
| else |
| requested.width = newSize; |
| this.dispatchEventToListeners(WebInspector.OverridesSupport.PageResizer.Events.ResizeRequested, requested); |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _onResizeEnd: function(event) |
| { |
| delete this._resizeStartSize; |
| }, |
| |
| /** |
| * Draws canvas of the specified css size in DevTools page space. |
| * Canvas contains grid and rulers. |
| * @param {number} cssCanvasWidth |
| * @param {number} cssCanvasHeight |
| */ |
| _drawCanvas: function(cssCanvasWidth, cssCanvasHeight) |
| { |
| if (!this._enabled) |
| return; |
| |
| var canvas = this._canvas; |
| var context = canvas.getContext("2d"); |
| canvas.style.width = cssCanvasWidth + "px"; |
| canvas.style.height = cssCanvasHeight + "px"; |
| |
| var zoomFactor = WebInspector.zoomManager.zoomFactor(); |
| var dipCanvasWidth = cssCanvasWidth * zoomFactor; |
| var dipCanvasHeight = cssCanvasHeight * zoomFactor; |
| |
| var deviceScaleFactor = window.devicePixelRatio; |
| canvas.width = deviceScaleFactor * cssCanvasWidth; |
| canvas.height = deviceScaleFactor * cssCanvasHeight; |
| context.scale(canvas.width / dipCanvasWidth, canvas.height / dipCanvasHeight); |
| context.font = "11px " + WebInspector.fontFamily(); |
| |
| const rulerStep = 100; |
| const rulerSubStep = 5; |
| const gridStep = 50; |
| const gridSubStep = 10; |
| const rulerBackgroundColor = "rgb(0, 0, 0)"; |
| const backgroundColor = "rgb(102, 102, 102)"; |
| const lightLineColor = "rgb(132, 132, 132)"; |
| const darkLineColor = "rgb(114, 114, 114)"; |
| const rulerColor = "rgb(125, 125, 125)"; |
| const textColor = "rgb(186, 186, 186)"; |
| |
| var scale = this._scale || 1; |
| var rulerWidth = WebInspector.ResponsiveDesignView.RulerWidth; |
| var dipGridWidth = dipCanvasWidth / scale - rulerWidth; |
| var dipGridHeight = dipCanvasHeight / scale - rulerWidth; |
| rulerWidth /= scale; |
| context.scale(scale, scale); |
| context.translate(rulerWidth, rulerWidth); |
| |
| context.fillStyle = rulerBackgroundColor; |
| context.fillRect(-rulerWidth, -rulerWidth, dipGridWidth + rulerWidth, rulerWidth); |
| context.fillRect(-rulerWidth, 0, rulerWidth, dipGridHeight); |
| |
| context.fillStyle = backgroundColor; |
| context.fillRect(0, 0, dipGridWidth, dipGridHeight); |
| |
| context.translate(0.5, 0.5); |
| context.strokeStyle = rulerColor; |
| context.fillStyle = textColor; |
| context.lineWidth = 1; |
| |
| // Draw vertical ruler. |
| for (var x = 0; x < dipGridWidth; x += rulerSubStep) { |
| var y = -rulerWidth / 4; |
| if (!(x % (rulerStep / 4))) |
| y = -rulerWidth / 2; |
| if (!(x % (rulerStep / 2))) |
| y = -rulerWidth + 2; |
| |
| if (!(x % rulerStep)) { |
| context.save(); |
| context.translate(x, 0); |
| context.fillText(x, 2, -rulerWidth / 2); |
| context.restore(); |
| y = -rulerWidth; |
| } |
| |
| context.beginPath(); |
| context.moveTo(x, y); |
| context.lineTo(x, 0); |
| context.stroke(); |
| } |
| |
| // Draw horizontal ruler. |
| for (var y = 0; y < dipGridHeight; y += rulerSubStep) { |
| x = -rulerWidth / 4; |
| if (!(y % (rulerStep / 4))) |
| x = -rulerWidth / 2; |
| if (!(y % (rulerStep / 2))) |
| x = -rulerWidth + 2; |
| |
| if (!(y % rulerStep)) { |
| context.save(); |
| context.translate(0, y); |
| context.rotate(-Math.PI / 2); |
| context.fillText(y, 2, -rulerWidth / 2); |
| context.restore(); |
| x = -rulerWidth; |
| } |
| |
| context.beginPath(); |
| context.moveTo(x, y); |
| context.lineTo(0, y); |
| context.stroke(); |
| } |
| |
| // Draw grid. |
| drawGrid(darkLineColor, gridSubStep); |
| drawGrid(lightLineColor, gridStep); |
| |
| /** |
| * @param {string} color |
| * @param {number} step |
| */ |
| function drawGrid(color, step) |
| { |
| context.strokeStyle = color; |
| for (var x = 0; x < dipGridWidth; x += step) { |
| context.beginPath(); |
| context.moveTo(x, 0); |
| context.lineTo(x, dipGridHeight); |
| context.stroke(); |
| } |
| for (var y = 0; y < dipGridHeight; y += step) { |
| context.beginPath(); |
| context.moveTo(0, y); |
| context.lineTo(dipGridWidth, y); |
| context.stroke(); |
| } |
| } |
| }, |
| |
| _updateUI: function() |
| { |
| if (!this._enabled || !this.isShowing()) |
| return; |
| |
| var zoomFactor = WebInspector.zoomManager.zoomFactor(); |
| var rect = this._canvas.parentElement.getBoundingClientRect(); |
| var availableDip = this._availableDipSize(); |
| var cssCanvasWidth = rect.width; |
| var cssCanvasHeight = rect.height; |
| |
| this._widthSlider.classList.toggle("hidden", !!this._scale); |
| this._heightSlider.classList.toggle("hidden", !!this._scale); |
| this._widthSlider.classList.toggle("reversed", !this._dipWidth); |
| this._heightSlider.classList.toggle("reversed", !this._dipHeight); |
| |
| if (this._cachedZoomFactor !== zoomFactor) { |
| var cssRulerWidth = WebInspector.ResponsiveDesignView.RulerWidth / zoomFactor + "px"; |
| this._rulerGlasspane.style.height = cssRulerWidth; |
| this._rulerGlasspane.style.left = cssRulerWidth; |
| this._slidersContainer.style.left = cssRulerWidth; |
| this._mediaInspector.translateZero(WebInspector.ResponsiveDesignView.RulerWidth / zoomFactor); |
| this._slidersContainer.style.top = cssRulerWidth; |
| this._warningMessage.style.height = cssRulerWidth; |
| |
| var cssSliderWidth = WebInspector.ResponsiveDesignView.SliderWidth / zoomFactor + "px"; |
| this._heightSliderContainer.style.flexBasis = cssSliderWidth; |
| this._heightSliderContainer.style.marginBottom = "-" + cssSliderWidth; |
| this._widthSliderContainer.style.flexBasis = cssSliderWidth; |
| this._widthSliderContainer.style.marginRight = "-" + cssSliderWidth; |
| } |
| |
| var cssWidth = this._dipWidth ? (this._dipWidth / zoomFactor + "px") : (availableDip.width / zoomFactor + "px"); |
| var cssHeight = this._dipHeight ? (this._dipHeight / zoomFactor + "px") : (availableDip.height / zoomFactor + "px"); |
| if (this._cachedCssWidth !== cssWidth || this._cachedCssHeight !== cssHeight) { |
| this._slidersContainer.style.width = cssWidth; |
| this._slidersContainer.style.height = cssHeight; |
| this._inspectedPagePlaceholder.onResize(); |
| } |
| |
| if (this._cachedScale !== this._scale || this._cachedCssCanvasWidth !== cssCanvasWidth || this._cachedCssCanvasHeight !== cssCanvasHeight || this._cachedZoomFactor !== zoomFactor) |
| this._drawCanvas(cssCanvasWidth, cssCanvasHeight); |
| |
| this._cachedScale = this._scale; |
| this._cachedCssCanvasWidth = cssCanvasWidth; |
| this._cachedCssCanvasHeight = cssCanvasHeight; |
| this._cachedCssHeight = cssHeight; |
| this._cachedCssWidth = cssWidth; |
| this._cachedZoomFactor = zoomFactor; |
| }, |
| |
| onResize: function() |
| { |
| if (!this._enabled || this._ignoreResize) |
| return; |
| var oldSize = this._availableSize; |
| delete this._availableSize; |
| var newSize = this._availableDipSize(); |
| if (!newSize.isEqual(oldSize)) |
| this.dispatchEventToListeners(WebInspector.OverridesSupport.PageResizer.Events.AvailableSizeChanged, newSize); |
| this._updateUI(); |
| this._inspectedPagePlaceholder.onResize(); |
| }, |
| |
| _onZoomChanged: function() |
| { |
| this._updateUI(); |
| }, |
| |
| _createToolbar: function() |
| { |
| this._toolbarElement = this._responsiveDesignContainer.element.createChild("div", "responsive-design-toolbar"); |
| this._createButtonsSection(); |
| this._toolbarElement.createChild("div", "responsive-design-separator"); |
| this._createDeviceSection(); |
| if (WebInspector.experimentsSettings.networkConditions.isEnabled()) { |
| this._toolbarElement.createChild("div", "responsive-design-separator"); |
| this._createNetworkSection(); |
| } |
| this._toolbarElement.createChild("div", "responsive-design-separator"); |
| |
| var moreButtonContainer = this._toolbarElement.createChild("div", "responsive-design-more-button-container"); |
| var moreButton = moreButtonContainer.createChild("button", "responsive-design-more-button"); |
| moreButton.title = WebInspector.UIString("More overrides"); |
| moreButton.addEventListener("click", this._showEmulationInDrawer.bind(this), false); |
| moreButton.textContent = "\u2026"; |
| }, |
| |
| _createButtonsSection: function() |
| { |
| var buttonsSection = this._toolbarElement.createChild("div", "responsive-design-section responsive-design-section-buttons"); |
| |
| var resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Reset all overrides."), "clear-status-bar-item"); |
| buttonsSection.appendChild(resetButton.element); |
| resetButton.addEventListener("click", WebInspector.overridesSupport.reset, WebInspector.overridesSupport); |
| |
| // Media Query Inspector. |
| this._toggleMediaInspectorButton = new WebInspector.StatusBarButton(WebInspector.UIString("Media queries."), "responsive-design-toggle-media-inspector"); |
| this._toggleMediaInspectorButton.toggled = WebInspector.settings.showMediaQueryInspector.get(); |
| this._toggleMediaInspectorButton.addEventListener("click", this._onToggleMediaInspectorButtonClick, this); |
| WebInspector.settings.showMediaQueryInspector.addChangeListener(this._updateMediaQueryInspector, this); |
| buttonsSection.appendChild(this._toggleMediaInspectorButton.element); |
| }, |
| |
| _createDeviceSection: function() |
| { |
| var deviceSection = this._toolbarElement.createChild("div", "responsive-design-section responsive-design-section-device"); |
| |
| // Device. |
| var deviceElement = deviceSection.createChild("div", "responsive-design-suite responsive-design-suite-top").createChild("div"); |
| var fieldsetElement = deviceElement.createChild("fieldset"); |
| fieldsetElement.createChild("label").textContent = WebInspector.UIString("Device"); |
| fieldsetElement.appendChild(WebInspector.overridesSupport.createDeviceSelect(document)); |
| |
| var separator = deviceSection.createChild("div", "responsive-design-section-separator"); |
| |
| var detailsElement = deviceSection.createChild("div", "responsive-design-suite"); |
| |
| // Dimensions. |
| var screenElement = detailsElement.createChild("div", ""); |
| fieldsetElement = screenElement.createChild("fieldset"); |
| var resolutionButton = new WebInspector.StatusBarButton(WebInspector.UIString("Screen resolution"), "responsive-design-icon responsive-design-icon-resolution"); |
| resolutionButton.setEnabled(false); |
| fieldsetElement.appendChild(resolutionButton.element); |
| fieldsetElement.appendChild(WebInspector.SettingsUI.createSettingInputField("", WebInspector.overridesSupport.settings.deviceWidth, true, 4, "3em", WebInspector.OverridesSupport.deviceSizeValidator, true, true, WebInspector.UIString("\u2013"))); |
| fieldsetElement.appendChild(document.createTextNode(" \u00D7 ")); |
| fieldsetElement.appendChild(WebInspector.SettingsUI.createSettingInputField("", WebInspector.overridesSupport.settings.deviceHeight, true, 4, "3em", WebInspector.OverridesSupport.deviceSizeValidator, true, true, WebInspector.UIString("\u2013"))); |
| |
| var swapButton = new WebInspector.StatusBarButton(WebInspector.UIString("Swap dimensions"), "responsive-design-icon responsive-design-icon-swap"); |
| swapButton.element.tabIndex = -1; |
| swapButton.addEventListener("click", WebInspector.overridesSupport.swapDimensions, WebInspector.overridesSupport); |
| fieldsetElement.appendChild(swapButton.element); |
| |
| // Device pixel ratio. |
| detailsElement.createChild("div", "responsive-design-suite-separator"); |
| var dprElement = detailsElement.createChild("div", ""); |
| fieldsetElement = dprElement.createChild("fieldset"); |
| var dprButton = new WebInspector.StatusBarButton(WebInspector.UIString("Device pixel ratio"), "responsive-design-icon responsive-design-icon-dpr"); |
| dprButton.setEnabled(false); |
| fieldsetElement.appendChild(dprButton.element); |
| fieldsetElement.appendChild(WebInspector.SettingsUI.createSettingInputField("", WebInspector.overridesSupport.settings.deviceScaleFactor, true, 4, "2.5em", WebInspector.OverridesSupport.deviceScaleFactorValidator, true, true, WebInspector.UIString("\u2013"))); |
| }, |
| |
| _createNetworkSection: function() |
| { |
| var networkSection = this._toolbarElement.createChild("div", "responsive-design-section responsive-design-section-network"); |
| |
| // Bandwidth. |
| var bandwidthElement = networkSection.createChild("div", "responsive-design-suite responsive-design-suite-top").createChild("div"); |
| var fieldsetElement = bandwidthElement.createChild("fieldset"); |
| var networkCheckbox = fieldsetElement.createChild("label"); |
| networkCheckbox.textContent = WebInspector.UIString("Network"); |
| fieldsetElement.appendChild(WebInspector.overridesSupport.createNetworkThroughputSelect(document)); |
| |
| var separator = networkSection.createChild("div", "responsive-design-section-separator"); |
| |
| // User agent. |
| var userAgentElement = networkSection.createChild("div", "responsive-design-suite").createChild("div"); |
| fieldsetElement = userAgentElement.createChild("fieldset"); |
| fieldsetElement.appendChild(WebInspector.SettingsUI.createSettingInputField("UA", WebInspector.overridesSupport.settings.userAgent, false, 0, "", undefined, false, false, WebInspector.UIString("No override"))); |
| }, |
| |
| _onToggleMediaInspectorButtonClick: function() |
| { |
| WebInspector.settings.showMediaQueryInspector.set(!this._toggleMediaInspectorButton.toggled); |
| }, |
| |
| _updateMediaQueryInspector: function() |
| { |
| this._toggleMediaInspectorButton.toggled = WebInspector.settings.showMediaQueryInspector.get(); |
| if (this._mediaInspector.isShowing() === WebInspector.settings.showMediaQueryInspector.get()) |
| return; |
| if (this._mediaInspector.isShowing()) |
| this._mediaInspector.detach(); |
| else |
| this._mediaInspector.show(this._mediaInspectorContainer); |
| this.onResize(); |
| }, |
| |
| _overridesWarningUpdated: function() |
| { |
| var message = WebInspector.overridesSupport.warningMessage(); |
| if (this._warningMessage.querySelector("span").textContent === message) |
| return; |
| this._warningMessage.classList.toggle("hidden", !message); |
| this._warningMessage.querySelector("span").textContent = message; |
| this._invalidateCache(); |
| this.onResize(); |
| }, |
| |
| _showEmulationInDrawer: function() |
| { |
| WebInspector.overridesSupport.reveal(); |
| }, |
| |
| __proto__: WebInspector.VBox.prototype |
| }; |