Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / screencast / ScreencastView.js
blobcb506fac5dc1dade2a986e66e72a687287b07204
1 /*
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
6 * met:
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
13 * distribution.
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.
31 /**
32 * @constructor
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;
41 this._domModel = WebInspector.DOMModel.fromTarget(target);
43 this.setMinimumSize(150, 150);
44 this.registerRequiredCSS("screencast/screencastView.css");
47 WebInspector.ScreencastView._bordersSize = 44;
49 WebInspector.ScreencastView._navBarHeight = 29;
51 WebInspector.ScreencastView._HttpRegex = /^https?:\/\/(.+)/;
53 WebInspector.ScreencastView._SchemeRegex = /^(https?|about|chrome):/;
55 WebInspector.ScreencastView.prototype = {
56 initialize: function()
58 this.element.classList.add("screencast");
60 this._createNavigationBar();
62 this._viewportElement = this.element.createChild("div", "screencast-viewport hidden");
63 this._canvasContainerElement = this._viewportElement.createChild("div", "screencast-canvas-container");
64 this._glassPaneElement = this._canvasContainerElement.createChild("div", "screencast-glasspane fill hidden");
66 this._canvasElement = this._canvasContainerElement.createChild("canvas");
67 this._canvasElement.tabIndex = 1;
68 this._canvasElement.addEventListener("mousedown", this._handleMouseEvent.bind(this), false);
69 this._canvasElement.addEventListener("mouseup", this._handleMouseEvent.bind(this), false);
70 this._canvasElement.addEventListener("mousemove", this._handleMouseEvent.bind(this), false);
71 this._canvasElement.addEventListener("mousewheel", this._handleMouseEvent.bind(this), false);
72 this._canvasElement.addEventListener("click", this._handleMouseEvent.bind(this), false);
73 this._canvasElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
74 this._canvasElement.addEventListener("keydown", this._handleKeyEvent.bind(this), false);
75 this._canvasElement.addEventListener("keyup", this._handleKeyEvent.bind(this), false);
76 this._canvasElement.addEventListener("keypress", this._handleKeyEvent.bind(this), false);
77 this._canvasElement.addEventListener("blur", this._handleBlurEvent.bind(this), false);
79 this._titleElement = this._canvasContainerElement.createChild("div", "screencast-element-title monospace hidden");
80 this._tagNameElement = this._titleElement.createChild("span", "screencast-tag-name");
81 this._nodeIdElement = this._titleElement.createChild("span", "screencast-node-id");
82 this._classNameElement = this._titleElement.createChild("span", "screencast-class-name");
83 this._titleElement.createTextChild(" ");
84 this._nodeWidthElement = this._titleElement.createChild("span");
85 this._titleElement.createChild("span", "screencast-px").textContent = "px";
86 this._titleElement.createTextChild(" \u00D7 ");
87 this._nodeHeightElement = this._titleElement.createChild("span");
88 this._titleElement.createChild("span", "screencast-px").textContent = "px";
89 this._titleElement.style.top = "0";
90 this._titleElement.style.left = "0";
92 this._imageElement = new Image();
93 this._isCasting = false;
94 this._context = this._canvasElement.getContext("2d");
95 this._checkerboardPattern = this._createCheckerboardPattern(this._context);
97 this._shortcuts = /** !Object.<number, function(Event=):boolean> */ ({});
98 this._shortcuts[WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl)] = this._focusNavigationBar.bind(this);
100 this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ScreencastFrame, this._screencastFrame, this);
101 this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ScreencastVisibilityChanged, this._screencastVisibilityChanged, this);
103 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChange, this);
104 this._updateGlasspane();
107 wasShown: function()
109 this._startCasting();
112 willHide: function()
114 this._stopCasting();
117 _startCasting: function()
119 if (WebInspector.targetManager.allTargetsSuspended())
120 return;
121 if (this._isCasting)
122 return;
123 this._isCasting = true;
125 const maxImageDimension = 2048;
126 var dimensions = this._viewportDimensions();
127 if (dimensions.width < 0 || dimensions.height < 0) {
128 this._isCasting = false;
129 return;
131 dimensions.width *= window.devicePixelRatio;
132 dimensions.height *= window.devicePixelRatio;
133 this._target.pageAgent().startScreencast("jpeg", 80, Math.min(maxImageDimension, dimensions.width), Math.min(maxImageDimension, dimensions.height));
134 this._domModel.setHighlighter(this);
137 _stopCasting: function()
139 if (!this._isCasting)
140 return;
141 this._isCasting = false;
142 this._target.pageAgent().stopScreencast();
143 this._domModel.setHighlighter(null);
147 * @param {!WebInspector.Event} event
149 _screencastFrame: function(event)
151 var metadata = /** type {PageAgent.ScreencastFrameMetadata} */(event.data.metadata);
152 var base64Data = /** type {string} */(event.data.data);
153 this._imageElement.src = "data:image/jpg;base64," + base64Data;
154 this._pageScaleFactor = metadata.pageScaleFactor;
155 this._screenOffsetTop = metadata.offsetTop;
156 this._scrollOffsetX = metadata.scrollOffsetX;
157 this._scrollOffsetY = metadata.scrollOffsetY;
159 if (event.data.frameNumber)
160 this._target.pageAgent().screencastFrameAck(event.data.frameNumber);
162 var deviceSizeRatio = metadata.deviceHeight / metadata.deviceWidth;
163 var dimensionsCSS = this._viewportDimensions();
165 this._imageZoom = Math.min(dimensionsCSS.width / this._imageElement.naturalWidth, dimensionsCSS.height / (this._imageElement.naturalWidth * deviceSizeRatio));
166 this._viewportElement.classList.remove("hidden");
167 var bordersSize = WebInspector.ScreencastView._bordersSize;
168 if (this._imageZoom < 1.01 / window.devicePixelRatio)
169 this._imageZoom = 1 / window.devicePixelRatio;
170 this._screenZoom = this._imageElement.naturalWidth * this._imageZoom / metadata.deviceWidth;
171 this._viewportElement.style.width = metadata.deviceWidth * this._screenZoom + bordersSize + "px";
172 this._viewportElement.style.height = metadata.deviceHeight * this._screenZoom + bordersSize + "px";
174 this.highlightDOMNode(this._highlightNode, this._highlightConfig);
177 _isGlassPaneActive: function()
179 return !this._glassPaneElement.classList.contains("hidden");
183 * @param {!WebInspector.Event} event
185 _screencastVisibilityChanged: function(event)
187 this._targetInactive = !event.data.visible;
188 this._updateGlasspane();
192 * @param {!WebInspector.Event} event
194 _onSuspendStateChange: function(event)
196 if (WebInspector.targetManager.allTargetsSuspended())
197 this._stopCasting();
198 else
199 this._startCasting();
200 this._updateGlasspane();
203 _updateGlasspane: function()
205 if (this._targetInactive) {
206 this._glassPaneElement.textContent = WebInspector.UIString("The tab is inactive");
207 this._glassPaneElement.classList.remove("hidden");
208 } else if (WebInspector.targetManager.allTargetsSuspended()) {
209 this._glassPaneElement.textContent = WebInspector.UIString("Profiling in progress");
210 this._glassPaneElement.classList.remove("hidden");
211 } else {
212 this._glassPaneElement.classList.add("hidden");
217 * @param {!Event} event
219 _handleMouseEvent: function(event)
221 if (this._isGlassPaneActive()) {
222 event.consume();
223 return;
226 if (!this._pageScaleFactor)
227 return;
229 if (!this._inspectModeConfig || event.type === "mousewheel") {
230 this._simulateTouchForMouseEvent(event);
231 event.preventDefault();
232 if (event.type === "mousedown")
233 this._canvasElement.focus();
234 return;
237 var position = this._convertIntoScreenSpace(event);
238 this._domModel.nodeForLocation(position.x / this._pageScaleFactor + this._scrollOffsetX, position.y / this._pageScaleFactor + this._scrollOffsetY, callback.bind(this));
241 * @param {?WebInspector.DOMNode} node
242 * @this {WebInspector.ScreencastView}
244 function callback(node)
246 if (!node)
247 return;
248 if (event.type === "mousemove")
249 this.highlightDOMNode(node, this._inspectModeConfig);
250 else if (event.type === "click")
251 WebInspector.Revealer.reveal(node);
256 * @param {!Event} event
258 _handleKeyEvent: function(event)
260 if (this._isGlassPaneActive()) {
261 event.consume();
262 return;
265 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(/** @type {!KeyboardEvent} */ (event));
266 var handler = this._shortcuts[shortcutKey];
267 if (handler && handler(event)) {
268 event.consume();
269 return;
272 var type;
273 switch (event.type) {
274 case "keydown": type = "keyDown"; break;
275 case "keyup": type = "keyUp"; break;
276 case "keypress": type = "char"; break;
277 default: return;
280 var text = event.type === "keypress" ? String.fromCharCode(event.charCode) : undefined;
281 this._target.inputAgent().invoke_dispatchKeyEvent({
282 type: type,
283 modifiers: this._modifiersForEvent(event),
284 timestamp: event.timeStamp / 1000,
285 text: text,
286 unmodifiedText: text ? text.toLowerCase() : undefined,
287 keyIdentifier: event.keyIdentifier,
288 code: event.code,
289 key: event.key,
290 windowsVirtualKeyCode: event.keyCode,
291 nativeVirtualKeyCode: event.keyCode,
292 autoRepeat: false,
293 isKeypad: false,
294 isSystemKey: false});
295 event.consume();
296 this._canvasElement.focus();
300 * @param {!Event} event
302 _handleContextMenuEvent: function(event)
304 event.consume(true);
308 * @param {!Event} event
310 _simulateTouchForMouseEvent: function(event)
312 const buttons = {0: "none", 1: "left", 2: "middle", 3: "right"};
313 const types = {"mousedown" : "mousePressed", "mouseup": "mouseReleased", "mousemove": "mouseMoved", "mousewheel": "mouseWheel"};
314 if (!(event.type in types) || !(event.which in buttons))
315 return;
316 if (event.type !== "mousewheel" && buttons[event.which] === "none")
317 return;
319 if (event.type === "mousedown" || typeof this._eventScreenOffsetTop === "undefined")
320 this._eventScreenOffsetTop = this._screenOffsetTop;
322 var modifiers = (event.altKey ? 1 : 0) | (event.ctrlKey ? 2 : 0) | (event.metaKey ? 4 : 0) | (event.shiftKey ? 8 : 0);
324 var convertedPosition = this._zoomIntoScreenSpace(event);
325 convertedPosition.y = Math.round(convertedPosition.y - this._eventScreenOffsetTop);
326 var params = {type: types[event.type], x: convertedPosition.x, y: convertedPosition.y, modifiers: modifiers, timestamp: event.timeStamp / 1000, button: buttons[event.which], clickCount: 0};
327 if (event.type === "mousewheel") {
328 params.deltaX = event.wheelDeltaX / this._screenZoom;
329 params.deltaY = event.wheelDeltaY / this._screenZoom;
330 } else {
331 this._eventParams = params;
333 if (event.type === "mouseup")
334 delete this._eventScreenOffsetTop;
335 WebInspector.targetManager.mainTarget().inputAgent().invoke_emulateTouchFromMouseEvent(params);
339 * @param {!Event} event
341 _handleBlurEvent: function(event)
343 if (typeof this._eventScreenOffsetTop !== "undefined") {
344 var params = this._eventParams;
345 delete this._eventParams;
346 params.type = "mouseReleased";
347 WebInspector.targetManager.mainTarget().inputAgent().invoke_emulateTouchFromMouseEvent(params);
352 * @param {!Event} event
353 * @return {!{x: number, y: number}}
355 _zoomIntoScreenSpace: function(event)
357 var position = {};
358 position.x = Math.round(event.offsetX / this._screenZoom);
359 position.y = Math.round(event.offsetY / this._screenZoom);
360 return position;
364 * @param {!Event} event
365 * @return {!{x: number, y: number}}
367 _convertIntoScreenSpace: function(event)
369 var position = this._zoomIntoScreenSpace(event);
370 position.y = Math.round(position.y - this._screenOffsetTop);
371 return position;
375 * @param {!Event} event
376 * @return {number}
378 _modifiersForEvent: function(event)
380 var modifiers = 0;
381 if (event.altKey)
382 modifiers = 1;
383 if (event.ctrlKey)
384 modifiers += 2;
385 if (event.metaKey)
386 modifiers += 4;
387 if (event.shiftKey)
388 modifiers += 8;
389 return modifiers;
392 onResize: function()
394 if (this._deferredCasting) {
395 clearTimeout(this._deferredCasting);
396 delete this._deferredCasting;
399 this._stopCasting();
400 this._deferredCasting = setTimeout(this._startCasting.bind(this), 100);
404 * @override
405 * @param {?WebInspector.DOMNode} node
406 * @param {?DOMAgent.HighlightConfig} config
407 * @param {!DOMAgent.BackendNodeId=} backendNodeId
408 * @param {!RuntimeAgent.RemoteObjectId=} objectId
410 highlightDOMNode: function(node, config, backendNodeId, objectId)
412 this._highlightNode = node;
413 this._highlightConfig = config;
414 if (!node) {
415 this._model = null;
416 this._config = null;
417 this._node = null;
418 this._titleElement.classList.add("hidden");
419 this._repaint();
420 return;
423 this._node = node;
424 node.boxModel(callback.bind(this));
427 * @param {?DOMAgent.BoxModel} model
428 * @this {WebInspector.ScreencastView}
430 function callback(model)
432 if (!model || !this._pageScaleFactor) {
433 this._repaint();
434 return;
436 this._model = this._scaleModel(model);
437 this._config = config;
438 this._repaint();
443 * @param {!DOMAgent.BoxModel} model
444 * @return {!DOMAgent.BoxModel}
446 _scaleModel: function(model)
449 * @param {!DOMAgent.Quad} quad
450 * @this {WebInspector.ScreencastView}
452 function scaleQuad(quad)
454 for (var i = 0; i < quad.length; i += 2) {
455 quad[i] = quad[i] * this._screenZoom;
456 quad[i + 1] = (quad[i + 1] + this._screenOffsetTop) * this._screenZoom;
460 scaleQuad.call(this, model.content);
461 scaleQuad.call(this, model.padding);
462 scaleQuad.call(this, model.border);
463 scaleQuad.call(this, model.margin);
464 return model;
467 _repaint: function()
469 var model = this._model;
470 var config = this._config;
472 var canvasWidth = this._canvasElement.getBoundingClientRect().width;
473 var canvasHeight = this._canvasElement.getBoundingClientRect().height;
474 this._canvasElement.width = window.devicePixelRatio * canvasWidth;
475 this._canvasElement.height = window.devicePixelRatio * canvasHeight;
477 this._context.save();
478 this._context.scale(window.devicePixelRatio, window.devicePixelRatio);
480 // Paint top and bottom gutter.
481 this._context.save();
482 this._context.fillStyle = this._checkerboardPattern;
483 this._context.fillRect(0, 0, canvasWidth, this._screenOffsetTop * this._screenZoom);
484 this._context.fillRect(0, this._screenOffsetTop * this._screenZoom + this._imageElement.naturalHeight * this._imageZoom, canvasWidth, canvasHeight);
485 this._context.restore();
487 if (model && config) {
488 this._context.save();
489 const transparentColor = "rgba(0, 0, 0, 0)";
490 var quads = [];
491 if (model.content && config.contentColor !== transparentColor)
492 quads.push({quad: model.content, color: config.contentColor});
493 if (model.padding && config.paddingColor !== transparentColor)
494 quads.push({quad: model.padding, color: config.paddingColor});
495 if (model.border && config.borderColor !== transparentColor)
496 quads.push({quad: model.border, color: config.borderColor});
497 if (model.margin && config.marginColor !== transparentColor)
498 quads.push({quad: model.margin, color: config.marginColor});
500 for (var i = quads.length - 1; i > 0; --i)
501 this._drawOutlinedQuadWithClip(quads[i].quad, quads[i - 1].quad, quads[i].color);
502 if (quads.length > 0)
503 this._drawOutlinedQuad(quads[0].quad, quads[0].color);
504 this._context.restore();
506 this._drawElementTitle();
508 this._context.globalCompositeOperation = "destination-over";
511 this._context.drawImage(this._imageElement, 0, this._screenOffsetTop * this._screenZoom, this._imageElement.naturalWidth * this._imageZoom, this._imageElement.naturalHeight * this._imageZoom);
512 this._context.restore();
518 * @param {!DOMAgent.Quad} quad1
519 * @param {!DOMAgent.Quad} quad2
520 * @return {boolean}
522 _quadsAreEqual: function(quad1, quad2)
524 for (var i = 0; i < quad1.length; ++i) {
525 if (quad1[i] !== quad2[i])
526 return false;
528 return true;
532 * @param {!DOMAgent.RGBA} color
533 * @return {string}
535 _cssColor: function(color)
537 if (!color)
538 return "transparent";
539 return WebInspector.Color.fromRGBA([color.r, color.g, color.b, color.a]).asString(WebInspector.Color.Format.RGBA) || "";
543 * @param {!DOMAgent.Quad} quad
544 * @return {!CanvasRenderingContext2D}
546 _quadToPath: function(quad)
548 this._context.beginPath();
549 this._context.moveTo(quad[0], quad[1]);
550 this._context.lineTo(quad[2], quad[3]);
551 this._context.lineTo(quad[4], quad[5]);
552 this._context.lineTo(quad[6], quad[7]);
553 this._context.closePath();
554 return this._context;
558 * @param {!DOMAgent.Quad} quad
559 * @param {!DOMAgent.RGBA} fillColor
561 _drawOutlinedQuad: function(quad, fillColor)
563 this._context.save();
564 this._context.lineWidth = 2;
565 this._quadToPath(quad).clip();
566 this._context.fillStyle = this._cssColor(fillColor);
567 this._context.fill();
568 this._context.restore();
572 * @param {!DOMAgent.Quad} quad
573 * @param {!DOMAgent.Quad} clipQuad
574 * @param {!DOMAgent.RGBA} fillColor
576 _drawOutlinedQuadWithClip: function (quad, clipQuad, fillColor)
578 this._context.fillStyle = this._cssColor(fillColor);
579 this._context.save();
580 this._context.lineWidth = 0;
581 this._quadToPath(quad).fill();
582 this._context.globalCompositeOperation = "destination-out";
583 this._context.fillStyle = "red";
584 this._quadToPath(clipQuad).fill();
585 this._context.restore();
588 _drawElementTitle: function()
590 if (!this._node)
591 return;
593 var canvasWidth = this._canvasElement.getBoundingClientRect().width;
594 var canvasHeight = this._canvasElement.getBoundingClientRect().height;
596 var lowerCaseName = this._node.localName() || this._node.nodeName().toLowerCase();
597 this._tagNameElement.textContent = lowerCaseName;
598 this._nodeIdElement.textContent = this._node.getAttribute("id") ? "#" + this._node.getAttribute("id") : "";
599 this._nodeIdElement.textContent = this._node.getAttribute("id") ? "#" + this._node.getAttribute("id") : "";
600 var className = this._node.getAttribute("class");
601 if (className && className.length > 50)
602 className = className.substring(0, 50) + "\u2026";
603 this._classNameElement.textContent = className || "";
604 this._nodeWidthElement.textContent = this._model.width;
605 this._nodeHeightElement.textContent = this._model.height;
607 this._titleElement.classList.remove("hidden");
608 var titleWidth = this._titleElement.offsetWidth + 6;
609 var titleHeight = this._titleElement.offsetHeight + 4;
611 var anchorTop = this._model.margin[1];
612 var anchorBottom = this._model.margin[7];
614 const arrowHeight = 7;
615 var renderArrowUp = false;
616 var renderArrowDown = false;
618 var boxX = Math.max(2, this._model.margin[0]);
619 if (boxX + titleWidth > canvasWidth)
620 boxX = canvasWidth - titleWidth - 2;
622 var boxY;
623 if (anchorTop > canvasHeight) {
624 boxY = canvasHeight - titleHeight - arrowHeight;
625 renderArrowDown = true;
626 } else if (anchorBottom < 0) {
627 boxY = arrowHeight;
628 renderArrowUp = true;
629 } else if (anchorBottom + titleHeight + arrowHeight < canvasHeight) {
630 boxY = anchorBottom + arrowHeight - 4;
631 renderArrowUp = true;
632 } else if (anchorTop - titleHeight - arrowHeight > 0) {
633 boxY = anchorTop - titleHeight - arrowHeight + 3;
634 renderArrowDown = true;
635 } else
636 boxY = arrowHeight;
638 this._context.save();
639 this._context.translate(0.5, 0.5);
640 this._context.beginPath();
641 this._context.moveTo(boxX, boxY);
642 if (renderArrowUp) {
643 this._context.lineTo(boxX + 2 * arrowHeight, boxY);
644 this._context.lineTo(boxX + 3 * arrowHeight, boxY - arrowHeight);
645 this._context.lineTo(boxX + 4 * arrowHeight, boxY);
647 this._context.lineTo(boxX + titleWidth, boxY);
648 this._context.lineTo(boxX + titleWidth, boxY + titleHeight);
649 if (renderArrowDown) {
650 this._context.lineTo(boxX + 4 * arrowHeight, boxY + titleHeight);
651 this._context.lineTo(boxX + 3 * arrowHeight, boxY + titleHeight + arrowHeight);
652 this._context.lineTo(boxX + 2 * arrowHeight, boxY + titleHeight);
654 this._context.lineTo(boxX, boxY + titleHeight);
655 this._context.closePath();
656 this._context.fillStyle = "rgb(255, 255, 194)";
657 this._context.fill();
658 this._context.strokeStyle = "rgb(128, 128, 128)";
659 this._context.stroke();
661 this._context.restore();
663 this._titleElement.style.top = (boxY + 3) + "px";
664 this._titleElement.style.left = (boxX + 3) + "px";
668 * @return {!{width: number, height: number}}
670 _viewportDimensions: function()
672 const gutterSize = 30;
673 const bordersSize = WebInspector.ScreencastView._bordersSize;
674 var width = this.element.offsetWidth - bordersSize - gutterSize;
675 var height = this.element.offsetHeight - bordersSize - gutterSize - WebInspector.ScreencastView._navBarHeight;
676 return { width: width, height: height };
680 * @override
681 * @param {!DOMAgent.InspectMode} mode
682 * @param {!DOMAgent.HighlightConfig} config
683 * @param {function(?Protocol.Error)=} callback
685 setInspectMode: function(mode, config, callback)
687 this._inspectModeConfig = mode !== DOMAgent.InspectMode.None ? config : null;
688 if (callback)
689 callback(null);
693 * @override
694 * @param {!PageAgent.FrameId} frameId
696 highlightFrame: function(frameId)
701 * @param {!CanvasRenderingContext2D} context
703 _createCheckerboardPattern: function(context)
705 var pattern = /** @type {!HTMLCanvasElement} */(createElement("canvas"));
706 const size = 32;
707 pattern.width = size * 2;
708 pattern.height = size * 2;
709 var pctx = pattern.getContext("2d");
711 pctx.fillStyle = "rgb(195, 195, 195)";
712 pctx.fillRect(0, 0, size * 2, size * 2);
714 pctx.fillStyle = "rgb(225, 225, 225)";
715 pctx.fillRect(0, 0, size, size);
716 pctx.fillRect(size, size, size, size);
717 return context.createPattern(pattern, "repeat");
720 _createNavigationBar: function()
722 this._navigationBar = this.element.createChild("div", "toolbar-background toolbar-colors screencast-navigation");
723 if (Runtime.queryParam("hideNavigation"))
724 this._navigationBar.classList.add("hidden");
726 this._navigationBack = this._navigationBar.createChild("button", "back");
727 this._navigationBack.disabled = true;
728 this._navigationBack.addEventListener("click", this._navigateToHistoryEntry.bind(this, -1), false);
730 this._navigationForward = this._navigationBar.createChild("button", "forward");
731 this._navigationForward.disabled = true;
732 this._navigationForward.addEventListener("click", this._navigateToHistoryEntry.bind(this, 1), false);
734 this._navigationReload = this._navigationBar.createChild("button", "reload");
735 this._navigationReload.addEventListener("click", this._navigateReload.bind(this), false);
737 this._navigationUrl = this._navigationBar.createChild("input");
738 this._navigationUrl.type = "text";
739 this._navigationUrl.addEventListener('keyup', this._navigationUrlKeyUp.bind(this), true);
741 this._navigationProgressBar = new WebInspector.ScreencastView.ProgressTracker(this._navigationBar.createChild("div", "progress"));
743 this._requestNavigationHistory();
744 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.InspectedURLChanged, this._requestNavigationHistory, this);
747 _navigateToHistoryEntry: function(offset)
749 var newIndex = this._historyIndex + offset;
750 if (newIndex < 0 || newIndex >= this._historyEntries.length)
751 return;
752 this._target.pageAgent().navigateToHistoryEntry(this._historyEntries[newIndex].id);
753 this._requestNavigationHistory();
756 _navigateReload: function()
758 this._target.resourceTreeModel.reloadPage();
761 _navigationUrlKeyUp: function(event)
763 if (event.keyIdentifier != 'Enter')
764 return;
765 var url = this._navigationUrl.value;
766 if (!url)
767 return;
768 if (!url.match(WebInspector.ScreencastView._SchemeRegex))
769 url = "http://" + url;
770 this._target.pageAgent().navigate(url);
771 this._canvasElement.focus();
774 _requestNavigationHistory: function()
776 this._target.pageAgent().getNavigationHistory(this._onNavigationHistory.bind(this));
779 _onNavigationHistory: function(error, currentIndex, entries)
781 if (error)
782 return;
784 this._historyIndex = currentIndex;
785 this._historyEntries = entries;
787 this._navigationBack.disabled = currentIndex == 0;
788 this._navigationForward.disabled = currentIndex == (entries.length - 1);
790 var url = entries[currentIndex].url;
791 var match = url.match(WebInspector.ScreencastView._HttpRegex);
792 if (match)
793 url = match[1];
794 InspectorFrontendHost.inspectedURLChanged(url);
795 this._navigationUrl.value = url;
798 _focusNavigationBar: function()
800 this._navigationUrl.focus();
801 this._navigationUrl.select();
802 return true;
805 __proto__: WebInspector.VBox.prototype
809 * @param {!Element} element
810 * @constructor
812 WebInspector.ScreencastView.ProgressTracker = function(element)
814 this._element = element;
816 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._onMainFrameNavigated, this);
817 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.Load, this._onLoad, this);
818 WebInspector.targetManager.addModelListener(WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this);
819 WebInspector.targetManager.addModelListener(WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this);
822 WebInspector.ScreencastView.ProgressTracker.prototype = {
823 _onMainFrameNavigated: function()
825 this._requestIds = {};
826 this._startedRequests = 0;
827 this._finishedRequests = 0;
828 this._maxDisplayedProgress = 0;
829 this._updateProgress(0.1); // Display first 10% on navigation start.
832 _onLoad: function()
834 delete this._requestIds;
835 this._updateProgress(1); // Display 100% progress on load, hide it in 0.5s.
836 setTimeout(function() {
837 if (!this._navigationProgressVisible())
838 this._displayProgress(0);
839 }.bind(this), 500);
842 _navigationProgressVisible: function()
844 return !!this._requestIds;
847 _onRequestStarted: function(event)
849 if (!this._navigationProgressVisible())
850 return;
851 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
852 // Ignore long-living WebSockets for the sake of progress indicator, as we won't be waiting them anyway.
853 if (request.type === WebInspector.resourceTypes.WebSocket)
854 return;
855 this._requestIds[request.requestId] = request;
856 ++this._startedRequests;
859 _onRequestFinished: function(event)
861 if (!this._navigationProgressVisible())
862 return;
863 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
864 if (!(request.requestId in this._requestIds))
865 return;
866 ++this._finishedRequests;
867 setTimeout(function() {
868 this._updateProgress(this._finishedRequests / this._startedRequests * 0.9); // Finished requests drive the progress up to 90%.
869 }.bind(this), 500); // Delay to give the new requests time to start. This makes the progress smoother.
872 _updateProgress: function(progress)
874 if (!this._navigationProgressVisible())
875 return;
876 if (this._maxDisplayedProgress >= progress)
877 return;
878 this._maxDisplayedProgress = progress;
879 this._displayProgress(progress);
882 _displayProgress: function(progress)
884 this._element.style.width = (100 * progress) + "%";