2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @extends {WebInspector.View}
34 * @implements {WebInspector.DOMNodeHighlighter}
35 * @param {!Element} statusBarButtonPlaceholder
37 WebInspector.ScreencastView = function(statusBarButtonPlaceholder)
39 WebInspector.View.call(this);
40 this.registerRequiredCSS("screencastView.css");
41 this._statusBarButtonPlaceholder = statusBarButtonPlaceholder;
44 WebInspector.ScreencastView._bordersSize = 40;
46 WebInspector.ScreencastView._navBarHeight = 29;
48 WebInspector.ScreencastView._HttpRegex = /^https?:\/\/(.+)/;
50 WebInspector.ScreencastView.prototype = {
51 initialize: function()
53 this.element.classList.add("screencast");
55 this._createNavigationBar();
57 this._viewportElement = this.element.createChild("div", "screencast-viewport hidden");
58 this._glassPaneElement = this.element.createChild("div", "screencast-glasspane hidden");
60 this._canvasElement = this._viewportElement.createChild("canvas");
61 this._canvasElement.tabIndex = 1;
62 this._canvasElement.addEventListener("mousedown", this._handleMouseEvent.bind(this), false);
63 this._canvasElement.addEventListener("mouseup", this._handleMouseEvent.bind(this), false);
64 this._canvasElement.addEventListener("mousemove", this._handleMouseEvent.bind(this), false);
65 this._canvasElement.addEventListener("mousewheel", this._handleMouseEvent.bind(this), false);
66 this._canvasElement.addEventListener("click", this._handleMouseEvent.bind(this), false);
67 this._canvasElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
68 this._canvasElement.addEventListener("keydown", this._handleKeyEvent.bind(this), false);
69 this._canvasElement.addEventListener("keyup", this._handleKeyEvent.bind(this), false);
70 this._canvasElement.addEventListener("keypress", this._handleKeyEvent.bind(this), false);
72 this._titleElement = this._viewportElement.createChild("div", "screencast-element-title monospace hidden");
73 this._tagNameElement = this._titleElement.createChild("span", "screencast-tag-name");
74 this._nodeIdElement = this._titleElement.createChild("span", "screencast-node-id");
75 this._classNameElement = this._titleElement.createChild("span", "screencast-class-name");
76 this._titleElement.appendChild(document.createTextNode(" "));
77 this._nodeWidthElement = this._titleElement.createChild("span");
78 this._titleElement.createChild("span", "screencast-px").textContent = "px";
79 this._titleElement.appendChild(document.createTextNode(" \u00D7 "));
80 this._nodeHeightElement = this._titleElement.createChild("span");
81 this._titleElement.createChild("span", "screencast-px").textContent = "px";
83 this._imageElement = new Image();
84 this._isCasting = false;
85 this._context = this._canvasElement.getContext("2d");
86 this._checkerboardPattern = this._createCheckerboardPattern(this._context);
88 this._shortcuts = /** !Object.<number, function(Event=):boolean> */ ({});
89 this._shortcuts[WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl)] = this._focusNavigationBar.bind(this);
91 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ScreencastFrame, this._screencastFrame, this);
92 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ScreencastVisibilityChanged, this._screencastVisibilityChanged, this);
94 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStarted, this._onTimeline.bind(this, true), this);
95 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStopped, this._onTimeline.bind(this, false), this);
96 this._timelineActive = WebInspector.timelineManager.isStarted();
98 WebInspector.cpuProfilerModel.addEventListener(WebInspector.CPUProfilerModel.EventTypes.ProfileStarted, this._onProfiler.bind(this, true), this);
99 WebInspector.cpuProfilerModel.addEventListener(WebInspector.CPUProfilerModel.EventTypes.ProfileStopped, this._onProfiler.bind(this, false), this);
100 this._profilerActive = WebInspector.cpuProfilerModel.isRecordingProfile();
102 this._updateGlasspane();
104 this._currentScreencastState = WebInspector.settings.createSetting("currentScreencastState", "");
105 this._lastScreencastState = WebInspector.settings.createSetting("lastScreencastState", "");
106 this._toggleScreencastButton = new WebInspector.StatusBarStatesSettingButton(
107 "screencast-status-bar-item",
108 ["disabled", "left", "top"],
109 [WebInspector.UIString("Disable screencast."), WebInspector.UIString("Switch to portrait screencast."), WebInspector.UIString("Switch to landscape screencast.")],
110 this._currentScreencastState,
111 this._lastScreencastState,
112 this._toggleScreencastButtonClicked.bind(this));
113 this._statusBarButtonPlaceholder.parentElement.insertBefore(this._toggleScreencastButton.element, this._statusBarButtonPlaceholder);
114 this._statusBarButtonPlaceholder.parentElement.removeChild(this._statusBarButtonPlaceholder);
118 * @param {string} state
120 _toggleScreencastButtonClicked: function(state)
122 if (state === "disabled")
123 WebInspector.inspectorView.hideScreencastView();
125 WebInspector.inspectorView.showScreencastView(this, state === "left");
130 this._startCasting();
138 _startCasting: function()
140 if (this._timelineActive || this._profilerActive)
144 this._isCasting = true;
146 const maxImageDimension = 1024;
147 var dimensions = this._viewportDimensions();
148 if (dimensions.width < 0 || dimensions.height < 0) {
149 this._isCasting = false;
152 dimensions.width *= WebInspector.zoomFactor();
153 dimensions.height *= WebInspector.zoomFactor();
154 PageAgent.startScreencast("jpeg", 80, Math.min(maxImageDimension, dimensions.width), Math.min(maxImageDimension, dimensions.height));
155 WebInspector.domAgent.setHighlighter(this);
158 _stopCasting: function()
160 if (!this._isCasting)
162 this._isCasting = false;
163 PageAgent.stopScreencast();
164 WebInspector.domAgent.setHighlighter(null);
168 * @param {!WebInspector.Event} event
170 _screencastFrame: function(event)
172 var metadata = /** type {PageAgent.ScreencastFrameMetadata} */(event.data.metadata);
174 if (!metadata.deviceScaleFactor) {
175 console.log(event.data.data);
179 var base64Data = /** type {string} */(event.data.data);
180 this._imageElement.src = "data:image/jpg;base64," + base64Data;
181 this._deviceScaleFactor = metadata.deviceScaleFactor;
182 this._pageScaleFactor = metadata.pageScaleFactor;
183 this._viewport = metadata.viewport;
186 var offsetTop = metadata.offsetTop || 0;
187 var offsetBottom = metadata.offsetBottom || 0;
189 var screenWidthDIP = this._viewport.width * this._pageScaleFactor;
190 var screenHeightDIP = this._viewport.height * this._pageScaleFactor + offsetTop + offsetBottom;
191 this._screenOffsetTop = offsetTop;
192 this._resizeViewport(screenWidthDIP, screenHeightDIP);
194 this._imageZoom = this._imageElement.naturalWidth ? this._canvasElement.offsetWidth / this._imageElement.naturalWidth : 1;
195 this.highlightDOMNode(this._highlightNodeId, this._highlightConfig);
198 _isGlassPaneActive: function()
200 return !this._glassPaneElement.classList.contains("hidden");
204 * @param {!WebInspector.Event} event
206 _screencastVisibilityChanged: function(event)
208 this._targetInactive = !event.data.visible;
209 this._updateGlasspane();
213 * @param {boolean} on
216 _onTimeline: function(on)
218 this._timelineActive = on;
219 if (this._timelineActive)
222 this._startCasting();
223 this._updateGlasspane();
227 * @param {boolean} on
230 _onProfiler: function(on, event) {
231 this._profilerActive = on;
232 if (this._profilerActive)
235 this._startCasting();
236 this._updateGlasspane();
239 _updateGlasspane: function()
241 if (this._targetInactive) {
242 this._glassPaneElement.textContent = WebInspector.UIString("The tab is inactive");
243 this._glassPaneElement.classList.remove("hidden");
244 } else if (this._timelineActive) {
245 this._glassPaneElement.textContent = WebInspector.UIString("Timeline is active");
246 this._glassPaneElement.classList.remove("hidden");
247 } else if (this._profilerActive) {
248 this._glassPaneElement.textContent = WebInspector.UIString("CPU profiler is active");
249 this._glassPaneElement.classList.remove("hidden");
251 this._glassPaneElement.classList.add("hidden");
256 * @param {number} screenWidthDIP
257 * @param {number} screenHeightDIP
259 _resizeViewport: function(screenWidthDIP, screenHeightDIP)
261 var dimensions = this._viewportDimensions();
262 this._screenZoom = Math.min(dimensions.width / screenWidthDIP, dimensions.height / screenHeightDIP);
264 var bordersSize = WebInspector.ScreencastView._bordersSize;
265 this._viewportElement.classList.remove("hidden");
266 this._viewportElement.style.width = screenWidthDIP * this._screenZoom + bordersSize + "px";
267 this._viewportElement.style.height = screenHeightDIP * this._screenZoom + bordersSize + "px";
271 * @param {!Event} event
273 _handleMouseEvent: function(event)
275 if (this._isGlassPaneActive()) {
283 if (!this._inspectModeConfig || event.type === "mousewheel") {
284 this._simulateTouchGestureForMouseEvent(event);
285 event.preventDefault();
286 if (event.type === "mousedown")
287 this._canvasElement.focus();
291 var position = this._convertIntoScreenSpace(event);
292 DOMAgent.getNodeForLocation(position.x / this._pageScaleFactor, position.y / this._pageScaleFactor, callback.bind(this));
295 * @param {?Protocol.Error} error
296 * @param {number} nodeId
297 * @this {WebInspector.ScreencastView}
299 function callback(error, nodeId)
303 if (event.type === "mousemove")
304 this.highlightDOMNode(nodeId, this._inspectModeConfig);
305 else if (event.type === "click")
306 WebInspector.domAgent.dispatchEventToListeners(WebInspector.DOMAgent.Events.InspectNodeRequested, nodeId);
311 * @param {!KeyboardEvent} event
313 _handleKeyEvent: function(event)
315 if (this._isGlassPaneActive()) {
320 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
321 var handler = this._shortcuts[shortcutKey];
322 if (handler && handler(event)) {
328 switch (event.type) {
329 case "keydown": type = "keyDown"; break;
330 case "keyup": type = "keyUp"; break;
331 case "keypress": type = "char"; break;
335 var text = event.type === "keypress" ? String.fromCharCode(event.charCode) : undefined;
336 InputAgent.dispatchKeyEvent(type, this._modifiersForEvent(event), event.timeStamp / 1000, text, text ? text.toLowerCase() : undefined,
337 event.keyIdentifier, event.keyCode /* windowsVirtualKeyCode */, event.keyCode /* nativeVirtualKeyCode */, undefined /* macCharCode */, false, false, false);
339 this._canvasElement.focus();
343 * @param {!Event} event
345 _handleContextMenuEvent: function(event)
351 * @param {!Event} event
353 _simulateTouchGestureForMouseEvent: function(event)
355 var position = this._convertIntoScreenSpace(event);
356 var timeStamp = event.timeStamp / 1000;
360 switch (event.which) {
362 if (event.type === "mousedown") {
363 InputAgent.dispatchGestureEvent("scrollBegin", x, y, timeStamp);
364 } else if (event.type === "mousemove") {
365 var dx = this._lastScrollPosition ? position.x - this._lastScrollPosition.x : 0;
366 var dy = this._lastScrollPosition ? position.y - this._lastScrollPosition.y : 0;
368 InputAgent.dispatchGestureEvent("scrollUpdate", x, y, timeStamp, dx, dy);
369 } else if (event.type === "mouseup") {
370 InputAgent.dispatchGestureEvent("scrollEnd", x, y, timeStamp);
371 } else if (event.type === "mousewheel") {
374 var scale = event.wheelDeltaY < 0 ? 1 / factor : factor;
375 InputAgent.dispatchGestureEvent("pinchBegin", x, y, timeStamp);
376 InputAgent.dispatchGestureEvent("pinchUpdate", x, y, timeStamp, 0, 0, scale);
377 InputAgent.dispatchGestureEvent("pinchEnd", x, y, timeStamp);
379 InputAgent.dispatchGestureEvent("scrollBegin", x, y, timeStamp);
380 InputAgent.dispatchGestureEvent("scrollUpdate", x, y, timeStamp, event.wheelDeltaX, event.wheelDeltaY);
381 InputAgent.dispatchGestureEvent("scrollEnd", x, y, timeStamp);
383 } else if (event.type === "click") {
384 InputAgent.dispatchMouseEvent("mousePressed", x, y, 0, timeStamp, "left", 1, true);
385 InputAgent.dispatchMouseEvent("mouseReleased", x, y, 0, timeStamp, "left", 1, true);
386 // FIXME: migrate to tap once it dispatches clicks again.
387 // InputAgent.dispatchGestureEvent("tapDown", x, y, timeStamp);
388 // InputAgent.dispatchGestureEvent("tap", x, y, timeStamp);
390 this._lastScrollPosition = position;
394 if (event.type === "mousedown") {
395 InputAgent.dispatchGestureEvent("tapDown", x, y, timeStamp);
396 } else if (event.type === "mouseup") {
397 InputAgent.dispatchGestureEvent("tap", x, y, timeStamp);
402 if (event.type === "mousedown") {
403 this._pinchStart = position;
404 InputAgent.dispatchGestureEvent("pinchBegin", x, y, timeStamp);
405 } else if (event.type === "mousemove") {
406 var dx = this._pinchStart ? position.x - this._pinchStart.x : 0;
407 var dy = this._pinchStart ? position.y - this._pinchStart.y : 0;
409 var scale = Math.pow(dy < 0 ? 0.999 : 1.001, Math.abs(dy));
410 InputAgent.dispatchGestureEvent("pinchUpdate", this._pinchStart.x, this._pinchStart.y, timeStamp, 0, 0, scale);
412 } else if (event.type === "mouseup") {
413 InputAgent.dispatchGestureEvent("pinchEnd", x, y, timeStamp);
422 * @param {!Event} event
423 * @return {!{x: number, y: number}}
425 _convertIntoScreenSpace: function(event)
427 var zoom = this._canvasElement.offsetWidth / this._viewport.width / this._pageScaleFactor;
429 position.x = Math.round(event.offsetX / zoom);
430 position.y = Math.round(event.offsetY / zoom - this._screenOffsetTop);
435 * @param {!Event} event
438 _modifiersForEvent: function(event)
454 if (this._deferredCasting) {
455 clearTimeout(this._deferredCasting);
456 delete this._deferredCasting;
460 this._deferredCasting = setTimeout(this._startCasting.bind(this), 100);
464 * @param {!DOMAgent.NodeId} nodeId
465 * @param {?DOMAgent.HighlightConfig} config
466 * @param {!RuntimeAgent.RemoteObjectId=} objectId
468 highlightDOMNode: function(nodeId, config, objectId)
470 this._highlightNodeId = nodeId;
471 this._highlightConfig = config;
476 this._titleElement.classList.add("hidden");
481 this._node = WebInspector.domAgent.nodeForId(nodeId);
482 DOMAgent.getBoxModel(nodeId, callback.bind(this));
485 * @param {?Protocol.Error} error
486 * @param {!DOMAgent.BoxModel} model
487 * @this {WebInspector.ScreencastView}
489 function callback(error, model)
495 this._model = this._scaleModel(model);
496 this._config = config;
502 * @param {!DOMAgent.BoxModel} model
503 * @return {!DOMAgent.BoxModel}
505 _scaleModel: function(model)
507 var scale = this._canvasElement.offsetWidth / this._viewport.width;
510 * @param {!DOMAgent.Quad} quad
511 * @this {WebInspector.ScreencastView}
513 function scaleQuad(quad)
515 for (var i = 0; i < quad.length; i += 2) {
516 quad[i] = (quad[i] - this._viewport.x) * scale;
517 quad[i + 1] = (quad[i + 1] - this._viewport.y) * scale + this._screenOffsetTop * this._screenZoom;
521 scaleQuad.call(this, model.content);
522 scaleQuad.call(this, model.padding);
523 scaleQuad.call(this, model.border);
524 scaleQuad.call(this, model.margin);
530 var model = this._model;
531 var config = this._config;
533 this._canvasElement.width = window.devicePixelRatio * this._canvasElement.offsetWidth;
534 this._canvasElement.height = window.devicePixelRatio * this._canvasElement.offsetHeight;
536 this._context.save();
537 this._context.scale(window.devicePixelRatio, window.devicePixelRatio);
539 // Paint top and bottom gutter.
540 this._context.save();
541 this._context.fillStyle = this._checkerboardPattern;
542 this._context.fillRect(0, 0, this._canvasElement.offsetWidth, this._screenOffsetTop * this._screenZoom);
543 this._context.fillRect(0, this._screenOffsetTop * this._screenZoom + this._imageElement.naturalHeight * this._imageZoom, this._canvasElement.offsetWidth, this._canvasElement.offsetHeight);
544 this._context.restore();
546 if (model && config) {
547 this._context.save();
548 const transparentColor = "rgba(0, 0, 0, 0)";
549 var hasContent = model.content && config.contentColor !== transparentColor;
550 var hasPadding = model.padding && config.paddingColor !== transparentColor;
551 var hasBorder = model.border && config.borderColor !== transparentColor;
552 var hasMargin = model.margin && config.marginColor !== transparentColor;
555 if (hasMargin && (!hasBorder || !this._quadsAreEqual(model.margin, model.border))) {
556 this._drawOutlinedQuadWithClip(model.margin, model.border, config.marginColor);
557 clipQuad = model.border;
559 if (hasBorder && (!hasPadding || !this._quadsAreEqual(model.border, model.padding))) {
560 this._drawOutlinedQuadWithClip(model.border, model.padding, config.borderColor);
561 clipQuad = model.padding;
563 if (hasPadding && (!hasContent || !this._quadsAreEqual(model.padding, model.content))) {
564 this._drawOutlinedQuadWithClip(model.padding, model.content, config.paddingColor);
565 clipQuad = model.content;
568 this._drawOutlinedQuad(model.content, config.contentColor);
569 this._context.restore();
571 this._drawElementTitle();
573 this._context.globalCompositeOperation = "destination-over";
576 this._context.drawImage(this._imageElement, 0, this._screenOffsetTop * this._screenZoom, this._imageElement.naturalWidth * this._imageZoom, this._imageElement.naturalHeight * this._imageZoom);
578 this._context.restore();
583 * @param {!DOMAgent.Quad} quad1
584 * @param {!DOMAgent.Quad} quad2
587 _quadsAreEqual: function(quad1, quad2)
589 for (var i = 0; i < quad1.length; ++i) {
590 if (quad1[i] !== quad2[i])
597 * @param {!DOMAgent.RGBA} color
600 _cssColor: function(color)
603 return "transparent";
604 return WebInspector.Color.fromRGBA([color.r, color.g, color.b, color.a]).toString(WebInspector.Color.Format.RGBA) || "";
608 * @param {!DOMAgent.Quad} quad
609 * @return {!CanvasRenderingContext2D}
611 _quadToPath: function(quad)
613 this._context.beginPath();
614 this._context.moveTo(quad[0], quad[1]);
615 this._context.lineTo(quad[2], quad[3]);
616 this._context.lineTo(quad[4], quad[5]);
617 this._context.lineTo(quad[6], quad[7]);
618 this._context.closePath();
619 return this._context;
623 * @param {!DOMAgent.Quad} quad
624 * @param {!DOMAgent.RGBA} fillColor
626 _drawOutlinedQuad: function(quad, fillColor)
628 this._context.save();
629 this._context.lineWidth = 2;
630 this._quadToPath(quad).clip();
631 this._context.fillStyle = this._cssColor(fillColor);
632 this._context.fill();
633 this._context.restore();
637 * @param {!DOMAgent.Quad} quad
638 * @param {!DOMAgent.Quad} clipQuad
639 * @param {!DOMAgent.RGBA} fillColor
641 _drawOutlinedQuadWithClip: function (quad, clipQuad, fillColor)
643 this._context.fillStyle = this._cssColor(fillColor);
644 this._context.save();
645 this._context.lineWidth = 0;
646 this._quadToPath(quad).fill();
647 this._context.globalCompositeOperation = "destination-out";
648 this._context.fillStyle = "red";
649 this._quadToPath(clipQuad).fill();
650 this._context.restore();
653 _drawElementTitle: function()
658 var canvasWidth = this._canvasElement.offsetWidth;
659 var canvasHeight = this._canvasElement.offsetHeight;
661 var lowerCaseName = this._node.localName() || this._node.nodeName().toLowerCase();
662 this._tagNameElement.textContent = lowerCaseName;
663 this._nodeIdElement.textContent = this._node.getAttribute("id") ? "#" + this._node.getAttribute("id") : "";
664 this._nodeIdElement.textContent = this._node.getAttribute("id") ? "#" + this._node.getAttribute("id") : "";
665 var className = this._node.getAttribute("class");
666 if (className && className.length > 50)
667 className = className.substring(0, 50) + "\u2026";
668 this._classNameElement.textContent = className || "";
669 this._nodeWidthElement.textContent = this._model.width;
670 this._nodeHeightElement.textContent = this._model.height;
672 var marginQuad = this._model.margin;
673 var titleWidth = this._titleElement.offsetWidth + 6;
674 var titleHeight = this._titleElement.offsetHeight + 4;
676 var anchorTop = this._model.margin[1];
677 var anchorBottom = this._model.margin[7];
679 const arrowHeight = 7;
680 var renderArrowUp = false;
681 var renderArrowDown = false;
683 var boxX = Math.max(2, this._model.margin[0]);
684 if (boxX + titleWidth > canvasWidth)
685 boxX = canvasWidth - titleWidth - 2;
688 if (anchorTop > canvasHeight) {
689 boxY = canvasHeight - titleHeight - arrowHeight;
690 renderArrowDown = true;
691 } else if (anchorBottom < 0) {
693 renderArrowUp = true;
694 } else if (anchorBottom + titleHeight + arrowHeight < canvasHeight) {
695 boxY = anchorBottom + arrowHeight - 4;
696 renderArrowUp = true;
697 } else if (anchorTop - titleHeight - arrowHeight > 0) {
698 boxY = anchorTop - titleHeight - arrowHeight + 3;
699 renderArrowDown = true;
703 this._context.save();
704 this._context.translate(0.5, 0.5);
705 this._context.beginPath();
706 this._context.moveTo(boxX, boxY);
708 this._context.lineTo(boxX + 2 * arrowHeight, boxY);
709 this._context.lineTo(boxX + 3 * arrowHeight, boxY - arrowHeight);
710 this._context.lineTo(boxX + 4 * arrowHeight, boxY);
712 this._context.lineTo(boxX + titleWidth, boxY);
713 this._context.lineTo(boxX + titleWidth, boxY + titleHeight);
714 if (renderArrowDown) {
715 this._context.lineTo(boxX + 4 * arrowHeight, boxY + titleHeight);
716 this._context.lineTo(boxX + 3 * arrowHeight, boxY + titleHeight + arrowHeight);
717 this._context.lineTo(boxX + 2 * arrowHeight, boxY + titleHeight);
719 this._context.lineTo(boxX, boxY + titleHeight);
720 this._context.closePath();
721 this._context.fillStyle = "rgb(255, 255, 194)";
722 this._context.fill();
723 this._context.strokeStyle = "rgb(128, 128, 128)";
724 this._context.stroke();
726 this._context.restore();
728 this._titleElement.classList.remove("hidden");
729 this._titleElement.style.top = (boxY + 3) + "px";
730 this._titleElement.style.left = (boxX + 3) + "px";
734 * @return {!{width: number, height: number}}
736 _viewportDimensions: function()
738 const gutterSize = 30;
739 const bordersSize = WebInspector.ScreencastView._bordersSize;
740 return { width: this.element.offsetWidth - bordersSize - gutterSize,
741 height: this.element.offsetHeight - bordersSize - gutterSize - WebInspector.ScreencastView._navBarHeight};
745 * @param {boolean} enabled
746 * @param {boolean} inspectShadowDOM
747 * @param {!DOMAgent.HighlightConfig} config
748 * @param {function(?Protocol.Error)=} callback
750 setInspectModeEnabled: function(enabled, inspectShadowDOM, config, callback)
752 this._inspectModeConfig = enabled ? config : null;
758 * @param {!CanvasRenderingContext2D} context
760 _createCheckerboardPattern: function(context)
762 var pattern = /** @type {!HTMLCanvasElement} */(document.createElement("canvas"));
764 pattern.width = size * 2;
765 pattern.height = size * 2;
766 var pctx = pattern.getContext("2d");
768 pctx.fillStyle = "rgb(195, 195, 195)";
769 pctx.fillRect(0, 0, size * 2, size * 2);
771 pctx.fillStyle = "rgb(225, 225, 225)";
772 pctx.fillRect(0, 0, size, size);
773 pctx.fillRect(size, size, size, size);
774 return context.createPattern(pattern, "repeat");
777 _createNavigationBar: function()
779 this._navigationBar = this.element.createChild("div", "toolbar-background screencast-navigation");
781 this._navigationBack = this._navigationBar.createChild("button", "back");
782 this._navigationBack.disabled = true;
783 this._navigationBack.addEventListener("click", this._navigateToHistoryEntry.bind(this, -1), false);
785 this._navigationForward = this._navigationBar.createChild("button", "forward");
786 this._navigationForward.disabled = true;
787 this._navigationForward.addEventListener("click", this._navigateToHistoryEntry.bind(this, 1), false);
789 this._navigationReload = this._navigationBar.createChild("button", "reload");
790 this._navigationReload.addEventListener("click", this._navigateReload.bind(this), false);
792 this._navigationUrl = this._navigationBar.createChild("input");
793 this._navigationUrl.type = "text";
794 this._navigationUrl.addEventListener('keyup', this._navigationUrlKeyUp.bind(this), true);
796 this._navigationProgressBar = new WebInspector.ScreencastView.ProgressTracker(this._navigationBar.createChild("div", "progress"));
798 this._requestNavigationHistory();
799 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._requestNavigationHistory, this);
802 _navigateToHistoryEntry: function(offset)
804 var newIndex = this._historyIndex + offset;
805 if (newIndex < 0 || newIndex >= this._historyEntries.length)
807 PageAgent.navigateToHistoryEntry(this._historyEntries[newIndex].id);
808 this._requestNavigationHistory();
811 _navigateReload: function()
813 WebInspector.resourceTreeModel.reloadPage();
816 _navigationUrlKeyUp: function(event)
818 if (event.keyIdentifier != 'Enter')
820 var url = this._navigationUrl.value;
823 if (!url.match(WebInspector.ScreencastView._HttpRegex))
824 url = "http://" + url;
825 PageAgent.navigate(url);
826 this._canvasElement.focus();
829 _requestNavigationHistory: function()
831 PageAgent.getNavigationHistory(this._onNavigationHistory.bind(this));
834 _onNavigationHistory: function(error, currentIndex, entries)
839 this._historyIndex = currentIndex;
840 this._historyEntries = entries;
842 this._navigationBack.disabled = currentIndex == 0;
843 this._navigationForward.disabled = currentIndex == (entries.length - 1);
845 var url = entries[currentIndex].url;
846 var match = url.match(WebInspector.ScreencastView._HttpRegex);
849 this._navigationUrl.value = url;
852 _focusNavigationBar: function()
854 this._navigationUrl.focus();
855 this._navigationUrl.select();
859 __proto__: WebInspector.View.prototype
863 * @param {!HTMLElement} element
866 WebInspector.ScreencastView.ProgressTracker = function(element) {
867 this._element = element;
869 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._onMainFrameNavigated, this);
870 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._onLoad, this);
872 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this);
873 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this);
876 WebInspector.ScreencastView.ProgressTracker.prototype = {
877 _onMainFrameNavigated: function()
879 this._requestIds = {};
880 this._startedRequests = 0;
881 this._finishedRequests = 0;
882 this._maxDisplayedProgress = 0;
883 this._updateProgress(0.1); // Display first 10% on navigation start.
888 delete this._requestIds;
889 this._updateProgress(1); // Display 100% progress on load, hide it in 0.5s.
890 setTimeout(function() {
891 if (!this._navigationProgressVisible())
892 this._displayProgress(0);
896 _navigationProgressVisible: function()
898 return !!this._requestIds;
901 _onRequestStarted: function(event)
903 if (!this._navigationProgressVisible())
905 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
906 // Ignore long-living WebSockets for the sake of progress indicator, as we won't be waiting them anyway.
907 if (request.type === WebInspector.resourceTypes.WebSocket)
909 this._requestIds[request.requestId] = request;
910 ++this._startedRequests;
913 _onRequestFinished: function(event)
915 if (!this._navigationProgressVisible())
917 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
918 if (!(request.requestId in this._requestIds))
920 ++this._finishedRequests;
921 setTimeout(function() {
922 this._updateProgress(this._finishedRequests / this._startedRequests * 0.9); // Finished requests drive the progress up to 90%.
923 }.bind(this), 500); // Delay to give the new requests time to start. This makes the progress smoother.
926 _updateProgress: function(progress)
928 if (!this._navigationProgressVisible())
930 if (this._maxDisplayedProgress >= progress)
932 this._maxDisplayedProgress = progress;
933 this._displayProgress(progress);
936 _displayProgress: function(progress)
938 this._element.style.width = (100 * progress) + "%";