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.VBox}
34 * @implements {WebInspector.DOMNodeHighlighter}
35 * @param {!WebInspector.Target} target
37 WebInspector.ScreencastView = function(target)
39 WebInspector.VBox.call(this);
40 this._target = target;
42 this.setMinimumSize(150, 150);
43 this.registerRequiredCSS("screencastView.css");
46 WebInspector.ScreencastView._bordersSize = 44;
48 WebInspector.ScreencastView._navBarHeight = 29;
50 WebInspector.ScreencastView._HttpRegex = /^https?:\/\/(.+)/;
52 WebInspector.ScreencastView.prototype = {
53 initialize: function()
55 this.element.classList.add("screencast");
57 this._createNavigationBar();
59 this._viewportElement = this.element.createChild("div", "screencast-viewport hidden");
60 this._canvasContainerElement = this._viewportElement.createChild("div", "screencast-canvas-container");
61 this._glassPaneElement = this._canvasContainerElement.createChild("div", "screencast-glasspane hidden");
63 this._canvasElement = this._canvasContainerElement.createChild("canvas");
64 this._canvasElement.tabIndex = 1;
65 this._canvasElement.addEventListener("mousedown", this._handleMouseEvent.bind(this), false);
66 this._canvasElement.addEventListener("mouseup", this._handleMouseEvent.bind(this), false);
67 this._canvasElement.addEventListener("mousemove", this._handleMouseEvent.bind(this), false);
68 this._canvasElement.addEventListener("mousewheel", this._handleMouseEvent.bind(this), false);
69 this._canvasElement.addEventListener("click", this._handleMouseEvent.bind(this), false);
70 this._canvasElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
71 this._canvasElement.addEventListener("keydown", this._handleKeyEvent.bind(this), false);
72 this._canvasElement.addEventListener("keyup", this._handleKeyEvent.bind(this), false);
73 this._canvasElement.addEventListener("keypress", this._handleKeyEvent.bind(this), false);
74 this._canvasElement.addEventListener("blur", this._handleBlurEvent.bind(this), false);
76 this._titleElement = this._canvasContainerElement.createChild("div", "screencast-element-title monospace hidden");
77 this._tagNameElement = this._titleElement.createChild("span", "screencast-tag-name");
78 this._nodeIdElement = this._titleElement.createChild("span", "screencast-node-id");
79 this._classNameElement = this._titleElement.createChild("span", "screencast-class-name");
80 this._titleElement.createTextChild(" ");
81 this._nodeWidthElement = this._titleElement.createChild("span");
82 this._titleElement.createChild("span", "screencast-px").textContent = "px";
83 this._titleElement.createTextChild(" \u00D7 ");
84 this._nodeHeightElement = this._titleElement.createChild("span");
85 this._titleElement.createChild("span", "screencast-px").textContent = "px";
87 this._imageElement = new Image();
88 this._isCasting = false;
89 this._context = this._canvasElement.getContext("2d");
90 this._checkerboardPattern = this._createCheckerboardPattern(this._context);
92 this._shortcuts = /** !Object.<number, function(Event=):boolean> */ ({});
93 this._shortcuts[WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl)] = this._focusNavigationBar.bind(this);
95 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ScreencastFrame, this._screencastFrame, this);
96 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ScreencastVisibilityChanged, this._screencastVisibilityChanged, this);
98 WebInspector.profilingLock().addEventListener(WebInspector.Lock.Events.StateChanged, this._onProfilingStateChange, this);
99 this._updateGlasspane();
104 this._startCasting();
112 _startCasting: function()
114 if (WebInspector.profilingLock().isAcquired())
118 this._isCasting = true;
120 const maxImageDimension = 2048;
121 var dimensions = this._viewportDimensions();
122 if (dimensions.width < 0 || dimensions.height < 0) {
123 this._isCasting = false;
126 dimensions.width *= window.devicePixelRatio;
127 dimensions.height *= window.devicePixelRatio;
128 this._target.pageAgent().startScreencast("jpeg", 80, Math.min(maxImageDimension, dimensions.width), Math.min(maxImageDimension, dimensions.height));
129 this._target.domModel.setHighlighter(this);
132 _stopCasting: function()
134 if (!this._isCasting)
136 this._isCasting = false;
137 this._target.pageAgent().stopScreencast();
138 this._target.domModel.setHighlighter(null);
142 * @param {!WebInspector.Event} event
144 _screencastFrame: function(event)
146 var metadata = /** type {PageAgent.ScreencastFrameMetadata} */(event.data.metadata);
147 var base64Data = /** type {string} */(event.data.data);
148 this._imageElement.src = "data:image/jpg;base64," + base64Data;
149 this._pageScaleFactor = metadata.pageScaleFactor;
150 this._screenOffsetTop = metadata.offsetTop;
151 this._deviceWidth = metadata.deviceWidth;
152 this._deviceHeight = metadata.deviceHeight;
153 this._scrollOffsetX = metadata.scrollOffsetX;
154 this._scrollOffsetY = metadata.scrollOffsetY;
156 var deviceSizeRatio = metadata.deviceHeight / metadata.deviceWidth;
157 var dimensionsCSS = this._viewportDimensions();
159 this._imageZoom = Math.min(dimensionsCSS.width / this._imageElement.naturalWidth, dimensionsCSS.height / (this._imageElement.naturalWidth * deviceSizeRatio));
160 this._viewportElement.classList.remove("hidden");
161 var bordersSize = WebInspector.ScreencastView._bordersSize;
162 if (this._imageZoom < 1.01 / window.devicePixelRatio)
163 this._imageZoom = 1 / window.devicePixelRatio;
164 this._screenZoom = this._imageElement.naturalWidth * this._imageZoom / metadata.deviceWidth;
165 this._viewportElement.style.width = metadata.deviceWidth * this._screenZoom + bordersSize + "px";
166 this._viewportElement.style.height = metadata.deviceHeight * this._screenZoom + bordersSize + "px";
168 this.highlightDOMNode(this._highlightNode, this._highlightConfig);
171 _isGlassPaneActive: function()
173 return !this._glassPaneElement.classList.contains("hidden");
177 * @param {!WebInspector.Event} event
179 _screencastVisibilityChanged: function(event)
181 this._targetInactive = !event.data.visible;
182 this._updateGlasspane();
186 * @param {!WebInspector.Event} event
188 _onProfilingStateChange: function(event)
190 if (WebInspector.profilingLock().isAcquired())
193 this._startCasting();
194 this._updateGlasspane();
197 _updateGlasspane: function()
199 if (this._targetInactive) {
200 this._glassPaneElement.textContent = WebInspector.UIString("The tab is inactive");
201 this._glassPaneElement.classList.remove("hidden");
202 } else if (WebInspector.profilingLock().isAcquired()) {
203 this._glassPaneElement.textContent = WebInspector.UIString("Profiling in progress");
204 this._glassPaneElement.classList.remove("hidden");
206 this._glassPaneElement.classList.add("hidden");
211 * @param {!Event} event
213 _handleMouseEvent: function(event)
215 if (this._isGlassPaneActive()) {
220 if (!this._pageScaleFactor)
223 if (!this._inspectModeConfig || event.type === "mousewheel") {
224 this._simulateTouchForMouseEvent(event);
225 event.preventDefault();
226 if (event.type === "mousedown")
227 this._canvasElement.focus();
231 var position = this._convertIntoScreenSpace(event);
232 this._target.domModel.nodeForLocation(position.x / this._pageScaleFactor + this._scrollOffsetX, position.y / this._pageScaleFactor + this._scrollOffsetY, callback.bind(this));
235 * @param {?WebInspector.DOMNode} node
236 * @this {WebInspector.ScreencastView}
238 function callback(node)
242 if (event.type === "mousemove")
243 this.highlightDOMNode(node, this._inspectModeConfig);
244 else if (event.type === "click")
245 WebInspector.Revealer.reveal(node);
250 * @param {!Event} event
252 _handleKeyEvent: function(event)
254 if (this._isGlassPaneActive()) {
259 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(/** @type {!KeyboardEvent} */ (event));
260 var handler = this._shortcuts[shortcutKey];
261 if (handler && handler(event)) {
267 switch (event.type) {
268 case "keydown": type = "keyDown"; break;
269 case "keyup": type = "keyUp"; break;
270 case "keypress": type = "char"; break;
274 var text = event.type === "keypress" ? String.fromCharCode(event.charCode) : undefined;
275 InputAgent.dispatchKeyEvent(type, this._modifiersForEvent(event), event.timeStamp / 1000, text, text ? text.toLowerCase() : undefined,
276 event.keyIdentifier, event.keyCode /* windowsVirtualKeyCode */, event.keyCode /* nativeVirtualKeyCode */, false, false, false);
278 this._canvasElement.focus();
282 * @param {!Event} event
284 _handleContextMenuEvent: function(event)
290 * @param {!Event} event
292 _simulateTouchForMouseEvent: function(event)
294 const buttons = {0: "none", 1: "left", 2: "middle", 3: "right"};
295 const types = {"mousedown" : "mousePressed", "mouseup": "mouseReleased", "mousemove": "mouseMoved", "mousewheel": "mouseWheel"};
296 if (!(event.type in types) || !(event.which in buttons))
298 if (event.type !== "mousewheel" && buttons[event.which] === "none")
301 if (event.type === "mousedown" || typeof this._eventScreenOffsetTop === "undefined")
302 this._eventScreenOffsetTop = this._screenOffsetTop;
304 var modifiers = (event.altKey ? 1 : 0) | (event.ctrlKey ? 2 : 0) | (event.metaKey ? 4 : 0) | (event.shiftKey ? 8 : 0);
306 var convertedPosition = this._zoomIntoScreenSpace(event);
307 convertedPosition.y = Math.round(convertedPosition.y - this._eventScreenOffsetTop);
308 var params = {type: types[event.type], x: convertedPosition.x, y: convertedPosition.y, modifiers: modifiers, timestamp: event.timeStamp / 1000, button: buttons[event.which], clickCount: 0};
309 if (event.type === "mousewheel") {
310 params.deltaX = event.wheelDeltaX / this._screenZoom;
311 params.deltaY = event.wheelDeltaY / this._screenZoom;
313 this._eventParams = params;
315 if (event.type === "mouseup")
316 delete this._eventScreenOffsetTop;
317 InputAgent.invoke_emulateTouchFromMouseEvent(params);
321 * @param {!Event} event
323 _handleBlurEvent: function(event)
325 if (typeof this._eventScreenOffsetTop !== "undefined") {
326 var params = this._eventParams;
327 delete this._eventParams;
328 params.type = "mouseReleased";
329 InputAgent.invoke_emulateTouchFromMouseEvent(params);
334 * @param {!Event} event
335 * @return {!{x: number, y: number}}
337 _zoomIntoScreenSpace: function(event)
340 position.x = Math.round(event.offsetX / this._screenZoom);
341 position.y = Math.round(event.offsetY / this._screenZoom);
346 * @param {!Event} event
347 * @return {!{x: number, y: number}}
349 _convertIntoScreenSpace: function(event)
351 var position = this._zoomIntoScreenSpace(event);
352 position.y = Math.round(position.y - this._screenOffsetTop);
357 * @param {!Event} event
360 _modifiersForEvent: function(event)
376 if (this._deferredCasting) {
377 clearTimeout(this._deferredCasting);
378 delete this._deferredCasting;
382 this._deferredCasting = setTimeout(this._startCasting.bind(this), 100);
386 * @param {?WebInspector.DOMNode} node
387 * @param {?DOMAgent.HighlightConfig} config
388 * @param {!RuntimeAgent.RemoteObjectId=} objectId
390 highlightDOMNode: function(node, config, objectId)
392 this._highlightNode = node;
393 this._highlightConfig = config;
398 this._titleElement.classList.add("hidden");
404 node.boxModel(callback.bind(this));
407 * @param {?DOMAgent.BoxModel} model
408 * @this {WebInspector.ScreencastView}
410 function callback(model)
412 if (!model || !this._pageScaleFactor) {
416 this._model = this._scaleModel(model);
417 this._config = config;
423 * @param {!DOMAgent.BoxModel} model
424 * @return {!DOMAgent.BoxModel}
426 _scaleModel: function(model)
429 * @param {!DOMAgent.Quad} quad
430 * @this {WebInspector.ScreencastView}
432 function scaleQuad(quad)
434 for (var i = 0; i < quad.length; i += 2) {
435 quad[i] = quad[i] * this._pageScaleFactor * this._screenZoom;
436 quad[i + 1] = (quad[i + 1] * this._pageScaleFactor + this._screenOffsetTop) * this._screenZoom;
440 scaleQuad.call(this, model.content);
441 scaleQuad.call(this, model.padding);
442 scaleQuad.call(this, model.border);
443 scaleQuad.call(this, model.margin);
449 var model = this._model;
450 var config = this._config;
452 var canvasWidth = this._canvasElement.getBoundingClientRect().width;
453 var canvasHeight = this._canvasElement.getBoundingClientRect().height;
454 this._canvasElement.width = window.devicePixelRatio * canvasWidth;
455 this._canvasElement.height = window.devicePixelRatio * canvasHeight;
457 this._context.save();
458 this._context.scale(window.devicePixelRatio, window.devicePixelRatio);
460 // Paint top and bottom gutter.
461 this._context.save();
462 this._context.fillStyle = this._checkerboardPattern;
463 this._context.fillRect(0, 0, canvasWidth, this._screenOffsetTop * this._screenZoom);
464 this._context.fillRect(0, this._screenOffsetTop * this._screenZoom + this._imageElement.naturalHeight * this._imageZoom, canvasWidth, canvasHeight);
465 this._context.restore();
467 if (model && config) {
468 this._context.save();
469 const transparentColor = "rgba(0, 0, 0, 0)";
470 var hasContent = model.content && config.contentColor !== transparentColor;
471 var hasPadding = model.padding && config.paddingColor !== transparentColor;
472 var hasBorder = model.border && config.borderColor !== transparentColor;
473 var hasMargin = model.margin && config.marginColor !== transparentColor;
476 if (hasMargin && (!hasBorder || !this._quadsAreEqual(model.margin, model.border))) {
477 this._drawOutlinedQuadWithClip(model.margin, model.border, config.marginColor);
478 clipQuad = model.border;
480 if (hasBorder && (!hasPadding || !this._quadsAreEqual(model.border, model.padding))) {
481 this._drawOutlinedQuadWithClip(model.border, model.padding, config.borderColor);
482 clipQuad = model.padding;
484 if (hasPadding && (!hasContent || !this._quadsAreEqual(model.padding, model.content))) {
485 this._drawOutlinedQuadWithClip(model.padding, model.content, config.paddingColor);
486 clipQuad = model.content;
489 this._drawOutlinedQuad(model.content, config.contentColor);
490 this._context.restore();
492 this._drawElementTitle();
494 this._context.globalCompositeOperation = "destination-over";
497 this._context.drawImage(this._imageElement, 0, this._screenOffsetTop * this._screenZoom, this._imageElement.naturalWidth * this._imageZoom, this._imageElement.naturalHeight * this._imageZoom);
498 this._context.restore();
504 * @param {!DOMAgent.Quad} quad1
505 * @param {!DOMAgent.Quad} quad2
508 _quadsAreEqual: function(quad1, quad2)
510 for (var i = 0; i < quad1.length; ++i) {
511 if (quad1[i] !== quad2[i])
518 * @param {!DOMAgent.RGBA} color
521 _cssColor: function(color)
524 return "transparent";
525 return WebInspector.Color.fromRGBA([color.r, color.g, color.b, color.a]).toString(WebInspector.Color.Format.RGBA) || "";
529 * @param {!DOMAgent.Quad} quad
530 * @return {!CanvasRenderingContext2D}
532 _quadToPath: function(quad)
534 this._context.beginPath();
535 this._context.moveTo(quad[0], quad[1]);
536 this._context.lineTo(quad[2], quad[3]);
537 this._context.lineTo(quad[4], quad[5]);
538 this._context.lineTo(quad[6], quad[7]);
539 this._context.closePath();
540 return this._context;
544 * @param {!DOMAgent.Quad} quad
545 * @param {!DOMAgent.RGBA} fillColor
547 _drawOutlinedQuad: function(quad, fillColor)
549 this._context.save();
550 this._context.lineWidth = 2;
551 this._quadToPath(quad).clip();
552 this._context.fillStyle = this._cssColor(fillColor);
553 this._context.fill();
554 this._context.restore();
558 * @param {!DOMAgent.Quad} quad
559 * @param {!DOMAgent.Quad} clipQuad
560 * @param {!DOMAgent.RGBA} fillColor
562 _drawOutlinedQuadWithClip: function (quad, clipQuad, fillColor)
564 this._context.fillStyle = this._cssColor(fillColor);
565 this._context.save();
566 this._context.lineWidth = 0;
567 this._quadToPath(quad).fill();
568 this._context.globalCompositeOperation = "destination-out";
569 this._context.fillStyle = "red";
570 this._quadToPath(clipQuad).fill();
571 this._context.restore();
574 _drawElementTitle: function()
579 var canvasWidth = this._canvasElement.getBoundingClientRect().width;
580 var canvasHeight = this._canvasElement.getBoundingClientRect().height;
582 var lowerCaseName = this._node.localName() || this._node.nodeName().toLowerCase();
583 this._tagNameElement.textContent = lowerCaseName;
584 this._nodeIdElement.textContent = this._node.getAttribute("id") ? "#" + this._node.getAttribute("id") : "";
585 this._nodeIdElement.textContent = this._node.getAttribute("id") ? "#" + this._node.getAttribute("id") : "";
586 var className = this._node.getAttribute("class");
587 if (className && className.length > 50)
588 className = className.substring(0, 50) + "\u2026";
589 this._classNameElement.textContent = className || "";
590 this._nodeWidthElement.textContent = this._model.width;
591 this._nodeHeightElement.textContent = this._model.height;
593 var marginQuad = this._model.margin;
594 var titleWidth = this._titleElement.offsetWidth + 6;
595 var titleHeight = this._titleElement.offsetHeight + 4;
597 var anchorTop = this._model.margin[1];
598 var anchorBottom = this._model.margin[7];
600 const arrowHeight = 7;
601 var renderArrowUp = false;
602 var renderArrowDown = false;
604 var boxX = Math.max(2, this._model.margin[0]);
605 if (boxX + titleWidth > canvasWidth)
606 boxX = canvasWidth - titleWidth - 2;
609 if (anchorTop > canvasHeight) {
610 boxY = canvasHeight - titleHeight - arrowHeight;
611 renderArrowDown = true;
612 } else if (anchorBottom < 0) {
614 renderArrowUp = true;
615 } else if (anchorBottom + titleHeight + arrowHeight < canvasHeight) {
616 boxY = anchorBottom + arrowHeight - 4;
617 renderArrowUp = true;
618 } else if (anchorTop - titleHeight - arrowHeight > 0) {
619 boxY = anchorTop - titleHeight - arrowHeight + 3;
620 renderArrowDown = true;
624 this._context.save();
625 this._context.translate(0.5, 0.5);
626 this._context.beginPath();
627 this._context.moveTo(boxX, boxY);
629 this._context.lineTo(boxX + 2 * arrowHeight, boxY);
630 this._context.lineTo(boxX + 3 * arrowHeight, boxY - arrowHeight);
631 this._context.lineTo(boxX + 4 * arrowHeight, boxY);
633 this._context.lineTo(boxX + titleWidth, boxY);
634 this._context.lineTo(boxX + titleWidth, boxY + titleHeight);
635 if (renderArrowDown) {
636 this._context.lineTo(boxX + 4 * arrowHeight, boxY + titleHeight);
637 this._context.lineTo(boxX + 3 * arrowHeight, boxY + titleHeight + arrowHeight);
638 this._context.lineTo(boxX + 2 * arrowHeight, boxY + titleHeight);
640 this._context.lineTo(boxX, boxY + titleHeight);
641 this._context.closePath();
642 this._context.fillStyle = "rgb(255, 255, 194)";
643 this._context.fill();
644 this._context.strokeStyle = "rgb(128, 128, 128)";
645 this._context.stroke();
647 this._context.restore();
649 this._titleElement.classList.remove("hidden");
650 this._titleElement.style.top = (boxY + 3) + "px";
651 this._titleElement.style.left = (boxX + 3) + "px";
655 * @return {!{width: number, height: number}}
657 _viewportDimensions: function()
659 const gutterSize = 30;
660 const bordersSize = WebInspector.ScreencastView._bordersSize;
661 var width = this.element.offsetWidth - bordersSize - gutterSize;
662 var height = this.element.offsetHeight - bordersSize - gutterSize - WebInspector.ScreencastView._navBarHeight;
663 return { width: width, height: height };
667 * @param {boolean} enabled
668 * @param {boolean} inspectUAShadowDOM
669 * @param {!DOMAgent.HighlightConfig} config
670 * @param {function(?Protocol.Error)=} callback
672 setInspectModeEnabled: function(enabled, inspectUAShadowDOM, config, callback)
674 this._inspectModeConfig = enabled ? config : null;
680 * @param {!CanvasRenderingContext2D} context
682 _createCheckerboardPattern: function(context)
684 var pattern = /** @type {!HTMLCanvasElement} */(document.createElement("canvas"));
686 pattern.width = size * 2;
687 pattern.height = size * 2;
688 var pctx = pattern.getContext("2d");
690 pctx.fillStyle = "rgb(195, 195, 195)";
691 pctx.fillRect(0, 0, size * 2, size * 2);
693 pctx.fillStyle = "rgb(225, 225, 225)";
694 pctx.fillRect(0, 0, size, size);
695 pctx.fillRect(size, size, size, size);
696 return context.createPattern(pattern, "repeat");
699 _createNavigationBar: function()
701 this._navigationBar = this.element.createChild("div", "toolbar-background toolbar-colors screencast-navigation");
702 if (Runtime.queryParam("hideNavigation"))
703 this._navigationBar.classList.add("hidden");
705 this._navigationBack = this._navigationBar.createChild("button", "back");
706 this._navigationBack.disabled = true;
707 this._navigationBack.addEventListener("click", this._navigateToHistoryEntry.bind(this, -1), false);
709 this._navigationForward = this._navigationBar.createChild("button", "forward");
710 this._navigationForward.disabled = true;
711 this._navigationForward.addEventListener("click", this._navigateToHistoryEntry.bind(this, 1), false);
713 this._navigationReload = this._navigationBar.createChild("button", "reload");
714 this._navigationReload.addEventListener("click", this._navigateReload.bind(this), false);
716 this._navigationUrl = this._navigationBar.createChild("input");
717 this._navigationUrl.type = "text";
718 this._navigationUrl.addEventListener('keyup', this._navigationUrlKeyUp.bind(this), true);
720 this._navigationProgressBar = new WebInspector.ScreencastView.ProgressTracker(this._navigationBar.createChild("div", "progress"));
722 this._requestNavigationHistory();
723 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.InspectedURLChanged, this._requestNavigationHistory, this);
726 _navigateToHistoryEntry: function(offset)
728 var newIndex = this._historyIndex + offset;
729 if (newIndex < 0 || newIndex >= this._historyEntries.length)
731 PageAgent.navigateToHistoryEntry(this._historyEntries[newIndex].id);
732 this._requestNavigationHistory();
735 _navigateReload: function()
737 WebInspector.resourceTreeModel.reloadPage();
740 _navigationUrlKeyUp: function(event)
742 if (event.keyIdentifier != 'Enter')
744 var url = this._navigationUrl.value;
747 if (!url.match(WebInspector.ScreencastView._HttpRegex))
748 url = "http://" + url;
749 PageAgent.navigate(url);
750 this._canvasElement.focus();
753 _requestNavigationHistory: function()
755 PageAgent.getNavigationHistory(this._onNavigationHistory.bind(this));
758 _onNavigationHistory: function(error, currentIndex, entries)
763 this._historyIndex = currentIndex;
764 this._historyEntries = entries;
766 this._navigationBack.disabled = currentIndex == 0;
767 this._navigationForward.disabled = currentIndex == (entries.length - 1);
769 var url = entries[currentIndex].url;
770 var match = url.match(WebInspector.ScreencastView._HttpRegex);
773 InspectorFrontendHost.inspectedURLChanged(url);
774 this._navigationUrl.value = url;
777 _focusNavigationBar: function()
779 this._navigationUrl.focus();
780 this._navigationUrl.select();
784 __proto__: WebInspector.VBox.prototype
788 * @param {!Element} element
791 WebInspector.ScreencastView.ProgressTracker = function(element)
793 this._element = element;
795 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._onMainFrameNavigated, this);
796 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.Load, this._onLoad, this);
797 WebInspector.targetManager.addModelListener(WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this);
798 WebInspector.targetManager.addModelListener(WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this);
801 WebInspector.ScreencastView.ProgressTracker.prototype = {
802 _onMainFrameNavigated: function()
804 this._requestIds = {};
805 this._startedRequests = 0;
806 this._finishedRequests = 0;
807 this._maxDisplayedProgress = 0;
808 this._updateProgress(0.1); // Display first 10% on navigation start.
813 delete this._requestIds;
814 this._updateProgress(1); // Display 100% progress on load, hide it in 0.5s.
815 setTimeout(function() {
816 if (!this._navigationProgressVisible())
817 this._displayProgress(0);
821 _navigationProgressVisible: function()
823 return !!this._requestIds;
826 _onRequestStarted: function(event)
828 if (!this._navigationProgressVisible())
830 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
831 // Ignore long-living WebSockets for the sake of progress indicator, as we won't be waiting them anyway.
832 if (request.type === WebInspector.resourceTypes.WebSocket)
834 this._requestIds[request.requestId] = request;
835 ++this._startedRequests;
838 _onRequestFinished: function(event)
840 if (!this._navigationProgressVisible())
842 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
843 if (!(request.requestId in this._requestIds))
845 ++this._finishedRequests;
846 setTimeout(function() {
847 this._updateProgress(this._finishedRequests / this._startedRequests * 0.9); // Finished requests drive the progress up to 90%.
848 }.bind(this), 500); // Delay to give the new requests time to start. This makes the progress smoother.
851 _updateProgress: function(progress)
853 if (!this._navigationProgressVisible())
855 if (this._maxDisplayedProgress >= progress)
857 this._maxDisplayedProgress = progress;
858 this._displayProgress(progress);
861 _displayProgress: function(progress)
863 this._element.style.width = (100 * progress) + "%";